// clang-format off
// RUN: if [ %llvmver -ne 15 ] && [ %llvmver -ne 16 ] && [ %llvmver -ne 17 ] && [ %llvmver -ne 18 ] && [ %llvmver -ne 19 ] && [ %hasMPFR == "yes" ] ; then %clang                -DTRUNC_OP -O0                %s -o %s.a.out %newLoadClangEnzyme -include enzyme/fprt/mpfr.h -lm -lmpfr && %s.a.out ; fi
// RUN: if [ %llvmver -ne 15 ] && [ %llvmver -ne 16 ] && [ %llvmver -ne 17 ] && [ %llvmver -ne 18 ] && [ %llvmver -ne 19 ] && [ %hasMPFR == "yes" ] ; then %clang                -DTRUNC_OP -O2    -ffast-math %s -o %s.a.out %newLoadClangEnzyme -include enzyme/fprt/mpfr.h -lm -lmpfr && %s.a.out ; fi
// RUN: if [ %llvmver -ne 15 ] && [ %llvmver -ne 16 ] && [ %llvmver -ne 17 ] && [ %llvmver -ne 18 ] && [ %llvmver -ne 19 ] && [ %hasMPFR == "yes" ] ; then %clang                           -O1 -g             %s -o %s.a.out %newLoadClangEnzyme -include enzyme/fprt/mpfr.h -lm -lmpfr && %s.a.out ; fi
// RUN: if [ %llvmver -ne 15 ] && [ %llvmver -ne 16 ] && [ %llvmver -ne 17 ] && [ %llvmver -ne 18 ] && [ %llvmver -ne 19 ] && [ %hasMPFR == "yes" ] ; then %clang    -DTRUNC_MEM -DTRUNC_OP -O2                %s -o %s.a.out %newLoadClangEnzyme -include enzyme/fprt/mpfr.h -lm -lmpfr && %s.a.out ; fi
// RUN: if [ %llvmver -ne 15 ] && [ %llvmver -ne 16 ] && [ %llvmver -ne 17 ] && [ %llvmver -ne 18 ] && [ %llvmver -ne 19 ] && [ %hasMPFR == "yes" ] ; then %clang -g -DTRUNC_MEM -DTRUNC_OP -O2                %s -o %s.a.out %newLoadClangEnzyme -include enzyme/fprt/mpfr.h -lm -lmpfr && %s.a.out ; fi

#include <math.h>

#include "../test_utils.h"

#define N 10

double simple_add(double a, double b) {
    return a + b;
}
double simple_cmp(double a, double b) {
    if (a > b)
        return a * 2;
    else
        return b + a;
}
double intrinsics(double a, double b) {
    return sqrt(a) * pow(b, 2);
}
// TODO trunc mem mode
double constt(double a, double b) {
    return 2;
}
double compute(double *A, double *B, double *C, int n) {
  for (int i = 0; i < n; i++) {
    C[i] = A[i] * 2 + B[i] * sqrt(A[i]);
  }
  return C[0];
}

typedef double (*fty)(double *, double *, double *, int);

typedef double (*fty2)(double, double);

extern fty __enzyme_truncate_mem_func_2(...);
extern fty2 __enzyme_truncate_mem_func(...);
extern fty __enzyme_truncate_op_func_2(...);
extern fty2 __enzyme_truncate_op_func(...);
extern double __enzyme_truncate_mem_value(...);
extern double __enzyme_expand_mem_value(...);

#define FROM 64
#define TO 32

#define TEST(F) do {


int main() {

    #ifdef TRUNC_MEM
    {
        double a = 1;
        APPROX_EQ(
            __enzyme_expand_mem_value(
                __enzyme_truncate_mem_value(a, FROM, TO) , FROM, TO),
            a, 1e-10);
    }

    {
        double a = 2;
        double b = 3;
        double truth = simple_cmp(a, b);
        a = __enzyme_truncate_mem_value(a, FROM, TO);
        b = __enzyme_truncate_mem_value(b, FROM, TO);
        double trunc = __enzyme_expand_mem_value(__enzyme_truncate_mem_func(simple_cmp, FROM, TO)(a, b), FROM, TO);
        APPROX_EQ(trunc, truth, 1e-5);
    }
    {
        double a = 2;
        double b = 3;
        double truth = simple_add(a, b);
        a = __enzyme_truncate_mem_value(a, FROM, TO);
        b = __enzyme_truncate_mem_value(b, FROM, TO);
        double trunc = __enzyme_expand_mem_value(__enzyme_truncate_mem_func(simple_add, FROM, TO)(a, b), FROM, TO);
        APPROX_EQ(trunc, truth, 1e-5);
    }
    {
        double a = 2;
        double b = 3;
        double truth = intrinsics(a, b);
        a = __enzyme_truncate_mem_value(a, FROM, TO);
        b = __enzyme_truncate_mem_value(b, FROM, TO);
        double trunc = __enzyme_expand_mem_value(__enzyme_truncate_mem_func(intrinsics, FROM, TO)(a, b), FROM, TO);
        APPROX_EQ(trunc, truth, 1e-5);
    }
    #endif
    // {
    //     double a = 2;
    //     double b = 3;
    //     double truth = intrinsics(a, b);
    //     a = __enzyme_truncate_mem_value(a, FROM, TO);
    //     b = __enzyme_truncate_mem_value(b, FROM, TO);
    //     double trunc = __enzyme_expand_mem_value(__enzyme_truncate_mem_func(constt, FROM, TO)(a, b), FROM, TO);
    //     APPROX_EQ(trunc, truth, 1e-5);
    // }

    #ifdef TRUNC_OP
    {
        double A[N];
        double B[N];
        double C[N];
        double D[N];


        for (int i = 0; i < N; i++) {
            A[i] = 1 + i % 5;
            B[i] = 1 + i % 3;
        }

        compute(A, B, D, N);

        // for (int i = 0; i < N; i++) {
        //     A[i] = __enzyme_truncate_mem_value(A[i], 64, 32);
        //     B[i] = __enzyme_truncate_mem_value(B[i], 64, 32);
        // }

        __enzyme_truncate_op_func_2(compute, 64, 32)(A, B, C, N);

        // for (int i = 0; i < N; i++) {
        //     C[i] = __enzyme_expand_mem_value(C[i], 64, 32);
        // }

        for (int i = 0; i < N; i++) {
            APPROX_EQ(D[i], C[i], 1e-5);
        }
    }
    #endif

}
