// Copyright (c) 2011 CNRS and LIRIS' Establishments (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org); you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 3 of the License,
// or (at your option) any later version.
//
// Licensees holding a valid commercial license may use this file in
// accordance with the commercial license agreement provided with the software.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// $URL$
// $Id$
//
// Author(s)     : Guillaume Damiand <guillaume.damiand@liris.cnrs.fr>
//                 Kumar Snehasish <kumar.snehasish@gmail.com>
//
#include "Viewer.h"
#include <vector>
#include <CGAL/bounding_box.h>
#include <QGLViewer/vec.h>
#include <CGAL/Linear_cell_complex_operations.h>

CGAL::Bbox_3 Viewer::bbox()
{
  CGAL::Bbox_3 bb;

  bool empty = true;
  for (LCC::Attribute_range<3>::type::iterator
       it=scene->lcc->attributes<3>().begin(),
       itend=scene->lcc->attributes<3>().end(); it!=itend; ++it )
  {
    if ( it->info().is_visible() )
    {
      if ( empty )
      {
        bb = scene->lcc->point(it->dart()).bbox();
        empty = false;
      }
      for( LCC::Dart_of_cell_range<3>::iterator
           it2=scene->lcc->darts_of_cell<3>(it->dart()).begin();
           it2.cont(); ++it2)
        bb = bb + scene->lcc->point(it2).bbox();
    }
  }

  if ( empty )
  {
    bb = LCC::Point(CGAL::ORIGIN).bbox();
    bb = bb + LCC::Point(1,1,1).bbox(); // To avoid a warning from Qglviewer
  }
  
  return bb;
}

void
Viewer::sceneChanged()
{
  CGAL::Bbox_3 bb = bbox();
   
  this->camera()->setSceneBoundingBox(qglviewer::Vec(bb.xmin(),
						     bb.ymin(),
						     bb.zmin()),
				      qglviewer::Vec(bb.xmax(),
						     bb.ymax(),
						     bb.zmax()));
    
  this->showEntireScene();
}


void Viewer::drawFacet(Dart_const_handle ADart)
{
  LCC &m = *scene->lcc;
  ::glBegin(GL_POLYGON);
  CGAL_assertion( m.attribute<3>(ADart)!=LCC::null_handle );

  //  double r = (double)ADart->attribute<3>()->info().r()/255.0;
  double r = (double)m.info<3>(ADart).color().r()/255.0;
  double g = (double)m.info<3>(ADart).color().g()/255.0;
  double b = (double)m.info<3>(ADart).color().b()/255.0;
  if ( !m.is_free(ADart, 3) )
  {
    r += (double)m.info<3>(m.beta(ADart,3)).color().r()/255.0;
    g += (double)m.info<3>(m.beta(ADart,3)).color().g()/255.0;
    b += (double)m.info<3>(m.beta(ADart,3)).color().b()/255.0;
    r /= 2; g /= 2; b /= 2;
  }

  ::glColor3f(r,g,b);

  // If Flat shading: 1 normal per polygon
  if (flatShading)
  {
    LCC::Vector n = CGAL::compute_normal_of_cell_2(m,ADart);
    n = n/(CGAL::sqrt(n*n));
    ::glNormal3d(n.x(),n.y(),n.z());
  }

  for ( LCC::Dart_of_orbit_range<1>::const_iterator it(m,ADart);
        it.cont(); ++it)
  {
    // If Gouraud shading: 1 normal per vertex
    if (!flatShading)
    {
      LCC::Vector n = CGAL::compute_normal_of_cell_0<LCC>(m,it);
      n = n/(CGAL::sqrt(n*n));
      ::glNormal3d(n.x(),n.y(),n.z());
    }

    LCC::Point p = m.point(it);
    ::glVertex3d( p.x(),p.y(),p.z());
  }
  ::glEnd();
}

/// Draw all the edge of the facet given by ADart
void Viewer::drawEdges(Dart_const_handle ADart)
{ 
  LCC &m = *scene->lcc;
  glBegin(GL_LINES);
  glColor3f(.2f,.2f,.6f);
  for ( LCC::Dart_of_orbit_range<1>::const_iterator it(m,ADart);
        it.cont(); ++it)
  {
    LCC::Point p = m.point(it);
    Dart_const_handle d2 = m.other_extremity(it);
    if ( d2!=LCC::null_handle )
    {
      LCC::Point p2 = m.point(d2);
      glVertex3f( p.x(),p.y(),p.z());
      glVertex3f( p2.x(),p2.y(),p2.z());
    }
  }
  glEnd();
}

void Viewer::draw_one_vol(Dart_const_handle adart, bool filled)
{
  LCC &m = *scene->lcc;

  if ( filled )
  {
    for (LCC::One_dart_per_incident_cell_range<2,3>::const_iterator it(m,adart);
         it.cont(); ++it)
    {
      drawFacet(it);
      if (edges) drawEdges(it);
    }
  }
  else
  {
    glBegin(GL_LINES);
    glColor3f(.2f,.2f,.6f);
    for (LCC::One_dart_per_incident_cell_range<1,3>::const_iterator
           it(m,adart); it.cont(); ++it)
    {
      if ( m.other_extremity(it)!=LCC::null_handle )
      {
        LCC::Point p1 = m.point(it);
        LCC::Point p2 = m.point(m.other_extremity(it));
        glVertex3f( p1.x(),p1.y(),p1.z());
        glVertex3f( p2.x(),p2.y(),p2.z());
      }
    }
    glEnd();
  }
}

