/** \file
 * \brief GLSubCanvas Control.
 *
 * See Copyright Notice in "iup.h"
 */

#ifdef WIN32
#include <windows.h>
#endif

#if defined (__APPLE__) || defined (OSX)
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif

#include <stdio.h>
#include <stdlib.h>

#include "iup.h"
#include "iupcbs.h"
#include "iupgl.h"

#include "iup_assert.h"
#include "iup_object.h"
#include "iup_attrib.h"
#include "iup_str.h"
#include "iup_layout.h"

#include "iup_glcontrols.h"
#include "iup_glfont.h"
#include "iup_glsubcanvas.h"


static void iGLFixRectY(Ihandle* gl_parent, int *y, int h)
{
  int border = iupAttribGetBoolean(gl_parent, "BORDER");

  /* y is at bottom and oriented bottom to top in OpenGL, both different from IUP */

  *y = *y + h - 1;  /* First, move to bottom */
  *y = gl_parent->currentheight - 2 * border - 1 - *y; /* Second, orient bottom to top */
}

static void iGLSaveClientClipping(Ihandle* ih, int clip_x, int clip_y, int clip_w, int clip_h)
{
  int x = 0, y = 0, w = 0, h = 0;

  IupGetIntInt(ih, "CLIENTSIZE", &w, &h);
  IupGetIntInt(ih, "CLIP_MIN", &x, &y);
  x += ih->x;
  y += ih->y;

  if (w <= 0 || h <= 0)
    return;

  if (x > clip_x + clip_w)
    return;
  if (y > clip_y + clip_h)
    return;
  if (x < clip_x)
    x = clip_x;
  if (y < clip_y)
    y = clip_y;

  if (x + w < clip_x)
    return;
  if (y + h < clip_y)
    return;
  if (x + w > clip_x + clip_w)
    w = clip_x + clip_w - x;
  if (y + h > clip_y + clip_h)
    h = clip_y + clip_h - y;

  if (w < 0) w = 0;
  if (h < 0) h = 0;

  iupAttribSetInt(ih, "CLIP_X", x);
  iupAttribSetInt(ih, "CLIP_Y", y);
  iupAttribSetInt(ih, "CLIP_W", w);
  iupAttribSetInt(ih, "CLIP_H", h);
}

static int iGLSetClipping(Ihandle* ih, int x, int y, int w, int h, Ihandle* gl_parent)
{
  int clip_x = iupAttribGetInt(ih->parent, "CLIP_X");
  int clip_y = iupAttribGetInt(ih->parent, "CLIP_Y");
  int clip_w = iupAttribGetInt(ih->parent, "CLIP_W");
  int clip_h = iupAttribGetInt(ih->parent, "CLIP_H");

  iupAttribSet(ih, "CLIP_W", "0");
  iupAttribSet(ih, "CLIP_H", "0");

  if (clip_w <= 0 || clip_h <= 0)
    return 0;
  if (w <= 0 || h <= 0)
    return 0;

  if (x > clip_x + clip_w)
    return 0;
  if (y > clip_y + clip_h)
    return 0;
  if (x < clip_x)
    x = clip_x;
  if (y < clip_y)
    y = clip_y;

  if (x + w < clip_x)
    return 0;
  if (y + h < clip_y)
    return 0;
  if (x + w > clip_x + clip_w)
    w = clip_x + clip_w - x;
  if (y + h > clip_y + clip_h)
    h = clip_y + clip_h - y;

  if (w < 0) w = 0;
  if (h < 0) h = 0;

  if (w == 0 || h == 0)
    return 0;

  if (ih->firstchild)
    iGLSaveClientClipping(ih, clip_x, clip_y, clip_w, clip_h);

  iGLFixRectY(gl_parent, &y, h);
  glScissor(x, y, w, h);
  return 1;
}

