/** \file
* \brief Tabs control
*
* See Copyright Notice in "iup.h"
*/

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

#include "iup.h"

#include "iup_object.h"
#include "iup_attrib.h"
#include "iup_str.h"
#include "iup_drvfont.h"
#include "iup_stdcontrols.h"
#include "iup_tabs.h"
#include "iup_varg.h"


char* iupTabsGetTabPaddingAttrib(Ihandle* ih)
{
  return iupStrReturnIntInt(ih->data->horiz_padding, ih->data->vert_padding, 'x');
}

static void iTabsGetMaxTabSize(Ihandle* ih, int* max_width, int* max_height)
{
  int width, height, pos;
  char *tabtitle, *tabimage;
  Ihandle* child;
  int max_w = 0;
  int max_h = 0;

  for (pos = 0, child = ih->firstchild; child; child = child->brother, pos++)
  {
    tabtitle = iupAttribGetId(ih, "TABTITLE", pos);
    if (!tabtitle) tabtitle = iupAttribGet(child, "TABTITLE");
    tabimage = iupAttribGetId(ih, "TABIMAGE", pos);
    if (!tabimage) tabimage = iupAttribGet(child, "TABIMAGE");
    if (!tabtitle && !tabimage)
      tabtitle = "     ";

    iupdrvTabsGetTabSize(ih, tabtitle, tabimage, &width, &height);

    if (width > max_w) max_w = width;
    if (height > max_h) max_h = height;
  }

  *max_width = max_w;
  *max_height = max_h;
}

static void iTabsGetDecorMargin(int *m, int *s)
{
  int e = iupdrvTabsExtraMargin();
  *m = 4 + e;
  *s = 2 + 2*e;
}

static void iTabsGetDecorSize(Ihandle* ih, int *width, int *height)
{
  int m, s;

  iTabsGetDecorMargin(&m, &s);

  if (ih->data->type == ITABS_LEFT || ih->data->type == ITABS_RIGHT)
  {
    /* Tab labels are beside the content.
       They only affect decoration width, not height. */
    int max_width, max_height;
    iTabsGetMaxTabSize(ih, &max_width, &max_height);

    *width = m + max_width + s + m;
    *height = m + m;

    if (ih->data->orientation == ITABS_HORIZONTAL)
    {
      if (iupdrvTabsExtraDecor(ih))
      {
        int h;
        iupdrvFontGetCharSize(ih, NULL, &h);
        *height += h + m;
      }
    }
    else
    {
      if (ih->handle && ih->data->is_multiline)
      {
        int num_lin = iupdrvTabsGetLineCountAttrib(ih);
        *width += (num_lin-1)*(max_width + 1);
      }
    }
  }
  else /* "BOTTOM" or "TOP" */
  {
    /* Tab labels are above/below the content.
       They only affect decoration height, not width. */
    int max_width, max_height;
    iTabsGetMaxTabSize(ih, &max_width, &max_height);

    if (ih->data->orientation == ITABS_HORIZONTAL)
    {
      *width = m + m;

      if (iupdrvTabsExtraDecor(ih))
      {
        int h;
        iupdrvFontGetCharSize(ih, NULL, &h);
        *width += h + m;
      }

      *height = m + (3 + max_height + 3) + s + m;

      if (ih->handle && ih->data->is_multiline)
      {
        int num_lin = iupdrvTabsGetLineCountAttrib(ih);
        *height += (num_lin-1)*(3 + max_height + 3 + 1);
      }
    }
    else
    {
      /* Vertical text: text width becomes the tab button height */
      *width = m + m;
      *height = m + (3 + max_width + 3) + s + m;
    }
  }

  *width  += ih->data->horiz_padding;
  *height += ih->data->vert_padding;
}

