namespace X3DTK {
namespace MESH {

template<class MData, class VData, class EData, class FData, bool RW>
TemplateFacesDrawerCoreVisitor<MData, VData, EData, FData, RW>::TemplateFacesDrawerCoreVisitor()
{
  // Enter and leave functions.
  define(Recorder<Scene>::getEnterFunction(&TemplateFacesDrawerCoreVisitor::enterScene));
  define(Recorder<Transform>::getEnterFunction(&TemplateFacesDrawerCoreVisitor::enterTransform));
  define(Recorder<TemplateMesh<MData, VData, EData, FData, RW> >::getEnterFunction(&TemplateFacesDrawerCoreVisitor::enterMesh));
  define(Recorder<Shape>::getEnterFunction(&TemplateFacesDrawerCoreVisitor::enterShape));
  define(Recorder<Shape>::getWalkOnFunction(&TemplateFacesDrawerCoreVisitor::walkOnShape));
  define(Recorder<Transform>::getLeaveFunction(&TemplateFacesDrawerCoreVisitor::leaveTransform));
}

template<class MData, class VData, class EData, class FData, bool RW>
void TemplateFacesDrawerCoreVisitor<MData, VData, EData, FData, RW>::enterScene(Scene *S)
{
  if (chdir(S->getFileName().getPath()) != 0)
  {
    cx3d << "MESH::TemplateFacesDrawerCoreVisitor::enterScene: unable to set the current working directory to " << S->getFileName().getPath() << "!" << std::endl; 
  }  
}

template<class MData, class VData, class EData, class FData, bool RW>
void TemplateFacesDrawerCoreVisitor<MData, VData, EData, FData, RW>::enterTransform(Transform *T)
{
  TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> *stateVariables = Singleton<TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> >::getInstance();
  glMatrixMode(GL_MODELVIEW);
  stateVariables->pushMatrix(T->getTransform());
  glPushMatrix();
  glMultMatrixf(T->getTransform().toFloat16());
}

template<class MData, class VData, class EData, class FData, bool RW>
void TemplateFacesDrawerCoreVisitor<MData, VData, EData, FData, RW>::leaveTransform(Transform *)
{
  TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> *stateVariables = Singleton<TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> >::getInstance();
  glMatrixMode(GL_MODELVIEW);
  stateVariables->popMatrix();
  glPopMatrix();
}

template<class MData, class VData, class EData, class FData, bool RW>
bool TemplateFacesDrawerCoreVisitor<MData, VData, EData, FData, RW>::walkOnShape(Shape *, SFNode child)
{
  TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> *stateVariables = Singleton<TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> >::getInstance();
  if (!stateVariables->getDrawingSelected())
    return true;
    
  MFNode selectedNodes = stateVariables->getSelectedNodes();
  return (std::find(selectedNodes.begin(), selectedNodes.end(), child) != selectedNodes.end());
}

template<class MData, class VData, class EData, class FData, bool RW>
void TemplateFacesDrawerCoreVisitor<MData, VData, EData, FData, RW>::enterMesh(TemplateMesh<MData, VData, EData, FData, RW> *M)
{
  TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> *stateVariables = Singleton<TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> >::getInstance();
  bool drawingSelected = stateVariables->getDrawingSelected();
  
  glEnable(GL_LIGHTING);
  
  stateVariables->storeMesh(M);
  int meshId = stateVariables->getCurrentMeshId();

  if (stateVariables->getSelectionMode())
    glPushName(meshId);

  bool colorPerVertex = true;
  
  if (!drawingSelected)
  {
    #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED
    if (MData::template find<MESH::MeshColorData>())
    #endif
    {
      if ((stateVariables->getRenderingMode() != REAL)
       || (stateVariables->getRenderingMode() == REAL)
       #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED
       && (M->template ogetData<MESH::MeshColorData>().hasColor())
       #endif
       )
        glEnable(GL_COLOR_MATERIAL);
      
      #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED  
      colorPerVertex = M->template ogetData<MESH::MeshColorData>().getColorPerVertex();
      #else
      colorPerVertex = M->data().getColorPerVertex();
      #endif
    }
  }
  else
    glEnable(GL_COLOR_MATERIAL);
  
  bool normalPerVertex = true;
  #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED
  if (MData::template find<MESH::MeshNormalData>())
    normalPerVertex = M->template ogetData<MESH::MeshNormalData>().getNormalPerVertex();
  #else
  normalPerVertex = M->data().getNormalPerVertex();
  #endif
  
  #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED
  if (MData::template find<MESH::MeshTexCoordData>())
  #endif
  {
    if (stateVariables->getRenderingMode() == REAL)
      glEnable(GL_TEXTURE_2D);
  }
  
  const typename TemplateMesh<MData, VData, EData, FData, RW>::MFFace &faces = M->getFaces();

  if (!drawingSelected)
  {
    if (stateVariables->getRenderingMode() != REAL)
    {
      SFNode N = 0;
      if (stateVariables->getRenderingMode() == IFS)
        N = M;
      else
        N = M->getVertex();

      static const unsigned int cr = 101;
      static const unsigned int cg = 117;
      static const unsigned int cb = 131;
  
      static const float icr = 1.0f/(2.0f*static_cast<float>(cr));
      static const float icg = 1.0f/(2.0f*static_cast<float>(cg));
      static const float icb = 1.0f/(2.0f*static_cast<float>(cb));
  
      float meshAddr = icr*static_cast<float>(reinterpret_cast<unsigned int>(N) % cr);
      float meshAddg = icg*static_cast<float>(reinterpret_cast<unsigned int>(N) % cg);
      float meshAddb = icb*static_cast<float>(reinterpret_cast<unsigned int>(N) % cb);
  
      glColor3f(meshAddr + 0.5f, meshAddg + 0.5f, meshAddb + 0.5f);
    }
  }
  else
    glColor3f(0.0f, 0.5f, 0.0f);
    
  for (typename TemplateMesh<MData, VData, EData, FData, RW>::MFFace::const_iterator f = faces.begin(), end = faces.end() ; f != end; ++f)
  {
    #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED
    if (FData::template find<MESH::FaceNormalData>() && !normalPerVertex)
    #endif
    {
      #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED
      glNormal3fv((*f)->template ogetData<MESH::FaceNormalData>().getNormal());
      #else
      glNormal3fv((*f)->data().getNormal());
      #endif
    }

    #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED
    if (FData::template find<MESH::FaceColorData>() && !colorPerVertex && stateVariables->getRenderingMode() == REAL)
    #endif
    {
      #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED
      SFColorRGBA color = (*f)->template ogetData<MESH::FaceColorData>().getColor();
      #else
      SFColorRGBA color = (*f)->data().getColor();
      #endif
      glColor4f(color.r, color.g, color.b, color.a);
    }
        
    if (stateVariables->getSelectionMode())
      glPushName((*f)->getIndex());

    glBegin(GL_POLYGON);
    const typename SFTemplateFace<MData, VData, EData, FData, RW>::MFEdge &edges = (*f)->getEdges();
    for (typename SFTemplateFace<MData, VData, EData, FData, RW>::MFEdge::const_iterator e = edges.begin(), end = edges.end() ; e != end; ++e)
    {
      #ifdef TEMPLATE_SPECIALIZATION_SUPPORTED
      if (VData::template find<MESH::VertexNormalData>() && normalPerVertex)
        glNormal3fv((*e)->getFromVertex()->template ogetData<MESH::VertexNormalData>().getNormalOfFace(*f));

      if (VData::template find<MESH::VertexColorData>() && colorPerVertex && stateVariables->getRenderingMode() == REAL)
      {
        SFColorRGBA color = (*e)->getFromVertex()->template ogetData<MESH::VertexColorData>().getColorOfFace(*f);
        glColor4f(color.r, color.g, color.b, color.a);
      }
      if (VData::template find<MESH::VertexTexCoordData>() && stateVariables->getRenderingMode() == REAL)
        glTexCoord2fv((*e)->getFromVertex()->template ogetData<MESH::VertexTexCoordData>().getTexCoordOfFace(*f));
      
      glVertex3fv((*e)->getFromVertex()->template ogetData<MESH::VertexPointData>().getPoint());
      
      #else
      
      glNormal3fv((*e)->getFromVertex()->data().getNormalOfFace(*f));
      
      SFColorRGBA color = (*e)->getFromVertex()->data().getColorOfFace(*f);
      glColor4f(color.r, color.g, color.b, color.a);
      
      glTexCoord2fv((*e)->getFromVertex()->data().getTexCoordOfFace(*f));
            
      glVertex3fv((*e)->getFromVertex()->data().getPoint());
      #endif
      
    }
    glEnd();

    if (stateVariables->getSelectionMode())
      glPopName();
  }

  if (stateVariables->getSelectionMode())
    glPopName();
    
  glDisable(GL_LIGHTING);
  glDisable(GL_COLOR_MATERIAL);
  glDisable(GL_TEXTURE_2D);
}

template<class MData, class VData, class EData, class FData, bool RW>
void TemplateFacesDrawerCoreVisitor<MData, VData, EData, FData, RW>::enterShape(Shape *S)
{
  TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> *stateVariables = Singleton<TemplateFacesDrawerStateVariables<MData, VData, EData, FData, RW> >::getInstance();
  if ((stateVariables->getRenderingMode() == REAL) && (!stateVariables->getNode(S->getAppearance())))
    stateVariables->buildGLNode(S->getAppearance());
  
  stateVariables->renderGLNode(S->getAppearance());  
}

}
}  
