/*
    C++ Mesh Generation Library
    Copyright (c) 2000echet <eric at bechet dot ca>

    This file is part of the C++ Mesh Generation Library.

    See the NOTICE & LICENSE files for conditions.
*/
#include "discrete_surface_geometry.h"

#include <fstream>
#include <cstring>
#include "simple_mesh.h"



Discrete_Surface_Geometry::Vertex_Iterator Discrete_Surface_Geometry::Add_Vertex ( Vertex_Type &Ver )
{
  pair<Vertex_Classification_Iterator,bool> P;
  Vertex_Iterator I;
  I=Vertices.insert ( Vertices.end(),Ver );
  P=Classification_Vertices.insert ( I );

  if ( P.second )
  {
    ( *I ).SetUniqueID();
    ( *I ).Set_Classification_Iterator ( P.first );
  }
  else
  {
    Vertices.erase ( I );
    I=* ( P.first );
  }

  return I;
}

Discrete_Surface_Geometry::Vertex_Type* Discrete_Surface_Geometry::Get_Vertex ( Vertex_Type &Ver )
{
  pair<Vertex_Classification_Iterator,bool> P;
  Vertex_Iterator I;
  I=Vertices.insert ( Vertices.end(),Ver );
  P=Classification_Vertices.insert ( I );


  if ( P.second )
  {
    Vertices.erase ( I );
    return NULL;
  }
  else
  {
    Vertices.erase ( I );
    I=* ( P.first );
  }

  return & ( *I );
}


Discrete_Surface_Geometry::Patch_Type* Discrete_Surface_Geometry::Add_Patch ( Patch_Type &Ele )
{
  Patch_Type_Container::iterator P;
  P=Patches.insert ( Patches.end(),Ele );
  ( *P ).SetUniqueID();
  return ( Patch_Type * ) & ( * ( P ) );
}


Discrete_Surface_Geometry::Topological_Face_Type* Discrete_Surface_Geometry::Add_Topological_Face ( Topological_Face_Type &Fac )
{
  pair<Topological_Face_Type_Container::iterator,bool> P;
  P=Topological_Faces.insert ( Fac );

  if ( P.second )
  {
    ( ( Topological_Face_Type * ) & ( * ( P.first ) ) )->SetUniqueID();  // ou comment trafiquer un const ...
  }

  return ( Topological_Face_Type * ) & ( * ( P.first ) );
}


/*  Discrete_Surface_Geometry::Patch_Type *Discrete_Surface_Geometry::Get_Patch(Patch_Type &Ele)
{
    Patch_Type_Container::iterator I;
    I=E.find(Ele);
    if (I==E.end()) return NULL;else return &((Patch_Type)(*I));
}

*/

#ifndef _WIN32
int stricmp ( const char *s1, const char*s2 )
{
  return strcasecmp ( s1,s2 );
}
#endif

void Discrete_Surface_Geometry::Read_STL_File ( char *filename )
{
  ifstream fe;
  char poub1[255];
  char poub2[255];
  long i,j;
  double coord[3];
  double nx,ny,nz;
  Vertex_Iterator VV[3];
//  Vertex_Type VER;
  Patch_Type EE;

  fe.open ( filename );

  if ( !fe.fail() )
  {
    fe>>poub1;
    fe.getline ( poub2,255 );

    if ( stricmp ( poub1,"solid" ) ==0 )
    {

      fe >> poub1 >> poub2 >> nx >> ny >> nz ;

      while ( !fe.eof() )
      {
        if ( ( stricmp ( poub1,"endsolid" ) ==0 ) )
        {
          break;
        }

        if ( ( stricmp ( poub1,"facet" ) !=0 ) || ( strcmp ( poub2,"normal" ) !=0 ) )
        {

          fe.close();
          throw BAD_STL_FILE_FORMAT;
          break;
        }

        fe >> poub1 >> poub2;

        if ( ( stricmp ( poub1,"outer" ) !=0 ) )
        {
          fe.close();
          throw BAD_STL_FILE_FORMAT;

          break;
        }

        for ( j=0; j<3; ++j )

        {

          fe >> poub1 >> coord[0] >> coord[1] >> coord[2];

//  poub1="vertex"; PUIS coordonnees du point

          if ( ( stricmp ( poub1,"vertex" ) !=0 ) )

          {
            fe.close();
            throw BAD_STL_FILE_FORMAT;
            break;
          }

          {
            Vertex_Type VE ( coord );
            VV[j]=Add_Vertex ( VE );
          }
        }

        {
          bool del=false;

          for ( j=0; j<2; ++j )
            for ( i=j+1; i<3; ++i )
              if ( VV[i]==VV[j] )
              {
                cout << "degenerated facet" << endl;
                del=true;
                break;
              }

          Patch_Type EL ( VV );

          if ( !del )
          {
            Add_Patch ( EL );
          }
        }

        fe >> poub1;
        //poub1=endloop
        fe >> poub2;
        //poub1=endfacet

        fe >> poub1 >> poub2 >> nx >> ny >> nz ;
      }

      fe.close();
    }
    else
    {
      fe.close();
      throw BAD_STL_FILE_FORMAT;
    }
  }
  else
  {
    throw ERR_FILE_NOT_FOUND;
  }

  Create_Topology();
}