static void iTabsGetDecorOffset(Ihandle* ih, int *dx, int *dy)
{
  int m, s;
  iTabsGetDecorMargin(&m, &s);

  if (ih->data->type == ITABS_LEFT || ih->data->type == ITABS_RIGHT)
  {
    if (ih->data->type == ITABS_LEFT)
    {
      if (ih->data->orientation == ITABS_HORIZONTAL)
      {
        int max_width, max_height;
        iTabsGetMaxTabSize(ih, &max_width, &max_height);
        *dx = m + (3 + max_width + 3) + s;
      }
      else
      {
        int max_width, max_height;
        iTabsGetMaxTabSize(ih, &max_width, &max_height);
        *dx = m + (3 + max_height + 3) + s;

        if (ih->handle && ih->data->is_multiline)
        {
          int num_lin = iupdrvTabsGetLineCountAttrib(ih);
          *dx += (num_lin-1)*(3 + max_height + 3 + 1);
        }
      }
    }
    else
      *dx = m;

    *dy = m;
  }
  else /* "BOTTOM" or "TOP" */
  {
    if (ih->data->type == ITABS_TOP)
    {
      if (ih->data->orientation == ITABS_HORIZONTAL)
      {
        int max_width, max_height;
        iTabsGetMaxTabSize(ih, &max_width, &max_height);
        *dy = m + (3 + max_height + 3) + s;

        if (ih->handle && ih->data->is_multiline)
        {
          int num_lin = iupdrvTabsGetLineCountAttrib(ih);
          *dy += (num_lin-1)*(3 + max_height + 3 + 1);
        }
      }
      else
      {
        int max_width, max_height;
        iTabsGetMaxTabSize(ih, &max_width, &max_height);
        *dy = m + (3 + max_width + 3) + s;
      }
    }
    else
      *dy = m;

    *dx = m;
  }

  *dx += ih->data->horiz_padding;
  *dy += ih->data->vert_padding;
}

void iupTabsCheckCurrentTab(Ihandle* ih, int pos, int removed)
{
  int cur_pos = iupdrvTabsGetCurrentTab(ih);
  if (cur_pos == pos)
  {
    int p;

    /* if given tab is the current tab,
       then the current tab must be changed to a visible tab */
    Ihandle* child;

    /* this function is called after the child has being removed from the hierarchy,
       but before the system tab being removed. */

    p = 0;
    if (removed && p == pos)
      p++;

    for (child = ih->firstchild; child; child = child->brother)
    {
      if (p != pos && iupdrvTabsIsTabVisible(child, p))
      {
        iupdrvTabsSetCurrentTab(ih, p);

        if (!iupAttribGetBoolean(ih, "CHILDSIZEALL"))
          IupRefresh(ih);

        return;
      }

      p++;
      if (removed && p == pos)
        p++;  /* increment twice to compensate for child already removed */
    }
  }
}

static void iTabsSetTab(Ihandle* ih, Ihandle* child, int pos)
{
  if (ih->handle)
  {
    int cur_pos = iupdrvTabsGetCurrentTab(ih);
    if (cur_pos != pos && iupdrvTabsIsTabVisible(child, pos))
    {
      iupdrvTabsSetCurrentTab(ih, pos);

      if (!iupAttribGetBoolean(ih, "CHILDSIZEALL"))
        IupRefresh(ih);
    }
  }
  else
    iupAttribSet(ih, "_IUPTABS_VALUE_HANDLE", (char*)child);
}

/* ------------------------------------------------------------------------- */
/* TABS - Sets and Gets - Attribs                                           */
/* ------------------------------------------------------------------------- */

char* iupTabsGetTabTypeAttrib(Ihandle* ih)
{
  switch(ih->data->type)
  {
  case ITABS_BOTTOM:
    return "BOTTOM";
  case ITABS_LEFT:
    return "LEFT";
  case ITABS_RIGHT:
    return "RIGHT";
  default:
    return "TOP";
  }
}

char* iupTabsGetTabOrientationAttrib(Ihandle* ih)
{
  if (ih->data->orientation == ITABS_HORIZONTAL)
    return "HORIZONTAL";
  else
    return "VERTICAL";
}

static char* iTabsGetCountAttrib(Ihandle* ih)
{
  return iupStrReturnInt(IupGetChildCount(ih));
}

static int iTabsSetValueHandleAttrib(Ihandle* ih, const char* value)
{
  int pos;
  Ihandle *child;

  child = (Ihandle*)value;

  if (!iupObjectCheck(child))
    return 0;

  pos = IupGetChildPos(ih, child);
  if (pos != -1) /* found child */
    iTabsSetTab(ih, child, pos);

  return 0;
}

static char* iTabsGetValueHandleAttrib(Ihandle* ih)
{
  if (ih->handle)
  {
    int pos = iupdrvTabsGetCurrentTab(ih);
    return (char*)IupGetChild(ih, pos);
  }
  else
    return iupAttribGet(ih, "_IUPTABS_VALUE_HANDLE");
}

static int iTabsSetValuePosAttrib(Ihandle* ih, const char* value)
{
  Ihandle* child;
  int pos;

  if (!iupStrToInt(value, &pos))
    return 0;

  child = IupGetChild(ih, pos);
  if (child) /* found child */
    iTabsSetTab(ih, child, pos);

  return 0;
}

