// 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 "nfltkwindow.h"
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Choice.H>
#include <FL/fl_ask.H>
#include "FL/Fl_Native_File_Chooser.H"
#include <FL/Fl_Group.H>
#include <FL/Fl_Button.H>
#include <cstring>

#ifdef WIN32
#include "windows.h"
#endif //WIN32

int nglwindow::handle ( int event ) // called from fltk
{
  int ret=caller->handle(event);
  if (!ret)// if event not handled by caller,
  {
    switch ( event )
    {
      case FL_KEYBOARD:
      {
        int key=Fl::event_key();
        switch ( key )
        {
          case FL_Escape :
          {
            return 1;
            break;
          }
        }
      }
    }
    return Fl_Gl_Window::handle ( event );
  }
  else
    return 1;
}

void nglwindow::draw() // called automatically at every refresh
{
  caller->draw();
}


int nInfo::handle( int event ) // called from fltk : event handling proc
{
  // avoid exiting on 'esc' & clear buffer
/*  switch ( event )
  {
    case FL_KEYBOARD:
    {
      int key=Fl::event_key();
      switch ( key )
      {
        case FL_Escape :
        {
          Fl::copy(value(), size(),1); // to clipboard
          value("");
          return 1;
          break;
        }
      }
    }
  }*/
  return Fl_Output::handle(event);
}

// menu callbacks
void quit_callback(Fl_Widget* w, void* p);
void drawP_callback(Fl_Widget* w, void* p);
void drawL_callback(Fl_Widget* w, void* p);
void drawS_callback(Fl_Widget* w, void* p);
void drawT_callback(Fl_Widget* w, void* p);
void antiA_callback(Fl_Widget* w, void* p);
void unhide_callback(Fl_Widget* w, void* p);
void save_callback(Fl_Widget* w, void* p);
void load_callback(Fl_Widget* w, void* p);
void resetV_callback(Fl_Widget* w, void* p);
void Pick_callback(Fl_Widget* w, void* p);
void Pick_individual_callback(Fl_Widget* w, void* p);
void Pick_GL_callback(Fl_Widget* w, void* p);
void About_callback(Fl_Widget* w, void* p);

// button callbacks
void butX_callback( Fl_Widget* w , void* p);
void butY_callback( Fl_Widget* w , void* p);
void butZ_callback( Fl_Widget* w , void* p);

Fl_Menu_Item menuitems_1[] = {
  { "&File",              0, 0, 0, FL_SUBMENU },
    { "&Retrieve buffer from disk",  FL_COMMAND + 'r', (Fl_Callback *)load_callback},
    { "&Write buffer to disk",  FL_COMMAND + 'w', (Fl_Callback *)save_callback, 0, FL_MENU_DIVIDER },
    { "&Quit", FL_COMMAND + 'q', (Fl_Callback *)quit_callback, 0 },
    { 0 },
  { "&Draw",              0, 0, 0, FL_SUBMENU },
    { "Reset &View",   FL_COMMAND + 'v', (Fl_Callback *)resetV_callback,  0, FL_MENU_DIVIDER},
    { "Hide &Points",   FL_COMMAND + 'p', (Fl_Callback *)drawP_callback, 0, FL_MENU_TOGGLE},
    { "Hide &Lines",    FL_COMMAND + 'l', (Fl_Callback *)drawL_callback, 0, FL_MENU_TOGGLE},
    { "Hide &Surfaces",  FL_COMMAND + 's', (Fl_Callback *)drawS_callback, 0, FL_MENU_TOGGLE},
    { "Hide &Texts",  FL_COMMAND + 't', (Fl_Callback *)drawT_callback, 0 , FL_MENU_TOGGLE },
    { "&Unhide all entities",  FL_COMMAND + 'u', (Fl_Callback *)unhide_callback, 0, FL_MENU_DIVIDER},
    { "&Antialiasing",  FL_COMMAND + 'a', (Fl_Callback *)antiA_callback, 0, FL_MENU_TOGGLE},
    { 0 },
  { "&Edit",              0, 0, 0, FL_SUBMENU },
    { "Toggle Pic&king",  FL_COMMAND + 'k', (Fl_Callback *)Pick_callback, 0 , FL_MENU_TOGGLE},
    { "Toggle Picking &Individual Entities",  FL_COMMAND + 'i', (Fl_Callback *)Pick_individual_callback , 0 , FL_MENU_TOGGLE},
    { "Toggle G&L Picking",  FL_COMMAND + 'l', (Fl_Callback *)Pick_GL_callback , 0 , FL_MENU_TOGGLE},
    { 0 },
};

