#include <cmath>
#include <iostream>
#include <sstream>
#include <vector>
#ifdef WIN32
#include <windows.h> // For some "OpenGL" definitions...
#endif // WIN32
#include <GL/gl.h>
#include <GL/glu.h>

#include "popassert.h"
#include "CColor.h"
#include "CVector3f.h"
#include "CVectBase.h"
#include "compute.h"
#include "CMathExpression.h"
#include "CRenderObj.h"
#include "CGLPreview.h"

CGLPreview::CGLPreview( QWidget *parent, const char *name)
  : QGLWidget(parent, name)
{
  m_pSurfaceObj = NULL;

  m_pSphere = gluNewQuadric();
  gluQuadricDrawStyle(m_pSphere, (GLenum) GLU_FILL);
  gluQuadricNormals  (m_pSphere, (GLenum) GLU_SMOOTH);
  setFixedWidth(300);
  setFixedHeight(300);
}


CGLPreview::~CGLPreview()
{
  gluDeleteQuadric(m_pSphere);
}


void CGLPreview::initializeGL()
{
  glEnable(GL_DEPTH_TEST);
  glShadeModel(GL_SMOOTH);
  glCullFace(GL_BACK);
  glDisable(GL_DITHER);

  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHTING);

  GLfloat LightColor[] = { 1, 1, 1, 1 };
  glLightfv(GL_LIGHT0, GL_AMBIENT, LightColor);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, LightColor);
  glLightfv(GL_LIGHT0, GL_SPECULAR, LightColor);

  glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1);

  glMateriali(GL_FRONT_AND_BACK, GL_SHININESS, 1);

  glColor3f(1,1,1);
}


void CGLPreview::resizeGL(int w, int h)
{
  glViewport(0,0,w,h);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (m_pSurfaceObj != NULL)
    setProjectionMatrix();
  else
    gluPerspective(70, (GLfloat) w / (GLfloat) h, 0.1, 100);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}


void CGLPreview::paintGL()
{
  //  makeCurrent();

  if (m_pSurfaceObj)
    {
      // Background Color
      glClearColor( m_pSurfaceObj->m_BackgroundColor[0],
                    m_pSurfaceObj->m_BackgroundColor[1],
                    m_pSurfaceObj->m_BackgroundColor[2],
                    m_pSurfaceObj->m_BackgroundColor[3] );
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      // Camera setting
      setProjectionMatrix();
      setModelviewMatrix();

      // Lighting direction
      GLfloat LightPosition[] = { -m_pSurfaceObj->m_LightDirection.x,
                                  -m_pSurfaceObj->m_LightDirection.y,
                                  -m_pSurfaceObj->m_LightDirection.z,
                                  0 };
      glLightfv(GL_LIGHT0, GL_POSITION, LightPosition);

      // Plane rendering
      if (m_pSurfaceObj->m_Options & OPTION_BOUNDINGBOX)
        drawPlanes();

      // Sphere rendering
      drawSphere();
    }
  else
    {
      glClearColor(0,0,0,0);
      glClear(GL_COLOR_BUFFER_BIT);
    }
}


void CGLPreview::setModelviewMatrix() const
{
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  const CVectBase& VectorBase = m_pSurfaceObj->getVectorBase();
  const CVector3f& UpVector   = VectorBase.getB3();
  CVector3f ViewerPos         =
    m_pSurfaceObj->m_ViewerPos - VectorBase.getB1() *
    (VectorBase.getRadius() / VectorBase.getB1().Norm() * 
     m_pSurfaceObj->m_Zoom);

  gluLookAt( ViewerPos.x,
             ViewerPos.y,
             ViewerPos.z,
             m_pSurfaceObj->getCenterPos().x,
             m_pSurfaceObj->getCenterPos().y,
             m_pSurfaceObj->getCenterPos().z,
             UpVector.x,
             UpVector.y,
             UpVector.z );
}


void CGLPreview::setProjectionMatrix() const
{
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  // These constants will be used a couple of times
  float B1Norm = m_pSurfaceObj->getVectorBase().getB1().Norm();
  float B1ZoomNorm = B1Norm +
    m_pSurfaceObj->getVectorBase().getRadius() * m_pSurfaceObj->m_Zoom;
  float Radius = m_pSurfaceObj->getVectorBase().getRadius();

  if (m_pSurfaceObj->m_Options & OPTION_PERSPECTIVE)
    {
      float Value = Radius * (1 - Radius / B1Norm );

      glFrustum(-Value, Value,
                -Value, Value,
                B1Norm - Radius, B1ZoomNorm + Radius);
    }
  else
    {
      glOrtho(-Radius, Radius,
              -Radius, Radius,
              B1Norm - Radius, B1Norm + Radius);
    }
}