static char* iTabsGetValuePosAttrib(Ihandle* ih)
{
  if (ih->handle)
  {
    int pos = iupdrvTabsGetCurrentTab(ih);
    return iupStrReturnInt(pos);
  }
  else
  {
    Ihandle* child = (Ihandle*)iupAttribGet(ih, "_IUPTABS_VALUE_HANDLE");
    int pos = IupGetChildPos(ih, child);
    if (pos != -1) /* found child */
      return iupStrReturnInt(pos);
  }

  return NULL;
}

static int iTabsSetValueAttrib(Ihandle* ih, const char* value)
{
  Ihandle *child;

  if (!value)
    return 0;

  child = IupGetHandle(value);
  if (!child)
    return 0;

  iTabsSetValueHandleAttrib(ih, (char*)child);

  return 0;
}

static char* iTabsGetValueAttrib(Ihandle* ih)
{
  Ihandle* child = (Ihandle*)iTabsGetValueHandleAttrib(ih);
  return IupGetName(child);  /* Name is guarantied at AddedMethod */
}

static char* iTabsGetClientSizeAttrib(Ihandle* ih)
{
  int width, height, decorwidth, decorheight;
  width = ih->currentwidth;
  height = ih->currentheight;
  iTabsGetDecorSize(ih, &decorwidth, &decorheight);
  width -= decorwidth;
  height -= decorheight;
  if (width < 0) width = 0;
  if (height < 0) height = 0;
  return iupStrReturnIntInt(width, height,'x');
}

static char* iTabsGetClientOffsetAttrib(Ihandle* ih)
{
  int dx, dy;
  iTabsGetDecorOffset(ih, &dx, &dy);
  return iupStrReturnIntInt(dx, dy, 'x');
}

char* iupTabsGetTabVisibleAttrib(Ihandle* ih, int pos)
{
  Ihandle* child = IupGetChild(ih, pos);
  if (child)
    return iupStrReturnBoolean(iupdrvTabsIsTabVisible(child, pos));
  else
    return NULL;
}

char* iupTabsGetTitleAttrib(Ihandle* ih, int pos)
{
  Ihandle* child = IupGetChild(ih, pos);
  if (child)
    return iupAttribGet(child, "TABTITLE");
  else
    return NULL;
}

static char* iTabsGetShowCloseAttrib(Ihandle* ih)
{
  return iupStrReturnBoolean (ih->data->show_close);
}

static int iTabsSetShowCloseAttrib(Ihandle* ih, const char* value)
{
  if (iupStrBoolean(value))
    ih->data->show_close = 1;
  else
    ih->data->show_close = 0;

  return 0;
}

/* ------------------------------------------------------------------------- */
/* TABS - Methods                                                            */
/* ------------------------------------------------------------------------- */

