// nUtil - An utility Library for gnurbs
// Copyright (C) 2008-2026 Eric Bechet
//
// See the LICENSE file for contributions and license information.
// Please report all bugs and problems to <bechet@cadxfem.org>.
//

#include "nglwindow.h"
#include "nglu.h"

int nglwindow::handle ( int event ) // called from fltk
{
  if (!caller->handle(event))// if event not handled by caller, 
  switch ( event )
  {
    case FL_PUSH:
//    ... mouse down event ...
//    ... position in Fl::event_x() and Fl::event_y()
    {
      npoint3 p;
      npoint3 v;
      GLdouble xpp,ypp,zz;
      xp=Fl::event_x();
      yp=h()-Fl::event_y();
      get_coords ( xp,yp, p,v,z);
/*      std::cout << "click on screen      : " <<  xp <<" " <<  yp << " " << z << std::endl;
      std::cout << "retrieve 3D coords   : " <<  p.x() <<" " <<  p.y() << " " << p.z() << std::endl;
      double finalMatrix[16];
      _gluMultMatricesd ( ( GLdouble * )ModelViewMatrix, ( GLdouble * )ProjectionMatrix,( GLdouble * ) finalMatrix );
      _gluProjectFast(p.x(),p.y(),p.z(),finalMatrix,( GLint * )Viewport,&xpp,&ypp,&zz);
      std::cout << "reproject ons screen : " <<  xpp <<" " <<  ypp << " " << zz << std::endl<< std::endl;*/
      orig=p;
      dinit=0.;
      for (int i=0;i<3;++i) {dinit+=(look[i]-vp[i])*(look[i]-vp[i]);}
      dinit=sqrt(dinit);
      orig=p;
//      redraw();
      return 1;
    }

    case FL_DRAG:
//    ... mouse moved while down event ...
    {
      if (Fl::event_button2())
      {
        npoint3 p;
        int xx= Fl::event_x();
        int yy=h()-Fl::event_y();
        get_coords (xx,yy,z, p);
        for (int i=0;i<3;++i) { look[i]-=p[i]-orig[i];vp[i]-=p[i]-orig[i];orig[i]=p[i];}
        set_view();
        redraw();
        return 1;
      }
      else if (Fl::event_button1())
      {
        npoint3 p;
        npoint3 dir;
        int xx= Fl::event_x();
        int yy=h()-Fl::event_y();
        get_coords (xx,yy,z, p);
        double d=0.0;
        for (int i=0;i<3;++i) {d+=(look[i]-vp[i]+p[i]-orig[i])*(look[i]-vp[i]+p[i]-orig[i]);}
        d=sqrt(d);
        for (int i=0;i<3;++i) { vp[i]-=(p[i]-orig[i]);orig[i]=p[i];}
        for (int i=0;i<3;++i) { vp[i]=(vp[i]-look[i])*dinit/d + look[i];}
        d=0.0;
        for (int i=0;i<3;++i) { dir[i]=vp[i]-look[i] ; d+=dir[i]*dir[i];}
        d=sqrt(d);
        for (int i=0;i<3;++i) { dir[i]/=d;}
        double pscal=0;
        for (int i=0;i<3;++i) { pscal+=dir[i]*up[i];}
        d=0.0;
        for (int i=0;i<3;++i) { up[i]-=dir[i]*pscal;d+=up[i]*up[i];}
        d=sqrt(d);
        for (int i=0;i<3;++i) { up[i]/=d;}
        set_view();
        redraw();
        return 1;
      }
    }



    case FL_RELEASE:
//    ... mouse up event ...
    { 
      get_matrices();
      return 1;
    }
    case FL_FOCUS :
    case FL_UNFOCUS :
//    ... Return 1 if you want keyboard events, 0 otherwise
      return 1;

    case FL_MOUSEWHEEL:
    {
      if (Fl::event_shift())
      {
        focal*=(1.0-Fl::event_dy()/10.0);
        fov=(( lens/2/focal ) * 2 * 180 / 3.14159265);
      }
      else
      {
        npoint3 p;
        npoint3 v;
        xp=Fl::event_x();
        yp=h()-Fl::event_y();
        get_coords ( xp,yp, p,v,z);
        orig=p;
        double d=0.0;
        for (int i=0;i<3;++i) {d+=(look[i]-vp[i])*(look[i]-vp[i]);}
        d=sqrt(d);
        d/=10.;
        for (int i=0;i<3;++i) {look[i]-=v[i]*Fl::event_dy()*d;vp[i]-=v[i]*Fl::event_dy()*d;}
      }
      set_view();
      redraw();
      get_matrices();
      return 1;
    }
    case FL_KEYBOARD:
    {
      switch ( Fl::event_key() )
      {
        case 65260 :      // MOUSEWHEEL en avant
        {
          break;
        }

        case 65261 :      // MOUSEWHEEL en arriere
        {
          break;
        }

        case 65361 :      // KEY LEFT
        {
          break;
        }

        case 65362 :      // KEY UP
        {
          break;
        }

        case 65363 :      // KEY RIGHT
        {
          break;
        }

        case 65364 :      // KEY DOWN
        {
          break;
        }
      }

//    ... keypress, key is in Fl::event_key(), ascii in Fl::event_text()
//    ... Return 1 if you understand/use the keyboard event, 0 otherwise...
      return 1;
    }

    case FL_SHORTCUT:
//    ... shortcut, key is in Fl::event_key(), ascii in Fl::event_text()
//    ... Return 1 if you understand/use the shortcut event, 0 otherwise...
      return 1;

    default:
      // pass other events to the base class...
      return Fl_Gl_Window::handle ( event );
  }
  else return 1;
}


