// Copyright (C) 2025 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#define BOOST_TEST_MODULE testGridAdapt2D
#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/GridAdapt2D.h"
#include "StOpt/core/grids//GridAdapt2DGeners.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(testGrid2DAdaptGeneration)
{

#if defined   __linux
    enable_abort_on_floating_point_exception();
#endif
    double xMin = 0;
    double xMax = 1.;
    int nbMeshX = 3;
    double yMin = 2.;
    double yMax = 4.;
    int nbMeshY = 5 ;
    GridAdapt2D theGrid(xMin, xMax, nbMeshX, yMin, yMax, nbMeshY);

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

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

    for (auto &meshAndPos : meshesAndPos)
    {
        shared_ptr< Mesh2D> mesh = meshAndPos.first;
        cout << "Mesh : left " << mesh->getXL() << " Right" << mesh->getXR() << " Bottom" <<  mesh->getYB() << " Top " <<  mesh->getYT() ;
        cout <<  "Coord : LT" <<  pt[mesh->getVerticeLT()][0]  << " ,"  << pt[mesh->getVerticeLT()][1] ;
        cout << " LB" <<  pt[mesh->getVerticeLB()][0]  << " ,"  << pt[mesh->getVerticeLB()][1] ;
        cout << " RB " <<   pt[mesh->getVerticeRB()][0] << " ,"  << pt[mesh->getVerticeRB()][1] ;
        cout << " RT " <<  pt[mesh->getVerticeRT()][0] << " ,"  <<  pt[mesh->getVerticeRT()][1] << endl ;
        // split the mesh
        theGrid.splitMesh(meshAndPos);
    }
    cout << "Refined GRID " << endl ;
    meshesAndPos = theGrid.getMeshes();
    pt =  theGrid.getPoints() ;
    for (auto  &meshAndPos : meshesAndPos)
    {
        shared_ptr< Mesh2D> mesh = meshAndPos.first;
        cout << "Mesh : left " << mesh->getXL() << " Right" << mesh->getXR() << " Bottom" <<  mesh->getYB() << " Top " <<  mesh->getYT() ;
        cout <<  "Coord : LT" <<  pt[mesh->getVerticeLT()][0]  << " ,"  << pt[mesh->getVerticeLT()][1] ;
        cout << " LB" <<  pt[mesh->getVerticeLB()][0]  << " ,"  << pt[mesh->getVerticeLB()][1] ;
        cout << " RB " <<   pt[mesh->getVerticeRB()][0] <<  " ,"  <<  pt[mesh->getVerticeRB()][1];
        cout << " RT " <<  pt[mesh->getVerticeRT()][0] <<  " ,"  << pt[mesh->getVerticeRT()][1] << endl ;
        // 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]) + fabs(pt[i][1] - pt[j][1]) > tiny);
        }

    meshesAndPos = theGrid.getMeshes();
    // check coordinate of each corner in mesh
    for (auto  &meshAndPos : meshesAndPos)
    {
        shared_ptr< Mesh2D> mesh = meshAndPos.first;
        double xl = mesh->getXL();
        double xr = mesh->getXR();
        double yb =  mesh->getYB();
        double yt =  mesh->getYT();
        int ivLT = mesh->getVerticeLT();
        int ivLB = mesh->getVerticeLB();
        int ivRB = mesh->getVerticeRB();
        int ivRT =  mesh->getVerticeRT();
        BOOST_CHECK(fabs(pt[ivLT][0] - xl) + fabs(pt[ivLT][1] - yt) < tiny);
        BOOST_CHECK(fabs(pt[ivLB][0] - xl) + fabs(pt[ivLB][1] - yb) < tiny);
        BOOST_CHECK(fabs(pt[ivRB][0] - xr) + fabs(pt[ivRB][1] - yb) < tiny);
        BOOST_CHECK(fabs(pt[ivRT][0] - xr) + fabs(pt[ivRT][1] - yt) < tiny);

    }

    // now intersect
    double xxMin =  0.2;
    double xxMax = 0.9;
    double yyMin = 0.;
    double yyMax = 3.;
    cout << "Refine and split " << endl ;
    list< pair< shared_ptr< Mesh2D>, shared_ptr<vector<ArrayXi > > > > meshesIntersect = theGrid.intersect(xxMin, xxMax, yyMin, yyMax);
    for (const auto &meshAndPos : meshesIntersect)
    {
        shared_ptr< Mesh2D>  mesh = meshAndPos.first;
        cout << "Mesh : left " << mesh->getXL() << " Right" << mesh->getXR() << " Bottom" <<  mesh->getYB() << " Top " <<  mesh->getYT() ;
        cout <<  "Coord : LT" << mesh->getVerticeLT() << " LB" <<  mesh->getVerticeLB() << " RB " <<  mesh->getVerticeRB() << " RT " <<  mesh->getVerticeRT() << endl ;
    }

    // check that that all point 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();
        double y = yMin + (yMax - yMin) * alea_u();
        shared_ptr< Mesh2D> theMesh = theGrid.getMeshWithPoint(x, y);
        BOOST_CHECK((x >= theMesh->getXL()) && (x <=  theMesh->getXR()));
        BOOST_CHECK((y >= theMesh->getYB()) && (y <=  theMesh->getYT()));
    }
}

BOOST_AUTO_TEST_CASE(testGrid2DAdaptSerialization)
{

#if defined   __linux
    enable_abort_on_floating_point_exception();
#endif
    double xMin = 0;
    double xMax = 1.;
    int nbMeshX = 3;
    double yMin = 2.;
    double yMax = 4.;
    int nbMeshY = 5 ;
    GridAdapt2D theGrid(xMin, xMax, nbMeshX, yMin, yMax, nbMeshY);
    // get points:
    vector< ArrayXd >   pt =  theGrid.getPoints() ;

    // get mesh
    list< pair< shared_ptr< Mesh2D>, 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");
        GridAdapt2D theGridNew;
        Reference<GridAdapt2D>(ar, "First grid", "Top").restore(0, &theGridNew);

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

        // get mesh
        list< pair< shared_ptr< Mesh2D>, 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]);
            BOOST_CHECK_EQUAL(ptNew[0][1], pt[0][1]);
        }

    }
}
