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

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

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

#include "iup_object.h"
#include "iup_attrib.h"
#include "iup_str.h"
#include "iup_register.h"

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



static Ihandle* iGLCanvasBoxPickChild(Ihandle* ih, int x, int y, int top)
{
  Ihandle* child = ih->firstchild;
  if (child)
  {
    /* ih is a container then must check first for the client area */
    int client_x = 0, client_y = 0, client_w = 0, client_h = 0;
    IupGetIntInt(ih, "CLIENTSIZE", &client_w, &client_h);
    IupGetIntInt(ih, "CLIP_MIN", &client_x, &client_y);
    if (!top)
    {
      client_x += ih->x;
      client_y += ih->y;
    }

    if (x >= client_x && x < client_x + client_w &&
        y >= client_y && y < client_y + client_h)
    {
      Ihandle** child_array = (Ihandle**)malloc(sizeof(Ihandle*) * IupGetChildCount(ih));
      int i=0;
      while (child)
      {
        child_array[i] = child;
        child = child->brother;
        i++;
      }

      while (i > 0)
      {
        child = child_array[i - 1]; /* start with the last child */

        if (iupAttribGetInt(child, "VISIBLE") &&
            x >= child->x && x < child->x + child->currentwidth &&
            y >= child->y && y < child->y + child->currentheight)
        {
          free(child_array);

          ih = iGLCanvasBoxPickChild(child, x, y, 0);
          if (ih)
            return ih;
          else
            return child;
        }

        i--;
      }

      free(child_array);
    }
  }

  return NULL;
}

static void iGLCanvasBoxCallGLChildAction(Ihandle* ih, Ihandle* gl_parent)
{
  Ihandle* child = ih->firstchild;
  while (child)
  {
    IFn cb = (IFn)IupGetCallback(child, "GL_ACTION");
    if (cb && iupAttribGetInt(child, "VISIBLE"))
    {
      if (iupGLSubCanvasSetTransform(child, gl_parent))
        cb(child);
    }

    iGLCanvasBoxCallGLChildAction(child, gl_parent);
    child = child->brother;
  }
}

static int iGLCanvasBoxSwapBuffers_CB(Ihandle* ih)
{
  /* called before the actual SwapBuffers */

  iupGLSubCanvasSaveState(ih);

  /* redraw all GL children */
  iGLCanvasBoxCallGLChildAction(ih, ih);

  iupGLSubCanvasRestoreState(ih);

  return IUP_DEFAULT;
}

static int iGLCanvasBoxACTION(Ihandle* ih, float posx, float posy)
{
  IFnff cb;

  IupGLMakeCurrent(ih);

  cb = (IFnff)IupGetCallback(ih, "APP_ACTION");
  if (cb)
    cb(ih, posx, posy);

  /* if double buffer is disabled must manually call our SwapBuffers callback, assuming IupGLSwapBuffers is not called inside APP_ACTION */
  if (!cb || !iupStrEqualNoCase(iupAttribGetStr(ih, "BUFFER"), "DOUBLE"))  
    iGLCanvasBoxSwapBuffers_CB(ih);

  return IUP_DEFAULT;
}

static int iGLCanvasBoxBUTTON_CB(Ihandle* ih, int button, int pressed, int x, int y, char* status)
{
  IFniiiis cb;
  Ihandle* child;

  child = iGLCanvasBoxPickChild(ih, x, y, 1);

  if (child || !pressed)
    iupAttribSet(ih, "_IUP_GLBOX_SELFBUTTON", NULL);
  else
    iupAttribSet(ih, "_IUP_GLBOX_SELFBUTTON", "1");

  if (child && iupAttribGetInt(child, "ACTIVE"))
  {
    int ret = IUP_DEFAULT;

    if (pressed)
      iupAttribSet(ih, "_IUP_GLBOX_LASTBUTTON", (char*)child);
    else
      iupAttribSet(ih, "_IUP_GLBOX_LASTBUTTON", NULL);

    if (button == IUP_BUTTON1)
    {
      if (pressed)
        iupAttribSet(child, "PRESSED", "1");
      else
        iupAttribSet(child, "PRESSED", NULL);
    }

    cb = (IFniiiis)IupGetCallback(child, "GL_BUTTON_CB");
    if (cb)
      ret = cb(child, button, pressed, x - child->x, y - child->y, status);

    if (ret != IUP_CONTINUE)
      return IUP_DEFAULT;
  }
  else
    iupAttribSet(ih, "_IUP_GLBOX_LASTBUTTON", NULL);

  cb = (IFniiiis)IupGetCallback(ih, "APP_BUTTON_CB");
  if (cb)
    return cb(ih, button, pressed, x, y, status);

  return IUP_DEFAULT;
}

