// 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 _LTETRA_CUTMESH_H_
#define _LTETRA_CUTMESH_H_

#include "common.h"
#include "LVertex.h"

#define NOT_REFINED 0
#define TO_REFINE 1
#define SPLITTED 2
#define REFINED 3

#define FULL_SPLIT 63

#define TO_ERASE 8
#define ERASED 100
#define UNDEFINED 1000

class LVertex;
class LTetra;

class LTetraRefineField
{
  public :
    char bit_edge;
    int _state;
    int refdepth;
    LTetra *_father;
    LTetra *_owner;
    LTetra *child[8];
    int generation;
    std::vector<LVertex *> incv;
    std::vector<LVertex *> incn;
    double rl;

  public :
    LTetraRefineField ( LTetra *t );

    void split_edge ( int edge );
    int edge_state ( int edge );
    int edge_split_count();

    inline int edge_code()
    {
      return ( int ) bit_edge;
    }

    inline void setState ( int state )
    {
      _state = state;
    }
    inline int getState()
    {
      return _state;
    }
    inline void setRefDepth ( int i )
    {
      refdepth = i;
    }
    inline int getRefDepth()
    {
      return refdepth;
    }

    void setFather ( LTetra* p );
    void resetFather();

    inline LTetra* getFather()
    {
      return _father;
    }
    inline void setChild ( int i, LTetra *c )
    {
      assert ( i>=0 && i<8 );
      child[i] = c;
    }
    inline LTetra* getChild ( int i )
    {
      assert ( i>=0 && i<8 );
      return child[i];
    }
    inline void setGen ( int g )
    {
      generation = g;
    }
    inline int getGen()
    {
      return generation;
    }

    inline void addInc ( LVertex *v, LVertex *n )
    {
      incv.push_back ( v );
      incn.push_back ( n );
    }

    inline void getInc ( int i, LVertex *&v, LVertex *&n )
    {
      assert ( i < incv.size() );
      v = incv[i];
      n = incn[i];
    }

    inline int inc_size()
    {
      return incv.size();
    }

    inline double getlenght()
    {
      return rl;
    }
};

class LTetra
{
  private :
    LVertex *_v[4];
    LTetra *_a[4];
    int _o[4];
    LTetra *_p;  // father
    std::vector<LTetra*> _c;
    int id;
    int index;
    int _vol;
    int _oldvol;
    int flag;
    int _is_parent;
    LTetraRefineField *_tf;