static void iTabsComputeNaturalSizeMethod(Ihandle* ih, int *w, int *h, int *children_expand)
{
  Ihandle* child;
  int children_naturalwidth, children_naturalheight;
  int decorwidth, decorheight;
  int childSizeAll = iupAttribGetBoolean(ih, "CHILDSIZEALL");
  Ihandle* current_child = childSizeAll? NULL: IupGetChild(ih, iupdrvTabsGetCurrentTab(ih));

  /* calculate total children natural size (even for hidden children) */
  children_naturalwidth = 0;
  children_naturalheight = 0;

  for (child = ih->firstchild; child; child = child->brother)
  {
    /* update child natural size first */
    if (!(child->flags & IUP_FLOATING_IGNORE))
      iupBaseComputeNaturalSize(child);

    if (!childSizeAll && child != current_child)
      continue;

    if (!(child->flags & IUP_FLOATING))
    {
      *children_expand |= child->expand;
      children_naturalwidth = iupMAX(children_naturalwidth, child->naturalwidth);
      children_naturalheight = iupMAX(children_naturalheight, child->naturalheight);
    }
  }

  iTabsGetDecorSize(ih, &decorwidth, &decorheight);

  /* For TOP/BOTTOM tabs: decoration is height
     For LEFT/RIGHT tabs: decoration is width */
  if (ih->data->type == ITABS_TOP || ih->data->type == ITABS_BOTTOM)
  {
    /* Tab labels are above/below content. decorwidth only has borders.
       For natural size, also consider total tab labels width. */
    int tab_labels_width = 0;
    if (ih->data->orientation == ITABS_HORIZONTAL)
    {
      int m, s;
      int pos;
      char *tabtitle, *tabimage;
      Ihandle* tab_child;
      int num_tabs = 0;

      iTabsGetDecorMargin(&m, &s);

      for (pos = 0, tab_child = ih->firstchild; tab_child; tab_child = tab_child->brother, pos++)
      {
        int tw, th;

        tabtitle = iupAttribGetId(ih, "TABTITLE", pos);
        if (!tabtitle) tabtitle = iupAttribGet(tab_child, "TABTITLE");
        tabimage = iupAttribGetId(ih, "TABIMAGE", pos);
        if (!tabimage) tabimage = iupAttribGet(tab_child, "TABIMAGE");
        if (!tabtitle && !tabimage)
          tabtitle = "     ";

        iupdrvTabsGetTabSize(ih, tabtitle, tabimage, &tw, &th);

        if (!ih->handle)
          tw = 3 + tw + 3;

        tab_labels_width += tw;
        num_tabs++;
      }

      tab_labels_width += m + m;
      if (num_tabs > 1)
        tab_labels_width += (num_tabs - 1) * s;
    }

    *w = iupMAX(children_naturalwidth + decorwidth, tab_labels_width);
    *h = children_naturalheight + decorheight;
  }
  else /* LEFT or RIGHT */
  {
    /* Tab labels are beside the content. decorheight only has borders.
       For natural size, also consider total tab labels height. */
    int tab_labels_height = 0;
    if (ih->data->orientation == ITABS_HORIZONTAL)
    {
      int m, s;
      int pos;
      char *tabtitle, *tabimage;
      Ihandle* tab_child;
      int num_tabs = 0;

      iTabsGetDecorMargin(&m, &s);

      for (pos = 0, tab_child = ih->firstchild; tab_child; tab_child = tab_child->brother, pos++)
      {
        int tw, th;

        tabtitle = iupAttribGetId(ih, "TABTITLE", pos);
        if (!tabtitle) tabtitle = iupAttribGet(tab_child, "TABTITLE");
        tabimage = iupAttribGetId(ih, "TABIMAGE", pos);
        if (!tabimage) tabimage = iupAttribGet(tab_child, "TABIMAGE");
        if (!tabtitle && !tabimage)
          tabtitle = "     ";

        iupdrvTabsGetTabSize(ih, tabtitle, tabimage, &tw, &th);

        if (!ih->handle)
          th = 3 + th + 3;

        tab_labels_height += th;
        num_tabs++;
      }

      tab_labels_height += m + m;
      if (num_tabs > 1)
        tab_labels_height += (num_tabs - 1) * s;
    }
    else
    {
      int m, s;
      int pos;
      char *tabtitle, *tabimage;
      Ihandle* tab_child;

      iTabsGetDecorMargin(&m, &s);

      for (pos = 0, tab_child = ih->firstchild; tab_child; tab_child = tab_child->brother, pos++)
      {
        int tw, th;

        tabtitle = iupAttribGetId(ih, "TABTITLE", pos);
        if (!tabtitle) tabtitle = iupAttribGet(tab_child, "TABTITLE");
        tabimage = iupAttribGetId(ih, "TABIMAGE", pos);
        if (!tabimage) tabimage = iupAttribGet(tab_child, "TABIMAGE");
        if (!tabtitle && !tabimage)
          tabtitle = "     ";

        iupdrvTabsGetTabSize(ih, tabtitle, tabimage, &tw, &th);

        if (!ih->handle)
          th = 3 + th + 3;

        tab_labels_height += th;
      }

      tab_labels_height += m + m;
    }

    *w = children_naturalwidth + decorwidth;
    *h = iupMAX(children_naturalheight + decorheight, tab_labels_height);
  }
}

static void iTabsSetChildrenCurrentSizeMethod(Ihandle* ih, int shrink)
{
  Ihandle* child;
  int width, height, decorwidth, decorheight;

  iTabsGetDecorSize(ih, &decorwidth, &decorheight);

  width = ih->currentwidth-decorwidth;
  height = ih->currentheight-decorheight;
  if (width < 0) width = 0;
  if (height < 0) height = 0;

  for (child = ih->firstchild; child; child = child->brother)
  {
    if (!(child->flags & IUP_FLOATING))
      iupBaseSetCurrentSize(child, width, height, shrink);
  }
}

static void iTabsSetChildrenPositionMethod(Ihandle* ih, int x, int y)
{
  /* In all systems, each tab is a native window covering the client area.
     Child coordinates are relative to client left-top corner of the tab page. */
  Ihandle* child;
  char* offset = iupAttribGet(ih, "CHILDOFFSET");

  /* Native container, position is reset */
  x = 0;
  y = 0;

  if (offset) iupStrToIntInt(offset, &x, &y, 'x');

  for (child = ih->firstchild; child; child = child->brother)
  {
    if (!(child->flags & IUP_FLOATING))
      iupBaseSetPosition(child, x, y);
  }
}

