/** \file
 * \brief plot binding for Lua 5.
 *
 * See Copyright Notice in "iup.h"
 */

#include <stdlib.h>

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#include "iup.h"
#include "iup_plot.h"

#include <cd.h>
#include <cdlua.h>

#include "iuplua.h"
#include "iuplua_plot.h"
#include "il.h"

#include "iup_object.h"
#include "iup_assert.h"
#include "iup_predialogs.h"
#include "iup_str.h"


typedef int(*IFnL)(Ihandle*, lua_State *L);

static void ShowFormulaError(Ihandle* ih, lua_State *L)
{
  const char* str_message = IupGetLanguageString("IUP_ERRORINVALIDFORMULA");
  const char* error = lua_tostring(L, -1);
  char msg[1024];
  sprintf(msg, "%s\n  Lua error: %s", str_message, error);
  IupMessageError(IupGetDialog(ih), msg);
}

int IupPlotSetFormula(Ihandle* ih, int sample_count, const char* formula, const char* init)
{
  lua_State *L;
  int i, ds_index, ret_count = 1;
  double min, max, step, p, x, y;
  char formula_func[1024];
  IFnL init_cb;

  iupASSERT(iupObjectCheck(ih));
  if (!iupObjectCheck(ih))
    return -1;

  /* must be an IupMatrix */
  if (ih->iclass->nativetype != IUP_TYPECANVAS ||
      !IupClassMatch(ih, "plot"))
    return -1;

  L = luaL_newstate();
  luaL_openlibs(L);

  {
    const char* register_global =
      "function openpackage(ns)\n"
      "  for n, v in pairs(ns) do _G[n] = v end\n"
      "end\n"
      "openpackage(math)\n";
    luaL_dostring(L, register_global);
  }

  if (init)
    luaL_dostring(L, init);

  init_cb = (IFnL)IupGetCallback(ih, "FORMULAINIT_CB");
  if (init_cb)
    init_cb(ih, L);

  lua_pushlightuserdata(L, ih);
  lua_setglobal(L, "plot");

  if (IupGetInt(ih, "FORMULA_PARAMETRIC"))
    ret_count = 2;

  sprintf(formula_func, "function plot_formula(sample_index, %s)\n"
          "  return %s\n"
          "end\n", ret_count == 2 ? "t" : "x", formula);

  if (luaL_dostring(L, formula_func) != 0)
  {
    ShowFormulaError(ih, L);
    lua_close(L);
    return -1;
  }

  min = IupGetDouble(ih, "FORMULA_MIN");
  max = IupGetDouble(ih, "FORMULA_MAX");
  step = (max - min) / (double)(sample_count - 1);

  IupPlotBegin(ih, 0);

  for (i = 0, p = min; i < sample_count; i++, p += step)
  {
    lua_getglobal(L, "plot_formula");
    lua_pushinteger(L, i);
    lua_pushnumber(L, p);

    if (lua_pcall(L, 2, ret_count, 0) != 0)
    {
      ShowFormulaError(ih, L);
      lua_close(L);
      return -1;
    }

    if (!lua_isnumber(L, -1))
    {
      const char* str_message = IupGetLanguageString("IUP_ERRORINVALIDFORMULA");
      IupMessageError(IupGetDialog(ih), str_message);
      lua_close(L);
      return -1;
    }

    if (ret_count == 2)
    {
      if (!lua_isnumber(L, -2))
      {
        const char* str_message = IupGetLanguageString("IUP_ERRORINVALIDFORMULA");
        IupMessageError(IupGetDialog(ih), str_message);
        lua_close(L);
        return -1;
      }

      x = lua_tonumber(L, -2);
      y = lua_tonumber(L, -1);

      lua_pop(L, 2);  /* remove the result from the stack */
    }
    else
    {
      x = p;
      y = lua_tonumber(L, -1);

      lua_pop(L, 1);  /* remove the result from the stack */
    }

    IupPlotAdd(ih, x, y);
  }

  ds_index = IupPlotEnd(ih);

  lua_close(L);
  return ds_index;
}

