//
// GLUT emulation routines for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2023 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file.  If this
// file is missing or damaged, see the license at:
//
//     https://www.fltk.org/COPYING.php
//
// Please see the following page on how to report bugs and issues:
//
//     https://www.fltk.org/bugs.php
//

// Emulation of Glut using FLTK.

// GLUT is Copyright (c) Mark J. Kilgard, 1994, 1995, 1996.
// "This program is freely distributable without licensing fees and is
// provided without guarantee or warrantee expressed or implied. This
// program is -not- in the public domain."

// Although I have copied the GLUT API, none of my code is based on
// any Glut implementation details and is therefore covered by the LGPL.

#include "flstring.h"
#if HAVE_GL
#  include "Fl_Gl_Window_Driver.H"
#  include "Fl_Screen_Driver.H"
#  include <FL/glut.H>
#  define MAXWINDOWS 32

static Fl_Glut_Window *windows[MAXWINDOWS+1];

static void (*glut_idle_func)() = 0; // global glut idle function

// The current GLUT window, may be NULL. See also STR #3458.
Fl_Glut_Window *glut_window;
int glut_menu;
void (*glut_menustate_function)(int);
void (*glut_menustatus_function)(int,int,int);

static void default_reshape(int w, int h) {glViewport(0,0,w,h);}
static void default_display() {}

void Fl_Glut_Window::make_current() {
  glut_window = this;
  if (shown()) Fl_Gl_Window::make_current();
}

static int indraw;
void Fl_Glut_Window::draw() {
  glut_window = this;
  indraw = 1;
  if (!valid()) {reshape(pixel_w(),pixel_h()); valid(1);}
  display();
  if (children()) {
    Fl_Gl_Window::draw(); // Draw FLTK child widgets
  }
  indraw = 0;
}

void glutSwapBuffers() {
  if (!indraw && glut_window)
    glut_window->swap_buffers();
}

void Fl_Glut_Window::draw_overlay() {
  glut_window = this;
  if (!valid()) {reshape(pixel_w(),pixel_h()); valid(1);}
  overlaydisplay();
}

static void domenu(int, int, int);

int Fl_Glut_Window::handle(int event) {
  make_current();
  int ex = Fl::event_x();
  int ey = Fl::event_y();
  float factor = pixels_per_unit();
  ex = int(ex * factor + 0.5f);
  ey = int(ey * factor + 0.5f);
  int button;
  switch (event) {

  case FL_PUSH:
    if (keyboard || special) Fl::focus(this);
    button = Fl::event_button()-1;
    if (button<0) button = 0;
    if (button>2) button = 2;
    if (menu[button]) {domenu(menu[button],ex,ey); return 1;}
    mouse_down |= 1<<button;
    if (mouse) {mouse(button,GLUT_DOWN,ex,ey); return 1;}
    if (motion) return 1;
    break;

  case FL_MOUSEWHEEL:
    button = Fl::event_dy();
    while (button < 0) {if (mouse) mouse(3,GLUT_DOWN,ex,ey); ++button;}
    while (button > 0) {if (mouse) mouse(4,GLUT_DOWN,ex,ey); --button;}
    return 1;

  case FL_RELEASE:
    for (button = 0; button < 3; button++) if (mouse_down & (1<<button)) {
      if (mouse) mouse(button,GLUT_UP,ex,ey);
    }
    mouse_down = 0;
    return 1;

  case FL_ENTER:
    if (entry) {entry(GLUT_ENTERED); return 1;}
    if (passivemotion) return 1;
    break;

  case FL_LEAVE:
    if (entry) {entry(GLUT_LEFT); return 1;}
    if (passivemotion) return 1;
    break;

  case FL_DRAG:
    if (motion) {motion(ex, ey); return 1;}
    break;

  case FL_MOVE:
    if (passivemotion) {passivemotion(ex, ey); return 1;}
    break;

  case FL_FOCUS:
    if (keyboard || special) return 1;
    break;

  case FL_SHORTCUT:
    if (!keyboard && !special) break;

  case FL_KEYBOARD:
      // keyboard() does not distinguish between recognized and unrecognized keystrokes
      // so check for window scaling keystroke before normal keystroke processing
    if ( Fl::event_command() && Fl_Screen_Driver::scale_handler(FL_SHORTCUT) ) return 1;
    if (Fl::event_text()[0]) {
      if (keyboard) {keyboard(Fl::event_text()[0],ex,ey); return 1;}
      break;
    } else {
      if (special) {
        int k = Fl::event_key();
        if (k > FL_F && k <= FL_F_Last) k -= FL_F;
        special(k,ex,ey);
        return 1;
      }
      break;
    }

  case FL_HIDE:
    if (visibility) visibility(GLUT_NOT_VISIBLE);
    break;

  case FL_SHOW:
    if (visibility) visibility(GLUT_VISIBLE);
    break;
  }

  return Fl_Gl_Window::handle(event);
}

