#ifndef GEOM_FILE
#define GEOM_FILE
#include <array>
#include <functional>             // function; C++11
#include <iostream>
#include <memory>                  // shared_ptr
#include <string>
#include <vector>

/**
 * Basis class for finite element meshes.
 */
class Mesh
{
public:
    /**
     * Constructor initializing the members with default values.
      *
     * @param[in] ndim  space dimensions (dimension for coordinates)
      * @param[in] nvert_e  number of vertices per element (dimension for connectivity)
      * @param[in] ndof_e   degrees of freedom per element (= @p nvert_e for linear elements)
      * @param[in] nedge_e  number of edges per element (= @p nvert_e for linear elements in 2D)
     */
    explicit Mesh(int ndim, int nvert_e = 0, int ndof_e = 0, int nedge_e = 0);

    __attribute__((noinline))
    Mesh(Mesh const &) = default;

    Mesh &operator=(Mesh const &) = delete;


    /**
     * Destructor.
     *
     * See clang warning on
     * <a href="https://stackoverflow.com/questions/28786473/clang-no-out-of-line-virtual-method-definitions-pure-abstract-c-class/40550578">weak-vtables</a>.
     */
    virtual ~Mesh();

    /**
     * Reads mesh data from a binary file.
     *
     * File format, see ascii_write_mesh.m
     *
     * @param[in] fname file name
    */
    explicit Mesh(std::string const &fname);

    /**
     * Reads mesh data from a binary file.
     *
     * File format, see ascii_write_mesh.m
     *
     * @param[in] fname file name
    */
    void ReadVertexBasedMesh(std::string const &fname);

    /**
     * Number of finite elements in (sub)domain.
     * @return number of elements.
     */
    int Nelems() const
    {
        return _nelem;
    }

    /**
     * Global number of vertices for each finite element.
     * @return number of vertices per element.
     */
    int NverticesElements() const
    {
        return _nvert_e;
    }

    /**
     * Global number of degrees of freedom (dof) for each finite element.
     * @return degrees of freedom per element.
     */
    int NdofsElement() const
    {
        return _ndof_e;
    }

    /**
     * Number of vertices in mesh.
     * @return number of vertices.
     */
    int Nnodes() const
    {
        return _nnode;
    }

    /**
     * Space dimension.
     * @return number of dimensions.
     */
    int Ndims() const
    {
        return _ndim;
    }

    /**
     * (Re-)Allocates memory for the element connectivity and redefines the appropriate dimensions.
      *
     * @param[in] nelem    number of elements
      * @param[in] nvert_e  number of vertices per element
     */
    void Resize_Connectivity(int nelem, int nvert_e)
    {
        SetNelem(nelem);               // number of elements
        SetNverticesElement(nvert_e);  // vertices per element
        _ia.resize(nelem * nvert_e);
    }

    /**
     * Read connectivity information (g1,g2,g3)_i.
     * @return connectivity vector [nelems*ndofs].
     */
    const std::vector<int>  &GetConnectivity() const
    {
        return _ia;
    }

    /**
     * Access/Change connectivity information (g1,g2,g3)_i.
     * @return connectivity vector [nelems*ndofs].
     */
    std::vector<int>  &GetConnectivity()
    {
        return _ia;
    }

    /**
     * (Re-)Allocates memory for the element connectivity and redefines the appropriate dimensions.
      *
     * @param[in] nnodes    number of nodes
      * @param[in] ndim      space dimension
     */
    void Resize_Coords(int nnodes, int ndim)
    {
        SetNnode(nnodes);       // number of nodes
        SetNdim(ndim);          // space dimension
        _xc.resize(nnodes * ndim);
    }

    /**
     * Read coordinates of vertices (x,y)_i.
     * @return coordinates vector [nnodes*2].
     */
    const std::vector<double> &GetCoords() const
    {
        return _xc;
    }

    /**
     * Access/Change coordinates of vertices (x,y)_i.
     * @return coordinates vector [nnodes*2].
     */
    std::vector<double> &GetCoords()
    {
        return _xc;
    }