Fl_Menu_Item menuitems_2[] = {
  { "&Help",              0, 0, 0, FL_SUBMENU },
    { "&About",  0, (Fl_Callback *)About_callback},
    { 0 },
};

Fl_Menu_Item menuitems_end = { 0 };
Fl_Menu_Item menuitems_end_user = { "USER_MENU_END" };
Fl_Menu_Item menuitems_no_default = { "NO_DEFAULT_MENU" };

int eval_fontsize(int scrW)
{
      if(scrW <= 800) return 10;
      if(scrW <= 1024) return 11;
      if(scrW <= 1440) return 12;
      if(scrW <= 1680) return 13;
      if(scrW <= 1920) return 14;
      if(scrW <= 2304) return 15;
      if(scrW <= 2560) return 16;
      if(scrW <= 2880) return 18;
      if(scrW <= 3200) return 24;
      return ((scrW-800)/160 + 8); 
}

int checkmenuitem(const char * i)
{
  if (i)
  {
    if (strcmp(i,menuitems_end_user.text)==0)
      return 0;
    else if (strcmp(i,menuitems_no_default.text)==0)
      return -1;
    else return 1;
  }
  else
    return 1;
  
}

void nFltkWindow::buildMenus(const Fl_Menu_Item *menuitems_c,void *ptr)
{
  int c=0;
  bool defmenus=true;
  menuitems.clear();
  m_hide_p=0;  m_hide_l=0;  m_hide_s=0;  m_hide_t=0;
  m_aa=0;
  m_pick=0;  m_indiv_pick=0;  m_gl_pick=0;

  if (menuitems_c)
    if (checkmenuitem(menuitems_c[0].text)==-1) defmenus=false;
  for (int i=0;i<sizeof(::menuitems_1)/sizeof(Fl_Menu_Item);++i)
  {
    menuitems.push_back(::menuitems_1[i]);
    menuitems.rbegin()->user_data_=this;
    if (defmenus) menuitems.rbegin()->show();
    else menuitems.rbegin()->hide();
  }
  
  int i=0;
  if (menuitems_c)
  {
    while ((c=checkmenuitem(menuitems_c[i].text)))
    {
      if (c==1) // valid menu entry
      {
        menuitems.push_back(menuitems_c[i]);
        menuitems.rbegin()->user_data_=ptr;
      }
      ++i;
    }
  }
  
  for (int i=0;i<sizeof(::menuitems_2)/sizeof(Fl_Menu_Item);++i)
  {
    menuitems.push_back(::menuitems_2[i]);
    menuitems.rbegin()->user_data_=this;
    if (defmenus) menuitems.rbegin()->show();
    else menuitems.rbegin()->hide();
  }
  menuitems.push_back(menuitems_end);
  menu->menu(&menuitems[0]);
  
  for ( int t=0; t<menuitems.size(); t++ )
  {                // walk array of items, find the items with toggle
    Fl_Menu_Item &item = menuitems[t];       // get each item
    if (item.flags & FL_MENU_TOGGLE)
    {
/*      if (item.label())
        menuitems_ptr.push_back(&item);*/
      if (item.label())
      {
        if (strcmp(item.label(),"Hide &Points")==0) m_hide_p=&item;
        if (strcmp(item.label(),"Hide &Lines")==0) m_hide_l=&item;
        if (strcmp(item.label(),"Hide &Surfaces")==0) m_hide_s=&item;
        if (strcmp(item.label(),"Hide &Texts")==0) m_hide_t=&item;
        if (strcmp(item.label(),"&Antialiasing")==0) m_aa=&item;
        if (strcmp(item.label(),"Toggle Pic&king")==0) m_pick=&item;
        if (strcmp(item.label(),"Toggle Picking &Individual Entities")==0) m_indiv_pick=&item;
        if (strcmp(item.label(),"Toggle G&L Picking")==0) m_gl_pick=&item;
      }
/*    fprintf(stderr, "item #%d -- label=%s, value=%s type=%s\n",
            t,
            item.label() ? item.label() : "(Null)",          // menu terminators have NULL labels
            (item.flags & FL_MENU_VALUE) ? "set" : "clear",  // value of toggle or radio items
            (item.flags & FL_SUBMENU) ? "Submenu" : "Item"); // see if item is a submenu or actual item*/
    }
  }
  if (m_aa) m_aa->check();
  menu->redraw();
}