static int glut_mode = GLUT_RGB | GLUT_SINGLE | GLUT_DEPTH;

void Fl_Glut_Window::_init() {
  for (number=1; number<MAXWINDOWS; number++) if (!windows[number]) break;
  windows[number] = this;
  menu[0] = menu[1] = menu[2] = 0;
  reshape = default_reshape;
  display = default_display;
  overlaydisplay = default_display;
  keyboard = 0;
  mouse = 0;
  motion = 0;
  passivemotion = 0;
  entry = 0;
  visibility = 0;
  special = 0;
  mouse_down = 0;
  mode(glut_mode);
}

/** Creates a glut window, registers to the glut windows list.*/
Fl_Glut_Window::Fl_Glut_Window(int W, int H, const char *t) :
  Fl_Gl_Window(W,H,t) {_init();}

/** Creates a glut window, registers to the glut windows list.*/
Fl_Glut_Window::Fl_Glut_Window(int X,int Y,int W,int H, const char *t) :
  Fl_Gl_Window(X,Y,W,H,t) {_init();}

static int initargc;
static char **initargv;
static char glut_starttime_set = 0;
static Fl_Timestamp glut_starttime;

void glutInit(int *argc, char **argv) {
  if (!glut_starttime_set) {
    glut_starttime = Fl::now();
    glut_starttime_set = 1;
  }
  initargc = *argc;
  initargv = new char*[*argc+1];
  int i,j;
  for (i=0; i<=*argc; i++) initargv[i] = argv[i];
  for (i=j=1; i<*argc; ) {
    if (Fl::arg(*argc,argv,i));
    else argv[j++] = argv[i++];
  }
  argv[j] = 0;
  *argc = j;
}

void glutInitDisplayMode(unsigned int mode) {
  glut_mode = mode;
}

void glutMainLoop() {Fl::run();}

////////////////////////////////////////////////////////////////

static int initx=0, inity=0, initw=300, inith=300, initpos=0;

void glutInitWindowPosition(int x, int y) {
  initx = x; inity = y; initpos = 1;
}

void glutInitWindowSize(int w, int h) {
  initw = w; inith = h;
}

int glutCreateWindow(char *title) {
  return glutCreateWindow((const char*)title);
}

int glutCreateWindow(const char *title) {
  Fl_Glut_Window *W;
  if (initpos) {
    W = new Fl_Glut_Window(initx,inity,initw,inith,title);
    initpos = 0;
  } else {
    W = new Fl_Glut_Window(initw,inith,title);
  }
  W->resizable(W);
  if (initargc) {
    W->show(initargc,initargv);
    initargc = 0;
  } else {
    W->show();
  }
  W->valid(0);
  W->context_valid(0);
  W->make_current();
  // some platforms (e.g., macOS 10.9) draw the window while show() runs
  // but the window's draw function is not yet set when this function runs
  W->redraw();
  return W->number;
}

int glutCreateSubWindow(int win, int x, int y, int w, int h) {
  Fl_Glut_Window *W = new Fl_Glut_Window(x,y,w,h,0);
  windows[win]->add(W);
  if (windows[win]->shown()) {
    W->show();
    W->make_current();
    W->redraw();
  }
  return W->number;
}
/** Destroys the glut window, first unregister it from the glut windows list */
Fl_Glut_Window::~Fl_Glut_Window() {
  if (glut_window == this) glut_window = 0;
  windows[number] = 0;
}

void glutDestroyWindow(int win) {
  // should destroy children!!!
  delete windows[win];
}

void glutPostWindowRedisplay(int win) {
  windows[win]->redraw();
}