static void* iTabsGetInnerNativeContainerHandleMethod(Ihandle* ih, Ihandle* child)
{
  while (child && child->parent != ih)
    child = child->parent;
  if (child)
    return iupAttribGet(child, "_IUPTAB_CONTAINER");
  else
    return NULL;
}

static int iTabsCreateMethod(Ihandle* ih, void **params)
{
  ih->data = iupALLOCCTRLDATA();

  /* add children */
  if(params)
  {
    Ihandle** iparams = (Ihandle**)params;
    while (*iparams)
    {
      IupAppend(ih, *iparams);
      iparams++;
    }
  }
  return IUP_NOERROR;
}

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

  ic->name = "tabs";
  ic->format = "g"; /* array of Ihandle */
  ic->nativetype = IUP_TYPECONTROL;
  ic->childtype = IUP_CHILDMANY;  /* can have children */
  ic->is_interactive = 1;
  ic->has_attrib_id = 1;

  /* Class functions */
  ic->New = iupTabsNewClass;
  ic->Create  = iTabsCreateMethod;
  ic->GetInnerNativeContainerHandle = iTabsGetInnerNativeContainerHandleMethod;

  ic->ComputeNaturalSize = iTabsComputeNaturalSizeMethod;
  ic->SetChildrenCurrentSize     = iTabsSetChildrenCurrentSizeMethod;
  ic->SetChildrenPosition        = iTabsSetChildrenPositionMethod;

  ic->LayoutUpdate = iupdrvBaseLayoutUpdateMethod;
  ic->UnMap = iupdrvBaseUnMapMethod;

  /* IupTabs Callbacks */
  iupClassRegisterCallback(ic, "TABCHANGE_CB", "nn");
  iupClassRegisterCallback(ic, "TABCHANGEPOS_CB", "ii");
  iupClassRegisterCallback(ic, "RIGHTCLICK_CB", "i");
  iupClassRegisterCallback(ic, "REORDER_CB", "ii");
  iupClassRegisterCallback(ic, "FOCUS_CB", "i");

  /* Common Callbacks */
  iupBaseRegisterCommonCallbacks(ic);

  /* Common */
  iupBaseRegisterCommonAttrib(ic);

  /* Visual */
  iupBaseRegisterVisualAttrib(ic);

  /* IupTabs only */
  iupClassRegisterAttribute(ic, "VALUE", iTabsGetValueAttrib, iTabsSetValueAttrib, NULL, NULL, IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "VALUEPOS", iTabsGetValuePosAttrib, iTabsSetValuePosAttrib, IUPAF_SAMEASSYSTEM, "0", IUPAF_NO_SAVE|IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "VALUE_HANDLE", iTabsGetValueHandleAttrib, iTabsSetValueHandleAttrib, NULL, NULL, IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT | IUPAF_IHANDLE | IUPAF_NO_STRING);
  iupClassRegisterAttribute(ic, "COUNT", iTabsGetCountAttrib, NULL, NULL, NULL, IUPAF_READONLY|IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "SHOWCLOSE", iTabsGetShowCloseAttrib, iTabsSetShowCloseAttrib, NULL, NULL, IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "CHILDSIZEALL", NULL, NULL, IUPAF_SAMEASSYSTEM, "Yes", IUPAF_NO_INHERIT);

  /* Base Container */
  iupClassRegisterAttribute(ic, "CLIENTSIZE", iTabsGetClientSizeAttrib, NULL, NULL, NULL, IUPAF_READONLY|IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "CLIENTOFFSET", iTabsGetClientOffsetAttrib, NULL, NULL, NULL, IUPAF_READONLY|IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "EXPAND", iupBaseContainerGetExpandAttrib, NULL, IUPAF_SAMEASSYSTEM, "YES", IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);

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

  iupdrvTabsInitClass(ic);

  return ic;
}

IUP_API Ihandle* IupTabsv(Ihandle** params)
{
  return IupCreatev("tabs", (void**)params);
}

IUP_API Ihandle* IupTabsV(Ihandle* child, va_list arglist)
{
  return IupCreateV("tabs", child, arglist);
}

IUP_API Ihandle* IupTabs(Ihandle* child, ...)
{
  Ihandle *ih;

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

  return ih;
}