nFltkWindow::nFltkWindow(int w_, int h_, const char* t_,fltkdisplay *disp) : Fl_Double_Window(w_,h_,t_)
{
  m_hide_p=0;  m_hide_l=0;  m_hide_s=0;  m_hide_t=0;
  m_aa=0;
  m_pick=0;  m_indiv_pick=0;  m_gl_pick=0;
  fontsize=eval_fontsize(Fl::w());
  FL_NORMAL_SIZE=fontsize;
  Fl::visual(FL_DOUBLE);
  begin();
  menu = new Fl_Menu_Bar(0, 0, 0, 0);
  int tsz= menu->textsize();
  menu->resize(0, 0, w(), 2*tsz);
  buildMenus(0,this);
  gl = new nglwindow ( 0,2*tsz,w(), h()-4*tsz,disp);
  Fl_Group *bottom = new Fl_Group(0,h()-2*tsz,w(),2*tsz);
  bottom->begin();
  info = new nInfo(0,h()-2*tsz,w()-6*tsz,2*tsz);
  butX = new Fl_Button( w()-6*tsz, h()-2*tsz, 2*tsz, 2*tsz, "&X" );
  butY = new Fl_Button( w()-4*tsz, h()-2*tsz, 2*tsz, 2*tsz, "&Y" );
  butZ = new Fl_Button( w()-2*tsz, h()-2*tsz, 2*tsz, 2*tsz, "&Z" );
  bottom->end();
  end();
  info->value("---");
  copy_label("| | |");
  butX -> callback( ( Fl_Callback* ) butX_callback,this );
  butY -> callback( ( Fl_Callback* ) butY_callback,this );
  butZ -> callback( ( Fl_Callback* ) butZ_callback,this );
  butX->clear_visible_focus();
  butY->clear_visible_focus();
  butZ->clear_visible_focus();
  info->clear_visible_focus();
  bottom->resizable(info);
  resizable(gl);
  fd=gl->caller;
}

nFltkWindow::~nFltkWindow()
{
  delete gl;
  delete menu;
  delete info;
  delete butX;
  delete butY;
  delete butZ;
}


int nFltkWindow::handle ( int event ) // called from fltk
{
//  int ret=fd->handle(event);
//  if (!ret)// if event not handled by caller, 
    return Fl_Double_Window::handle ( event );
//  else
//    return 1;
}

void quit_callback(Fl_Widget* w, void* p)
{
  if (fl_choice("Are you sure you want to quit?", fl_no, fl_yes, 0))
    exit(0);
}

void drawP_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  if (win->m_hide_p) fd->drawpoints=win->m_hide_p->value()?false:true;
  else fd->drawpoints=!fd->drawpoints;
  fd->up_to_date=false;
  win->gl->redraw();
}

void drawL_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  if (win->m_hide_l) fd->drawlines=win->m_hide_l->value()?false:true;
  else fd->drawlines=!fd->drawlines;
  fd->up_to_date=false;
  win->gl->redraw();
}

void drawS_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  if (win->m_hide_s) fd->drawsurfaces=win->m_hide_s->value()?false:true;
  else fd->drawsurfaces=!fd->drawsurfaces;
  fd->up_to_date=false;
  win->gl->redraw();
}