static int plot_postdraw_cb(Ihandle *self, cdCanvas* cnv)
{
  lua_State *L = iuplua_call_start(self, "postdraw_cb");
  cdlua_pushcanvas(L, cnv);
  return iuplua_call(L, 1);
}

static int plot_predraw_cb(Ihandle *self, cdCanvas* cnv)
{
  lua_State *L = iuplua_call_start(self, "predraw_cb");
  cdlua_pushcanvas(L, cnv);
  return iuplua_call(L, 1);
}

static int plot_ytickformatnumber_cb(Ihandle *self, char * p0, char * p1, double p2, char * p3)
{
  int status;
  lua_State *L = iuplua_call_start(self, "ytickformatnumber_cb");
  lua_pushstring(L, p1);
  lua_pushnumber(L, p2);
  lua_pushstring(L, p3);
  status = iuplua_call_raw(L, 3, 2);

  if (status != LUA_OK)
    return IUP_DEFAULT;
  else
  {
    char* tmp_s = lua_isnil(L, -2) ? NULL : (char*)lua_tostring(L, -2);
    int tmp_i = (int)lua_isnil(L, -1) ? IUP_DEFAULT : (int)lua_tointeger(L, -1);
    lua_pop(L, 2);  /* remove the results */
    if (tmp_i == IUP_DEFAULT && tmp_s)
      iupStrCopyN(p0, 128, tmp_s);
    return tmp_i;
  }
}

static int plot_xtickformatnumber_cb(Ihandle *self, char * p0, char * p1, double p2, char * p3)
{
  int status;
  lua_State *L = iuplua_call_start(self, "xtickformatnumber_cb");
  lua_pushstring(L, p1);
  lua_pushnumber(L, p2);
  lua_pushstring(L, p3);

  status = iuplua_call_raw(L, 3, 2);
  if (status != LUA_OK)
    return IUP_DEFAULT;
  else
  {
    char* tmp_s = lua_isnil(L, -2) ? NULL : (char*)lua_tostring(L, -2);
    int tmp_i = (int)lua_isnil(L, -1) ? IUP_DEFAULT : (int)lua_tointeger(L, -1);
    lua_pop(L, 2);  /* remove the results */
    if (tmp_i == IUP_DEFAULT && tmp_s)
      iupStrCopyN(p0, 128, tmp_s);
    return tmp_i;
  }
}

static int PlotBegin(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L,1);
  IupPlotBegin(ih, luaL_checkinteger(L,2));
  return 0;
}

static int PlotAdd(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L,1);
  IupPlotAdd(ih, luaL_checknumber(L,2), luaL_checknumber(L,3));
  return 0;
}

static int PlotAddStr(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L,1);
  IupPlotAddStr(ih, luaL_checkstring(L,2), luaL_checknumber(L,3));
  return 0;
}

static int PlotAddSegment(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  IupPlotAddSegment(ih, luaL_checknumber(L, 2), luaL_checknumber(L, 3));
  return 0;
}

static int PlotEnd(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L,1);
  int ret = IupPlotEnd(ih);
  lua_pushinteger(L, ret);
  return 1;
}

static int PlotLoadData(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  int ret = IupPlotLoadData(ih, luaL_checkstring(L, 2), luaL_checkinteger(L, 3));
  lua_pushinteger(L, ret);
  return 1;
}

static int PlotSetFormula(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  int ret = IupPlotSetFormula(ih, luaL_checkinteger(L, 2), luaL_checkstring(L, 3), luaL_optstring(L, 4, NULL));
  lua_pushinteger(L, ret);
  return 1;
}

static int PlotFindSample(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  int ds_index, sample_index;
  int ret = IupPlotFindSample(ih, luaL_checknumber(L, 2), luaL_checknumber(L, 3), &ds_index, &sample_index);
  lua_pushinteger(L, ret);
  if (ret)
  {
    lua_pushinteger(L, ds_index);
    lua_pushinteger(L, sample_index);
    return 3;
  }
  else
    return 1;
}

