#include <models/CMesh.h>

#include <MDSTk/Module/mdsSerializable.h>
#include <MDSTk/Math/mdsRandom.h>

//=====================================================================================================================
CSmoothingMesh::CSmoothingMesh() :
        vctl::MCTriS()
{
}

//=====================================================================================================================
CSmoothingMesh::~CSmoothingMesh()
{
    this->ClearAll();
    this->disposeVertexInfo();
}

//=====================================================================================================================
bool    CSmoothingMesh::loadFile( const std::string & name )
{
    mds::mod::CFileChannel	    channel( mds::mod::CH_IN, name );
    bool                        result = this->LoadSTL( channel );

    if ( result )
    {
        vctl::MCVerticeS *  vertices    = this->GetVerticeS();
        vctl::MCVertex * vertex         = vertices->GetFirst();

        while ( vertex )
        {
            vertex->SetValuePtr( 0 );
            vertex = vertex->GetNext();
        }

        //bMeshDirty = true;
        return true;
    }
    else return false;
}

//=====================================================================================================================
bool    CSmoothingMesh::saveFile( const std::string & name )
{
    mds::mod::CFileChannel      channel( mds::mod::CH_OUT, name );
    bool ok = this->SaveSTL( channel );
    channel.disconnect();
    return ok;
}

//=====================================================================================================================
bool    CSmoothingMesh::disposeVertexInfo()
{
    vctl::MCVerticeS * vertices = this->GetVerticeS();
    vctl::MCVertex * vertex     = vertices->GetFirst();
    CVertexInfo * info;

    while ( vertex )
    {
        info = static_cast< CVertexInfo* >( vertex->GetValuePtr() );
        if ( info )
        {
            delete info;
            vertex->SetValuePtr( 0 );            
        }
        vertex = vertex->GetNext();
    }
	return true;
}

//=====================================================================================================================
bool    CSmoothingMesh::getNeighbourTri( vctl::MCVertex * actual_vertex, tTriPtrList & neighbour_tri, int neighbour_layers )
{
 std::vector<vctl::MCTri *>                work_tri_vector;                // vektor lokalnich tri kolem pracovniho vrcholu
  vctl::MCTri                               * work_tri;                     // ukazatel na aktualni pracovni tri
  std::vector<vctl::MCTri *>::size_type     neighbour_tri_find_size;        // pocet sousdnich tri k prohledani
  vctl::MCVertex                            * work_vertex;                  // ukazatel na pracovni vrchol prochazeneho tri
  const int                                 mark_flag = 1024;               // hodnota flagu pro oznacovani prochazenych prvku


  // ziskani tri okolo aktualniho vrcholu
  actual_vertex->GetRegisteredTriList(neighbour_tri);

  // oznaceni aktualniho vrcholu
  actual_vertex->SetFlag(mark_flag);

  // cyklus poctu okolnich vrstev
  for (int neighbour_layers_index = 1; neighbour_layers_index < neighbour_layers; ++neighbour_layers_index)
  {
    // nastaveni poctu a iteratoru sousednich tri k prohledani
    neighbour_tri_find_size = neighbour_tri.size();

    // cyklus okolnich tri okolo aktualniho vrcholu
    for (std::vector<vctl::MCTri *>::size_type neighbour_tri_index = 0; neighbour_tri_index < neighbour_tri_find_size; ++neighbour_tri_index)
    {
      // ziskani ukazatele na aktualne prohledavany tri
      work_tri = neighbour_tri[neighbour_tri_index];
      // cyklus vrcholu aktualniho tri vektoru
      for (int vertext_index = 0; vertext_index < 3; ++vertext_index)
      {
        // ziskani ukazatele na vrchol aktualniho tri
        work_vertex = work_tri->GetVertex(vertext_index);
        // test oznaceni aktualniho vrcholu aktualniho tri
        if ( ! work_vertex->TestFlag(mark_flag))
        {
          // ziskani tri okolo daneho vrcholu
          work_vertex->GetRegisteredTriList(work_tri_vector);
          // cyklus lokalnich tri kolem pracovniho vrcholu
          for (std::vector<vctl::MCTri *>::iterator work_tri_iter = work_tri_vector.begin(); work_tri_iter != work_tri_vector.end(); ++work_tri_iter)
          {
            // test oznaceni lokalniho tri kolem pracovniho vrcholu
            if ( ! (*work_tri_iter)->TestFlag(mark_flag))
            {
              // oznaceni lokalniho tri
              (*work_tri_iter)->SetFlag(mark_flag);
              // vlozeni lokalniho tri do vysledneho vektoru sousednich tri
              neighbour_tri.push_back( *work_tri_iter );
            }
          }
          // oznaceni pracovniho vrcholu
          work_vertex->SetFlag(mark_flag);
        }
      }
    }
  }

  // cyklus nalezenych tri
  for (std::vector<vctl::MCTri *>::iterator neighbour_tri_iter = neighbour_tri.begin(); neighbour_tri_iter != neighbour_tri.end(); ++neighbour_tri_iter)
  {
    // zruzeni oznaceni aktualniho tri i jejich vrcholu
    (*neighbour_tri_iter)->MaskFlag(mark_flag);
    (*neighbour_tri_iter)->GetVertex(0)->MaskFlag(mark_flag);
    (*neighbour_tri_iter)->GetVertex(1)->MaskFlag(mark_flag);
    (*neighbour_tri_iter)->GetVertex(2)->MaskFlag(mark_flag);
  }

  return 0;
}