    /**
     * Calculate values in vector @p v via function @p func(x,y)
     * @param[in] v     vector
      * @param[in] func  function of (x,y) returning a double value.
     */
    void SetValues(std::vector<double> &v, const std::function<double(double, double)> &func) const;
    void SetBoundaryValues(std::vector<double> &v, const std::function<double(double, double)> &func) const;
    void SetDirchletValues(std::vector<double> &v, const std::function<double(double, double)> &func) const;

    /**
     * Prints the information for a finite element mesh
     */
    void Debug() const;

    /**
     * Prints the edge based information for a finite element mesh
     */
    void DebugEdgeBased() const;

    /**
     * Determines the indices of those vertices with Dirichlet boundary conditions
     * @return index vector.
     */
    virtual std::vector<int> Index_DirichletNodes() const;
    virtual std::vector<int> Index_BoundaryNodes() const;

    /**
     * Write vector @p v together with its mesh information to an ASCii file @p fname.
      *
      * The data are written in C-style.
      *
      * @param[in] fname  file name
     * @param[in] v      vector
     */
    void Write_ascii_matlab(std::string const &fname, std::vector<double> const &v) const;

    /**
     * Exports the mesh information to ASCii files  @p basename + {_coords|_elements}.txt.
      *
      * The data are written in C-style.
      *
      * @param[in] basename  first part of file names
     */
    void Export_scicomp(std::string const &basename) const;

    /**
     * Visualize @p v together with its mesh information via matlab or octave.
      *
      * Comment/uncomment those code lines in method Mesh:Visualize (geom.cpp)
      * that are supported on your system.
      *
     * @param[in] v      vector
      *
      * @warning matlab files ascii_read_meshvector.m  visualize_results.m
      *          must be in the executing directory.
     */
    void Visualize(std::vector<double> const &v) const;

    /**
     * Global number of edges.
     * @return number of edges in mesh.
     */
    int Nedges() const
    {
        return _nedge;
    }

    /**
     * Global number of edges for each finite element.
     * @return number of edges per element.
     */
    int NedgesElements() const
    {
        return _nedge_e;
    }

    /**
     * Read edge connectivity information (e1,e2,e3)_i.
     * @return edge connectivity vector [nelems*_nedge_e].
     */
    const std::vector<int>  &GetEdgeConnectivity() const
    {
        return _ea;
    }

    /**
     * Access/Change edge connectivity information (e1,e2,e3)_i.
     * @return edge connectivity vector [nelems*_nedge_e].
     */
    std::vector<int>  &GetEdgeConnectivity()
    {
        return _ea;
    }

    /**
     * Read edge information (v1,v2)_i.
     * @return edge connectivity vector [_nedge*2].
     */
    const std::vector<int>  &GetEdges() const
    {
        return _edges;
    }

    /**
     * Access/Change edge information (v1,v2)_i.
     * @return edge connectivity vector [_nedge*2].
     */
    std::vector<int>  &GetEdges()
    {
        return _edges;
    }

    /**
     * Determines all node to node connections from the vertex based mesh.
      *
     * @return vector[k][] containing all connections of vertex k, including to itself.
     */
    std::vector<std::vector<int>> Node2NodeGraph() const
    {
        //// Check version 2 wrt. version 1
        //auto v1=Node2NodeGraph_1();
        //auto v2=Node2NodeGraph_2();
        //if ( equal(v1.cbegin(),v1.cend(),v2.begin()) )
        //{
        //std::cout << "\nidentical Versions\n";
        //}
        //else
        //{
        //std::cout << "\nE R R O R   in Versions\n";
        //}

        //return Node2NodeGraph_1();
        return Node2NodeGraph_2();        // 2 times faster than version 1
    }

    /**
     * Accesses the father-of-nodes relation.
      *
     * @return  vector of length 0 because no relation available.
      *
     */
    virtual std::vector<int> const &GetFathersOfVertices() const
    {
        return _dummy;
    }