static int PlotFindSegment(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  int ds_index, sample_index1, sample_index2;
  int ret = IupPlotFindSegment(ih, luaL_checknumber(L, 2), luaL_checknumber(L, 3), &ds_index, &sample_index1, &sample_index2);
  lua_pushinteger(L, ret);
  if (ret)
  {
    lua_pushinteger(L, ds_index);
    lua_pushinteger(L, sample_index1);
    lua_pushinteger(L, sample_index2);
    return 4;
  }
  else
    return 1;
}

static int PlotInsert(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  IupPlotInsert(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), luaL_checknumber(L, 4), luaL_checknumber(L, 5));
  return 0;
}

static int PlotInsertStr(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L,1);
  IupPlotInsertStr(ih, luaL_checkinteger(L,2), luaL_checkinteger(L,3), luaL_checkstring(L,4), luaL_checknumber(L,5));
  return 0;
}

static int PlotInsertSegment(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  IupPlotInsertSegment(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), luaL_checknumber(L, 4), luaL_checknumber(L, 5));
  return 0;
}

static int PlotInsertSamples(lua_State *L)
{
  double *px, *py;
  int count = luaL_checkinteger(L, 6);
  px = iuplua_checkdouble_array(L, 4, count);
  py = iuplua_checkdouble_array(L, 5, count);
  IupPlotInsertSamples(iuplua_checkihandle(L,1), luaL_checkinteger(L,2), luaL_checkinteger(L,3), px, py, count);
  free(px);
  free(py);
  return 0;
}

static int PlotInsertStrSamples(lua_State *L)
{
  double *py;
  char* *px;
  int count = luaL_checkinteger(L, 6);
  px = iuplua_checkstring_array(L, 4, count);
  py = iuplua_checkdouble_array(L, 5, count);
  IupPlotInsertStrSamples(iuplua_checkihandle(L,1), luaL_checkinteger(L,2), luaL_checkinteger(L,3), px, py, count);
  free(px);
  free(py);
  return 0;
}

static int PlotAddSamples(lua_State *L)
{
  double *px, *py;
  int count = luaL_checkinteger(L, 5);
  px = iuplua_checkdouble_array(L, 3, count);
  py = iuplua_checkdouble_array(L, 4, count);
  IupPlotAddSamples(iuplua_checkihandle(L,1), luaL_checkinteger(L,2), px, py, count);
  free(px);
  free(py);
  return 0;
}

static int PlotAddStrSamples(lua_State *L)
{
  double *py;
  char* *px;
  int count = luaL_checkinteger(L, 5);
  px = iuplua_checkstring_array(L, 3, count);
  py = iuplua_checkdouble_array(L, 4, count);
  IupPlotAddStrSamples(iuplua_checkihandle(L,1), luaL_checkinteger(L,2), px, py, count);
  free(px);
  free(py);
  return 0;
}

static int PlotGetSample(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  double x, y;
  IupPlotGetSample(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), &x, &y);
  lua_pushnumber(L, x);
  lua_pushnumber(L, y);
  return 2;
}

static int PlotGetSampleStr(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  double y;
  const char* x;
  IupPlotGetSampleStr(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), &x, &y);
  lua_pushstring(L, x);
  lua_pushnumber(L, y);
  return 2;
}

static int PlotGetSampleSelection(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  int selected = IupPlotGetSampleSelection(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3));
  lua_pushboolean(L, selected);
  return 1;
}

static int PlotGetSampleExtra(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  double extra = IupPlotGetSampleExtra(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3));
  lua_pushnumber(L, extra);
  return 1;
}

static int PlotSetSample(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  IupPlotSetSample(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), luaL_checknumber(L, 4), luaL_checknumber(L, 5));
  return 0;
}

static int PlotSetSampleStr(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  IupPlotSetSampleStr(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), luaL_checkstring(L, 4), luaL_checknumber(L, 5));
  return 0;
}

static int PlotSetSampleSelection(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  IupPlotSetSampleSelection(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), luaL_checkinteger(L, 4));
  return 0;
}

static int PlotSetSampleExtra(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L, 1);
  IupPlotSetSampleExtra(ih, luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), luaL_checknumber(L, 4));
  return 0;
}