static void iGLCanvasBoxEnterChild(Ihandle* ih, Ihandle* child, int x, int y)
{
  Ihandle* last_child = (Ihandle*)iupAttribGet(ih, "_IUP_GLBOX_LAST_ENTER");

  if (last_child && last_child != child)
  {
    if (iupAttribGetInt(last_child, "ACTIVE"))
    {
      IFn cb;
      char* value;

      value = iupAttribGet(ih, "_IUPGLBOX_TIP_SET");
      if (value)
      {
        value = iupAttribGet(ih, "_IUPGLBOX_TIP");
        IupSetStrAttribute(ih, "TIP", value);  /* reset attribute if it was set */
        iupAttribSet(ih, "_IUPGLBOX_TIP", NULL);
        iupAttribSet(ih, "_IUPGLBOX_TIP_SET", NULL);
      }

      iupAttribSet(last_child, "HIGHLIGHT", NULL);
      iupAttribSet(last_child, "PRESSED", NULL);

      cb = (IFn)IupGetCallback(last_child, "GL_LEAVEWINDOW_CB");
      if (cb)
        cb(last_child);

      value = iupAttribGet(ih, "_IUPGLBOX_CURSOR");
      if (value)
      {
        IupSetStrAttribute(ih, "CURSOR", value);  /* reset attribute if it was set */
        iupAttribSet(ih, "_IUPGLBOX_CURSOR", NULL);
      }
    }

    iupAttribSet(ih, "_IUP_GLBOX_LAST_ENTER", NULL);
  }

  if (child && child != last_child)
  {
    if (iupAttribGetInt(child, "ACTIVE"))
    {
      IFnii cb;
      char* value;

      value = iupAttribGet(child, "TIP");
      if (value)
      {
        iupAttribSet(ih, "_IUPGLBOX_TIP_SET", "1");  /* TIP can be NULL */
        iupAttribSetStr(ih, "_IUPGLBOX_TIP", IupGetAttribute(ih, "TIP"));
        IupSetStrAttribute(ih, "TIP", value);
        IupSetAttribute(ih, "TIPVISIBLE", "Yes");
      }

      iupAttribSet(child, "HIGHLIGHT", "1");

      cb = (IFnii)IupGetCallback(child, "GL_ENTERWINDOW_CB");
      if (cb)
        cb(child, x, y);

      value = iupAttribGet(child, "CURSOR");
      if (value)
      {
        iupAttribSetStr(ih, "_IUPGLBOX_CURSOR", IupGetAttribute(ih, "CURSOR"));
        IupSetStrAttribute(ih, "CURSOR", value);
      }
    }

    iupAttribSet(ih, "_IUP_GLBOX_LAST_ENTER", (char*)child);
  }
}

static int iGLCanvasBoxMOTION_CB(Ihandle* ih, int x, int y, char *status)
{
  IFniis cb;

  /* only handle child if not pressed at self */
  if (!iupAttribGet(ih, "_IUP_GLBOX_SELFBUTTON"))
  {
    Ihandle* child = (Ihandle*)iupAttribGet(ih, "_IUP_GLBOX_LASTBUTTON");
    if (!child)
      child = iGLCanvasBoxPickChild(ih, x, y, 1);

    if (child)
      iGLCanvasBoxEnterChild(ih, child, x - child->x, y - child->y);
    else
      iGLCanvasBoxEnterChild(ih, NULL, 0, 0);

    if (child && iupAttribGetInt(child, "ACTIVE"))
    {
      int ret = IUP_DEFAULT;

      cb = (IFniis)IupGetCallback(child, "GL_MOTION_CB");
      if (cb)
        ret = cb(child, x - child->x, y - child->y, status);

      if (ret != IUP_CONTINUE)
        return IUP_DEFAULT;
    }
  }

  cb = (IFniis)IupGetCallback(ih, "APP_MOTION_CB");
  if (cb)
    return cb(ih, x, y, status);

  return IUP_DEFAULT;
}

static int iGLCanvasBoxWHEEL_CB(Ihandle* ih, float delta, int x, int y, char *status)
{
  IFnfiis cb;
  Ihandle* child;

  child = iGLCanvasBoxPickChild(ih, x, y, 1);
  if (child)
  {
    int ret = IUP_DEFAULT;

    cb = (IFnfiis)IupGetCallback(child, "GL_WHEEL_CB");
    if (cb && iupAttribGetInt(child, "ACTIVE"))
      ret = cb(child, delta, x - child->x, y - child->y, status);

    if (ret != IUP_CONTINUE)
      return IUP_DEFAULT;
  }

  cb = (IFnfiis)IupGetCallback(ih, "APP_WHEEL_CB");
  if (cb)
    return cb(ih, delta, x, y, status);

  return IUP_DEFAULT;
}