void glutSetWindow(int win) {
  windows[win]->make_current();
}

////////////////////////////////////////////////////////////////
#include <FL/Fl_Menu_Item.H>

struct menu {
  void (*cb)(int);
  Fl_Menu_Item *m;
  int size;
  int alloc;
};

#define MAXMENUS 32
static menu menus[MAXMENUS+1];

static void domenu(int n, int ex, int ey) {
  glut_menu = n;
  menu *m = &menus[n];
  if (glut_menustate_function) glut_menustate_function(1);
  if (glut_menustatus_function) glut_menustatus_function(1,ex,ey);
  const Fl_Menu_Item* g = m->m->popup(Fl::event_x(), Fl::event_y(), 0);
  if (g && g->callback_) ((void (*)(int))(g->callback_))(int(g->argument()));
  if (glut_menustatus_function) glut_menustatus_function(0,ex,ey);
  if (glut_menustate_function) glut_menustate_function(0);
}

int glutCreateMenu(void (*cb)(int)) {
  int i;
  for (i=1; i<MAXMENUS; i++) if (!menus[i].cb) break;
  menu *m = &menus[i];
  m->cb = cb;
  return glut_menu = i;
}

void glutDestroyMenu(int n) {
  menu *m = &menus[n];
  delete[] m->m;
  m->m = 0;
  m->cb = 0;
  m->size = m->alloc = 0;
}

static Fl_Menu_Item* additem(menu *m) {
  if (m->size+1 >= m->alloc) {
    m->alloc = m->size*2+10;
    Fl_Menu_Item* nm = new Fl_Menu_Item[m->alloc];
    for (int i=0; i<m->size; i++) nm[i] = m->m[i];
    delete[] m->m;
    m->m = nm;
  }
  int n = m->size++;
  m->m[n+1].text = 0;
  Fl_Menu_Item* i = &(m->m[n]);
  i->shortcut_ = 0;
  i->flags = 0;
  i->labeltype_ = i->labelfont_ = i->labelsize_ = i->labelcolor_ = 0;
  return i;
}

void glutAddMenuEntry(const char *label, int value) {
  menu *m = &menus[glut_menu];
  Fl_Menu_Item* i = additem(m);
  i->text = label;
  i->callback_ = (Fl_Callback*)(m->cb);
  i->argument(value);
}

void glutAddSubMenu(char *label, int submenu) {
  menu *m = &menus[glut_menu];
  Fl_Menu_Item* i = additem(m);
  i->text = label;
  i->callback_ = 0;
  i->user_data_ = (void *)(menus[submenu].m);
  i->flags = FL_PUP_SUBMENU;
}

void glutChangeToMenuEntry(int item, char *label, int value) {
  menu *m = &menus[glut_menu];
  Fl_Menu_Item* i = &m->m[item-1];
  i->text = label;
  i->callback_ = (Fl_Callback*)(m->cb);
  i->argument(value);
  i->flags = 0;
}

void glutChangeToSubMenu(int item, char *label, int submenu) {
  menu *m = &menus[glut_menu];
  Fl_Menu_Item* i = &m->m[item-1];
  i->text = label;
  i->callback_ = 0;
  i->user_data_ = (void *)(menus[submenu].m);
  i->flags = FL_PUP_SUBMENU;
}

void glutRemoveMenuItem(int item) {
  menu *m = &menus[glut_menu];
  if (item > m->size || item < 1) return;
  for (int i = item-1; i <= m->size; i++) m->m[i] = m->m[i+1];
  m->size--;
}

////////////////////////////////////////////////////////////////