void Discrete_Surface_Geometry::Create ( int nbn,double *tabn, int nbelt,int* tabelt )
{
  double* coord;
  int* nodes;
  Vertex_Iterator TABV[3];
  Vertex_Iterator *VV=new Vertex_Iterator[nbn];
  Patch_Type EE;
  int i,j;
  coord=tabn;

  for ( i=0; i<nbn; i++ )
  {
    Vertex_Type VE ( coord );
    VV[j]=Add_Vertex ( VE );
    coord+=3;
  }

  nodes=tabelt;

  for ( i=0; i<nbelt; i++ )
  {
    for ( j=0; i<3; j++ )
    {
      TABV[j]=VV[* ( nodes++ )];
    }

    Patch_Type EL ( TABV );
    Add_Patch ( EL );
  }

  delete[] VV;
  Create_Topology();
}





void Discrete_Surface_Geometry::Create_Topology ( void )
{
  Patch_Type_Container::iterator ele;
  Topological_Face_Type_Container::iterator tp,tp1;


  Vertex_Type::Iterator VV[10];
  Geometry_Patch<3,Vertex_Type>** ELEMENTS;
  Vector Vect[2],Normale ( 3 );
  Vect[0].Resize ( 3 );
  Vect[1].Resize ( 3 );

  Patch_Type *ELE;

  for ( ele=Patches.begin(); ele!=Patches.end(); ++ele )
  {
    int ii;
    ELE=& ( *ele );
    ELE->Get_Vertices ( VV );

    for ( ii=0; ii<3; ++ii )
    {
      Topological_Face_Type F;
      Topological_Face_Type *FF;
      F.Add_Vertex ( &*VV[ii] );
      F.Add_Vertex ( &*VV[ ( ii+1 ) %3] );
      FF=Add_Topological_Face ( F );
      FF->Add_Element ( ELE );
    }

    for ( ii=0; ii<2; ++ii )
    {
      Vect[ii].Init ( *VV[ii],*VV[ ( ii+1 ) %3] );
    }

    Normale.Cross ( Vect[0],Vect[1] );
    Normale.Normalize();
    ELE->Set_Normal ( Normale );
  }


  for ( tp=Topological_Faces.begin(); tp!=Topological_Faces.end(); )
  {
    int i,j;
    int num= ( *tp ).Get_Number_Elements();

    if ( num==1 );

    ELEMENTS=new Geometry_Patch<3,Vertex_Type>*[num];

    ( *tp ).Get_Elements ( ELEMENTS );

    for ( i=0; i<num; ++i )
    {
      for ( j=0; j<i; ++j )
      {
        ELEMENTS[i]->Add_Connected_Geometry_Patch ( ELEMENTS[j] );
      }

      for ( j=i+1; j<num; ++j )
      {
        ELEMENTS[i]->Add_Connected_Geometry_Patch ( ELEMENTS[j] );
      }
    }

    tp1=tp++;
    Topological_Faces.erase ( tp1 );
    delete[] ELEMENTS;
  }
}


bool Discrete_Surface_Geometry::Project_Point_On_Patch ( const Vertex_Type &V,Vertex_Type &VP,Vector &dir,Geometry_Patch<3,Vertex_Type> &ele,double &t )
{
  return ( ele.Project_Point ( V,VP,dir,t ) );
}


