// Copyright (C) 2025 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#include <iostream>
#include <algorithm>
#include <Eigen/Dense>
#include "StOpt/core/grids/GridAdapt1D.h"
#include "StOpt/core/utils/constant.h"

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

GridAdapt1D::GridAdapt1D(const double &p_xMin, const double &p_xMax, const int &p_nbMeshX):
    m_xMin(p_xMin), m_xMax(p_xMax)
{
    double dx = (p_xMax - p_xMin) / p_nbMeshX;
    for (int i = 0; i <  p_nbMeshX ; ++i)
    {
        double xL = p_xMin + i * dx;
        shared_ptr<Mesh1D> theMesh = make_shared<Mesh1D>(xL, xL + dx, i, i + 1);
        vector< ArrayXi > vecPos = {{{i}}} ; //{aPos};
        shared_ptr< vector< ArrayXi > > thePosition = make_shared<vector< ArrayXi > >(vecPos);
        m_meshes.push_back(make_pair(theMesh, thePosition));
    }
    // store points
    m_points.reserve((p_nbMeshX + 1));
    for (int i = 0; i <  p_nbMeshX + 1 ; ++i)
    {
        double x = p_xMin + i * dx;
        m_points.push_back({{x}});
    }
}

GridAdapt1D::GridAdapt1D(const double &p_xMin, const double &p_xMax,
                         const list< pair< shared_ptr< Mesh1D>, std::shared_ptr< vector< ArrayXi > > > >  &p_meshes,  const vector< ArrayXd > &p_points) :
    GridAdaptBase(p_points), m_meshes(p_meshes), m_xMin(p_xMin), m_xMax(p_xMax)
{}

GridAdapt1D::GridAdapt1D(const double &p_xMin, const double &p_xMax,
                         const vector< ArrayXd > &p_points) :
    GridAdaptBase(p_points), m_xMin(p_xMin), m_xMax(p_xMax)
{
}


void GridAdapt1D::splitMesh(pair< shared_ptr< Mesh1D >, std::shared_ptr<vector<ArrayXi > > >  &p_meshToSplit)
{
    // current number of points on grid
    size_t nbpt = m_points.size();
    pair<array< shared_ptr< Mesh1D >, 2 >, vector<ArrayXd > > newMeshAndPoints = p_meshToSplit.first->split(m_points);
    std::shared_ptr<vector<ArrayXi > > iposToAdapt = p_meshToSplit.second;
    // erase old mesh
    auto it = find(m_meshes.begin(), m_meshes.end(), p_meshToSplit);
    m_meshes.erase(it);
    // store new meshes
    // mesh splitting
    //  0 1
    std::shared_ptr<vector<ArrayXi > > iposToAdapt1   = make_shared<vector<ArrayXi > >(*iposToAdapt);
    iposToAdapt1->push_back({{0}}); // (0) position in pair
    m_meshes.push_back(make_pair(newMeshAndPoints.first[0], iposToAdapt1));
    std::shared_ptr<vector<ArrayXi > > iposToAdapt2   = make_shared<vector<ArrayXi > >(*iposToAdapt);
    iposToAdapt2->push_back({{1}}); // (1) position in pair
    m_meshes.push_back(make_pair(newMeshAndPoints.first[1], iposToAdapt2));
    // store new points
    m_points.resize(nbpt + newMeshAndPoints.second.size());
    for (size_t i = nbpt; i < nbpt + newMeshAndPoints.second.size(); ++i)
        m_points[i] = newMeshAndPoints.second[i - nbpt];
}

list< pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > GridAdapt1D::intersect(const double &p_xMin, const double &p_xMax) const
{
    list<  pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > meshRet;
    for (const auto &meshAPos : m_meshes)
    {
        shared_ptr< Mesh1D>   mesh = meshAPos.first ;
        bool bX = ((mesh->getXL() - p_xMin) * (mesh->getXL() - p_xMax) <= 0.) || ((mesh->getXR() - p_xMin) * (mesh->getXR() - p_xMax) <= 0.) || ((mesh->getXL() <= p_xMin+tiny) && (mesh->getXR() >= p_xMax -tiny));
        if (bX)
        {
            meshRet.push_back(make_pair(mesh, meshAPos.second));
        }
    }
    return meshRet;
}


shared_ptr< Mesh1D>   GridAdapt1D::getMeshWithPoint(const double &p_x) const
{
    for (const auto &meshAPos : m_meshes)
    {
        shared_ptr< Mesh1D>   mesh = meshAPos.first ;
        if ((mesh->getXL() <=  p_x) && (mesh->getXR() >= p_x))
        {
            return mesh;
        }
    }
    abort();
}