void CGLPreview::drawPlanes() const
{
  const CVector3f& MinVector = m_pSurfaceObj->m_MinVector;
  const CVector3f& MaxVector = m_pSurfaceObj->m_MaxVector;

  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,
               (float *) &m_pSurfaceObj->m_BoxAmbientColor);
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,
               (float *) &m_pSurfaceObj->m_BoxDiffuseColor);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,
               (float *) &m_pSurfaceObj->m_BoxSpecularColor);

  glEnable(GL_CULL_FACE);

  // Xmin plane
  glBegin(GL_QUADS);
  glNormal3f(1,0,0);
  glVertex3f(MinVector.x, MinVector.y, MinVector.z);
  glVertex3f(MinVector.x, MaxVector.y, MinVector.z);
  glVertex3f(MinVector.x, MaxVector.y, MaxVector.z);
  glVertex3f(MinVector.x, MinVector.y, MaxVector.z);
  glEnd();
 
  // Xmax plane
  glBegin(GL_QUADS);
  glNormal3f(-1,0,0);
  glVertex3f(MaxVector.x, MinVector.y, MinVector.z);
  glVertex3f(MaxVector.x, MinVector.y, MaxVector.z);
  glVertex3f(MaxVector.x, MaxVector.y, MaxVector.z);
  glVertex3f(MaxVector.x, MaxVector.y, MinVector.z);
  glEnd();

  // Ymin plane
  glBegin(GL_QUADS);
  glNormal3f(0,1,0);
  glVertex3f(MinVector.x, MinVector.y, MinVector.z);
  glVertex3f(MinVector.x, MinVector.y, MaxVector.z);
  glVertex3f(MaxVector.x, MinVector.y, MaxVector.z);
  glVertex3f(MaxVector.x, MinVector.y, MinVector.z);
  glEnd();

  // Ymax plane
  glBegin(GL_QUADS);
  glNormal3f(0,-1,0);
  glVertex3f(MinVector.x, MaxVector.y, MinVector.z);
  glVertex3f(MaxVector.x, MaxVector.y, MinVector.z);
  glVertex3f(MaxVector.x, MaxVector.y, MaxVector.z);
  glVertex3f(MinVector.x, MaxVector.y, MaxVector.z);
  glEnd();

  // Zmin plane
  glBegin(GL_QUADS);
  glNormal3f(0,0,1);
  glVertex3f(MinVector.x, MinVector.y, MinVector.z);
  glVertex3f(MaxVector.x, MinVector.y, MinVector.z);
  glVertex3f(MaxVector.x, MaxVector.y, MinVector.z);
  glVertex3f(MinVector.x, MaxVector.y, MinVector.z);
  glEnd();

  // Zmax plane
  glBegin(GL_QUADS);
  glNormal3f(0,0,-1);
  glVertex3f(MinVector.x, MinVector.y, MaxVector.z);
  glVertex3f(MinVector.x, MaxVector.y, MaxVector.z);
  glVertex3f(MaxVector.x, MaxVector.y, MaxVector.z);
  glVertex3f(MaxVector.x, MinVector.y, MaxVector.z);
  glEnd();

  glDisable(GL_CULL_FACE);
}


void CGLPreview::drawSphere() const
{
  glPushMatrix();

  glTranslatef( m_pSurfaceObj->getCenterPos().x,
                m_pSurfaceObj->getCenterPos().y,
                m_pSurfaceObj->getCenterPos().z );
  glScalef( m_pSurfaceObj->m_MaxVector.x - m_pSurfaceObj->m_MinVector.x,
            m_pSurfaceObj->m_MaxVector.y - m_pSurfaceObj->m_MinVector.y,
            m_pSurfaceObj->m_MaxVector.z - m_pSurfaceObj->m_MinVector.z);

  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,
               (float *) &m_pSurfaceObj->m_SurfaceAmbientColor);
  glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE,
               (float *) &m_pSurfaceObj->m_SurfaceDiffuseColor);
  glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,
               (float *) &m_pSurfaceObj->m_SurfaceSpecularColor);

  // Could use GL_RESCALE_NORMAL on appropriate OpenGL implementations
  glEnable(GL_NORMALIZE);
  gluSphere(m_pSphere,
            sqrt(3) / 4,
            100,
            100);
  glDisable(GL_NORMALIZE);

  glPopMatrix();
}