Geometry_Patch<3,Discrete_Surface_Geometry::Vertex_Type>* Discrete_Surface_Geometry::Project_Point_On_Geometry ( const Vertex_Type &V,Vertex_Type &VP,Vector &dir,list<Geometry_Patch<3,Vertex_Type>*> Hint,double dist_max, double &distance )
{

  Patch_Type_Container::iterator ele_it;
  Geometry_Patch<3,Vertex_Type> *ele;
  Geometry_Patch<3,Vertex_Type> *ele_min;

  typedef set<Geometry_Patch<3,Vertex_Type>*,less<Geometry_Patch<3,Vertex_Type>*> > set_patches;
  typedef list<Geometry_Patch<3,Vertex_Type>*> boundary_list;


  set_patches main_set;
  set_patches *sets;
  boundary_list boundary;

  Vertex_Type Vtmp;
  list<Geometry_Patch<3,Vertex_Type>*>::iterator it_list,it_list2;
  set_patches::iterator it1;
  boundary_list::iterator itb,itb2;

  double t,dist,distmin=1e300;
  double normdir;
  bool inside,trouve=false;
  int i,nb_hint=Hint.size();
  int num=0;


  Vtmp=V;
  normdir=dir.Norm();

  if ( !Hint.empty() )
  {
    sets=new set_patches[nb_hint];

    for ( it_list=Hint.begin(),i=0; it_list!=Hint.end(); ++it_list,++i )
    {
      int compte_hint=nb_hint;
      trouve=false;
      boundary.clear();
      sets[i].clear();
      boundary.push_back ( *it_list );
      sets[i].insert ( *it_list );
      itb=boundary.begin();

      while ( ( !trouve ) && ( itb!=boundary.end() ) )
      {
        for ( it_list2=Hint.begin(); it_list2!=Hint.end(); ++it_list2 )
          if ( ( *it_list2 ) == ( *itb ) )
          {
            compte_hint--;
          }

        if ( compte_hint==0 )
        {
          trouve=true;
        }

        else
        {
          int nb_conn_gp= ( *itb )->Get_Number_Connected_Geometry_Patch();
          Geometry_Patch<3,Vertex_Type> **connected_patches=new Geometry_Patch<3,Vertex_Type>*[nb_conn_gp];
          ( *itb )->Get_Connected_Geometry_Patch ( connected_patches );

          for ( int j=0; j<nb_conn_gp; ++j )
          {
            pair<set_patches::iterator, bool> p=sets[i].insert ( connected_patches[j] );

            if ( p.second )
            {
              boundary.push_back ( connected_patches[j] );
            }
          }

          delete[] connected_patches;
          itb2=itb;
          ++itb;
          boundary.erase ( itb2 );
        }
      }
    }



    for ( int i=1; i<nb_hint; ++i )
    {
      main_set.clear();
      set_intersection ( sets[0].begin(), sets[0].end(), sets[i].begin(), sets[i].end(),
                         inserter ( main_set, main_set.begin() ),
                         less<Geometry_Patch<3,Vertex_Type>*>() );

      if ( i< ( nb_hint-1 ) )
      {
        copy ( main_set.begin(), main_set.end(), inserter ( sets[0], sets[0].begin() ) );
      }

    }



    delete[] sets;

    trouve=false;

    //      cout << main_set.size() << endl;
    for ( it1=main_set.begin(); it1!=main_set.end(); ++it1 )
    {
      ele=*it1;
//          cout << ele->GetID() << " ";
      inside=Project_Point_On_Patch ( V,Vtmp,dir,*ele,t );

      dist=fabs ( t ) *normdir;

      if ( ( distmin>dist ) &&inside )
      {
        VP=Vtmp;
        distmin=dist;
        ele_min=ele;

        if ( dist<dist_max )
        {
          trouve=true;
        }

      }
    }

    ++num;
  }

  //  if (trouve)
  //      cout << num << " " ;
  if ( !trouve )
  {
    for ( ele_it=Patches.begin(); ele_it!=Patches.end(); ++ele_it )
    {
      ele=& ( *ele_it );
      inside=Project_Point_On_Patch ( V,Vtmp,dir,*ele,t );
      ++num;
      dist=fabs ( t ) *normdir;

      if ( ( distmin>dist ) &&inside )
      {
        VP=Vtmp;
        distmin=dist;
        ele_min=ele;

        if ( dist<dist_max )
        {
          trouve=true;
        }

      }
    }
  }

//  cout << num<< endl;
  if ( trouve )
  {
    distance=distmin;
    return ele_min;
  }
  else
  {
//      VP=V;
//      cout << "houla" << endl;
//      cout << V.GetID() << endl;
    distance=distmin;
    return ele_min;
  }
}


void Discrete_Surface_Geometry::Tessellate ( Simple_Mesh<3,3,Element_Type>& M )
{
  Patch_Type_Container::iterator e;
  Simple_Mesh<3,3,Element_Type>::Element_Iterator E;
  Vertex_Iterator V[3];
  Simple_Mesh<3,3,Element_Type>::Vertex_Iterator VV[3];

  for ( e=Patches.begin(); e!=Patches.end(); ++e )
  {
    Patch_Type PPP;
    PPP=*e;
    PPP.Get_Vertices ( V );

    for ( int i=0; i<3; ++i )
    {
      VV[i]=M.Add_Vertex ( * ( V[i] ) );
      //(*(VV[i])).set_boundary_condition(((V[i]));
      ( *VV[i] ).Set_Geometry_Patch ( & ( *e ) );
    }

    Element_Type EE ( VV );
    EE.Set_Zone ( PPP.Get_Zone() );
    E=M.Add_Element ( EE );
  }

  M.Set_Geometry ( this );
}

 