//=====================================================================================================================
bool    CSmoothingMesh::getNeighbourVert( vctl::MCVertex * actual_vertex, tVertPtrList & neighbour_vertex, int neighbour_layers )
{

std::vector<vctl::MCTri *>                    neighbour_tri;                  // vektor sousednich tri daneho vrcholu
  vctl::MCVertex                                * tri_vertex[3];                // pole vrcholu aktualniho tri
  const int                                     mark_flag = 1024;               // hodnota flagu pro oznacovani prochazenych prvku
  std::vector<vctl::MCVertex *>::size_type      neighbour_vertex_find_size;     // pocet sousdnich vrcholu k prohledani
  std::vector<vctl::MCVertex *>::size_type      neighbour_vertex_index;         // index aktualniho sousedniho vrcholu
  vctl::MCVertex                                * work_vertex;                  // ukazatel na aktualni pracovni vrchol
  std::vector<vctl::MCTri *>::iterator          neighbour_tri_iter;             // iterator na tri sousedni pro aktualni vrchol


  // ziskani tri okolo aktualniho vrcholu
  actual_vertex->GetRegisteredTriList(neighbour_tri);

  // oznaceni aktualniho vrcholu
  actual_vertex->SetFlag(mark_flag);

  // inicializace vysledneho vektoru
  neighbour_vertex.clear();

  // cyklus sousednich tri okolo aktualniho vrcholu
  for (neighbour_tri_iter = neighbour_tri.begin(); neighbour_tri_iter != neighbour_tri.end(); ++neighbour_tri_iter)
  {
    // ziskani vrcholu aktualniho tri
    (*neighbour_tri_iter)->GetVerticeS(tri_vertex);
    // cyklus vrcholu aktualniho tri
    for (int vertex_index = 0; vertex_index < 3; ++vertex_index)
    {
      // test oznaceni vrcholu aktualniho tri
      if ( ! tri_vertex[vertex_index]->TestFlag(mark_flag))
      {
        // oznaceni aktualniho vrcholu aktualniho tri
        tri_vertex[vertex_index]->SetFlag(mark_flag);
        // vlozeni aktualniho vrcholu aktualniho tri do vektoru sousednich vrcholu
        neighbour_vertex.push_back(tri_vertex[vertex_index]);
      }
    }
  }

  // cyklus poctu okolnich vrstev
  for (int neighbour_layers_index = 1; neighbour_layers_index < neighbour_layers; ++neighbour_layers_index)
  {
    // nastaveni poctu sousednich vrcholu k prohledani
    neighbour_vertex_find_size = neighbour_vertex.size();

    // cyklus okolnich tri okolo aktualniho vrcholu
    for (neighbour_vertex_index = 0; neighbour_vertex_index < neighbour_vertex_find_size; ++neighbour_vertex_index)
    {
      // ziskani ukazatele na aktualne prohledavany tri
      work_vertex = neighbour_vertex[neighbour_vertex_index];
      // ziskani tri okolo pracovniho vrcholu
      work_vertex->GetRegisteredTriList(neighbour_tri);
      // cyklus sousednich tri okolo aktualniho vrcholu
      for (neighbour_tri_iter = neighbour_tri.begin(); neighbour_tri_iter != neighbour_tri.end(); ++neighbour_tri_iter)
      {
        // ziskani vrcholu aktualniho tri
        (*neighbour_tri_iter)->GetVerticeS(tri_vertex);
        // cyklus vrcholu aktualniho tri
        for (int vertex_index = 0; vertex_index < 3; ++vertex_index)
        {
          // test oznaceni vrcholu aktualniho tri
          if ( ! tri_vertex[vertex_index]->TestFlag(mark_flag))
          {
            // oznaceni aktualniho vrcholu aktualniho tri
            tri_vertex[vertex_index]->SetFlag(mark_flag);
            // vlozeni aktualniho vrcholu aktualniho tri do vektoru sousednich vrcholu
            neighbour_vertex.push_back(tri_vertex[vertex_index]);
          }
        }
      }
    }
  }

  // cyklus nalezenych vrcholu pro vymazani jejich oznaceni
  for (std::vector<vctl::MCVertex *>::iterator neighbour_vertex_iter = neighbour_vertex.begin(); neighbour_vertex_iter != neighbour_vertex.end(); ++neighbour_vertex_iter)
    // zruzeni oznaceni aktualniho vrcholu
    (*neighbour_vertex_iter)->MaskFlag(mark_flag);

  // zruseni oznaceni aktualniho vrcholu
  actual_vertex->MaskFlag(mark_flag);

  return true;
}

