// Cutmesh - Copyright (C) 2010-2018 T. Mouton, E. Bechet 
//
// See the LICENSE file for license information and contributions.
// bugs and problems to <thibaud.mouton@gmail.com>.

#ifndef _SURFACE_CUTMESH_H_
#define _SURFACE_CUTMESH_H_

#include "common.h"
#include "mesh.h"

class CutEdge;
class CutFace;

class CutFace
{
private:
    LTetra *_t[2];
    int _f[2];
    std::vector<CutEdge *> _edges;
    std::vector<int> _surf;
    int free_edge;
    int _color;

public:
    CutFace(LTetra *t, int f, std::vector<int> surf_id, int sign)
    {
        if (sign)
        {
            _t[0] = t->getAdj(f);
            _f[0] = t->getOpp(f);
            _t[1] = t;
            _f[1] = f;
        }
        else
        {
            _t[0] = t;
            _f[0] = f;
            _t[1] = t->getAdj(f);
            _f[1] = t->getOpp(f);
        }
        for (int i = 0; i < surf_id.size(); i++)
            _surf.push_back(surf_id[i]);
        free_edge = 0;
        _color = -1;
    }
//     inline size_t memory()
//     {
//       return sizeof(*this) + _edges.size()*sizeof(CutEdge*) + _surf.size()*sizeof(int);
//     }
    inline LVertex *get_face_vertex(int i)
    {
        assert((i>=0) && (i<3));
        return _t[0]->getFaceVertex(_f[0], i);
    }
    inline LVertex *get_edge_vertex(int edge, int i)
    {
        assert((i>=0) && (i<2));
        return _t[0]->getFaceEdgeVertex(_f[0], edge, i);
    }
    inline LVertex *get_tetra_vertex(int i)
    {
        assert((i>=0) && (i<4));
        return _t[0]->getVertex(i);
    }
    inline void add_edge(CutEdge *e)
    {
        _edges.push_back(e);
    }
    inline CutEdge *get_edge(int i)
    {
        assert(i<_edges.size());
        return _edges[i];
    }
    inline int edge_size()
    {
        return _edges.size();
    }
    inline LTetra *int_tetra()
    {
        return _t[0];
    }
    inline int int_face()
    {
        return _f[0];
    }
    inline LTetra *ext_tetra()
    {
        return _t[1];
    }
    inline int ext_face()
    {
        return _f[1];
    }
    inline LVertex *int_vertex()
    {
        return _t[0]->getVertex(_f[0]);
    }
    inline LVertex *ext_vertex()
    {
        if (_t[1])
	  return _t[1]->getVertex(_f[1]);
	else
	  return NULL;
    }
    inline void inc_free()
    {
        free_edge++;
    }
    inline void dec_free()
    {
        free_edge--;
    }
    inline void set_free(int i)
    {
        free_edge = i;
    }
    inline int free()
    {
        return free_edge;
    }
    inline void add_surface(int surf)
    {
        for (int i = 0; i < _surf.size(); i++)
            if (_surf[i] == surf)
                return;
        _surf.push_back(surf);
        std::sort(_surf.begin(), _surf.end());
    }
    inline int surface(int i)
    {
        assert(i<_surf.size());
        return _surf[i];
    }
    inline int same_surface(CutFace *cf)
    {
        assert(cf != NULL);
        ////////////////////////////////////
        //////////// version 1 /////////////
        ////////////////////////////////////
#if 0
        if (_surf.size() != cf->surf_size())
            return false;
        for (int i = 0; i < _surf.size(); i++)
            if (_surf[i] != cf->surface(i))
                return false;
        return true;
#endif
        ////////////////////////////////////
        //////////// version 2 /////////////
        ////////////////////////////////////
#if 0
        int found = 0;
        for (int i = 0; i < _surf.size(); i++)
        {
            for (int j = 0; j < cf->surf_size(); j++)
            {
                if (_surf[i] == cf->surface(j))
                {
                    found++;
                    break;
                }
            }
        }
        if ((_surf.size() >= cf->surf_size()) && (found == cf->surf_size()))
            return true;
        else if (found == _surf.size())
            return true;
        return false;
#endif
        ////////////////////////////////////
        //////////// version 3 /////////////
        ////////////////////////////////////
	// au moins une couleur correspond !
#if 1
        for (int i = 0; i < _surf.size(); i++)
            for (int j = 0; j < cf->surf_size(); j++)
                if (_surf[i] == cf->surface(j)) 
		  return true;
        return false;
#endif
    }
    inline int surf_size()
    {
        return _surf.size();
    }
    inline void set_no_adj()
    {
        _t[0]->setAdj(_f[0], NULL, -1);
        _t[1]->setAdj(_f[1], NULL, -1);
    }
    inline void set_adj()
    {
        _t[0]->setAdj(_f[0], _t[1], _f[1]);
        _t[1]->setAdj(_f[1], _t[0], _f[0]);
    }
    inline void set_color( int color )
    {
        _color = color;
    }
    inline int get_color()
    {
        return _color;
    }
};

