// 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 "fltkdisplay.h"
#include <cstring>
#include <iostream>
#include <FL/Fl.H>
#include <FL/gl.h>
#include <FL/Fl_Gl_Window.H>
#ifdef WIN32
#include "windows.h"
#endif //WIN32
#include <limits>
#include "nglwindow.h"


void fltkdisplay::draw_model(void) // called automatically at every refresh
{
  glDepthMask(GL_TRUE);
  draw_model(false);// display only solid elements
  glDepthMask(GL_FALSE);
  draw_model(true); // display only transparent elements
  glDepthMask(GL_TRUE);
}


void fltkdisplay::draw_model(bool transparent) // draw transparent or solid elements
{
  // draw quads
  if (dq)
  for (int blk=0;blk<dq->getnumpropquads();++blk)
  {
    glEnable(GL_LIGHTING);
    const properties &p=dq->getpropquads(blk);
    int last=(blk+1<dq->getnumpropquads()) ? dq->getindexquads(blk+1) : dq->nb_quads();
    if ((p.c.A!=std::numeric_limits<unsigned char>::max()) != (!transparent))
    {
      glColor4ub(p.c.R,p.c.G,p.c.B,p.c.A);
      glBegin(GL_QUADS);
      for (int i=dq->getindexquads(blk);i!=last;++i)
      {
        const quad &q=dq->get_quad(i);
        npoint3 A,B,normal;
        A=q.pts[1]-q.pts[0];
        B=q.pts[3]-q.pts[0];
        normal.crossprod(A,B);
        normal.normalize();
        glNormal3d(normal.x(),normal.y(),normal.z());
        for (int j=0;j<4;++j)
          glVertex3d(q.pts[j].x(),q.pts[j].y(),q.pts[j].z());
      }
      glEnd();
    }
    if (p.edgeon)
    {
      if ((p.edgecolor.A!=std::numeric_limits<unsigned char>::max()) != (!transparent))
      {
        glDisable(GL_LIGHTING);
        glColor4ub(p.edgecolor.R,p.edgecolor.G,p.edgecolor.B,p.edgecolor.A);
        glLineWidth(p.edgethickness);
        glBegin(GL_LINES);
        for (int i=dq->getindexquads(blk);i!=last;++i)
        {
          const quad &q=dq->get_quad(i);
          for (int j=0;j<4;++j)
          {
            glVertex3d(q.pts[j].x(),q.pts[j].y(),q.pts[j].z());
            glVertex3d(q.pts[(j+1)%4].x(),q.pts[(j+1)%4].y(),q.pts[(j+1)%4].z());
          }
        }
        glEnd();
      }
    }
  }

  // draw triangles
  if (dt)
  for (int blk=0;blk<dt->getnumproptriangles();++blk)
  {
    glEnable(GL_LIGHTING);
    const properties &p=dt->getproptriangles(blk);
    int last=(blk+1<dt->getnumproptriangles()) ? dt->getindextriangles(blk+1) : dt->nb_triangles();
    if ((p.c.A!=std::numeric_limits<unsigned char>::max()) != (!transparent))
    {
      glColor4ub(p.c.R,p.c.G,p.c.B,p.c.A);
      glBegin(GL_TRIANGLES);
      for (int i=dt->getindextriangles(blk);i!=last;++i)
      {
        const triangle &t=dt->get_triangle(i);
        const quad &q=dq->get_quad(i);
        npoint3 A,B,normal;
        A=t.pts[1]-t.pts[0];
        B=t.pts[2]-t.pts[0];
        normal.crossprod(A,B);
        normal.normalize();
        glNormal3d(normal.x(),normal.y(),normal.z());
        for (int j=0;j<3;++j)
        {
          glVertex3d(t.pts[j].x(),t.pts[j].y(),t.pts[j].z());
        }
      }
      glEnd();
    }
    if (p.edgeon)
    {
      glDisable(GL_LIGHTING);
      if ((p.edgecolor.A!=std::numeric_limits<unsigned char>::max()) != (!transparent))
      {
        glColor4ub(p.edgecolor.R,p.edgecolor.G,p.edgecolor.B,p.edgecolor.A);
        glLineWidth(p.edgethickness);
        glBegin(GL_LINES);
        for (int i=dt->getindextriangles(blk);i!=last;++i)
        {
          const triangle &t=dt->get_triangle(i);
          for (int j=0;j<3;++j)
          {
            glVertex3d(t.pts[j].x(),t.pts[j].y(),t.pts[j].z());
            glVertex3d(t.pts[(j+1)%3].x(),t.pts[(j+1)%3].y(),t.pts[(j+1)%3].z());
          }
        }
        glEnd();
      }
    }
  }
  
  // draw lines
  glDisable(GL_LIGHTING);
  if (dl)
  for (int blk=0;blk<dl->getnumproplines();++blk)
  {
    const properties &p=dl->getproplines(blk);
    int last=(blk+1<dl->getnumproplines()) ? dl->getindexlines(blk+1) : dl->nb_lines();
    if ((p.c.A!=std::numeric_limits<unsigned char>::max()) != (!transparent))
    {
      glColor4ub(p.c.R,p.c.G,p.c.B,p.c.A);
      glLineWidth(p.thickness);
      glBegin(GL_LINES);
      glNormal3f(0.,0.,0.);
      for (int i=dl->getindexlines(blk);i!=last;++i)
      {
        const line &l=dl->get_line(i);
        for (int j=0;j<2;++j)
        glVertex3d(l.pts[j].x(),l.pts[j].y(),l.pts[j].z());
      }
      glEnd();
    }
  }
  
  // draw points
  glDisable(GL_LIGHTING);
  if (dv)
  for (int blk=0;blk<dv->getnumproppoints();++blk)
  {
    const properties &p=dv->getproppoints(blk);
    int last=(blk+1<dv->getnumproppoints()) ? dv->getindexpoints(blk+1) : dv->nb_points();
    if ((p.c.A!=std::numeric_limits<unsigned char>::max()) != (!transparent))
    {
      glColor4ub(p.c.R,p.c.G,p.c.B,p.c.A);
      glPointSize(p.pointsize);
      glBegin(GL_POINTS);
      glNormal3f(0.,0.,0.);
      for (int i=dv->getindexpoints(blk);i!=last;++i)
      {
        const point &p=dv->get_point(i);
        glVertex3d(p.pts.x(),p.pts.y(),p.pts.z());
      }
      glEnd();
    }
  }
  
  //draw texts
  if ((!transparent))
  {
    glDisable(GL_LIGHTING);
    
    if (dtext)
    {
      gl_font(0, 12);
      glColor3f(1.0, 1.0, 1.0);
      for (int dim=0;dim<3;dim++)
      {
      
        int nb=dtext->nb_texts(dim);
        for (int i=0;i<nb;i++)
        {
          const point &pt=dtext->get_text(dim,i);
          const npoint3 &pos=pt.pts;        // 3D geometric position
          const char *txt=pt.info.c_str();  // actual text string
          glRasterPos3f(pos[0],pos[1],pos[2]);
          gl_draw(txt, strlen(txt));
        }
      }
    }
  }
  glEnable(GL_LIGHTING);
}