  public :
    LTetra ( LVertex *v0, LVertex *v1, LVertex *v2, LVertex *v3, LTetra *parent );
    inline void setAdj ( int i, LTetra *t, int opp )
    {
      _a[i] = t;
      _o[i] = opp;
    }
    inline LTetra *getAdj ( int i )
    {
      assert ( i>=0 && i<4 );
      return _a[i];
    }
    int getOpp ( int i )
    {
      assert ( i>=0 && i<4 );
      return _o[i];
    }
    inline void getAdjOpp ( int i, LTetra **t, int *opp )
    {
      assert ( i>=0 && i<4 );
      *t = _a[i];
      *opp = _o[i];
    }
    inline LVertex *getVertex ( int i )
    {
      assert ( i>=0 && i<4 );
      return _v[i];
    }
    inline void setVertex ( int i, LVertex *v )
    {
      assert ( i>=0 && i<4 );
      assert ( v != NULL );
      _v[i] = v;
    }
    inline int getEdgeIndex ( LVertex *v0, LVertex *v1 )
    {
      assert ( v0 != NULL );
      assert ( v1 != NULL );
      int edge = 0;
      for ( int i = 0; i < 4; i++ )
        if ( v0 == getVertex ( i ) )
          for ( int j = i+1; j < 4; j++ )
            if ( v1 == getVertex ( j ) )
              return edge;
            else
              edge++;
        else if ( v1 == getVertex ( i ) )
          for ( int j = i+1; j < 4; j++ )
            if ( v0 == getVertex ( j ) )
              return edge;
            else
              edge++;
        else
          edge += ( 3-i );
        return edge;
    }
    inline int getEdgeIndex ( int v0, int v1 )
    {
      assert ( ( v0 >= 0 ) && ( v0 < 4 ) );
      assert ( ( v1 >= 0 ) && ( v1 < 4 ) );
      static int e[4][4] =
      {
        {-1, 0, 1, 2},
        {0, -1, 3, 4},
        {1, 3, -1, 5},
        {2, 4, 5, -1}
      };

      return e[v0][v1];
    }
    inline int getFaceEdgeIndex ( int face, int edge )
    {
      assert ( face>=0 && face<4 );
      assert ( edge>=0 && edge<3 );

      static int fe[4][3] = {{4,5,3,},{1,5,2},{2,4,0},{0,3,1}}; // indice arete face
      return fe[face][edge];
    }
    inline int getFaceIndex ( int v0, int v1, int v2 )
    {
      assert ( ( v0 >= 0 ) && ( v0 < 4 ) );
      assert ( ( v1 >= 0 ) && ( v1 < 4 ) );
      assert ( ( v2 >= 0 ) && ( v2 < 4 ) );
      static int f[4][4][4] =
      {
        {{-1, -1, -1, -1}, {-1, -1, 3, 2}, {-1, 3, -1, 1}, {-1, 2, 1, -1}},
        {{-1, -1, 3, 2}, {-1, -1, -1, -1}, {3, -1, -1, 0}, {3, -1, 0, -1}},
        {{-1, 3, -1, 1}, {3, -1, -1, 0}, {-1, -1, -1, -1}, {1, 0, -1, -1}},
        {{-1, 2, 1, -1}, {3, -1, 0, -1}, {1, 0, -1, -1}, {-1, -1, -1, -1}}
      };

      return f[v0][v1][v2];
    }
    inline void setMutualAdj ( int opp1, LTetra *t2, int opp2 )
    {
      assert ( opp1>=0 && opp1<4 );
      assert ( opp2>=0 && opp2<4 );
      assert ( t2 != NULL );
      setAdj ( opp1, t2, opp2 );
      t2->setAdj ( opp2, this, opp1 );
    }
    inline int getEdgeVertexIndex ( int edge, int vertex )
    {
      assert ( edge>=0 && edge<6 );
      assert ( vertex>=0 && vertex<2 );

      static int ev[6][2] = {{0, 1},{0, 2},{0, 3},{1, 2},{1, 3},{2, 3}}; // indice sommet arete
      return ev[edge][vertex];
    }
    inline LVertex *getEdgeVertex ( int edge, int vertex )
    {
      assert ( edge>=0 && edge<6 );
      assert ( vertex>=0 && vertex<2 );

      return getVertex ( getEdgeVertexIndex ( edge, vertex ) );
    }
    inline int getOppEdgeVertexIndex ( int edge, int vertex )
    {
      assert ( edge>=0 && edge<6 );
      assert ( vertex>=0 && vertex<2 );

      static int ev[6][2] = {{0, 1},{0, 2},{0, 3},{1, 2},{1, 3},{2, 3}}; // indice sommet arete
      static int eo[6] = {5, 4, 3, 2, 1, 0}; // indice arete opposee
      return ev[eo[edge]][vertex];
    }

    inline LVertex *getOppEdgeVertex ( int edge, int vertex )
    {
      assert ( edge>=0 && edge<6 );
      assert ( vertex>=0 && vertex<2 );

      return getVertex ( getOppEdgeVertexIndex ( edge, vertex ) );
    }