//=====================================================================================================================
vctl::MCVector3D CSmoothingMesh::getNormal( vctl::MCVertex * vert )
{
    std::vector< vctl::MCTri *> tri_list;
    vert->GetRegisteredTriList( tri_list );

    vctl::MCVector3D    n( 0.0, 0.0, 0.0 );
    vctl::MCVector3D    nt;

    int c = 0;
    for ( int j = 0; j < (int)tri_list.size(); j++ )
    {
        tri_list[j]->GetNormal( nt );
        n = n + nt;
        c++;
    }

    n.SetX( n.GetX() / c );
    n.SetY( n.GetY() / c );
    n.SetZ( n.GetZ() / c );
    return n;
}

//=====================================================================================================================
void     CSmoothingMesh::getNormal(vctl::MCVertex * actual_vertex, vctl::MCVector3D * new_normal)
{
  std::vector<vctl::MCTri *>          neighbour_tri;                    // vektor nalezenych sousednich tri daneho vrcholu
  vctl::MCVector3D                    actual_tri_normal;                // normala aktualniho tri
  double                              actual_tri_area;                  // plocha aktualniho tri
  double                              neighbour_tri_area_sum = 0;       // celkova plocha sousednich tri


  // ziskani tri okolo aktualniho vrcholu
  actual_vertex->GetRegisteredTriList(neighbour_tri);

  // inicializace normaly
  new_normal->SetXYZ(0, 0, 0);

  // cyklus sousednich tri kolem daneho vrcholu
  for (std::vector<vctl::MCTri *>::iterator neighbour_tri_iter = neighbour_tri.begin(); neighbour_tri_iter != neighbour_tri.end(); ++neighbour_tri_iter)
  {
    // vypocet plochy aktualniho sousedniho tri
    actual_tri_area = (*neighbour_tri_iter)->GetArea();
    // vypocet normaly aktualniho sousedniho tri
    actual_tri_normal = (*neighbour_tri_iter)->GetNormal();
    // vazeni normaly plochou jeho tri
    actual_tri_normal *= actual_tri_area;
    // kumulace hodnot
    (*new_normal) += actual_tri_normal;
    neighbour_tri_area_sum += actual_tri_area;
  }

  // normalizace vysledne normaly celkovou plochou
  (*new_normal) *= (1.0 / neighbour_tri_area_sum);
  new_normal->Normalization();
}

