/***************************************************************************
 *   Copyright (C) 2004 - 2005 by Raphael Langerhorst                      *
 *   raphael-langerhorst@gmx.at                                            *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/


#include "GOpenGLFrame.h"
#include <GElement.h>
#include <GEnergy.h>
#include <GObject.h>

#include <GDataController.h>
#include <GXmlDataController.h>

#include <GObject.h>

#include <exception>

#include <qapplication.h>
#include <qptrstack.h>
#include <qdom.h>

// @todo GOpenGLForm is depracted, thus the client engine needs to be converted as well.

using namespace std;

using namespace GCS;

using namespace GWE;

namespace GCE
{


GOpenGLFrame::GOpenGLFrame(const GweController* gwe, QWidget* parent, const char* name)
: QGLWidget(parent,name),
  Gwe(gwe),
  CameraParent(0),
  FramesPerSecond(0),
  TimeSinceFpsUpdate(0)
{
  connect(qApp,SIGNAL(aboutToQuit()),this,SLOT(stopRendering()));
  connect(&RedrawTimer,SIGNAL(timeout()),this,SLOT(update()));
}


void GOpenGLFrame::transform(const GForm* form) const //please make sure this is called in a valid OpenGL context!!
{
  glTranslated(form->Position.x,form->Position.y,form->Position.z);
  if (form->Rotation.x)
    glRotated(form->Rotation.x,1,0,0);
  if (form->Rotation.y)
    glRotated(form->Rotation.y,0,1,0);
  if (form->Rotation.z)
    glRotated(form->Rotation.z,0,0,1);
}

void GOpenGLFrame::render(const GForm* form, float* RGBA) const
{
  GLUquadric* QuadricObject = gluNewQuadric();
  Q_CHECK_PTR(QuadricObject);
  
  if (QuadricObject)
  {
    glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,RGBA);
    
    //@todo draw an ellipsoid, not a sphere
    gluSphere(QuadricObject,form->getRadiusMax(),16,16);
    gluQuadricNormals(QuadricObject,GLU_SMOOTH);
    gluDeleteQuadric(QuadricObject);
  }
}


void GOpenGLFrame::renderElement(const GElement* element)
{

  //   qDebug(QString("drawing child element: ") + QString::number(element->getElementID().getID()));

  const GDataController* data = Gwe->getDataController();

  GWE::GXmlDataController* data_xml = NULL;
  if (data->inherits("GWE::GXmlDataController"))
  {
    data_xml = (GWE::GXmlDataController*)data;
  }


  const GObject* object = element->getObject();

  if (object->hasForm())
  {

    const GForm* f = object->getForm();
//     const GEnergy* e = object->getEnergy();
    float RGBA[4];
    
    const QDomDocument* element_data = object->getElementData();
    QDomElement dom_child = element_data->elementsByTagName("appearance").item(0).toElement();
    
    if (dom_child.isNull())
    {
//       qWarning("element data has no appearance data, using half opaque white colour by default");
      RGBA[0]=1;
      RGBA[1]=1;
      RGBA[2]=1;
      RGBA[3]=0.5;
    }
    dom_child = dom_child.elementsByTagName("colour").item(0).toElement();
    
    if (dom_child.isNull())
    {
//       qWarning("element data has no colour data, using half opaque white colour by default");
      RGBA[0]=1;
      RGBA[1]=1;
      RGBA[2]=1;
      RGBA[3]=0.5;
    }
    
    bool ok;
    RGBA[0] = dom_child.elementsByTagName("r").item(0).toElement().text().toDouble(&ok);
    if (!ok)
    {
//       qWarning("element data has no red colour data, using default 1");
      RGBA[0] = 1;
    }
    
    RGBA[1] = dom_child.elementsByTagName("g").item(0).toElement().text().toDouble(&ok);
    if (!ok)
    {
//       qWarning("element data has no green colour data, using default 1");
      RGBA[1] = 1;
    }
    
    RGBA[2] = dom_child.elementsByTagName("b").item(0).toElement().text().toDouble(&ok);
    if (!ok)
    {
//       qWarning("element data has no blue colour data, using default 1");
      RGBA[2] = 1;
    }
    
    RGBA[3] = dom_child.elementsByTagName("a").item(0).toElement().text().toDouble(&ok);
    if (!ok)
    {
//       qWarning("element data has no alpha colour data, using default 0.5");
      RGBA[3] = 0.5;
    }

    //         qDebug("element position: " + QString::number(f.Position.x) + ", " + QString::number(f.Position.y) + ", " + QString::number(f.Position.z));

    glPushMatrix();

    transform(f);

    //render children
    QValueList<GElementID> children = element->getObject()->getChildren();
    for ( QValueListIterator<GElementID> it = children.begin(); it != children.end(); it++)
    {
      const GElement* element = data->read((*it));

      Q_CHECK_PTR(element);

      if (element)
      {
        renderElement(element);
      }
    }

    if (RGBA[3] > 0.001)
    {
      //in case we have a networked environment, let's also see the server
      if (data_xml!=NULL && CameraParent == element->getObject()->getParent())
      {
        QString s = data_xml->getManagingServerOfElement(element->getElementID());
        if (!s.isEmpty())
        {
          this->qglColor(Qt::white);
          this->renderText(f->Position.x-this->ViewProperties.ViewPosition.x,f->Position.y-this->ViewProperties.ViewPosition.y,f->Position.z-this->ViewProperties.ViewPosition.z,s);
        }
      }
      render(f,RGBA);
    }

    glPopMatrix();

  }
  else
  {
    //render children
    QValueList<GElementID> children = element->getObject()->getChildren();
    for ( QValueListIterator<GElementID> it = children.begin(); it != children.end(); it++)
    {
      const GElement* element = data->read((*it));

      Q_CHECK_PTR(element);

      if (element)
      {
        renderElement(element);
      }
    }
  }
}

void GOpenGLFrame::startRendering(int sleep_ms_between_updates_min)
{
  this->StopRendering = false;
  this->RedrawSleepTime_ms = sleep_ms_between_updates_min;
  this->RedrawTimer.start(this->RedrawSleepTime_ms);
  this->FpsTimer.start();
}

void GOpenGLFrame::stopRendering()
{
  this->StopRendering = true;
}

void GOpenGLFrame::initializeGL()
{
  this->makeCurrent();
  glClearColor(0,0,0,0);  //black background
  glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); //start off with an empty background
  glShadeModel(GL_SMOOTH);
  glEnable(GL_DEPTH_TEST); //enable depth test
  glEnable(GL_BLEND); //enable blending - needed for alpha blending
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //alpha blending
  glEnable(GL_LIGHTING); //globally enable lighting
//   glEnable(GL_POLYGON_SMOOTH);
  
  //@todo: the light sources are ... a bit outdated, they were useful until 0.3.x versions. A new lighting model should be created.
  
//   create a light source
  glLightf(GL_LIGHT0,GL_CONSTANT_ATTENUATION,1);
  float v[] = {-1,5,-3,1};
  glLightfv(GL_LIGHT0,GL_POSITION,v);
  v[0] = v[1] = v[2] = 0.5;
  v[3] = 1;
  glLightfv(GL_LIGHT0,GL_AMBIENT,v);
  v[0] = 0.5;
  v[1] = 0.5;
  v[2] = 0.5;
  v[3] = 1;
  glLightfv(GL_LIGHT0,GL_DIFFUSE,v);
  glLightfv(GL_LIGHT0,GL_SPECULAR,v);
  glEnable(GL_LIGHT0);
  
  //another one:
//   glLightf(GL_LIGHT1,GL_CONSTANT_ATTENUATION,1);
//   v[0] = 0;
//   v[1] = -5;
//   v[2] = 3;
//   v[3] = 1;
//   glLightfv(GL_LIGHT1,GL_POSITION,v);
//   v[0] = v[1] = v[2] = 0;
//   v[3] = 0;
//   glLightfv(GL_LIGHT1,GL_AMBIENT,v);
//   v[0] = 0.5;
//   v[1] = 0.5;
//   v[2] = 0.5;
//   v[3] = 1;
//   glLightfv(GL_LIGHT1,GL_DIFFUSE,v);
//   glLightfv(GL_LIGHT1,GL_SPECULAR,v);
//   glEnable(GL_LIGHT1);
  
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void GOpenGLFrame::resizeGL(int w, int h)
{
  ViewProperties.width = w;
  ViewProperties.height = h;
  ViewProperties.update();
}

void GOpenGLFrame::paintGL()
{
  emit this->beforeRendering();
//   qDebug("rendering OpenGL frame");
  this->lock();

  Q_CHECK_PTR(Gwe);
  
  const GDataController* data = Gwe->getDataController();
  
  Q_CHECK_PTR(data);

  if (TopElements.isEmpty() || Gwe == NULL)
  {
//     qWarning("No top element given for rendering!");
  }
  else
  {
    //@todo we DO get a problem here if we do not have double buffering
    // because the screen gets cleared all the time (so nothing to
    // be seen...)

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  //   glClear(GL_DEPTH_BUFFER_BIT );
    glMatrixMode(GL_MODELVIEW);


    //draw top forms:
    for ( QValueListIterator<GElementID> it = TopElements.begin(); it != TopElements.end(); it++)
    {
      const GElement* element = data->read((*it));
      
      if (element == NULL)
      {
        qWarning(QString("top element with ID %1 not found!").arg((*it).getID()));
        continue; //well, nothing to render here
      }
      
      glLoadIdentity();
      
      //start with default values:
      GVector3 view_position(ViewProperties.ViewPosition);
      GVector3 view_target(ViewProperties.ViewTarget);
      GVector3 view_up(ViewProperties.ViewUp);
      
      //load element to which the camera is set:
      const GElement* camera_element = data->read(ViewProperties.CameraElement);
      
      if (camera_element)
      {
        this->CameraParent = camera_element->getObject()->getParent();
        QPtrStack<const GElement> camera_element_hierarchy;
        camera_element_hierarchy.push(camera_element);
        
        //push parent elements onto stack until we either reach the current top element or there is no further parent
        while (camera_element && camera_element->getElementID() != camera_element->getObject()->getParent() && camera_element->getElementID() != (*it))
        {
          camera_element = data->read(camera_element->getObject()->getParent());
          if (camera_element)
            camera_element_hierarchy.push(camera_element);
        }
        
        //now we have the whole element hierarchy that holds the camera element in one stack
        //so we can apply the camera transformations now to get final view positions and directions
        
        for (const GElement* el = camera_element_hierarchy.pop(); camera_element_hierarchy.isEmpty() == false; el = camera_element_hierarchy.pop())
        {
          const GObject* object = el->getObject();
          if (object->hasForm())
          {
            const GForm* f = object->getForm();
            //@todo we need to build translation and rotation matrix functionality into GMatrix44!!
            
            //first rotate
            view_position.turnAroundAxis(GVector3(1,0,0),f->Rotation.x);
            view_position.turnAroundAxis(GVector3(0,1,0),f->Rotation.y);
            view_position.turnAroundAxis(GVector3(0,0,1),f->Rotation.z);
            
            view_target.turnAroundAxis(GVector3(1,0,0),f->Rotation.x);
            view_target.turnAroundAxis(GVector3(0,1,0),f->Rotation.y);
            view_target.turnAroundAxis(GVector3(0,0,1),f->Rotation.z);
            
            view_up.turnAroundAxis(GVector3(1,0,0),f->Rotation.x);
            view_up.turnAroundAxis(GVector3(0,1,0),f->Rotation.y);
            view_up.turnAroundAxis(GVector3(0,0,1),f->Rotation.z);
            
            //then translate
            view_position.add(f->Position);
            view_target.add(f->Position);
          }
        }
        
      }
      else
      {
  //      qWarning("No camera element set for the view properties or element not available!");
      }
      
      gluLookAt(view_position.x,view_position.y,view_position.z,
                view_target.x,view_target.y,view_target.z,
                view_up.x,view_up.y,view_up.z);
      
      //do the actual hierarchical rendering
      renderElement(element);
      
    }
  
    //show some position and direction information:
    this->qglColor(Qt::white);
    this->renderText(10,20,QString("Camera Position: %1").arg(this->ViewProperties.ViewPosition.toString()));
    this->renderText(10,40,QString("User Position: %1").arg(this->ViewProperties.ViewTarget.toString()));
    this->renderText(10,60,QString("Direction: %1").arg((this->ViewProperties.ViewTarget - this->ViewProperties.ViewPosition).toString()));
    //@todo, remove navigation instructions! They should be shown on startup in a messagebox...
    this->renderText(10,100,"Navigation:");
    this->renderText(20,120,"spacebar: hold down to move forward");
    this->renderText(20,140,"enter: full stop");
    this->renderText(20,160,"arrow keys: turn camera around");
    
    if (!this->hasFocus())
    {
      this->renderText(10,this->height()-40,"OpenGL Frame does not have focus, click here to enable navigation!");
    }
    
    // FPS calculations
    double delta_t = FpsTimer.restart();
    this->TimeSinceFpsUpdate += delta_t;
    this->RenderingPeriods.append(delta_t);
    
    if (this->TimeSinceFpsUpdate >= 1000)
    {
      //recalculate fps:
      int n = this->RenderingPeriods.count();
      double sum_t = 0;
      QValueList<double>::iterator it;
      for (it = this->RenderingPeriods.begin(); it != this->RenderingPeriods.end(); ++it)
      {
        sum_t += *it;
      }
      
//       qDebug(QString("fps: n = %1, sum_t = %2").arg(QString::number(n)).arg(QString::number(sum_t)));
      
      if (sum_t > 0)
        this->FramesPerSecond = n/sum_t*1000;
      else
        this->FramesPerSecond = 0;
      
      this->RenderingPeriods.clear();
      this->TimeSinceFpsUpdate = 0;
    }
    
    //show FPS
    this->renderText(this->width()-100,20,QString("FPS: ") + QString::number(this->FramesPerSecond,'f',2));
    
    //at the end load an identitiy matrix
    glLoadIdentity();
    glFlush(); //make sure everything is drawn
    
  }
  
  if (!this->StopRendering)
  {
//     qDebug("Restaring rendering timer.");
    this->RedrawTimer.start(this->RedrawSleepTime_ms,TRUE);
  }
  
  this->unlock();
  
  emit this->afterRendering();
}

void GOpenGLFrame::addTopElement(const GElementID& element)
{
  QMutexLocker m(this);
  this->TopElements.append(element);
}

void GOpenGLFrame::removeTopElement(const GElementID& element)
{
  QMutexLocker m(this);
  this->TopElements.remove(element);
}

}


syntax highlighted by Code2HTML, v. 0.9.1