    inline int getFaceVertexIndex ( int face, int vertex )
    {
      assert ( face>=0 && face<4 );
      assert ( vertex>=0 && vertex<3 );

      static int fv[4][3] = {{1,3,2},{0,2,3},{0,3,1},{0,1,2}}; // indice sommet  face
      return fv[face][vertex];
    }
    inline LVertex *getFaceVertex ( int face, int vertex )
    {
      assert ( face>=0 && face<4 );
      assert ( vertex>=0 && vertex<3 );

      return getVertex ( getFaceVertexIndex ( face, vertex ) );
    }
    inline int getFaceEdgeVertexIndex ( int face, int edge, int vertex )
    {
      assert ( face>=0 && face<4 );
      assert ( edge>=0 && edge<3 );
      assert ( vertex>=0 && vertex<2 );

      return getFaceVertexIndex ( face, ( edge+vertex ) %3 );
    }
    inline LVertex *getFaceEdgeVertex ( int face, int edge, int vertex )
    {
      assert ( face>=0 && face<4 );
      assert ( edge>=0 && edge<3 );
      assert ( vertex>=0 && vertex<2 );

      return getVertex ( getFaceEdgeVertexIndex ( face, edge, vertex ) );
    }
    inline int getOppEdgeOppVertexIndex ( int edge, int vertex )
    {
      assert ( edge>=0 && edge<6 );
      assert ( vertex>=0 && vertex<4 );

      static int ev[6][4] = {{-1, -1, 3, 2},{-1, 3, -1, 1},{-1, 2, 1, -1},{3, -1, -1, 0},{2, -1, 0, -1},{1, 0, -1, -1}}; // indice sommet arete
      assert ( ev[edge][vertex] != -1 );
      return ev[edge][vertex];
    }
    inline LVertex *getOppEdgeOppVertex ( int edge, int vertex )
    {
      assert ( edge>=0 && edge<6 );
      assert ( vertex>=0 && vertex<4 );

      return getVertex ( getOppEdgeOppVertexIndex ( edge, vertex ) );
    }
    LVertex normal ( int face );
    int barycentric ( LVertex *c, double b[4] );
    int barycentric ( int face, LVertex *c, double b[3] );
    int contain ( LVertex *v );
    int visibility ( int face, LVertex *v, double b[4], LVertex **inter, int *inside );
    double volume();
    double volume ( int face, LVertex *v );
    void split_edge ( int edge, LVertex *newv, LTetra **t1, LTetra **t2 );
    void split_edge ( int edge )
    {
      //   printf("before --> %d\n", edge_state(edge));
      assert ( edge>= 0 && edge <6 );
      getRefineField()->bit_edge |= ( 1U << edge );
      //   printf("after --> %d\n", edge_state(edge));
    }
    void split_face ( int face, LVertex *newv, LTetra **t1, LTetra **t2, LTetra **t3 );
    void split ( LVertex *newv, LTetra **t1, LTetra **t2, LTetra **t3, LTetra **t4 );
    inline void resetFlag ()
    {
      flag = -1;
    }
    inline void setFlag ( int f )
    {
      flag = f;
    }
    inline void incFlag ()
    {
      flag++;
    }
    inline void decFlag ()
    {
      flag--;
    }
    inline int getFlag()
    {
      return flag;
    }
    inline void setIndex ( int i )
    {
      index = i;
    }
    inline int getIndex()
    {
      return index;
    }
    inline void setId ( int i )
    {
      if ( id == -1 )
        id = i;
    }
    inline int getId()
    {
      return id;
    }
    inline void setParent()
    {
      _is_parent = 1;
    }
    inline int isParent()
    {
      return _is_parent;
    }

    inline void setParent ( LTetra *parent )
    {
      if ( _p == NULL )
        _p = parent;
    }
    inline LTetra *getParent()
    {
      return _p;
    }
    inline LTetra *getAncestor()
    {
      if ( !getParent() )
        return NULL;
      LTetra *p = this;
      while ( p->getParent() != NULL )
      {
        p = p->getParent();
      }
      return p;
    }
    inline void add_child ( LTetra *t )
    {
      assert ( t != NULL );
//       printf("add %p (%d) to %p (%d)\n", t, t->getIndex(), this, this->getIndex());
      _c.push_back ( t );
    }
    inline LTetra* get_child ( int i )
    {
      assert ( i>=0 && i<_c.size() );
      return _c[i];
    }
    inline int child_size()
    {
      return _c.size();
    }
    inline void clear_child()
    {
      _c.clear();
    }
    inline void setVol ( int v )
    {
      _vol = v;
    }
    inline int getVol()
    {
      return _vol;
    }
    inline void setOldVol ( int v )
    {
      _oldvol = v;
    }
    inline int getOldVol()
    {
      return _oldvol;
    }
    inline void addRefineField()
    {
      _tf = new LTetraRefineField ( this );
    }
    inline void removeRefineField()
    {
      delete _tf;
      _tf = NULL;
    }

    inline LTetraRefineField *getRefineField()
    {
      assert ( _tf );
      return _tf;
    }

    void print();

};

#endif
