// Copyright (C) 2025 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#define BOOST_TEST_MODULE testGridAdapt1D
#define BOOST_TEST_DYN_LINK
#include <fstream>
#include <list>
#include <cmath>
#include <boost/test/unit_test.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_01.hpp>
#include <Eigen/Dense>
#include "geners/BinaryFileArchive.hh"
#include "geners/Record.hh"
#include "geners/Reference.hh"
#include "StOpt/core/grids/GridAdapt1D.h"
#include "StOpt/core/grids//GridAdapt1DGeners.h"
#include "StOpt/core/utils/constant.h"

using namespace std;
using namespace Eigen;
using namespace gs;
using namespace StOpt;

double accuracyEqual = 1e-10;

#if defined   __linux
#include <fenv.h>
#define enable_abort_on_floating_point_exception() feenableexcept(FE_DIVBYZERO | FE_INVALID)
#endif


/// For Clang < 3.7 (and above ?) to be compatible GCC 5.1 and above
namespace boost
{
namespace unit_test
{
namespace ut_detail
{
std::string normalize_test_case_name(const_string name)
{
    return (name[0] == '&' ? std::string(name.begin() + 1, name.size() - 1) : std::string(name.begin(), name.size()));
}
}
}
}

BOOST_AUTO_TEST_CASE(testGrid1DAdaptGeneration)
{

#if defined   __linux
    enable_abort_on_floating_point_exception();
#endif
    double xMin = 0;
    double xMax = 1.;
    int nbMeshX = 3;
    GridAdapt1D theGrid(xMin, xMax, nbMeshX);

    // get points:
    vector< ArrayXd >   pt =  theGrid.getPoints() ;

    // get mesh
    list<  pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > >  meshesAndPos = theGrid.getMeshes();

    for (auto &meshAndPos : meshesAndPos)
    {
        shared_ptr< Mesh1D> mesh = meshAndPos.first;
        cout << "Mesh : left " << mesh->getXL() << " Right" << mesh->getXR() ;
        cout <<  "Coord : L" <<  pt[mesh->getVerticeL()][0];
        cout << " R " <<   pt[mesh->getVerticeR()][0];
        // split the mesh
        theGrid.splitMesh(meshAndPos);
    }
    cout << "Refined GRID " << endl ;
    meshesAndPos = theGrid.getMeshes();
    pt =  theGrid.getPoints() ;
    for (auto  &meshAndPos : meshesAndPos)
    {
        shared_ptr< Mesh1D> mesh = meshAndPos.first;
        cout << "Mesh : left " << mesh->getXL() << " Right" << mesh->getXR() ;
        cout <<  "Coord : L" <<  pt[mesh->getVerticeL()][0];
        cout << " R " <<   pt[mesh->getVerticeR()][0];
        // split the mesh
        theGrid.splitMesh(meshAndPos);
    }
    // check all point in grid are defined ones
    pt =  theGrid.getPoints() ;
    for (size_t i = 0; i < pt.size(); ++i)
        for (size_t j = i + 1; j < pt.size(); ++j)
        {
            BOOST_CHECK(fabs(pt[i][0] - pt[j][0]) > tiny);
        }

    meshesAndPos = theGrid.getMeshes();
    // check coordinate of each side in mesh
    for (auto  &meshAndPos : meshesAndPos)
    {
        shared_ptr< Mesh1D> mesh = meshAndPos.first;
        double xl = mesh->getXL();
        double xr = mesh->getXR();
        int ivL = mesh->getVerticeL();
        int ivR = mesh->getVerticeR();
        BOOST_CHECK(fabs(pt[ivL][0] - xl) < tiny);
        BOOST_CHECK(fabs(pt[ivR][0] - xr) < tiny);

    }

    // now intersect
    double xxMin =  0.2;
    double xxMax = 0.9;
    cout << "Refine and split " << endl ;
    list< pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > meshesIntersect = theGrid.intersect(xxMin, xxMax);
    for (const auto &meshAndPos : meshesIntersect)
    {
        shared_ptr< Mesh1D>  mesh = meshAndPos.first;
        cout << "Mesh : left " << mesh->getXL() << " Right" << mesh->getXR() ;
        cout <<  "Coord : L" << mesh->getVerticeL() << " R " <<  mesh->getVerticeR() << endl ;
    }

    // check that that all points are in a single mesh
    int nbSim = 10000;
    boost::mt19937 generator;
    boost::uniform_01<boost::mt19937> alea_u(generator);
    for (int isim = 0; isim < nbSim; ++isim)
    {
        double x = xMin + (xMax - xMin) * alea_u();
        shared_ptr< Mesh1D> theMesh = theGrid.getMeshWithPoint(x);
        BOOST_CHECK((x >= theMesh->getXL()) && (x <=  theMesh->getXR()));
    }
}

BOOST_AUTO_TEST_CASE(testGrid1DAdaptSerialization)
{

#if defined   __linux
    enable_abort_on_floating_point_exception();
#endif
    double xMin = 0;
    double xMax = 1.;
    int nbMeshX = 3;
    GridAdapt1D theGrid(xMin, xMax, nbMeshX);
    // get points:
    vector< ArrayXd >   pt =  theGrid.getPoints() ;

    // get mesh
    list< pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > meshes = theGrid.getMeshes();


    // The archive to use
    {
        // default non compression
        BinaryFileArchive ar("archiveGrid", "w");
        ar << Record(theGrid, "First grid", "Top");
        ar.flush();
    }

    {
        BinaryFileArchive ar("archiveGrid", "r");
        GridAdapt1D theGridNew;
        Reference<GridAdapt1D>(ar, "First grid", "Top").restore(0, &theGridNew);

        // get points:
        vector< ArrayXd >   ptNew =  theGridNew.getPoints() ;

        // get mesh
        list< pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > meshesNew = theGridNew.getMeshes();

        for (size_t i = 0; i < pt.size(); ++i)
        {
            BOOST_CHECK_EQUAL(ptNew[0][0], pt[0][0]);
        }

    }
}