class CutEdge
{
private:
    std::vector<CutFace *> _faces;
    std::vector<int> _edge_id;
    CutEdge *_adj[2];
    int _dir;
    int _eid;
    int _color;
    int _tang;
    int _flag;

public:
    CutEdge(CutFace *f, int e_id)
    {
        _faces.push_back(f);
        _edge_id.push_back(e_id);
        _adj[0] = NULL;
        _adj[1] = NULL;
	_dir = 0;
        _eid = 0;
	_tang = 0;
	_color = -1;
        _flag = -1;
    }
//     inline size_t memory()
//     {
//       return sizeof(*this) + _faces.size()*sizeof(CutFace*) + _edge_id.size()*sizeof(int);
//     }
    inline void addFace(CutFace *f, int e_id)
    {
        assert(f != NULL);
        assert(e_id < 3);
        _faces.push_back(f);
        _edge_id.push_back(e_id);
    }

    inline CutFace *get_face(int i)
    {
        assert(i<_faces.size());
        return _faces[i];
    }
    inline int get_edge_id(int i)
    {
        assert(i<_edge_id.size());
        return _edge_id[i];
    }
    inline int get_default_face_color()
    {
        return _faces[_eid]->get_color();
    }
    inline CutFace *get_default_face()
    {
        return _faces[_eid];
    }
    inline int get_default_edge_id()
    {
        return _edge_id[_eid];
    }
    inline void reset_default()
    {
        _eid = 0;
        for (int i = 0; i < _faces.size(); i++)
            if (_faces[_eid]->get_color() < _faces[i]->get_color())
                _eid = i;
    }
    inline LVertex *get_vertex(int i)
    {
        assert((i>=0) && (i<2));
        return get_face(_eid)->get_edge_vertex(_edge_id[_eid], (i+_dir)%2);
    }

    inline LVertex *get_face_vertex(int i)
    {
        assert((i>=0) && (i<3));
        return get_face(_eid)->get_face_vertex(i);
    }
    inline LVertex *get_tetra_vertex(int i)
    {
        assert((i>=0) && (i<4));
        return get_face(_eid)->get_tetra_vertex(i);
    }

    inline int face_size()
    {
        return _faces.size();
    }

    inline void erase(int i)
    {
        _faces.erase(_faces.begin()+i);
        _edge_id.erase(_edge_id.begin()+i);
    }
    inline CutEdge *get_adj(int i)
    {
        assert(i<2);
        return _adj[i];
    }
    inline LTetra *get_rand_tetra()
    {
      for (int i = 0; i < face_size(); i++)
      {
	LTetra* t = get_face(i)->int_tetra();
	if (t)
	  return t;
      }
      return NULL; // pb !
    }
    inline void set_adj(int i, CutEdge *ce)
    {
        assert(i<2);
        _adj[i] = ce;
    }
    inline void invert_dir()
    {
        _dir = !_dir;
    }
    inline void invert_adj()
    {
        CutEdge *tmp = _adj[0];
        _adj[0] = _adj[1];
        _adj[1] = tmp;
    }
    inline void set_color( int color )
    {
        _color = color;
    }
    inline int get_color()
    {
        return _color;
    }
    inline void set_tangent()
    {
        _tang = 1;
    }
    inline void unset_tangent()
    {
        _tang = 0;
    }
    inline int is_tangent()
    {
        return _tang;
    }
    inline void set_flag(int i)
    {
        _flag = i;
    }
    inline void inc_flag ()
    {
        _flag++;
    }
    inline void dec_flag ()
    {
        _flag--;
    }
    inline int get_flag()
    {
        return _flag;
    }
};

