//=============================================================================
//                                                                            
//                               OpenMesh                                     
//      Copyright (C) 2001-2005 by Computer Graphics Group, RWTH Aachen       
//                           www.openmesh.org                                 
//                                                                            
//-----------------------------------------------------------------------------
//                                                                            
//                                License                                     
//                                                                            
//   This library is free software; you can redistribute it and/or modify it 
//   under the terms of the GNU Library General Public License as published  
//   by the Free Software Foundation, version 2.                             
//                                                                             
//   This library is distributed in the hope that it will be useful, but       
//   WITHOUT ANY WARRANTY; without even the implied warranty of                
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU         
//   Library General Public License for more details.                          
//                                                                            
//   You should have received a copy of the GNU Library General Public         
//   License along with this library; if not, write to the Free Software       
//   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.                 
//                                                                            
//=============================================================================


#include <memory>
#include <iostream>
#include <iomanip>
#include <stdexcept>

#define OSG_WITH_GLUT
#include <GL/glut.h>
#include <OpenSG/OSGConfig.h>
#include <OpenSG/OSGGLUTWindow.h>
#include <OpenSG/OSGSimpleSceneManager.h>
#include <OpenSG/OSGSceneFileHandler.h>
#include <OpenSG/OSGTriangleIterator.h>
#include <OpenSG/OSGFaceIterator.h>

#include <OpenMesh/Core/IO/MeshIO.hh> // always before kernel type
#include <OpenMesh/Tools/Kernel_OSG/TriMesh_OSGArrayKernelT.hh>
#include <OpenMesh/Tools/Kernel_OSG/bindT.hh>
#include <OpenMesh/Tools/Utils/Timer.hh>
#include <OpenMesh/Tools/Subdivider/Uniform/LoopT.hh>
#include <OpenMesh/Tools/Smoother/JacobiLaplaceSmootherT.hh>


//-----------------------------------------------------------------------------


using namespace OSG;


//-----------------------------------------------------------------------------


struct MyTraits : public OpenMesh::Kernel_OSG::Traits
{
  HalfedgeAttributes( OpenMesh::Attributes::PrevHalfedge );
  VertexAttributes  ( OpenMesh::Attributes::Normal       );
  FaceAttributes    ( OpenMesh::Attributes::Normal       );
};

typedef OpenMesh::Kernel_OSG::TriMesh_OSGArrayKernelT<MyTraits>   MyMesh;
typedef OpenMesh::Subdivider::Uniform::LoopT<MyMesh>              Loop;
typedef OpenMesh::Smoother::JacobiLaplaceSmootherT<MyMesh>        Smoother;


//-----------------------------------------------------------------------------


class MeshContainer
{
public:

  typedef std::vector<MyMesh*> meshbag_t;

  const size_t InvalidIndex;

public:

  MeshContainer() : InvalidIndex(size_t(-1))  {}

  ~MeshContainer()
  {
    std::vector<MyMesh*>::iterator it = meshes_.begin();
    for(;it != meshes_.end(); ++it)
      delete *it;
  }

  size_t size() const { return meshes_.size(); }

  MyMesh& operator[]( size_t idx ) { return *(meshes_[idx]); }
  const MyMesh& operator [] ( size_t idx ) const { return *(meshes_[idx]); }

  bool bind( osg::GeometryPtr geo )
  {
    std::auto_ptr<MyMesh> obj(new MyMesh);
    return (OpenMesh::Kernel_OSG::bind< MyMesh >( *obj, geo))
      ? (meshes_.push_back(obj.release()), true)
      : false;
  }


private:

  std::vector<MyMesh*> meshes_;
};


//-----------------------------------------------------------------------------


struct Globals
{  
  // OpenSG specific entities
  SimpleSceneManager* mgr;
  GLUTWindowPtr       gwin;
  NodePtr             root;
  std::vector<GeometryPtr> geos;
  size_t              sel;
  bool                statistics;

  // OpenMesh specific entities
  MeshContainer       meshes;

} g;


//-----------------------------------------------------------------------------


void display(void);
void reshape(int width, int height);
void mouse(int button, int state, int x, int y);
void keyboard(unsigned char key, int x, int y);
void motion(int x, int y);
int  setupGLUT(int *argc, char *argv[]);


//-----------------------------------------------------------------------------


int setupGLUT(int *argc, char *argv[])
{
  glutInit(argc, argv);
  glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
  
  int winid = glutCreateWindow("OpenMesh within OpenSG");
  
  glutReshapeFunc(reshape);
  glutDisplayFunc(display);
  glutMouseFunc(mouse);
  glutMotionFunc(motion);
  glutKeyboardFunc(keyboard);
    
  return winid;
}


//-----------------------------------------------------------------------------


/*
  This function uses the fact that ::dcast() acts like
  dynamic_cast. It tries to dcast the core to a GeometryPtr, and tests
  the result to see if it actually was derived from Geometry.
*/