static int iGLCanvasBoxLEAVEWINDOW_CB(Ihandle* ih)
{
  IFn app_cb;

  iGLCanvasBoxEnterChild(ih, NULL, 0, 0);

  app_cb = (IFn)IupGetCallback(ih, "APP_LEAVEWINDOW_CB");
  if (app_cb)
    app_cb(ih);
  return IUP_DEFAULT;
}

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

static void iGLCanvasBoxComputeNaturalSizeMethod(Ihandle* ih, int *w, int *h, int *children_expand)
{
  Ihandle* child;
  for (child = ih->firstchild; child; child = child->brother)
  {
    /* update child natural size only */
    iupBaseComputeNaturalSize(child);
  }

  /* Also set expand to its own expand so it will not depend on children */
  *children_expand = ih->expand;

  (void)w;
  (void)h;
}

static void iGLCanvasBoxSetChildrenCurrentSizeMethod(Ihandle* ih, int shrink)
{
  Ihandle* child;
  for (child = ih->firstchild; child; child = child->brother)
  {
    /* update children to their own natural size */
    int width = child->naturalwidth;
    int height = child->naturalheight;
    if (iupAttribGetBoolean(child, "EXPANDHORIZONTAL"))
      width = ih->currentwidth;
    if (iupAttribGetBoolean(child, "EXPANDVERTICAL"))
      height = ih->currentheight;

    iupBaseSetCurrentSize(child, width, height, shrink);
  }
}

static int iGLCanvasBoxGetVerticalAlign(Ihandle* child)
{
  char* value = iupAttribGet(child, "VERTICALALIGN");
  if (!value)
    return -1;  /* FLOAT */
  if (iupStrEqualNoCase(value, "ABOTTOM"))
    return IUP_ALIGN_ABOTTOM;
  else if (iupStrEqualNoCase(value, "ACENTER"))
    return IUP_ALIGN_ACENTER;
  else if (iupStrEqualNoCase(value, "ATOP"))
    return IUP_ALIGN_ATOP;
  return -1;  /* FLOAT */
}

static int iGLCanvasBoxGetHorizontalAlign(Ihandle* child)
{
  char* value = iupAttribGet(child, "HORIZONTALALIGN");
  if (!value)
    return -1; /* FLOAT */
  if (iupStrEqualNoCase(value, "ARIGHT"))
    return IUP_ALIGN_ARIGHT;
  else if (iupStrEqualNoCase(value, "ACENTER"))
    return IUP_ALIGN_ACENTER;
  else if (iupStrEqualNoCase(value, "ALEFT"))
    return IUP_ALIGN_ALEFT;
  return -1;  /* FLOAT */
}

static void iGLCanvasBoxSetChildrenPositionMethod(Ihandle* ih, int x, int y)
{
  Ihandle* child;
  int horiz_margin = 0, vert_margin = 0;

  IupGetIntInt(ih, "MARGIN", &horiz_margin, &vert_margin);

  /* since GLCanvas is a native container ignore parameters x and y */

  for (child = ih->firstchild; child; child = child->brother)
  {
    int border = iupAttribGetBoolean(ih, "BORDER");

    int vert_pos = iGLCanvasBoxGetVerticalAlign(child);
    int horiz_pos = iGLCanvasBoxGetHorizontalAlign(child);

    if (vert_pos == IUP_ALIGN_ACENTER)
      y = (ih->currentheight - 2* border - child->currentheight) / 2;
    else if (vert_pos == IUP_ALIGN_ABOTTOM)
      y = ih->currentheight - 2 * border - child->currentheight - vert_margin;
    else if (vert_pos == IUP_ALIGN_ATOP)
      y = vert_margin;
    else  /* FLOAT */
      y = child->y;

    if (horiz_pos == IUP_ALIGN_ACENTER)
      x = (ih->currentwidth - 2 * border - child->currentwidth) / 2;
    else if (horiz_pos == IUP_ALIGN_ARIGHT)
      x = ih->currentwidth - 2 * border - child->currentwidth - horiz_margin;
    else if (horiz_pos == IUP_ALIGN_ALEFT)
      x = horiz_margin;
    else  /* FLOAT */ 
      x = child->x;

    /* do not include border in the positioning. It is already considered in CLIENTOFFSET. */

    iupBaseSetPosition(child, x, y);
  }
}

#define CB_NAMES_COUNT 5
static const char* iglcanvasbox_cb_names[CB_NAMES_COUNT] = {
  "ACTION", 
  "BUTTON_CB", 
  "MOTION_CB", 
  "WHEEL_CB",
  "LEAVEWINDOW_CB" };