void nglwindow::draw_model ( void ) // called automatically at every refresh
{

  caller->draw_model();
/*  glColor4ub(255,255,255,255);
  glBegin(GL_POINTS);
  glVertex3d(pt.x(),pt.y(),pt.z());
  glEnd();*/
}

void  nglwindow::draw ( void )  // called from fltk
{
  if ( !valid() )
  {
    set_view();
    get_matrices();
    print_info();
  }
  glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  draw_model();
  swap_buffers();
  glFlush();
}

bool nglwindow::get_coords ( GLfloat i,GLfloat j, npoint3 &p,npoint3 &v,GLfloat &z)
{
  npoint3 p2;
  bool object=true;
  z = 0.0f;
  glReadBuffer(GL_FRONT);
  glReadPixels(i, j, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z);
  if (z==1.0f) // no object or out of frustrum : we take the point at which we look to as reference distance
  {
    object=false;
    double finalMatrix[16];
    double xpp,ypp,zz;
    _gluMultMatricesd ( ( GLdouble * )ModelViewMatrix, ( GLdouble * )ProjectionMatrix,( GLdouble * ) finalMatrix );
    _gluProjectFast(look[0],look[1],look[2],finalMatrix,( GLint * )Viewport,&xpp,&ypp,&zz);
    z=zz;
  }
  _gluUnProjectFast ( i*1.0,j*1.0,z, ( GLdouble* ) Inv, ( GLint * ) Viewport,&p[0], &p[1], &p[2] );
  _gluUnProjectFast ( i*1.0,j*1.0,0.0, ( GLdouble* ) Inv, ( GLint * ) Viewport,&p2[0], &p2[1], &p2[2]);
  v=p-p2;
  v.normalize();
  if (z==1.0f) // no object or out of frustrum
  {
    p=p;
  }
  return object; // object found or not
}

bool nglwindow::get_coords ( GLfloat di,GLfloat dj,GLfloat z_, npoint3 &p)
{
  npoint3 p2;
  _gluUnProjectFast ( di*1.0,dj*1.0,z_, ( GLdouble* ) Inv, ( GLint * ) Viewport,&p2[0], &p2[1], &p2[2]);
  p=p2;
  return true;
}


void nglwindow::get_matrices ( void )
{
  glGetDoublev ( GL_MODELVIEW_MATRIX, ( GLdouble * ) ModelViewMatrix );
  glGetDoublev ( GL_PROJECTION_MATRIX, ( GLdouble * ) ProjectionMatrix );
  glGetIntegerv ( GL_VIEWPORT, ( GLint * ) Viewport );
  _gluMultMatricesd ( ( GLdouble * ) ModelViewMatrix, ( GLdouble * )  ProjectionMatrix, ( GLdouble * ) Inv );
  _gluInvertMatrixd ( ( GLdouble * ) Inv, ( GLdouble * ) Inv );
}


void nglwindow::set_view (double lens_,double focal_, double fov_,double nearclip_,double farclip_,const double vp_[3],const double look_[3],const double up_[3])
{
  lens=lens_;focal=focal_,fov=fov_,nearclip=nearclip_,farclip=farclip_;
  for (int i=0;i<3;++i) {vp[i]=vp_[i];look[i]=look_[i];up[i]=up_[i];}
  set_view();
}

void nglwindow::set_view (void)
{
//  glDepthFunc ( GL_LESS );
  glEnable ( GL_DEPTH_TEST );
  glEnable( GL_BLEND ); 
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
  glPolygonMode ( GL_FRONT_AND_BACK,GL_FILL);
  glLoadIdentity();
  glViewport ( 0,0,w(),h() );
  glOrtho ( 0,w(),0,h(),0,1 );

  // couleur de fond (RGBT)
  glClearColor ( c.R/255., c.G/255., c.B/255.,c.A/255.);
  glClearAccum(0.0,0.0,0.0,0.0);
  // Remplissage
//  glShadeModel ( GL_FLAT);
  glShadeModel ( GL_SMOOTH );

  glMatrixMode ( GL_PROJECTION );
  glPushMatrix();
  glLoadIdentity();
  _gluPerspective ( fov, ( GLdouble ) w() / ( GLdouble ) h(),
                      nearclip, farclip );
  glMatrixMode ( GL_MODELVIEW );
  glPushMatrix();
  glLoadIdentity();
  _gluLookAt ( vp,
                  look,    /* lookat point */
                  up );  /* up is in +ive y */


  glEnable(GL_LINE_SMOOTH);
  glEnable(GL_POINT_SMOOTH);
  glEnable ( GL_LIGHTING );
  glEnable(GL_COLOR_MATERIAL);
  glEnable(GL_LIGHTING);
  add_lights();  
}

void nglwindow::add_lights ( void )
{
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
  GLfloat light_ambient[] =
  {0.0, 0.0, 0.0, 0.0};
  GLfloat light_diffuse[] =
  {1.0, 1.0, 1.0, 1.0};
  GLfloat light_specular[] =
  {0.0, 0.0, 0.0, 1.0};
  GLfloat light_position[] =
  {vp[0]-look[0],vp[1]-look[1],vp[2]-look[2], 0.0};
  glLightfv ( GL_LIGHT0, GL_AMBIENT, light_ambient );
  glLightfv ( GL_LIGHT0, GL_DIFFUSE, light_diffuse );
  glLightfv ( GL_LIGHT0, GL_SPECULAR, light_specular );
  glLightfv ( GL_LIGHT0, GL_POSITION, light_position );
  glEnable ( GL_LIGHT0);
}