int iupGLSubCanvasSetTransform(Ihandle* ih, Ihandle* gl_parent)
{
  int x = ih->x;
  int y = ih->y;
  int w = ih->currentwidth;
  int h = ih->currentheight;

  /* crop to parent's rectangle */
  if (!iGLSetClipping(ih, x, y, w, h, gl_parent))
    return 0;

  iGLFixRectY(gl_parent, &y, h);
  glViewport(x, y, w, h);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0, w, 0, h, -1, 1);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.375, 0.375, 0.0);  /* render all primitives at integer positions */

  /* An optimum compromise that allows all primitives to be specified at integer positions, 
     while still ensuring predictable rasterization, is to translate x and y by 0.375. 
     Such a translation keeps polygon and pixel image edges safely away from the centers of pixels, 
     while moving line vertices close enough to the pixel centers.
     From: OpenGL Programming Guide (Red Book) - Appendix G "Programming Tips" - OpenGL Correctness Tips
  */

  return 1;
}

void iupGLSubCanvasSaveState(Ihandle* gl_parent)
{
  int border = iupAttribGetBoolean(gl_parent, "BORDER");
  char* saved = iupAttribGet(gl_parent, "_IUP_GLSUBCANVAS_SAVED");
  iupASSERT(saved == NULL);
  if (saved)
    return;

  /* save all changes, except transformation matrix */
  glPushAttrib(GL_ENABLE_BIT | GL_HINT_BIT | GL_VIEWPORT_BIT);

  /* save transformation matrix */
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();

  /* save transformation matrix */
  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();

  /* clipping */
  glEnable(GL_SCISSOR_TEST);
  /* alpha enabled */
  glEnable(GL_BLEND);     /* GL_COLOR_BUFFER_BIT or GL_ENABLE_BIT */
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  /* image data alignment */
  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);  /* not saved */
  /* disable Z axis */
  glDisable(GL_DEPTH_TEST);  /* GL_DEPTH_BUFFER_BIT or GL_ENABLE_BIT */
  /* disable lighting */
  glDisable(GL_LIGHTING);
  /* anti-alias */
  glEnable(GL_POINT_SMOOTH);     /* GL_LINE_BIT or GL_ENABLE_BIT */
  glEnable(GL_LINE_SMOOTH);      /* GL_POINT_BIT or GL_ENABLE_BIT */
  glEnable(GL_POLYGON_SMOOTH);    /* GL_POLYGON_BIT or GL_ENABLE_BIT */
  glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);   /* GL_HINT_BIT */
  glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
  glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);

  iupAttribSet(gl_parent, "_IUP_GLSUBCANVAS_SAVED", "1");

  iupAttribSetInt(gl_parent, "CLIP_X", 0);
  iupAttribSetInt(gl_parent, "CLIP_Y", 0);
  iupAttribSetInt(gl_parent, "CLIP_W", gl_parent->currentwidth - 2 * border);
  iupAttribSetInt(gl_parent, "CLIP_H", gl_parent->currentheight - 2 * border);
}

void iupGLSubCanvasRestoreState(Ihandle* gl_parent)
{
  char* saved = iupAttribGet(gl_parent, "_IUP_GLSUBCANVAS_SAVED");
  iupASSERT(saved != NULL);
  if (!saved)
    return;

  /* restore transformation matrix */
  glMatrixMode(GL_MODELVIEW);
  glPopMatrix();

  /* restore transformation matrix */
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();

  /* restore everything else */
  glPopAttrib();

  iupAttribSet(gl_parent, "_IUP_GLSUBCANVAS_SAVED", NULL);
}

static void iGLSubCanvasRedrawFront(Ihandle* ih)
{
  IFn cb = (IFn)IupGetCallback(ih, "GL_ACTION");
  if (cb && iupAttribGetInt(ih, "VISIBLE"))
  {
    Ihandle* gl_parent = (Ihandle*)iupAttribGet(ih, "_IUP_GLCANVAS_PARENT");
    IupGLMakeCurrent(gl_parent);
    glDrawBuffer(GL_FRONT);
    iupGLSubCanvasSaveState(gl_parent);
    if (iupGLSubCanvasSetTransform(ih, gl_parent))
      cb(ih);
    iupGLSubCanvasRestoreState(gl_parent);
    glDrawBuffer(GL_BACK);
  }
}