void drawT_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  if (win->m_hide_t) fd->drawtexts=win->m_hide_t->value()?false:true;
  else fd->drawtexts=!fd->drawtexts;
  fd->up_to_date=false;
  win->gl->redraw();
}

void antiA_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  if (win->m_aa) fd->AA=win->m_aa->value()?true:false;
  else fd->AA=!fd->AA;
  fd->update_view();
  fd->up_to_date=false;
  win->gl->redraw();
  win->gl->take_focus(); // because of the change of context due to modified GL parameters in fd->gl->set_view();
}

void unhide_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  if (win->m_hide_p) win->m_hide_p->clear();
  if (win->m_hide_l) win->m_hide_l->clear();
  if (win->m_hide_s) win->m_hide_s->clear();
  if (win->m_hide_t) win->m_hide_t->clear();
  fd->unhide();
  fd->drawtexts=true;
  fd->drawpoints=true;
  fd->drawlines=true;
  fd->drawsurfaces=true;
  fd->up_to_date=false;
  win->gl->redraw();
}

void save_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  nglwindow *gl=win->gl;
  Fl_Native_File_Chooser *chooser = new Fl_Native_File_Chooser();
  chooser->type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
  chooser->preset_file("export.d3d");
  chooser->filter("D3D buffer files\t*.d3d");
  chooser->title("Save buffer");
  switch ( chooser->show() )
  {
    case -1:    // ERROR
      std::cout << "*** ERROR show() failed:" << chooser->errmsg()<< std::endl;
      break;
    case 1:     // CANCEL
      break;
    default:    // USER PICKED A FILE
      fd->save(chooser->filename());
      break;
  }
  delete chooser;
}

void load_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  nglwindow *gl=win->gl;
  Fl_Native_File_Chooser *chooser = new Fl_Native_File_Chooser();
  chooser->type(Fl_Native_File_Chooser::BROWSE_FILE);
  chooser->filter("D3D nutil buffer files\t*.d3d");
  chooser->title("Read buffer");
  switch ( chooser->show() )
  {
    case -1:    // ERROR
      std::cout << "*** ERROR show() failed:" << chooser->errmsg()<< std::endl;
      break;
    case 1:     // CANCEL
      break;
    default:    // USER PICKED A FILE
      fd->load(chooser->filename());
      break;
  }
  delete chooser;
}


void resetV_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  nglwindow *gl=win->gl;
  fd->reset_view();
  fd->setup_gl();
  win->gl->redraw();
}

void Pick_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  if (win->m_pick) fd->setpick(win->m_pick->value()?true:false);
  else fd->setpick(!fd->getpick());
  win->gl->redraw();
}

void Pick_individual_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  if (win->m_indiv_pick) fd->tagid=win->m_indiv_pick->value()?true:false;
  else fd->tagid=!fd->tagid;
  win->gl->redraw();
}

void Pick_GL_callback(Fl_Widget* w, void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  if (win->m_gl_pick) fd->gl_picks(win->m_gl_pick->value()?true:false);
  else fd->gl_picks(!fd->gl_picks());
  win->gl->redraw();
}

void About_callback(Fl_Widget* w, void* p)
{
  fl_message("   FLTK Display - a lightweight opengl display library\n\nThis free software is (c) 2007 Eric Bechet. You should have obtained\na copy of the license along with the source code of this executable.\nIf not, please write to bechet AT cadxfem DOT org");
}

void butX_callback( Fl_Widget* w , void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  nglwindow *gl=win->gl;
  fd->update_view_X(true);
  fd->setup_gl();
  win->gl->redraw();
}

void butY_callback( Fl_Widget* w , void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  nglwindow *gl=win->gl;
  fd->update_view_Y(true);
  fd->setup_gl();
  win->gl->redraw();
}

void butZ_callback( Fl_Widget* w , void* p)
{
  nFltkWindow *win = (nFltkWindow *)p;
  fltkdisplay *fd=win->fd;
  nglwindow *gl=win->gl;
  fd->update_view_Z(true);
  fd->setup_gl();
  win->gl->redraw();
}