static int PlotTransform(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L,1);
  double ix, iy;
  IupPlotTransform(ih, luaL_checknumber(L,2), luaL_checknumber(L,3), &ix, &iy);
  lua_pushnumber(L, ix);
  lua_pushnumber(L, iy);
  return 2;
}

static int PlotTransformTo(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L,1);
  double rx, ry;
  IupPlotTransformTo(ih, (int)luaL_checkinteger(L, 2), (int)luaL_checkinteger(L, 3), &rx, &ry);
  lua_pushnumber(L, rx);
  lua_pushnumber(L, ry);
  return 2;
}

static int PlotPaintTo(lua_State *L)
{
  Ihandle *ih = iuplua_checkihandle(L,1);
  IupPlotPaintTo(ih, cdlua_checkcanvas(L,2));
  return 0;
}

void iuplua_plotfuncs_open (lua_State *L)
{
  iuplua_register_cb(L, "PREDRAW_CB", (lua_CFunction)plot_predraw_cb, NULL);
  iuplua_register_cb(L, "POSTDRAW_CB", (lua_CFunction)plot_postdraw_cb, NULL); 
  iuplua_register_cb(L, "XTICKFORMATNUMBER_CB", (lua_CFunction)plot_xtickformatnumber_cb, NULL);
  iuplua_register_cb(L, "YTICKFORMATNUMBER_CB", (lua_CFunction)plot_ytickformatnumber_cb, NULL);

  iuplua_register(L, PlotBegin       ,"PlotBegin");
  iuplua_register(L, PlotAdd         ,"PlotAdd");
  iuplua_register(L, PlotAddStr      ,"PlotAddStr");
  iuplua_register(L, PlotAddSegment  ,"PlotAddSegment");
  iuplua_register(L, PlotEnd         ,"PlotEnd");
  iuplua_register(L, PlotLoadData    ,"PlotLoadData");
  iuplua_register(L, PlotSetFormula  ,"PlotSetFormula");
  iuplua_register(L, PlotFindSample  ,"PlotFindSample");
  iuplua_register(L, PlotFindSegment, "PlotFindSegment");
  iuplua_register(L, PlotInsert, "PlotInsert");
  iuplua_register(L, PlotInsertStr   ,"PlotInsertStr");
  iuplua_register(L, PlotInsertSegment, "PlotInsertSegment");
  iuplua_register(L, PlotInsertSamples    ,"PlotInsertSamples");
  iuplua_register(L, PlotInsertStrSamples ,"PlotInsertStrSamples");
  iuplua_register(L, PlotAddSamples       ,"PlotAddSamples");
  iuplua_register(L, PlotAddStrSamples    ,"PlotAddStrSamples");
  iuplua_register(L, PlotGetSample, "PlotGetSample");
  iuplua_register(L, PlotGetSampleStr, "PlotGetSampleStr");
  iuplua_register(L, PlotGetSampleSelection, "PlotGetSampleSelection");
  iuplua_register(L, PlotGetSampleExtra, "PlotGetSampleExtra");
  iuplua_register(L, PlotSetSample, "PlotSetSample");
  iuplua_register(L, PlotSetSampleStr, "PlotSetSampleStr");
  iuplua_register(L, PlotSetSampleSelection, "PlotSetSampleSelection");
  iuplua_register(L, PlotSetSampleExtra, "PlotSetSampleExtra");

  iuplua_register(L, PlotTransform, "PlotTransform");
  iuplua_register(L, PlotTransformTo, "PlotTransformTo");
  iuplua_register(L, PlotPaintTo, "PlotPaintTo");
}

int iupplotlua_open(lua_State * L);

int iup_plotlua_open(lua_State * L)
{
  if (iuplua_opencall_internal(L))
    IupPlotOpen();

  iuplua_get_env(L);
  iupplotlua_open(L);
  return 0;
}

/* obligatory to use require"iuplua_plot" */
int luaopen_iuplua_plot(lua_State* L)
{
  return iup_plotlua_open(L);
}