int iupGLSubCanvasRedraw(Ihandle* ih)
{
  Ihandle* gl_parent = (Ihandle*)iupAttribGet(ih, "_IUP_GLCANVAS_PARENT");
  if (iupAttribGetInt(ih, "REDRAWALL"))
    IupSetAttribute(gl_parent, "REDRAW", NULL);  /* redraw the whole box */
  else
    iGLSubCanvasRedrawFront(ih);  /* redraw only the sub-canvas in the front buffer */
  return IUP_DEFAULT;  /* return IUP_DEFAULT so it can be used as a callback */
}

void iupGLSubCanvasStartMoving(Ihandle* ih, int x, int y)
{
  /* Used for MOVABLE controls */
  iupAttribSetInt(ih, "_IUP_GLSUBCANVAS_START_X", ih->x + x);
  iupAttribSetInt(ih, "_IUP_GLSUBCANVAS_START_Y", ih->y + y);

  if (iupAttribGetInt(ih, "MOVETOTOP"))
    IupSetAttribute(ih, "ZORDER", "TOP");
}

int iupGLSubCanvasMove(Ihandle* ih, int x, int y)
{
  /* Used for MOVABLE controls */
  int moved = 0;
  int start_x = iupAttribGetInt(ih, "_IUP_GLSUBCANVAS_START_X");
  int start_y = iupAttribGetInt(ih, "_IUP_GLSUBCANVAS_START_Y");

  x += ih->x;
  y += ih->y;

  if ((x != start_x) || (y != start_y))
  {
    Ihandle* gl_parent = (Ihandle*)iupAttribGet(ih, "_IUP_GLCANVAS_PARENT");
    IFnii cb = (IFnii)IupGetCallback(ih, "MOVE_CB");

    /* clear canvas box alignment */
    iupAttribSet(ih, "VERTICALALIGN", NULL);
    iupAttribSet(ih, "HORIZONTALALIGN", NULL);

    iupBaseSetPosition(ih, ih->x + (x - start_x), ih->y + (y - start_y));
    iupLayoutUpdate(ih);

    IupSetAttribute(gl_parent, "REDRAW", NULL);  /* redraw the whole box */
    moved = 1;

    if (cb)
      cb(ih, ih->x, ih->y);
  }

  iupAttribSetInt(ih, "_IUP_GLSUBCANVAS_START_X", x);
  iupAttribSetInt(ih, "_IUP_GLSUBCANVAS_START_Y", y);
  return moved;
}


/****************************************************************/


static char* iGLSubCanvasGetCharSizeAttrib(Ihandle* ih)
{
  int charwidth, charheight;

  iupGLFontGetCharSize(ih, &charwidth, &charheight);
  if (charwidth == 0 || charheight == 0)
    return NULL;

  return iupStrReturnIntInt(charwidth, charheight, 'x');
}

static int iGLSubCanvasSetSizeAttrib(Ihandle* ih, const char* value)
{
  if (!value)
  {
    ih->userwidth = 0;
    ih->userheight = 0;
  }
  else
  {
    /* if not specified, the value is 0 */
    int w = 0, h = 0;
    int charwidth, charheight;
    iupGLFontGetCharSize(ih, &charwidth, &charheight);
    iupStrToIntInt(value, &w, &h, 'x');
    if (w < 0) w = 0;
    if (h < 0) h = 0;

    ih->userwidth = iupWIDTH2RASTER(w, charwidth);
    ih->userheight = iupHEIGHT2RASTER(h, charheight);
  }
  return 1;  /* always save in the hash table, so when FONT is changed SIZE can be updated */
}

static char* iGLSubCanvasGetSizeAttrib(Ihandle* ih)
{
  int charwidth, charheight, width, height;

  if (ih->handle)
  {
    width = ih->currentwidth;
    height = ih->currentheight;
  }
  else
  {
    width = ih->userwidth;
    height = ih->userheight;
  }

  iupGLFontGetCharSize(ih, &charwidth, &charheight);
  if (charwidth == 0 || charheight == 0)
    return NULL;  /* if font failed get from the hash table */

  return iupStrReturnIntInt(iupRASTER2WIDTH(width, charwidth), iupRASTER2HEIGHT(height, charheight), 'x');
}

