/*
 *  Copyright (C) 2004-2024 Edward F. Valeev
 *
 *  This file is part of Libint compiler.
 *
 *  Libint compiler is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Libint compiler is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Libint compiler.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <global_macros.h>
#include <iter.h>
#include <libint/util.h>
#include <libint2.h>
#include <libint2/deriv_iter.h>
#include <libint2/util/memory.h>
#include <policy_spec.h>
#include <rr.h>
#include <test_eri/eri.h>
#include <test_eri/prep_libint2.h>

#include <cmath>
#include <iostream>
// generated by test_eri.pl
#include <test_eri_conf.h>

using namespace std;
using namespace libint2;

// these are used for casts
namespace libint2 {

template <typename Output, typename Input>
inline Output cast(Input i) {
  return Output(i);
}

#ifdef LIBINT2_HAVE_AGNER_VECTORCLASS
// AVX
template <>
inline double cast<double, Vec4d>(Vec4d i) {
  return i[0];
}
template <>
inline double cast<double, Vec8f>(Vec8f i) {
  return i[0];
}
template <>
inline float cast<float, Vec8f>(Vec8f i) {
  return i[0];
}

// SSE
template <>
inline double cast<double, Vec2d>(Vec2d i) {
  return i[0];
}
template <>
inline double cast<double, Vec4f>(Vec4f i) {
  return i[0];
}
template <>
inline float cast<float, Vec4f>(Vec4f i) {
  return i[0];
}
#endif

};  // namespace libint2

typedef unsigned int uint;

libint2::FmEval_Chebyshev7<double> fmeval_chebyshev(28);
libint2::FmEval_Taylor<double, 6> fmeval_taylor(28, 1e-15);

int main(int argc, char** argv) {
#if defined(__cplusplus)
//  mpf_set_default_prec(256);
//  mpfr_set_default_prec(256);
#endif

  const uint veclen = LIBINT2_MAX_VECLEN;
#if LIBINT_CONTRACTED_INTS
  const uint contrdepth = 3;
#else
  const uint contrdepth = 1;
#endif
  const uint contrdepth4 = contrdepth * contrdepth * contrdepth * contrdepth;
  RandomShellSet<4> rsqset(am, veclen, contrdepth);

  const unsigned int deriv_order = 0;
  CartesianDerivIterator<4> diter(deriv_order);
  const unsigned int nderiv = diter.range_size();

  CGShell sh0(am[0]);
  CGShell sh1(am[1]);
  CGShell sh2(am[2]);
  CGShell sh3(am[3]);

  const double* A = &(rsqset.R[0][0]);
  const double* B = &(rsqset.R[1][0]);
  const double* C = &(rsqset.R[2][0]);
  const double* D = &(rsqset.R[3][0]);
  LIBINT2_REF_REALTYPE Aref[4];
  for (int i = 0; i < 4; ++i) Aref[i] = A[i];
  LIBINT2_REF_REALTYPE Bref[4];
  for (int i = 0; i < 4; ++i) Bref[i] = B[i];
  LIBINT2_REF_REALTYPE Cref[4];
  for (int i = 0; i < 4; ++i) Cref[i] = C[i];
  LIBINT2_REF_REALTYPE Dref[4];
  for (int i = 0; i < 4; ++i) Dref[i] = D[i];

  typedef SubIteratorBase<CGShell> iter;
  std::shared_ptr<iter> sh0_iter(new iter(sh0));
  std::shared_ptr<iter> sh1_iter(new iter(sh1));
  std::shared_ptr<iter> sh2_iter(new iter(sh2));
  std::shared_ptr<iter> sh3_iter(new iter(sh3));

  Libint_eri0_t* erieval = libint2::malloc<Libint_eri0_t>(contrdepth4);
  const int max_am = max(max(am[0], am[1]), max(am[2], am[3]));
  LIBINT2_PREFIXED_NAME(libint2_init_eri0)(&erieval[0], max_am, 0);
  prep_libint2(erieval, rsqset, 0);

  cout << "Testing (" << sh0.label() << sh1.label() << "|" << sh2.label()
       << sh3.label() << ") ";
  if (deriv_order > 0) {
    cout << " deriv order = " << deriv_order;
  }
  cout << endl;

  double scale_target = 1.0;
#if LIBINT_ACCUM_INTS
  // if accumulating integrals, zero out first, then compute twice
  erieval->zero_out_targets = 1;
  scale_target = 0.5;
  COMPUTE_XX_ERI_XX(erieval);
#endif
#if LIBINT_CONTRACTED_INTS
  erieval[0].contrdepth = contrdepth4;
#endif
  COMPUTE_XX_ERI_XX(&erieval[0]);

  bool success = true;
  int ijkl = 0;
  for (sh0_iter->init(); int(*sh0_iter); ++(*sh0_iter)) {
    for (sh1_iter->init(); int(*sh1_iter); ++(*sh1_iter)) {
      for (sh2_iter->init(); int(*sh2_iter); ++(*sh2_iter)) {
        for (sh3_iter->init(); int(*sh3_iter); ++(*sh3_iter), ijkl++) {
#if USE_BRAKET_H
          CGF bf0 = sh0_iter->elem();
          CGF bf1 = sh1_iter->elem();
          CGF bf2 = sh2_iter->elem();
          CGF bf3 = sh3_iter->elem();

          uint l0 = bf0.qn(0);
          uint m0 = bf0.qn(1);
          uint n0 = bf0.qn(2);
          uint l1 = bf1.qn(0);
          uint m1 = bf1.qn(1);
          uint n1 = bf1.qn(2);
          uint l2 = bf2.qn(0);
          uint m2 = bf2.qn(1);
          uint n2 = bf2.qn(2);
          uint l3 = bf3.qn(0);
          uint m3 = bf3.qn(1);
          uint n3 = bf3.qn(2);
#else
          std::shared_ptr<CGF> bf0 = sh0_iter->elem();
          std::shared_ptr<CGF> bf1 = sh1_iter->elem();
          std::shared_ptr<CGF> bf2 = sh2_iter->elem();
          std::shared_ptr<CGF> bf3 = sh3_iter->elem();

          uint l0 = bf0->qn(0);
          uint m0 = bf0->qn(1);
          uint n0 = bf0->qn(2);
          uint l1 = bf1->qn(0);
          uint m1 = bf1->qn(1);
          uint n1 = bf1->qn(2);
          uint l2 = bf2->qn(0);
          uint m2 = bf2->qn(1);
          uint n2 = bf2->qn(2);
          uint l3 = bf3->qn(0);
          uint m3 = bf3->qn(1);
          uint n3 = bf3->qn(2);
#endif

          for (uint v = 0; v < veclen; v++) {
            std::vector<LIBINT2_REF_REALTYPE> ref_eri(
                nderiv, LIBINT2_REF_REALTYPE(0.0));

            uint p0123 = 0;
            for (uint p0 = 0; p0 < contrdepth; p0++) {
              for (uint p1 = 0; p1 < contrdepth; p1++) {
                for (uint p2 = 0; p2 < contrdepth; p2++) {
                  for (uint p3 = 0; p3 < contrdepth; p3++, p0123++) {
                    const LIBINT2_REF_REALTYPE alpha0 = rsqset.exp[0][v][p0];
                    const LIBINT2_REF_REALTYPE alpha1 = rsqset.exp[1][v][p1];
                    const LIBINT2_REF_REALTYPE alpha2 = rsqset.exp[2][v][p2];
                    const LIBINT2_REF_REALTYPE alpha3 = rsqset.exp[3][v][p3];

                    const LIBINT2_REF_REALTYPE c0 = rsqset.coef[0][v][p0];
                    const LIBINT2_REF_REALTYPE c1 = rsqset.coef[1][v][p1];
                    const LIBINT2_REF_REALTYPE c2 = rsqset.coef[2][v][p2];
                    const LIBINT2_REF_REALTYPE c3 = rsqset.coef[3][v][p3];
                    const LIBINT2_REF_REALTYPE c0123 = c0 * c1 * c2 * c3;

                    CartesianDerivIterator<4> diter(deriv_order);
                    bool last_deriv = false;
                    unsigned int di = 0;
                    do {
                      ref_eri[di++] +=
                          c0123 * eri(&(*diter)[0], l0, m0, n0, alpha0, Aref,
                                      l1, m1, n1, alpha1, Bref, l2, m2, n2,
                                      alpha2, Cref, l3, m3, n3, alpha3, Dref,
                                      0);
                      last_deriv = diter.last();
                      if (!last_deriv) diter.next();
                    } while (!last_deriv);
                  }
                }
              }
            }

            for (unsigned int di = 0; di < nderiv; ++di) {
              const LIBINT2_REALTYPE new_eri =
                  scale_target * erieval[0].targets[di][ijkl * veclen + v];

              if (abs(ref_eri[di] -
                      libint2::cast<LIBINT2_REF_REALTYPE>(new_eri)) > 1.0E-10) {
                std::cout << "Elem " << ijkl << " di= " << di << " v=" << v
                          << " : eri.cc = " << ref_eri[di] << " libint = "
                          << libint2::cast<LIBINT2_REF_REALTYPE>(new_eri)
                          << " (relerr = "
                          << abs((ref_eri[di] -
                                  libint2::cast<LIBINT2_REF_REALTYPE>(
                                      new_eri)) /
                                 ref_eri[di])
                          << ")" << endl;
                success = false;
              }
            }

          }  // end of vector loop
        }
      }
    }
  }

  LIBINT2_PREFIXED_NAME(libint2_cleanup_eri0)(&erieval[0]);
  free(erieval);

  cout << "test " << (success ? "ok" : "failed") << endl;

  return success ? 0 : 1;
}