    /**
     * Deletes all edge connectivity information (saves memory).
     */
    void Del_EdgeConnectivity();

protected:
    //public:
    void SetNelem(int nelem)
    {
        _nelem = nelem;
    }

    void SetNverticesElement(int nvert)
    {
        _nvert_e = nvert;
    }

    void SetNdofsElement(int ndof)
    {
        _ndof_e = ndof;
    }

    void SetNnode(int nnode)
    {
        _nnode = nnode;
    }

    void SetNdim(int ndim)
    {
        _ndim = ndim;
    }

    void SetNedge(int nedge)
    {
        _nedge = nedge;
    }

    /**
     * Reads vertex based mesh data from a binary file.
     *
     * File format, see ascii_write_mesh.m
     *
     * @param[in] fname file name
    */
    void ReadVectexBasedMesh(std::string const &fname);

    /**
     * The vertex based mesh data are used to derive the edge based data.
     *
     *  @warning Exactly 3 vertices, 3 edges per element are assumed (linear triangle in 2D)
    */
    void DeriveEdgeFromVertexBased()
    {
        //DeriveEdgeFromVertexBased_slow();
        //DeriveEdgeFromVertexBased_fast();
        DeriveEdgeFromVertexBased_fast_2();
    }
    void DeriveEdgeFromVertexBased_slow();
    void DeriveEdgeFromVertexBased_fast();
    void DeriveEdgeFromVertexBased_fast_2();



    /**
     * The edge based mesh data are used to derive the vertex based data.
      *
      *  @warning Exactly 3 vertices, 3 edges per element are assumed (linear triangle in 2D)
    */
    void DeriveVertexFromEdgeBased();

    /**
     * Determines the indices of those vertices with Dirichlet boundary conditions
     * @return index vector.
     */
    int Nnbedges() const
    {
        return static_cast<int>(_bedges.size());
    }

    /**
     * Checks whether the array dimensions fit to their appropriate size parameters
     * @return index vector.
     */
    virtual bool Check_array_dimensions() const;

    /**
     * Permutes the vertex information in an edge based mesh.
      *
     * @param[in] old2new   new indices of original vertices.
     */
    void PermuteVertices_EdgeBased(std::vector<int> const &old2new);

private:
    /**
     * Determines all node to node connections from the vertex based mesh.
      *
     * @return vector[k][] containing all connections of vertex k, including to itself.
     */
    std::vector<std::vector<int>> Node2NodeGraph_1() const;  // is correct

    /**
     * Determines all node to node connections from the vertex based mesh.
      *
      * Faster than @p Node2NodeGraph_1().
      *
     * @return vector[k][] containing all connections of vertex k, including to itself.
     */
    std::vector<std::vector<int>> Node2NodeGraph_2() const;  // is correct

    //private:
protected:
    int _nelem;         //!< number elements
    int _nvert_e;       //!< number of vertices per element
    int _ndof_e;        //!< degrees of freedom (d.o.f.) per element
    int _nnode;         //!< number nodes/vertices
    int _ndim;          //!< space dimension of the problem (1, 2, or 3)
    std::vector<int> _ia;    //!< element connectivity
    std::vector<double> _xc; //!< coordinates

protected:
    // B.C.
    std::vector<int> _bedges;     //!< boundary edges [nbedges][2] storing start/end vertex
// 2020-01-08
    std::vector<int> _sdedges;    //!< boundary edges [nbedges][2] with left/right subdomain number

    //private:
protected:
    // edge based connectivity
    int _nedge;              //!< number of edges in mesh
    int _nedge_e;            //!< number of edges per element
    std::vector<int> _edges; //!< edges of mesh (vertices ordered ascending)
    std::vector<int> _ea;    //!< edge based element connectivity
    // B.C.
    std::vector<int> _ebedges; //!< boundary edges [nbedges]

private:
    const std::vector<int> _dummy; //!< empty dummy vector

};


// *********************************************************************