static int iGLSubCanvasSetRedrawFrontAttrib(Ihandle* ih, const char* value)
{
  iGLSubCanvasRedrawFront(ih);
  (void)value;
  return 0;
}

static int iGLSubCanvasSetZorder(Ihandle* parent, Ihandle* child, int top)
{
  Ihandle *c,
    *c_prev = NULL;

  if (top && child->brother == NULL)  /* already at the top */
    return 0;

  if (!top && parent->firstchild == child)  /* already at the bottom */
    return 0;

  /* Removes the child entry inside the parent's child list */
  for (c = parent->firstchild; c; c = c->brother)
  {
    if (c == child)
    {
      if (c_prev == NULL)
        parent->firstchild = child->brother;
      else
        c_prev->brother = child->brother;

      child->brother = NULL;
      break;
    }

    c_prev = c;
  }

  if (top)
  {
    /* insert at last element */
    for (c = parent->firstchild; c; c = c->brother)
    {
      if (!c->brother)
      {
        c->brother = child;
        break;
      }
    }
  }
  else
  {
    /* insert at first */
    child->brother = parent->firstchild;
    parent->firstchild = child;
  }

  return 1;
}

static int iGLSubCanvasSetZorderAttrib(Ihandle* ih, const char* value)
{
  Ihandle* gl_parent = (Ihandle*)iupAttribGet(ih, "_IUP_GLCANVAS_PARENT");
  int redraw = 0;
  int top = 1;
  if (iupStrEqualNoCase(value, "BOTTOM"))
    top = 0;

  /* move everyone in the same hierarchy */
  while (ih != gl_parent)
  {
    if (iGLSubCanvasSetZorder(ih->parent, ih, top))
      redraw = 1;
    ih = ih->parent;
  }

  if (redraw)
    IupSetAttribute(gl_parent, "REDRAW", NULL);  /* redraw the whole box */

  return 0;
}


/****************************************************************/


static int iGLSubCanvasMapMethod(Ihandle* ih)
{
  Ihandle* gl_parent;
  for (gl_parent = ih->parent; gl_parent; gl_parent = gl_parent->parent)
  {
    if (gl_parent->iclass->nativetype == IUP_TYPECANVAS)
      break;
  }

  /* must have a canvas parent, and it must be a glcanvasbox */
  if (!gl_parent || !iupClassMatch(gl_parent->iclass, "glcanvasbox"))
    return IUP_ERROR;

  iupAttribSet(ih, "_IUP_GLCANVAS_PARENT", (char*)gl_parent);

  /* use the handle of the native parent */
  ih->handle = gl_parent->handle;
  return IUP_NOERROR;
}

static void iGLSubCanvasReleaseMethod(Iclass* ic)
{
  iupGLFontFinish();
  (void)ic;
}

static int iGLSubCanvasCreateMethod(Ihandle* ih, void** params)
{
  (void)params;
  // to avoid system fonts that are not well processed by FreeType
  iupAttribSetClassObject(ih, "FONTFACE", "Helvetica");
  return IUP_NOERROR;
}

