#include "BeachBall.h"


#include <igl/draw_beach_ball.h>
#include <igl/quat_to_mat.h>
#include <igl/unproject_to_zero_plane.h>
#include <igl/unproject.h>
#include <igl/project.h>
#include <igl/quat_mult.h>
#include <igl/quat_conjugate.h>
#include <igl/trackball.h>
#include <igl/mat_to_quat.h>

#include <OpenGL/GL.h>

#include <iostream>
#include <cmath>

BeachBall::BeachBall():
  radius(1),
  is_hover(false),
  is_down(false),
  trackball_on(false),
  down_x(-1),
  down_y(-1)
{
  r[0]=r[1]=r[2]=0;r[3]=1;
  t[0]=t[1]=t[2]=0;t[3]=0;
}

void BeachBall::pushmv() const
{
  glPushMatrix();
  glLoadIdentity();
  glMultMatrixd(mv);
}
void BeachBall::popmv() const
{
  glPopMatrix();
}

void BeachBall::push() const
{
  using namespace igl;
  glPushMatrix();
  // Translate to relative origin
  glTranslated(t[0],t[1],t[2]);
  // Rotate to relative orientation
  double mat[4*4]; quat_to_mat(r,mat); glMultMatrixd(mat);
}
void BeachBall::pop() const
{
  glPopMatrix();
}

void BeachBall::draw()
{
  using namespace std;
  using namespace igl;
  // Keep track of relative identity (modelview) matrix at draw call
  glGetDoublev(GL_MODELVIEW_MATRIX,mv);
  push();
  // Dim if hovered over
  float ld[4];
  glGetLightfv(GL_LIGHT0,GL_DIFFUSE,ld);
  if(is_hover)
  {
    float ld_hover[4];
    ld_hover[0] = 0.5*ld[0];
    ld_hover[1] = 0.5*ld[1];
    ld_hover[2] = 0.5*ld[2];
    ld_hover[3] = 0.5*ld[3];
    glLightfv(GL_LIGHT0,GL_DIFFUSE,ld_hover);
  }
  // Adjust scale only after setting origin
  glPushMatrix();
  glScaled(radius,radius,radius);
  // draw oriented glyph 
  draw_beach_ball();
  // Pop scale
  glPopMatrix();
  // Reset lighting
  glLightfv(GL_LIGHT0,GL_DIFFUSE,ld);
  // Reset relative identity
  pop();
}

bool BeachBall::in(const int x,const int y) const
{
  using namespace igl;
  using namespace std;
  pushmv();
  push();
  // Now origin is center of object
  double obj[3];
  // Check if unprojected screen point is nearby
  unproject_to_zero_plane(x,y, &obj[0], &obj[1], &obj[2]);
  bool near = (obj[0]*obj[0] + obj[1]*obj[1] + obj[2]*obj[2])<radius*radius;
  pop();
  popmv();
  return near;
}

bool BeachBall::hover(const int x,const int y)
{
  return is_hover = in(x,y);
}

bool BeachBall::down(const int x,const int y)
{
  using namespace std;
  using namespace igl;
  is_down = in(x,y);
  if(is_down)
  {
    copy(r,r+4,down_r);
    copy(t,t+4,down_t);
    down_x = x;
    down_y = y;
    trackball_on = true;
  }
  return is_down;
}

bool BeachBall::drag(const int x,const int y)
{
  using namespace igl;
  using namespace std;
  if(!is_down)
  {
    return false;
  }
  if(trackball_on)
  {
    // Pick up origin location
    pushmv();
    push();
    double origin[3];
    project(0,0,0,&origin[0],&origin[1],&origin[2]);
    pop();
    popmv();
    double rot[4];
    int VP[4];
    glGetIntegerv(GL_VIEWPORT,VP);
    trackball<double>(
      VP[2],
      VP[3],
      2,
            (down_x-origin[0]+VP[2]/2),
      VP[3]-(down_y-origin[1]+VP[3]/2),
            (     x-origin[0]+VP[2]/2),
      VP[3]-(     y-origin[1]+VP[3]/2),
      rot);
    {
      // We've computed change in rotation according to this view:
      // R = mv * r, R' = rot * (mv * r)
      // But we only want new value for r:
      // R' = mv * r'
      // mv * r' = rot * (mv * r)
      // r' = mv* * rot * sr * r
      // Convert modelview matrix to quaternion
      double sr_conj[4],scene_rot[4],t1[4],t2[4];
      mat4_to_quat(mv,scene_rot);
      //cout<<"::sr: "<<
      //  BeachBall::scene_rot[0]<<" "<<
      //  BeachBall::scene_rot[1]<<" "<<
      //  BeachBall::scene_rot[2]<<" "<<
      //  BeachBall::scene_rot[3]<<" "<<
      //  endl;
      //cout<<"  sr: "<<
      //  scene_rot[0]<<" "<<
      //  scene_rot[1]<<" "<<
      //  scene_rot[2]<<" "<<
      //  scene_rot[3]<<" "<<
      //  endl;
      // Normalize to unit quaternion (rotation only)
      double len = 
        sqrt(scene_rot[0]*scene_rot[0]+
        scene_rot[1]*scene_rot[1]+
        scene_rot[2]*scene_rot[2]+
        scene_rot[3]*scene_rot[3]);
      for(int j = 0;j<4;j++)
      {
        scene_rot[j] /= len;
      }
      // TODO: Just use Eigen::Quaterniond
      quat_conjugate(scene_rot,sr_conj);
      quat_mult<double>(sr_conj,rot,t1);
      quat_mult<double>(t1,scene_rot,t2);
      quat_mult<double>(t2,down_r,r);

    }
  }else
  {
    // We want that origin follows mouse move. First define plane we
    // projecteing screen mouse movement to as perpendicular plan passing
    // through this origin.
    pushmv();
    // down_t projected to screen to get depth value
    double p[3];
    project(down_t[0],down_t[1],down_t[2],&p[0],&p[1],&p[2]);
    // unprojected down_x,down_y with down_t depth
    double du[3];
    unproject(down_x,down_y,p[2],&du[0],&du[1],&du[2]);
    // unprojected x,y with down_t depth
    double u[3];
    unproject(x,y,p[2],&u[0], &u[1], &u[2]);
    popmv();
    // Then move this origin according to project mouse displacment
    t[0] = down_t[0] + (u[0]-du[0]);
    t[1] = down_t[1] + (u[1]-du[1]);
    t[2] = down_t[2] + (u[2]-du[2]);
  }
  return is_down;
}

bool BeachBall::right_down(const int x,const int y)
{
  using namespace std;
  using namespace igl;
  is_down = in(x,y);
  if(is_down)
  {
    copy(r,r+4,down_r);
    copy(t,t+4,down_t);
    down_x = x;
    down_y = y;
  }
  return is_down;
}

bool BeachBall::up(const int /*x*/,const int /*y*/)
{
  trackball_on = false;
  return is_down = false;
}