class RefinedMesh: public Mesh
{
public:
    /**
     * Constructs a refined mesh according to the marked elements in @p ibref.
     *
     * If the vector @p ibref has size 0 then all elements will be refined.
     *
     * @param[in] cmesh  original mesh for coarsening.
     * @param[in] ibref  vector containing True/False regarding refinement for each element
     *
     */
    //explicit RefinedMesh(Mesh const &cmesh, std::vector<bool> const &ibref = std::vector<bool>(0));
    RefinedMesh(Mesh const &cmesh, std::vector<bool> const &ibref);
    //RefinedMesh(Mesh const &cmesh, std::vector<bool> const &ibref);

    /**
    * Constructs a refined mesh by regulare refinement of all elements.
    *
    * @param[in] cmesh  original mesh for coarsening.
    *
    */
    explicit RefinedMesh(Mesh const &cmesh)
        : RefinedMesh(cmesh, std::vector<bool>(0))
    {}


    RefinedMesh(RefinedMesh const &) = delete;
    //RefinedMesh(RefinedMesh const&&) = delete;

    RefinedMesh &operator=(RefinedMesh const &) = delete;
    //RefinedMesh& operator=(RefinedMesh const&&) = delete;

    /**
     * Destructor.
     */
    virtual ~RefinedMesh() override;

    /**
     * Refines the mesh according to the marked elements.
     *
     * @param[in] ibref  vector containing True/False regarding refinement for each element
     *
     * @return the refined mesh
     *
     */
    Mesh RefineElements(std::vector<bool> const &ibref);

    /**
     * Refines all elements in the actual mesh.
     *
     * @param[in] nref  number of regular refinements to perform
     *
     */
    void RefineAllElements(int nref = 1);

    /**
     * Accesses the father-of-nodes relation.
      *
     * @return  father-of-nodes relation [nnodes][2]
      *
     */
    std::vector<int> const &GetFathersOfVertices() const override
    {
        return _vfathers;
    }

protected:
    /**
     * Checks whether the array dimensions fit to their appropriate size parameters
     * @return index vector.
     */
    bool Check_array_dimensions() const override;

    /**
     * Permutes the vertex information in an edge based mesh.
      *
     * @param[in] old2new   new indices of original vertices.
     */
    void PermuteVertices_EdgeBased(std::vector<int> const &old2new);


private:
    //Mesh const              & _cmesh; //!< coarse mesh
    std::vector<bool> const   _ibref; //!< refinement info
    int                       _nref;  //!< number of regular refinements performed
    std::vector<int>          _vfathers; //!< stores the 2 fathers of each vertex (equal fathers denote original coarse vertex)

};

// *********************************************************************

class gMesh_Hierarchy
{
public:
    /**
     * Constructs mesh hierarchy of @p nlevel levels starting with coarse mesh @p cmesh.
      * The coarse mesh @p cmesh will be @p nlevel-1 times geometrically refined.
      *
      * @param[in] cmesh   initial coarse mesh
     * @param[in] nlevel  number levels in mesh hierarchy
      *
     */
    gMesh_Hierarchy(Mesh const &cmesh, int nlevel);

    size_t size() const
    {
        return _gmesh.size();
    }

    /**
     * Access to mesh @p lev from mesh hierarchy.
      *
     * @return mesh @p lev
      * @warning An out_of_range exception might be thrown.
      *
     */
    Mesh const &operator[](int lev) const
    {
        return *_gmesh.at(lev);
    }

    /**
     * Access to finest mesh in mesh hierarchy.
      *
     * @return finest mesh
      *
     */
    Mesh const &finest() const
    {
        return *_gmesh.back();
    }

    /**
     * Access to coarest mesh in mesh hierarchy.
      *
     * @return coarsest mesh
      *
     */
    Mesh const &coarsest() const
    {
        return *_gmesh.front();
    }

private:
    std::vector<std::shared_ptr<Mesh>> _gmesh; //!< mesh hierarchy from coarse ([0]) to fine.

};



// *********************************************************************
/**
 * 2D finite element mesh of the square consisting of linear triangular elements.
 */
