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

#include "common.h"
#include "LTetra.h"

class LTetra;

class LVertex
{
  private :
    double _p[3];
    LTetra *_t;
    int id;
    int index;
    std::vector<double> _value;
    std::vector<int> _surf;
    std::vector<int> _zero;
    LVertex *_pv[2];
    std::vector<LVertex*> _rv;
    int flag;
    double _c;

  public :
    LVertex()
    {
      _p[0] = _p[1] = _p[2] = 0.0;
      _t = NULL;
      _pv[0] = _pv[1] = NULL;
      id = -1;
      index = -1;
      flag = -1;
      _c = 0.0;
    }
    LVertex ( int i, double x, double y, double z )
    {
      _p[0] = x;
      _p[1] = y;
      _p[2] = z;
      id = i;
      _t = NULL;
      _pv[0] = _pv[1] = NULL;
      index = -1;
      flag = -1;
      _c = 0.0;
    }
    LVertex ( double x, double y, double z )
    {
      _p[0] = x;
      _p[1] = y;
      _p[2] = z;
      id = -1;
      _t = NULL;
      _pv[0] = _pv[1] = NULL;
      index = 0;
      flag = 0;
      _c = 0.0;
    }
    inline void setPosition ( double x, double y, double z )
    {
      _p[0]=x;
      _p[1]=y;
      _p[2]=z;
    }
    inline void setVector ( LVertex *v0, LVertex *v1 )
    {
      _p[0]=v1->x()-v0->x();
      _p[1]=v1->y()-v0->y();
      _p[2]=v1->z()-v0->z();
    }
    inline void setParent ( LVertex *v0, LVertex *v1 )
    {
      _pv[0] = v0;
      _pv[1] = v1;
    }
    inline LVertex *getParent ( int i )
    {
      assert ( i < 2 );
      return _pv[i];
    }
    inline void clearRootParent()
    {
      _rv.clear();
    }
    inline void addRootParent ( LVertex *v )
    {
      assert ( _rv.size() < 4 );
      _rv.push_back ( v );
    }
    inline LVertex *getRootParent ( int i )
    {
      assert ( i < _rv.size() );
      return _rv[i];
    }
    inline int getRootParentSize()
    {
      return _rv.size();
    }
    inline double x()
    {
      return _p[0];
    }
    inline double y()
    {
      return _p[1];
    }
    inline double z()
    {
      return _p[2];
    }
    inline void setTetra ( LTetra *t )
    {
      _t = t;
    }
    inline LTetra *getTetra()
    {
      return _t;
    }
    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 int getId()
    {
      return id;
    }
    inline void crossproduct ( LVertex *v1, LVertex *r )
    {
      r->setPosition ( _p[1]*v1->z()-_p[2]*v1->y(), _p[2]*v1->x()-_p[0]*v1->z(), _p[0]*v1->y()-_p[1]*v1->x() );
    }
    inline double dotproduct ( LVertex *v1 )
    {
      return _p[0]*v1->x() + _p[1]*v1->y() + _p[2]*v1->z();
    }
    inline double norm()
    {
      return sqrt ( _p[0]*_p[0] + _p[1]*_p[1] + _p[2]*_p[2] );
    }
    inline double sq_norm()
    {
      return _p[0]*_p[0] + _p[1]*_p[1] + _p[2]*_p[2];
    }
    inline void normalize()
    {
      double l = norm();
      _p[0] /= l;
      _p[1] /= l;
      _p[2] /= l;
    }
    inline void addSurf ( int s, double v )
    {
      for ( int i = 0; i < _surf.size(); i++ )
        if ( _surf[i] > s )
        {
          _surf.insert ( _surf.begin() +i, s );
          _value.insert ( _value.begin() +i, v );
          return;
        }
      _surf.insert ( _surf.end(), s );
      _value.insert ( _value.end(), v );
    }
    inline void mergeSurf ( LVertex *newv )
    {
      for ( int i = 0; i < newv->nbSurf(); i++ )
      {
        int surf = newv->surfId ( i );
        int value = newv->surfVal ( surf );

        if ( !checkSurf ( surf ) )
        {
          addSurf ( surf, value );
          if ( newv->checkZero ( surf ) )
            addSurfZero ( surf );
        }
      }
    }
    inline void setSurf ( int s, double v )
    {
      for ( int i = 0; i < _surf.size(); i++ )
        if ( _surf[i] == s )
        {
          _value[i] = v;
          return;
        }
      printf ( "Aaaaaaargh !!!\n" );
      assert ( 0 );
    }
    inline void addSurfZero ( int s )
    {
      _zero.push_back ( s );
      setSurf ( s, 0.0 );
    }
    inline double surfVal ( int s )
    {
      for ( int i = 0; i < _surf.size(); i++ )
        if ( _surf[i] == s )
          return _value[i];
      return INFINITY;
    }
    inline int checkSurf ( int s )
    {
      for ( int i = 0; i < _surf.size(); i++ )
        if ( _surf[i] == s )
          return true;
      return false;
    }
    inline int surfId ( int i )
    {
      assert ( i>=0 && i<_surf.size() );
      return _surf[i];
    }
    inline int surfZeroId ( int i )
    {
      assert ( i>=0 && i<_zero.size() );
      return _zero[i];
    }
    inline int checkZero ( int s )
    {
      for ( int i = 0; i < _zero.size(); i++ )
        if ( _zero[i] == s )
          return true;
      return false;
    }
    inline int nbSurf()
    {
      return _surf.size();
    }
    inline int nbSurfZero()
    {
      return _zero.size();
    }
    inline void dumpSurf()
    {
      printf ( "## LVertex %d --> surf :", index );
      for ( int i = 0; i < _surf.size(); i++ )
      {
        printf ( " %d (%lf)", _surf[i], _value[i] );
      }
      printf ( "\n" );
    }
    inline void dumpSurfZero()
    {
      printf ( "## LVertex %d --> surf zero :", index );
      for ( int i = 0; i < _zero.size(); i++ )
      {
        printf ( " %d", _zero[i] );
      }
      printf ( "\n" );
    }
    inline void eraseSurf ( int s )
    {
      printf ( "Erasing surf %d on tetra %d\n", s, getIndex() );
      for ( int i = 0; i < _surf.size(); i++ )
        if ( _surf[i] == s )
          _surf.erase ( _surf.begin() + i );
      for ( int i = 0; i < _zero.size(); i++ )
        if ( _zero[i] == s )
          _zero.erase ( _zero.begin() + i );
    }
    void computeRootParents();
    inline void setCurvature ( double c )
    {
      _c = c;
    }
    inline double getCurvature()
    {
      return _c;
    }
};

#endif