fltkdisplay::fltkdisplay(color _c,char *_name) : llf(std::numeric_limits< double >::max(),std::numeric_limits< double >::max(),std::numeric_limits< double >::max()),
                                                urb(std::numeric_limits< double >::min(),std::numeric_limits< double >::min(),std::numeric_limits< double >::min()),
                                                dv(NULL),dq(NULL),dl(NULL),dt(NULL),dtext(NULL)
{
  if (_name)
  {
    name=new char[strlen(_name)+1];
    strcpy(name,_name);
  }
  else
    name=NULL;
  c=_c;
}

void fltkdisplay::reset_bb(void)
{
  llf=npoint3(std::numeric_limits< double >::max(),std::numeric_limits< double >::max(),std::numeric_limits< double >::max());
  urb=npoint3(std::numeric_limits< double >::min(),std::numeric_limits< double >::min(),std::numeric_limits< double >::min());
}



void chk_bb(npoint3 &llf,npoint3 &urb,const npoint3 &t)
{
  for (int i=0;i<3 ; ++i)
  {
    if (llf[i]>t[i]) llf[i]=t[i];
    if (urb[i]<t[i]) urb[i]=t[i];
  }
}

void fltkdisplay::compute_bb(void)
{
  if (dq)
  for (int i=0;i!=dq->nb_quads();++i)
  {
    const quad &q=dq->get_quad(i);
    for (int j=0;j<4;++j)
    {
      chk_bb(llf,urb,q.pts[j]);
    }
  }
  if (dt)
  for (int i=0;i!=dt->nb_triangles();++i)
  {
    const triangle &t=dt->get_triangle(i);
    for (int j=0;j<3;++j)
    {
      chk_bb(llf,urb,t.pts[j]);
    }
  }
  if (dl)
  for (int i=0;i!=dl->nb_lines();++i)
  {
    const line &l=dl->get_line(i);
    for (int j=0;j<2;++j)
    {
      chk_bb(llf,urb,l.pts[j]);
    }
  }
  if (dv)
  for (int i=0;i!=dt->nb_points();++i)
  {
    const point &p=dt->get_point(i);
    chk_bb(llf,urb,p.pts);
  }
  if (dtext)
  for (int dim=0;dim<3;dim++)
  {
    int nb=dtext->nb_texts(dim);
    for (int i=0;i<nb;i++)
    {
      const point &pt=dtext->get_text(dim,i);
      chk_bb(llf,urb,pt.pts);
    }
  }
  double sz[3];
  double mean[3];
  int max=-1;
  double ratio=0.001;
  double valmax=-1;
  for (int i=0;i<3;++i)
  {
    sz[i]=fabs(llf[i]-urb[i]);
    mean[i]=(llf[i]+urb[i])/2.0;
    if (valmax<sz[i])
    {
      valmax=sz[i];
      max=i;
    }
  }
  if (valmax==0.0) { valmax=1.0; max=-1;}
  for (int i=0;i<3;++i)
  {
    if (i!=max)
      if (sz[i]<ratio*valmax)
      {
        llf[i]=mean[i]-valmax*ratio/2.0;
        urb[i]=mean[i]+valmax*ratio/2.0;
            sz[i]=fabs(llf[i]-urb[i]);
            mean[i]=(llf[i]+urb[i])/2.0;

      }
  }
}