int glutGet(GLenum type) {
  switch (type) {
  case GLUT_RETURN_ZERO: return 0;
  case GLUT_WINDOW_X: return glut_window ? glut_window->x() : 0;
  case GLUT_WINDOW_Y: return glut_window ? glut_window->y() : 0;
  case GLUT_WINDOW_WIDTH: return glut_window ? glut_window->pixel_w() : 0;
  case GLUT_WINDOW_HEIGHT: return glut_window ? glut_window->pixel_h() : 0;
  case GLUT_WINDOW_PARENT:
    if (glut_window && glut_window->parent())
      return ((Fl_Glut_Window *)(glut_window->parent()))->number;
    else
      return 0;
//case GLUT_WINDOW_NUM_CHILDREN:
//case GLUT_WINDOW_CURSOR: return
  case GLUT_SCREEN_WIDTH: return Fl::w();
  case GLUT_SCREEN_HEIGHT: return Fl::h();
//case GLUT_SCREEN_WIDTH_MM:
//case GLUT_SCREEN_HEIGHT_MM:
  case GLUT_MENU_NUM_ITEMS: return menus[glut_menu].size;
  case GLUT_DISPLAY_MODE_POSSIBLE: return Fl_Gl_Window::can_do(glut_mode);
  case GLUT_INIT_WINDOW_X: return initx;
  case GLUT_INIT_WINDOW_Y: return inity;
  case GLUT_INIT_WINDOW_WIDTH: return initw;
  case GLUT_INIT_WINDOW_HEIGHT: return inith;
  case GLUT_INIT_DISPLAY_MODE: return glut_mode;
  case GLUT_ELAPSED_TIME:
      if (!glut_starttime_set) {
        glut_starttime = Fl::now();
        glut_starttime_set = 1;
      }
      return (int)(Fl::seconds_since(glut_starttime)*1000.0); // milliseconds
  case GLUT_WINDOW_BUFFER_SIZE:
    if (glutGet(GLUT_WINDOW_RGBA))
      return glutGet(GLUT_WINDOW_RED_SIZE)+
        glutGet(GLUT_WINDOW_GREEN_SIZE)+
        glutGet(GLUT_WINDOW_BLUE_SIZE)+
        glutGet(GLUT_WINDOW_ALPHA_SIZE);
    else
      return glutGet(GLUT_WINDOW_COLORMAP_SIZE);
  case GLUT_VERSION: return 20400;
  default: {GLint p; glGetIntegerv(type, &p); return p;}
  }
}

int glutLayerGet(GLenum type) {
  switch (type) {
  case GLUT_OVERLAY_POSSIBLE: return glut_window ? glut_window->can_do_overlay() : 0;
//case GLUT_LAYER_IN_USE:
//case GLUT_HAS_OVERLAY:
  case GLUT_TRANSPARENT_INDEX: return 0; // true for SGI
  case GLUT_NORMAL_DAMAGED: return glut_window ? glut_window->damage() : 0;
  case GLUT_OVERLAY_DAMAGED: return 1; // kind of works...
  default: return 0;
  }
}

int glutDeviceGet(GLenum type) {
  switch (type) {
    case GLUT_HAS_KEYBOARD : return 1;
    case GLUT_HAS_MOUSE : return 1;
    case GLUT_NUM_MOUSE_BUTTONS : return 3;
    default : return 0;
  }
}

// Get extension function address...
GLUTproc glutGetProcAddress(const char *procName) {
  return (GLUTproc)Fl_Gl_Window_Driver::global()->GetProcAddress(procName);
}

// Parse the GL_EXTENSIONS string to see if the named extension is
// supported.
//
// This code was copied from FreeGLUT 2.4.0 which carries the
// following notice:
//
//     Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
int glutExtensionSupported( const char* extension )
{
  if (!extension || strchr(extension, ' ')) return 0;

  const char *extensions, *start;
  const int len = (int)strlen(extension);

  start = extensions = (const char *) glGetString(GL_EXTENSIONS);

  if (!extensions) return 0;

  for (;;) {
    const char *p = strstr(extensions, extension);
    if (!p) return 0;  /* not found */
    /* check that the match isn't a super string */
    if ((p == start || p[-1] == ' ') &&
        (p[len] == ' ' || p[len] == 0)) return 1;
    /* skip the false match and continue */
    extensions = p + len;
  }
}

// Add a mechanism to handle adding/removing the glut idle function
// without depending on the (deprecated) set_idle method.
void glutIdleFunc(void (*f)())
{
  // no change
  if(glut_idle_func == f) return;
  // remove current idle
  if(glut_idle_func) Fl::remove_idle((void (*)(void *))glut_idle_func);
  // install new idle func - if one was passed
  if(f) Fl::add_idle((void (*)(void *))f);
  // record new idle func - even if it is NULL
  glut_idle_func = f;
} // glutIdleFunc

#endif // HAVE_GL