static Icallback iglcanvasbox_cbs[CB_NAMES_COUNT] = {
  (Icallback)iGLCanvasBoxACTION, 
  (Icallback)iGLCanvasBoxBUTTON_CB, 
  (Icallback)iGLCanvasBoxMOTION_CB, 
  (Icallback)iGLCanvasBoxWHEEL_CB,
  (Icallback)iGLCanvasBoxLEAVEWINDOW_CB };

static int iGLCanvasBoxMapMethod(Ihandle* ih)
{
  int i;
  char new_name[50];

  for (i = 0; i < CB_NAMES_COUNT; i++)
  {
    Icallback cb = IupGetCallback(ih, iglcanvasbox_cb_names[i]);
    if (cb)
    {
      /* save the application callbacks */
      sprintf(new_name, "APP_%s", iglcanvasbox_cb_names[i]);
      IupSetCallback(ih, new_name, cb);
    }

    /* always set GL_* callbacks */
    IupSetCallback(ih, iglcanvasbox_cb_names[i], iglcanvasbox_cbs[i]);
  }

  return IUP_NOERROR;
}

static void iGLCanvasBoxUnMapMethod(Ihandle* ih)
{
  iupGLFontRelease(ih);
}

static int iGLCanvasBoxCreateMethod(Ihandle* ih, void** params)
{
  int i;
  char new_name[50];

  for (i = 0; i < CB_NAMES_COUNT; i++)
  {
    /* save the internal callbacks */
    sprintf(new_name, "GLBOX_%s", iglcanvasbox_cb_names[i]);
    IupSetCallback(ih, new_name, iglcanvasbox_cbs[i]);
  }

  IupSetCallback(ih, "SWAPBUFFERS_CB", iGLCanvasBoxSwapBuffers_CB);

  if (params)
  {
    Ihandle** iparams = (Ihandle**)params;
    while (*iparams)
    {
      IupAppend(ih, *iparams);
      iparams++;
    }
  }

  return IUP_NOERROR;
}

Iclass* iupGLCanvasBoxNewClass(void)
{
  Iclass* ic = iupClassNew(iupRegisterFindClass("glcanvas"));

  ic->name = "glcanvasbox";
  ic->cons = "GLCanvasBox";
  ic->format = "g"; /* array of Ihandle */
  ic->nativetype = IUP_TYPECANVAS;
  ic->childtype = IUP_CHILDMANY;  /* can have children */
  ic->is_interactive = 1;

  ic->New = iupGLCanvasBoxNewClass;
  ic->Create = iGLCanvasBoxCreateMethod;
  ic->Map = iGLCanvasBoxMapMethod;
  ic->UnMap = iGLCanvasBoxUnMapMethod;

  ic->ComputeNaturalSize = iGLCanvasBoxComputeNaturalSizeMethod;
  ic->SetChildrenCurrentSize = iGLCanvasBoxSetChildrenCurrentSizeMethod;
  ic->SetChildrenPosition = iGLCanvasBoxSetChildrenPositionMethod;

  /* Base Container */
  /* DO not set the default container behavior for EXPAND */
  /* iupClassRegisterAttribute(ic, "EXPAND", iupBaseContainerGetExpandAttrib, NULL, IUPAF_SAMEASSYSTEM, "YES", IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT); */
  iupClassRegisterAttribute(ic, "CLIENTOFFSET", iupBaseCanvasGetClientOffsetAttrib, NULL, NULL, NULL, IUPAF_NOT_MAPPED | IUPAF_READONLY | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "CLIENTSIZE", iupBaseCanvasGetClientSizeAttrib, NULL, NULL, NULL, IUPAF_NOT_MAPPED | IUPAF_READONLY | IUPAF_NO_INHERIT);

  /* Native Container */
  iupClassRegisterAttribute(ic, "CHILDOFFSET", NULL, NULL, NULL, NULL, IUPAF_NO_INHERIT);

  iupClassRegisterAttribute(ic, "REDRAW", NULL, iGLCanvasBoxSetRedrawAttrib, NULL, NULL, IUPAF_WRITEONLY | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "MARGIN", NULL, NULL, IUPAF_SAMEASSYSTEM, "0x0", IUPAF_NO_INHERIT);

  /* At Children:
       VERTICALALIGN
       HORIZONTALALIGN
  */

  return ic;
}

Ihandle* IupGLCanvasBoxv(Ihandle** children)
{
  return IupCreatev("glcanvasbox", (void**)children);
}

Ihandle* IupGLCanvasBoxV(Ihandle* child, va_list arglist)
{
  return IupCreateV("glcanvasbox", child, arglist);
}

Ihandle* IupGLCanvasBox(Ihandle* child, ...)
{
  Ihandle* ih;

  va_list arglist;
  va_start(arglist, child);
  ih = IupCreateV("glcanvasbox", child, arglist);
  va_end(arglist);

  return ih;
}