fltkdisplay::~fltkdisplay()
{
  if (name) delete[] name;
}

void fltkdisplay::display(bool perspectiveon)
{
  Fl_Window *win =new Fl_Window ( 600, 600,name);
  nglwindow* gl = new nglwindow ( 0,0,win->w(), win->h(),c,this);
/*  FL_RGB - RGB color (not indexed)
    FL_RGB8 - RGB color with at least 8 bits of each color
    FL_INDEX - Indexed mode
    FL_SINGLE - not double buffered
    FL_DOUBLE - double buffered
    FL_ACCUM - accumulation buffer
    FL_ALPHA - alpha channel in color
    FL_DEPTH - depth buffer
    FL_STENCIL - stencil buffer
    FL_MULTISAMPLE */
/*
  std::cout << (gl->can_do(FL_RGB)?"FL_RGB ":"") ;
  std::cout << (gl->can_do(FL_RGB8)?"FL_RGB8 ":"");
  std::cout << (gl->can_do(FL_INDEX)?"FL_INDEX ":"");
  std::cout << (gl->can_do(FL_SINGLE)?"FL_SINGLE ":"");
  std::cout << (gl->can_do(FL_DOUBLE)?"FL_DOUBLE ":"");
  std::cout << (gl->can_do(FL_ACCUM)?"FL_ACCUM ":"");
  std::cout << (gl->can_do(FL_ALPHA)?"FL_ALPHA ":"");
  std::cout << (gl->can_do(FL_DEPTH)?"FL_DEPTH ":"");
  std::cout << (gl->can_do(FL_STENCIL)?"FL_STENCIL ":"");
  std::cout << (gl->can_do(FL_MULTISAMPLE)?"FL_MULTISAMPLE ":"") << std::endl;*/
  gl->mode(FL_ALPHA|FL_DOUBLE|FL_ACCUM|FL_DEPTH|FL_MULTISAMPLE);
  double focal=2.8;
  double lens=4.326661531;
  double fov =  (lens/2/focal ) * 2 * 180 / 3.14159265;
  npoint3 center=(llf+urb)/2.0;
  npoint3 size=urb-llf;
  double nearclip=size[2]/100;
  double farclip=(size[2])*100;
//  double size2=sqrt(size[0]*size[0]+size[1]*size[1]);
  const double look[3]={center[0],center[1],center[2]};
  const double vp[3]={center[0],center[1],center[2]+size[2]*1.5};
  const double up[3]={0.,1.,0.};
  gl->set_view(lens,focal,fov,nearclip,farclip,vp,look,up);
  win->end();
  win->resizable ( gl );
  win->show();
  Fl::run();
  delete gl;
  delete win;
}

void fltkdisplay::init_data_vertices(data_container & data)
{
  dv=&data;
}

void fltkdisplay::init_data_textes(data_container & data)
{
  dtext=&data;
}

void fltkdisplay::init_data_lines(data_container & data)
{
  dl=&data;
}

void fltkdisplay::init_data_triangles(data_container & data)
{
  dt=&data;
}

void fltkdisplay::init_data_quads(data_container & data)
{
  dq=&data;
}

void fltkdisplay::init_data(data_container & data)
{
  init_data_vertices(data);
  init_data_lines(data);
  init_data_triangles(data);
  init_data_quads(data);
  init_data_textes(data);
  reset_bb();
  compute_bb();
}
