// Copyright (C) 2025 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef  MESH2D_H
#define  MESH2D_H
#include <memory>
#include <vector>
#include <array>
#include <math.h>
#include <iostream>
#include <Eigen/Dense>
#include "StOpt/core/utils/constant.h"

/** \file Mesh2D.h
 * \brief Defines a 2D mesh used in a 2 dimensional adaptive grid
 * \author  Xavier wARIN
 */

namespace StOpt
{
  /// \class Mesh2D.h Mesh2D.h
  /// defines a class for a 2D mesh
  class Mesh2D
  {
  private:
    
    double m_xL ; // Left coordinate
    double m_xR; // right coordinate
    double m_yB; // Bottom coordinate
    double  m_yT ; // Top coordinate

    int m_verticeLT ; // vertices left top -> point number
    int m_verticeLB; // vertices left bottom-> point number
    int m_verticeRB; // vertices right bottom-> point number
    int m_verticeRT; // vertices right top-> point number

  public:

    /// \param p_xL left  x coordinate
    /// \param p_xR right x coordinate
    /// \param p_yB bottom y coordinate
    /// \param p_yT top y    coordinate
    /// \param  p_verticeLT point number in grid of vertice left top
    /// \param  p_verticeLB point number in grid of vertice left bottom
    /// \param  p_verticeRB point number in grid of vertice right bottom
    /// \param  p_verticeRT point number in grid of vertice right top
    Mesh2D( const double & p_xL,    const double & p_xR , const  double &  p_yB,   const double &  p_yT,  
	  const int & p_verticeLT,  const int & p_verticeLB,  const int & p_verticeRB,   const int & p_verticeRT): m_xL(p_xL), m_xR( p_xR),m_yB(p_yB), m_yT( p_yT),
      m_verticeLT( p_verticeLT),  m_verticeLB( p_verticeLB), m_verticeRB(p_verticeRB), m_verticeRT(p_verticeRT)
      {
      }

    /// \brief  split a mesh in 4 meshes of equal size
    /// \param  list of existing points
    /// \return 4 new mesh, coordinates of the 5 new points with increasing number in above grid
    inline std::pair< std::array< std::shared_ptr< Mesh2D > ,4 > , std::vector<Eigen::ArrayXd > > split(const std::vector< Eigen::ArrayXd > & p_vertices) const
      {
	int idec =p_vertices.size();
	std::vector<Eigen::ArrayXd> ptToAdd;
	ptToAdd.reserve(5);
	// add middle point
	int iptLRBT= idec++;
	ptToAdd.push_back({{0.5*(m_xL+m_xR),0.5*( m_yB+ m_yT)}});
	//  point (m_xL,0.5*( m_yB+ m_yT)) new ?
	bool bAdd = true;
	int iptLBT;
	for (size_t ipt =0; ipt < p_vertices.size(); ++ipt)
	  if(std::fabs(p_vertices[ipt][0]- m_xL)+std::fabs(p_vertices[ipt][1]-0.5*( m_yB+ m_yT)) < tiny)
	    {
	      iptLBT = ipt;
	      bAdd = false;
	      break;
	    }
	if (bAdd)
	  {
	    iptLBT= idec++;
	    ptToAdd.push_back({{m_xL,0.5*( m_yB+ m_yT)}});
	  }
	//  point (0.5*(m_xL+m_xR), m_yB ) new ?
	bAdd = true;
	int iptLRB;
	for (size_t ipt =0; ipt < p_vertices.size(); ++ipt)
	  if(std::fabs(p_vertices[ipt][0]- 0.5*(m_xL+m_xR))+std::fabs(p_vertices[ipt][1]-m_yB) < tiny)
	    {
	      iptLRB = ipt;
	      bAdd = false;
	      break;
	    }
	if (bAdd)
	  {
	    iptLRB= idec++;
	    ptToAdd.push_back({{0.5*(m_xL+m_xR), m_yB}}); 
	  }
	// point '(m_xR, 0.5*( m_yB+ m_yT)) new ?
	bAdd = true;
	int iptRBT;
	for (size_t ipt =0; ipt < p_vertices.size(); ++ipt)
	  if(std::fabs(p_vertices[ipt][0]- m_xR)+std::fabs(p_vertices[ipt][1]-0.5*( m_yB+ m_yT)) < tiny)
	    {
	      iptRBT = ipt;
	      bAdd = false;
	      break;
	    }
	if (bAdd)
	  {
	    iptRBT= idec++;
	    ptToAdd.push_back({{m_xR,0.5*( m_yB+ m_yT)}});
	  }
	// point  (0.5*(m_xL+m_xR), m_yT) new ?
	bAdd = true;
	int iptLRT;
	for (size_t ipt =0; ipt < p_vertices.size(); ++ipt)
	  if(std::fabs(p_vertices[ipt][0]- 0.5*(m_xL+m_xR))+std::fabs(p_vertices[ipt][1]-m_yT) < tiny)
	    {
	      iptLRT = ipt;
	      bAdd = false;
	      break;
	    }
	if (bAdd)
	  {
	    iptLRT= idec++;
	    ptToAdd.push_back({{0.5*(m_xL+m_xR), m_yT}}); 
	  }	
	
	std::array< std::shared_ptr< Mesh2D > ,4 > mesh =  {std::make_shared<Mesh2D>(m_xL, 0.5*(m_xL+m_xR), 0.5*( m_yB+ m_yT),m_yT , m_verticeLT, iptLBT, iptLRBT, iptLRT),
	      std::make_shared<Mesh2D>(m_xL, 0.5*(m_xL+m_xR), m_yB, 0.5*( m_yB+ m_yT), iptLBT, m_verticeLB, iptLRB , iptLRBT),
	      std::make_shared<Mesh2D>(0.5*(m_xL+m_xR), m_xR,  m_yB, 0.5*( m_yB+ m_yT), iptLRBT, iptLRB,  m_verticeRB, iptRBT),
	      std::make_shared<Mesh2D>(0.5*(m_xL+m_xR), m_xR, 0.5*( m_yB+ m_yT),m_yT , iptLRT, iptLRBT, iptRBT, m_verticeRT)};
	// std::cout << " SPLI PT TO ADD " ;
	// for (const auto  & pt :ptToAdd)
	//   std::cout << " pt " << pt[0] << "  "  << pt[1] ;
	// std::cout << std::endl ;
	return std::make_pair( mesh, ptToAdd);	      
      }

    
    inline double getXL() const { return m_xL;}
    inline double getXR() const { return m_xR;}
    inline double getYB() const { return m_yB;}
    inline double getYT() const { return m_yT;}
    inline int getVerticeLT() const { return   m_verticeLT;}
    inline int getVerticeLB() const { return   m_verticeLB;}
    inline int getVerticeRB() const { return   m_verticeRB;}
    inline int getVerticeRT()  const { return   m_verticeRT;}

    
  };
}
#endif