Action::ResultE bindGeo(NodePtr& node)
{   
  GeometryPtr geo = GeometryPtr::dcast(node->getCore());
    
  if (geo!=NullFC)
  {
    if ( g.meshes.bind( geo ) )
    {
      std::cout << "  Geometry connected to OpenMesh object\n";
      g.geos.push_back(geo);
      g.sel = g.meshes.size()-1;
      assert( g.geos.size() == g.meshes.size() );
    }
    else
      std::cerr << "  Warning! Could not bind the OpenMesh"
		<< " object to the geometry!\n";
  }
  return Action::Continue; 
}


//-----------------------------------------------------------------------------


int main(int argc, char **argv)
{
  // OSG init
  osgInit(argc,argv);
  int winid = setupGLUT(&argc, argv);
  g.gwin = GLUTWindow::create();
  g.gwin->setId(winid);
  g.gwin->init();



  // create root node with core
  std::cout << "Create root node with core\n";

  g.root           = Node::create();
  NodeCorePtr core = Group::create();
  
  osg::beginEditCP(g.root);
  {
    g.root->setCore(core);
  }
  osg::endEditCP(g.root);



  // load the scene
  std::cout << "Load a scene from '" << argv[1] << "'\n";
  NodePtr     node = SceneFileHandler::the().read( argv[1] );
  if ( node != NullFC )
  {
    osg::beginEditCP(g.root);
    {
      g.root->addChild(node);
    }
    osg::endEditCP(g.root);
  }
  else
    return 1;



  // bind all geometry nodes to an OpenMesh
  std::cout << "Bind all geometry nodes\n";

  traverse(g.root, 
	   osgTypedFunctionFunctor1CPtrRef<Action::ResultE,NodePtr>(bindGeo));

  if (!g.meshes.size())
  {
    std::cerr << "  No geometry found. Nothing to do!\n";
    return 1;
  }
  else
  {
    std::cout << "  Number of bound geometry: " 
	      << g.meshes.size() << std::endl;
    g.meshes[0].update_face_normals();
    g.meshes[0].update_vertex_normals();
  }



  // create the SimpleSceneManager helper
  std::cout << "Create simple scene manager\n";

  g.mgr = new SimpleSceneManager;
  
  // tell the manager what to manage
  g.mgr->setWindow(g.gwin);
  g.mgr->setRoot  (g.root);

  // 
  g.mgr->useOpenSGLogo();
  g.mgr->setStatistics(false);
  


  // show the whole scene
  std::cout << "Display everything\n";
  g.mgr->showAll();
  glutMainLoop();
  return 0;
}


// --------------------------------------------------------------- display ----


void display(void)
{
  g.mgr->redraw();
}


// --------------------------------------------------------------- reshape ----


void reshape(int w, int h)
{
  g.mgr->resize(w, h);
  glutPostRedisplay();
}


// ----------------------------------------------------------------- mouse ----


void mouse(int button, int state, int x, int y)
{
  if (state)
    g.mgr->mouseButtonRelease(button, x, y);
  else
    g.mgr->mouseButtonPress(button, x, y);
  glutPostRedisplay();
}


//----------------------------------------------------------------- motion ----


void motion(int x, int y)
{
  g.mgr->mouseMove(x, y);
  glutPostRedisplay();
}


// -------------------------------------------------------------- keyboard ----


void keyboard(unsigned char key, int x, int y)
{
#define MESH g.meshes[g.sel]
#define GEO g.geos[g.sel]

  OpenMesh::Utils::Timer t;

  using namespace std;

  switch(key)
  {
    case 27: // escape
      exit(0);
      break;


    case 'i':
      cout << "OpenMesh information for obj #" << g.sel << ":\n";
      cout << "  #Vertices: " << MESH.n_vertices() << endl;
      cout << "     #Faces: " << MESH.n_faces() << endl;
      cout << "     #Edges: " << MESH.n_edges() << endl;
      break;


    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      if ((size_t(key)-size_t('0')) < g.meshes.size())
      {
        g.sel = (size_t(key)-size_t('0'));
        cout << "Selected geometry #" << g.sel << endl;
      }
      break;


    case '+':
      g.sel = ++g.sel % g.meshes.size(); 
      cout << "Selected geometry #" << g.sel << endl;
      break;
      

    case '-':
      g.sel = (g.sel + g.meshes.size()-1) % g.meshes.size();
      cout << "Selected geometry #" << g.sel << endl;
      break;


    case 's':
    {
      cout << "Smoothing selected geometry...";
      t.start();
      Smoother smoother( g.meshes[g.sel] );
      smoother.initialize( Smoother::Normal, Smoother::C0 );
      beginEditCP(g.geos[g.sel]);
      smoother.smooth(2);
      endEditCP(g.geos[g.sel]);
      t.stop();
      cout << "done. " << t.as_string() << endl;
      glutPostRedisplay();
      break;
    }


    case 'u':
    {
      cout << "Loop subdivision...";
      t.start();
      Loop loop;
      beginEditCP( GEO );
      loop( MESH, 1 );
      MESH.update_normals();
      endEditCP( GEO );
      t.stop();
      cout << "done. " << t.as_string() << endl;
      glutPostRedisplay();
      break;
    }
  }

#undef GEO
#undef MESH
}


//=============================================================================