Iclass* iupGLSubCanvasNewClass(void)
{
  Iclass* ic = iupClassNew(NULL);

  ic->name = "glsubcanvas";
  ic->cons = "GLSubCanvas";
  ic->format = NULL;  /* no parameters */
  ic->nativetype = IUP_TYPEVOID;
  ic->childtype = IUP_CHILDNONE;
  ic->is_interactive = 0;

  /* Class functions */
  ic->New = iupGLSubCanvasNewClass;
  ic->Create = iGLSubCanvasCreateMethod;
  ic->Release = iGLSubCanvasReleaseMethod;
  ic->Map = iGLSubCanvasMapMethod;

  /* Callbacks */
  iupClassRegisterCallback(ic, "GL_ACTION", "");
  iupClassRegisterCallback(ic, "GL_BUTTON_CB", "iiiis");
  iupClassRegisterCallback(ic, "GL_ENTERWINDOW_CB", "ii");
  iupClassRegisterCallback(ic, "GL_LEAVEWINDOW_CB", "");
  iupClassRegisterCallback(ic, "GL_MOTION_CB", "iis");
  iupClassRegisterCallback(ic, "GL_WHEEL_CB", "fiis");

  /* Base Callbacks */
  iupBaseRegisterBaseCallbacks(ic);

  /* Common */
  iupBaseRegisterCommonAttrib(ic);

  /* redefine common attributes */
  iupClassRegisterAttribute(ic, "SIZE", iGLSubCanvasGetSizeAttrib, iGLSubCanvasSetSizeAttrib, NULL, NULL, IUPAF_NO_SAVE | IUPAF_NO_DEFAULTVALUE | IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "CHARSIZE", iGLSubCanvasGetCharSizeAttrib, NULL, NULL, NULL, IUPAF_NO_DEFAULTVALUE | IUPAF_READONLY | IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "FONT", NULL, iupGLFontSetFontAttrib, IUPAF_SAMEASSYSTEM, "DEFAULTFONT", IUPAF_NOT_MAPPED);  /* inherited */

  /* Visual */
  iupBaseRegisterVisualAttrib(ic);

  /* redefine native visual attributes */
  iupClassRegisterAttribute(ic, "VISIBLE", NULL, NULL, IUPAF_SAMEASSYSTEM, "YES", IUPAF_NO_SAVE | IUPAF_DEFAULT);  /* inheritable */
  iupClassRegisterAttribute(ic, "ACTIVE", NULL, NULL, IUPAF_SAMEASSYSTEM, "YES", IUPAF_DEFAULT);  /* inheritable */
  iupClassRegisterAttribute(ic, "ZORDER", NULL, iGLSubCanvasSetZorderAttrib, NULL, NULL, IUPAF_WRITEONLY | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "TIP", NULL, NULL, NULL, NULL, IUPAF_NO_DEFAULTVALUE | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "TIPVISIBLE", NULL, NULL, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "CURSOR", NULL, NULL, IUPAF_SAMEASSYSTEM, "ARROW", IUPAF_IHANDLENAME | IUPAF_NO_INHERIT);

  /* Common visual */
  iupClassRegisterAttribute(ic, "BORDERCOLOR", NULL, NULL, IUPAF_SAMEASSYSTEM, "50 150 255", IUPAF_DEFAULT);  /* inheritable */
  iupClassRegisterAttribute(ic, "BORDERWIDTH", NULL, NULL, IUPAF_SAMEASSYSTEM, "1", IUPAF_DEFAULT);  /* inheritable */
  iupClassRegisterAttribute(ic, "BGCOLOR", NULL, NULL, NULL, NULL, IUPAF_DEFAULT);  /* inheritable */
  iupClassRegisterAttribute(ic, "HLCOLOR", NULL, NULL, IUPAF_SAMEASSYSTEM, "200 225 245", IUPAF_DEFAULT);  /* inheritable */
  iupClassRegisterAttribute(ic, "PSCOLOR", NULL, NULL, IUPAF_SAMEASSYSTEM, "150 200 235", IUPAF_DEFAULT);  /* inheritable */

  iupClassRegisterAttribute(ic, "UNDERLINE", NULL, NULL, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "REDRAWFRONT", NULL, iGLSubCanvasSetRedrawFrontAttrib, NULL, NULL, IUPAF_WRITEONLY | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "REDRAWALL", NULL, NULL, IUPAF_SAMEASSYSTEM, "Yes", IUPAF_NO_INHERIT);

  iupClassRegisterAttribute(ic, "HIGHLIGHT", NULL, NULL, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "PRESSED", NULL, NULL, NULL, NULL, IUPAF_NO_INHERIT);

  iupGLFontInit();

  return ic;
}

Ihandle* IupGLSubCanvas(void)
{
  return IupCreate("glsubcanvas");
}