void Viewer::draw()
{
  LCC &m = *scene->lcc;

  if ( m.is_empty() ) return;

  for (LCC::Attribute_range<3>::type::iterator
       it=m.attributes<3>().begin(),
       itend=m.attributes<3>().end(); it!=itend; ++it )
  {
    if ( it->info().is_visible() )
    {
      // TODO allow to select one volume ?
      // if(selectedVolumeIndex == (int)i) glLineWidth(5.0f);
      draw_one_vol(it->dart(), it->info().is_filled());
      // if(selectedVolumeIndex == (int)i) glLineWidth(1.4f);

      if(vertices)
      {
        for( LCC::One_dart_per_incident_cell_range<0,3>::iterator
             it2(m, it->dart()); it2.cont(); ++it2)
        {
          LCC::Point p = m.point(it2);
          glBegin(GL_POINTS);
          glColor3f(.6f,.2f,.8f);
          glVertex3f( p.x(),p.y(),p.z());
          glEnd();
        }
      }
    }
  }
}

void Viewer::init()
{
  // Restore previous viewer state.
  restoreStateFromFile();

  // Define 'Control+Q' as the new exit shortcut (default was 'Escape')
  setShortcut(EXIT_VIEWER, Qt::CTRL+Qt::Key_Q);

  // Add custom key description (see keyPressEvent).
  setKeyDescription(Qt::Key_W, "Toggles wire frame display");
  setKeyDescription(Qt::Key_F, "Toggles flat shading display");
  setKeyDescription(Qt::Key_E, "Toggles edges display");
  setKeyDescription(Qt::Key_V, "Toggles vertices display");

  // Light default parameters
  ::glLineWidth(1.4f);
  ::glPointSize(4.f);
  ::glEnable(GL_POLYGON_OFFSET_FILL);
  ::glPolygonOffset(1.0f,1.0f);
  ::glClearColor(1.0f,1.0f,1.0f,0.0f);
  ::glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);

  ::glEnable(GL_LIGHTING);
    
  ::glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
  // ::glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);

  if (flatShading)
  {
    ::glShadeModel(GL_FLAT);
    ::glDisable(GL_BLEND); 
    ::glDisable(GL_LINE_SMOOTH); 
    ::glDisable(GL_POLYGON_SMOOTH_HINT); 
    ::glBlendFunc(GL_ONE, GL_ZERO); 
    ::glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST);
  }
  else
  {
    ::glShadeModel(GL_SMOOTH);
    ::glEnable(GL_BLEND);
    ::glEnable(GL_LINE_SMOOTH);
    ::glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  }    
}

void Viewer::keyPressEvent(QKeyEvent *e)
{
  const Qt::KeyboardModifiers modifiers = e->modifiers();

  bool handled = false;
  if ((e->key()==Qt::Key_W) && (modifiers==Qt::NoButton))
  {
    wireframe = !wireframe;
    if (wireframe)
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    else
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    handled = true;
    updateGL();
  }
  else if ((e->key()==Qt::Key_F) && (modifiers==Qt::NoButton))
  {
    flatShading = !flatShading;
    if (flatShading)
    {
      ::glShadeModel(GL_FLAT);
      ::glDisable(GL_BLEND); 
      ::glDisable(GL_LINE_SMOOTH); 
      ::glDisable(GL_POLYGON_SMOOTH_HINT); 
      ::glBlendFunc(GL_ONE, GL_ZERO); 
      ::glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST);
    }
    else
    {
      ::glShadeModel(GL_SMOOTH);
      ::glEnable(GL_BLEND);
      ::glEnable(GL_LINE_SMOOTH);
      ::glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
      ::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    }
    handled = true;
    updateGL();
  }
  else if ((e->key()==Qt::Key_E) && (modifiers==Qt::NoButton))
  {
    edges = !edges;
    handled = true;
    updateGL();
  }
  else if ((e->key()==Qt::Key_V) && (modifiers==Qt::NoButton))
  {
    vertices = !vertices;
    handled = true;
    updateGL();
  }
    
  if (!handled)
    QGLViewer::keyPressEvent(e);
}

QString Viewer::helpString() const
{
  QString text("<h2>L C C   V i e w e r</h2>");
  text += "Use the mouse to move the camera around the object. ";
  text += "You can respectively revolve around, zoom and translate with "
    "the three mouse buttons. ";
  text += "Left and middle buttons pressed together rotate around the "
    "camera view direction axis<br><br>";
  text += "Pressing <b>Alt</b> and one of the function keys "
    "(<b>F1</b>..<b>F12</b>) defines a camera keyFrame. ";
  text += "Simply press the function key again to restore it. Several "
    "keyFrames define a ";
  text += "camera path. Paths are saved when you quit the application and "
    "restored at next start.<br><br>";
  text += "Press <b>F</b> to display the frame rate, <b>A</b> for the "
    "world axis, ";
  text += "<b>Alt+Return</b> for full screen mode and <b>Control+S</b> to "
    "save a snapshot. ";
  text += "See the <b>Keyboard</b> tab in this window for a complete "
    "shortcut list.<br><br>";
  text += "Double clicks automates single click actions: A left button "
    "double click aligns the closer axis with the camera (if close enough). ";
  text += "A middle button double click fits the zoom of the camera and "
    "the right button re-centers the scene.<br><br>";
  text += "A left button double click while holding right button pressed "
    "defines the camera <i>Revolve Around Point</i>. ";
  text += "See the <b>Mouse</b> tab and the documentation web pages for "
    "details.<br><br>";
  text += "Press <b>Escape</b> to exit the viewer.";
  return text;
}

#include "Viewer.moc"