class LSurface
{
protected:
    std::vector<CutFace*> _face_list;
    std::vector<CutEdge*> _edge_list;
    std::multimap<int, hashEdgeKey*> _edge_hashtable;
    std::multimap<int, hashFaceKey*> _face_hashtable;
    std::queue<CutFace *> _sc; // pile de coloration
    std::vector<CutEdge *> _border_edge_list;
    std::vector<LVertex *> _vertex_border_list;
    std::multimap<LVertex *, CutEdge *> _border_edge_hashtable;
    int _nb_topo_face;
    int _nb_topo_edge;
    
    std::map<int, int> _vertex_to_topoface_index;
    std::map<int, int> _vertex_to_topoedge_index;
    std::map<int, int> _vertex_to_topovertex_index;
    
    EntityManager *_em;
    options *_opts;
    

public:
    LSurface(options *opt, EntityManager *em)
    {
      _nb_topo_face = 0;
      _nb_topo_edge = 0;
      _opts = opt;
      _em = em;
    }
//     inline size_t memory()
//     {
//       return sizeof(*this) + _face_list.size()*(sizeof(CutFace) + sizeof(CutFace*)) + _edge_list.size()*(sizeof(CutEdge) + sizeof(CutEdge*));
//     }
    void make_correction(Mesh *m);
    void extract_cutfaces(Mesh* m);
    void build_surface(Mesh *m);
    void process_tangent_edges();
    int erode_surface();
    void compute_face_topo();
    void compute_edge_topo();
//     void analyse_surface (Mesh *m);
    int addFace(LTetra *t, int face, std::vector<int>surf, int sign);
    int addEdges(CutFace *face);
    void addBorderEdge(CutEdge *ce);
    void linkBorderEdges();
    void debug_border_edge();
    void debug_edge();
    void debug_edge_vertices();
    inline CutFace *get_face(int i)
    {
        assert(i < _face_list.size());
        return _face_list[i];
    }
    inline CutEdge *get_edge(int i)
    {
        assert(i < _edge_list.size());
        return _edge_list[i];
    }
    inline CutEdge *get_border_edge(int i)
    {
        assert(i < _border_edge_list.size());
        return _border_edge_list[i];
    }
    inline int face_size()
    {
        return _face_list.size();
    }
    inline int edge_size()
    {
        return _edge_list.size();
    }
    inline int border_edge_size()
    {
        return _border_edge_list.size();
    }
    inline int topo_edge_size()
    {
        return _nb_topo_edge;
    }
    inline int topo_face_size()
    {
        return _nb_topo_face;
    }
    inline void set_link(CutEdge *p, CutEdge *n)
    {
	assert(p != NULL);
	assert(n != NULL);
        assert(p->get_vertex(1) == n->get_vertex(0));
        n->set_adj(0, p);
        p->set_adj(1, n);
    }
    inline void unset_link(CutEdge *p, CutEdge *n)
    {
      	assert(p != NULL);
	assert(n != NULL);
	assert(p->get_vertex(1) == n->get_vertex(0));
        n->set_adj(0, NULL);
        p->set_adj(1, NULL);
    }
    inline void seed(CutFace *face)
    {
        _sc.push(face);
    }
    int color(int surf, int unfilled_surf);
    void dumpVTK(const char *name, bool number=false);
    void debugDumpVTK(const char *name);
    void clear();
    
    inline EntityManager* get_entity_manager()
    {
      return _em;
    } 
};

#endif