void CGLPreview::mousePressEvent( QMouseEvent * e )
{
  m_CursorPosX = e->x();
  m_CursorPosY = e->y();
}



void CGLPreview::mouseMoveEvent( QMouseEvent * e )
{
  // Rotation regarding horizontal displacement
  int dX = e->x() - m_CursorPosX;
  if (dX != 0)
    {
      m_CursorPosX = e->x();

      CVector3f Direction =
        m_pSurfaceObj->m_ViewerPos - m_pSurfaceObj->getCenterPos();

      // Up vector
      const CVector3f& Axe = m_pSurfaceObj->getVectorBase().getB3();

      CVector3f NewPos;

      float Cos = cos((float) dX / 100.0f);
      float Sin = sin((float) dX / 100.0f);

      // Rotation around the up vector
      NewPos.x  = (Cos + (1 - Cos) * Axe.x * Axe.x)         * Direction.x;
      NewPos.x += ((1 - Cos) * Axe.x * Axe.y - Axe.z * Sin) * Direction.y;
      NewPos.x += ((1 - Cos) * Axe.x * Axe.z + Axe.y * Sin) * Direction.z;

      NewPos.y  = ((1 - Cos) * Axe.x * Axe.y + Axe.z * Sin) * Direction.x;
      NewPos.y += (Cos + (1 - Cos) * Axe.y * Axe.y)         * Direction.y;
      NewPos.y += ((1 - Cos) * Axe.y * Axe.z - Axe.x * Sin) * Direction.z;

      NewPos.z  = ((1 - Cos) * Axe.x * Axe.z - Axe.y * Sin) * Direction.x;
      NewPos.z += ((1 - Cos) * Axe.y * Axe.z + Axe.x * Sin) * Direction.y;
      NewPos.z += (Cos + (1 - Cos) * Axe.z * Axe.z)         * Direction.z;

      m_pSurfaceObj->m_ViewerPos = m_pSurfaceObj->getCenterPos() + NewPos;
      m_pSurfaceObj->UpdateVectorBase();
    }

  // Rotation regarding vertical displacement
  int dY = e->y() - m_CursorPosY;
  if (dY != 0)
    {
      m_CursorPosY = e->y();

      CVector3f Direction =
        m_pSurfaceObj->m_ViewerPos - m_pSurfaceObj->getCenterPos();

      // Up vector
      const CVector3f& Axe = m_pSurfaceObj->getVectorBase().getB2();

      CVector3f NewPos;

      float Cos = cos((float) dY / 100.0f);
      float Sin = sin((float) dY / 100.0f);

      // Rotation around the up vector
      NewPos.x  = (Cos + (1 - Cos) * Axe.x * Axe.x)         * Direction.x;
      NewPos.x += ((1 - Cos) * Axe.x * Axe.y - Axe.z * Sin) * Direction.y;
      NewPos.x += ((1 - Cos) * Axe.x * Axe.z + Axe.y * Sin) * Direction.z;

      NewPos.y  = ((1 - Cos) * Axe.x * Axe.y + Axe.z * Sin) * Direction.x;
      NewPos.y += (Cos + (1 - Cos) * Axe.y * Axe.y)         * Direction.y;
      NewPos.y += ((1 - Cos) * Axe.y * Axe.z - Axe.x * Sin) * Direction.z;

      NewPos.z  = ((1 - Cos) * Axe.x * Axe.z - Axe.y * Sin) * Direction.x;
      NewPos.z += ((1 - Cos) * Axe.y * Axe.z + Axe.x * Sin) * Direction.y;
      NewPos.z += (Cos + (1 - Cos) * Axe.z * Axe.z)         * Direction.z;

      m_pSurfaceObj->m_ViewerPos = m_pSurfaceObj->getCenterPos() + NewPos;
      m_pSurfaceObj->UpdateVectorBase();
    }

  emit(UpdatedVectorBase());
  updateGL();
}