class Mesh_2d_3_square: public Mesh
{
public:
    /**
     * Generates the f.e. mesh for the unit square.
     *
     * @param[in] nx    number of discretization intervals in x-direction
     * @param[in] ny    number of discretization intervals in y-direction
     * @param[in] myid  my MPI-rank / subdomain
     * @param[in] procx number of ranks/subdomains in x-direction
     * @param[in] procy number of processes in y-direction
    */
    Mesh_2d_3_square(int nx, int ny, int myid = 0, int procx = 1, int procy = 1);

    /**
     * Destructor
     */
    ~Mesh_2d_3_square() override;

    /**
     * Set solution vector based on a tensor product grid in the rectangle.
     * @param[in] u solution vector
     */
    void SetU(std::vector<double> &u) const;

    /**
     * Set right hand side (rhs) vector on a tensor product grid in the rectangle.
     * @param[in] f rhs vector
     */
    void SetF(std::vector<double> &f) const;

    /**
     * Determines the indices of those vertices with Dirichlet boundary conditions
     * @return index vector.
     */
    std::vector<int> Index_DirichletNodes() const override;
    std::vector<int> Index_BoundaryNodes() const override;

    /**
      * Stores the values of vector @p u of (sub)domain into a file @p name for further processing in gnuplot.
      * The file stores rowise the x- and y- coordinates together with the value from  @p u .
      * The domain [@p xl, @p xr] x [@p yb, @p yt] is discretized into @p nx x @p ny intervals.
      *
      * @param[in] name  basename of file name (file name will be extended by the rank number)
      * @param[in] u     local vector
      *
      * @warning   Assumes tensor product grid in unit square; rowise numbered
      *            (as generated in class constructor).
      *            The output is provided for tensor product grid visualization
      *            ( similar to Matlab-surf() ).
      *
      * @see Mesh_2d_3_square
      */
    void SaveVectorP(std::string const &name, std::vector<double> const &u) const;

    // here will still need to implement in the class
    //  GetBound(), AddBound()
    //  or better a generalized way with indices and their appropriate ranks for MPI communication

private:
    /**
      * Determines the coordinates of the discretization nodes of the domain [@p xl, @p xr] x [@p yb, @p yt]
      * which is discretized into @p nx x @p ny intervals.
      * @param[in] nx    number of discretization intervals in x-direction
      * @param[in] ny    number of discretization intervals in y-direction
      * @param[in] xl    x-coordinate of left boundary
      * @param[in] xr    x-coordinate of right boundary
      * @param[in] yb    y-coordinate of lower boundary
      * @param[in] yt    y-coordinate of upper boundary
      * @param[out] xc   coordinate vector of length 2n with x(2*k,2*k+1) as coordinates of node k
      */

    void GetCoordsInRectangle(int nx, int ny, double xl, double xr, double yb, double yt,
                              double xc[]);
    /**
      * Determines the element connectivity of linear triangular elements of a FEM discretization
      * of a rectangle using @p nx x @p ny equidistant intervals for discretization.
      * @param[in] nx    number of discretization intervals in x-direction
      * @param[in] ny    number of discretization intervals in y-direction
      * @param[out] ia   element connectivity matrix with ia(3*s,3*s+1,3*s+2) as node numbers od element s
      */
    void GetConnectivityInRectangle(int nx, int ny, int ia[]);

private:
    int _myid;          //!< my MPI rank
    int _procx;         //!< number of MPI ranks in x-direction
    int _procy;         //!< number of MPI ranks in y-direction
    std::array<int, 4> _neigh; //!< MPI ranks of neighbors (negative: no neighbor but b.c.)
    int _color;         //!< red/black coloring (checker board) of subdomains

    double _xl;         //!< x coordinate of lower left  corner of square
    double _xr;         //!< x coordinate of lower right corner of square
    double _yb;         //!< y coordinate or lower left  corner of square
    double _yt;         //!< y coordinate of upper right corner of square
    int    _nx;         //!< number of intervals in x-direction
    int    _ny;         //!< number of intervals in y-direction
};

// *********************************************************************




#endif