//=====================================================================================================================
void    CSmoothingMesh::removeVerticesNormals()
{
  vctl::MCTriS &    processed_mesh = *this;
  vctl::MCVertex    * actual_vertex = (processed_mesh.GetVerticeS())->GetFirst();      // ukazatel na aktualni vrchol site

  // cyklus vrcholu site pro vypocet normal
  while (actual_vertex != NULL)
  {
    // uvolneni alokovane normaly
    if (actual_vertex->GetValuePtr() != NULL)
    {
        delete((vctl::MCVector3D *) actual_vertex->GetValuePtr());
        actual_vertex->SetValuePtr(NULL);
    }

    // ziskani dalsiho vrcholu site a dalsiho bodu
    actual_vertex = actual_vertex->GetNext();
  }
}

//=====================================================================================================================
void    CSmoothingMesh::precomputeVerticesNormals()
{
  vctl::MCTriS     & processed_mesh = *this;
  vctl::MCVertex   * actual_vertex  = (processed_mesh.GetVerticeS())->GetFirst();      // ukazatel na aktualni vrchol site
  vctl::MCVector3D * new_normal;                    // ukazatel na alokovany objekt nove normaly


  // cyklus vrcholu site pro vypocet normal
  while (actual_vertex != NULL)
  {
    // test existence normaly vrcholu
    if (actual_vertex->GetValuePtr() == NULL)
    {
      // alokace objektu nove normaly
      new_normal = new vctl::MCVector3D();
      // vlozeni nove normaly do vrcholu
      actual_vertex->SetValuePtr(new_normal);
    }
    else
    {
        // ziskani ukazatele na existujici normalu
        new_normal = (vctl::MCVector3D *) actual_vertex->GetValuePtr();
    }

    // vypocet prumerne normaly aktualniho vrcholu
    this->getNormal(actual_vertex, new_normal);

    // ziskani dalsiho vrcholu site a dalsiho bodu
    actual_vertex = actual_vertex->GetNext();
  }
}

//=====================================================================================================================
double  CSmoothingMesh::getVolume()
{
    // Model volume
    double Volume = 0.0;

    // Model range
    vctl::MCPoint3D min, max;
    this->GetVerticeS()->GetRange(min, max);

    // Minimal z coordinate
    double MinZ = min.GetZ();

    // Actual tri
    vctl::MCTri * pTri = this->GetFirst();

    while(pTri != NULL)
    {
        // Test tri orientation, tris perpendicular to xy-plane do not involve to accumulate volume
        if(pTri->GetNormal().GetZ() != 0.0)
        {
            // New triangle forms a prism which is added or subtracted from accumulated volume
            vctl::MCVertex v0(*(pTri->GetVertex(0)));
            vctl::MCVertex v1(*(pTri->GetVertex(1)));
            vctl::MCVertex v2(*(pTri->GetVertex(2)));

            v0.SetValuePtr( 0 );
            v1.SetValuePtr( 0 );
            v2.SetValuePtr( 0 );

            double z = ((v0.GetZ() + v1.GetZ() + v2.GetZ()) / 3.0) - MinZ;

            v0.SetZ(z);
            v1.SetZ(z);
            v2.SetZ(z);

            vctl::MCTri tri(&v0, &v1, &v2);

            if(pTri->GetNormal().GetZ() > 0.0)  Volume += tri.GetArea() * z;
            else Volume -= tri.GetArea() * z;
        }
        pTri = pTri->GetNext();
    }
    return Volume;
}
