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

#include "TuioListener.h"
#include "TuioClient.h"

#include "iup.h"
#include "iuptuio.h"
#include "iupcbs.h"

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

#include "iup_object.h"
#include "iup_assert.h"
#include "iup_str.h"
#include "iup_register.h"
#include "iup_drv.h"
#include "iup_drvinfo.h"


using namespace TUIO;

struct iTuioCursorEnvent{
  int id;
  float x, y;
  char state;

  iTuioCursorEnvent(int _id, float _x, float _y, char _state);
};

iTuioCursorEnvent::iTuioCursorEnvent(int _id, float _x, float _y, char _state)
:id(_id), x(_x), y(_y), state(_state)
{
}

class IupTuioListener : public TuioListener 
{
  int changed, locked;
  TuioClient* client;
  Ihandle* ih;
  std::list<iTuioCursorEnvent> cursor_events;

  void processCursor(TuioCursor *tcur, const char* state, const char* action);
  void initCursorInfo(std::list<TuioCursor*> &cursorList, int cursor_count, int* pid, int* pstate);
  void finishCursorInfo(std::list<TuioCursor*> &cursorList, int cursor_count, int* px, int* py, int* pstate, int w, int h, int use_client_coord, Ihandle* ih_canvas);
  void updateCursorInfo(int *cursor_count, int* pid, int* px, int* py, int* pstate, int id, int x, int y, int state);
  int GetMainCursor(std::list<TuioCursor*> &cursorList);

  static int timer_action_cb(Ihandle *timer);

  public:
    int debug;
    Ihandle* timer;

    IupTuioListener(Ihandle* _ih, TuioClient* _client);

    void addTuioObject(TuioObject *tobj);
    void updateTuioObject(TuioObject *tobj);
    void removeTuioObject(TuioObject *tobj);

    void addTuioCursor(TuioCursor *tcur);
    void updateTuioCursor(TuioCursor *tcur);
    void removeTuioCursor(TuioCursor *tcur);

    void addTuioBlob(TuioBlob *tblb);
    void updateTuioBlob(TuioBlob *tblb);
    void removeTuioBlob(TuioBlob *tblb);

    void refresh(TuioTime frameTime);
};

IupTuioListener::IupTuioListener(Ihandle* _ih, TuioClient* _client)
  :changed(0), locked(0), client(_client), ih(_ih), debug(0)
{
  timer = IupTimer();
  IupSetAttribute(timer, "TIME", "50");
  IupSetAttribute(timer, "_IUP_TUIOLISTENER", (char*)this);
  IupSetCallback(timer, "ACTION_CB", timer_action_cb);
}

void IupTuioListener::addTuioObject(TuioObject*) 
{
  /* unused */
}

void IupTuioListener::updateTuioObject(TuioObject*) 
{
  /* unused */
}

void IupTuioListener::removeTuioObject(TuioObject*) 
{
  /* unused */
}

void IupTuioListener::addTuioBlob(TuioBlob*)
{
  /* unused */
}

void IupTuioListener::updateTuioBlob(TuioBlob*)
{
  /* unused */
}

void IupTuioListener::removeTuioBlob(TuioBlob*)
{
  /* unused */
}

void IupTuioListener::addTuioCursor(TuioCursor *tcur) 
{
  IupTuioListener::processCursor(tcur, "DOWN", "AddCursor"); 
}

void IupTuioListener::updateTuioCursor(TuioCursor *tcur) 
{
  IupTuioListener::processCursor(tcur, "MOVE", "UpdateCursor"); 
}

void IupTuioListener::removeTuioCursor(TuioCursor *tcur) 
{
  IupTuioListener::processCursor(tcur, "UP", "RemoveCursor"); 
}

void IupTuioListener::processCursor(TuioCursor *tcur, const char* state, const char* action) 
{
  float x = tcur->getX();
  float y = tcur->getY();
  int id = (int)tcur->getSessionID();
  cursor_events.push_back(iTuioCursorEnvent(id, x, y, state[0]));

  this->changed = 1;

  if (this->debug)
    printf("IupTuioClient-%s(id=%d sid=%d x=%d y=%d)\n", action, tcur->getCursorID(), (int)tcur->getSessionID(), (int)tcur->getX(), (int)tcur->getY());
}

void  IupTuioListener::refresh(TuioTime frameTime) 
{
  if (this->changed)
  {
    Ihandle* ih_canvas = IupGetAttributeHandle(this->ih, "TARGETCANVAS");
    if (!ih_canvas)
      ih_canvas = this->ih;

    this->changed = 0;

    IFniiis cb = (IFniiis)IupGetCallback(ih_canvas, "TOUCH_CB");
    IFniIIII mcb = (IFniIIII)IupGetCallback(ih_canvas, "MULTITOUCH_CB");
    if (cb || mcb)
    {
      this->client->lockCursorList();
      this->locked = 1;
    }

    if (this->debug)
      printf("IupTuioClient-RefreshChanged(time=%d)\n", (int)frameTime.getTotalMilliseconds());
  }
}

void IupTuioListener::initCursorInfo(std::list<TuioCursor*> &cursorList, int cursor_count, int* pid, int* pstate)
{
  std::list <TuioCursor*>::iterator iter;
  int i;

  for (i = 0, iter = cursorList.begin() ; i<cursor_count; iter++, i++) 
  {
    TuioCursor* tcur = (*iter);
    pid[i] = (int)tcur->getSessionID();
    pstate[i] = 0;  /* mark to be updated later */
  }
}

int IupTuioListener::GetMainCursor(std::list<TuioCursor*> &cursorList)
{
  std::list <TuioCursor*>::iterator iter;
  std::list <TuioCursor*>::iterator end = cursorList.end();
  int min_id = -1;

  for (iter = cursorList.begin(); iter!=end; iter++) 
  {
    TuioCursor* tcur = (*iter);
    int id = (int)tcur->getSessionID();
    if (min_id == -1 || id < min_id)
      min_id = id;
  }

  return min_id;
}

void IupTuioListener::finishCursorInfo(std::list<TuioCursor*> &cursorList, int cursor_count, int* px, int* py, int* pstate, int w, int h, int use_client_coord, Ihandle* ih_canvas)
{
  std::list <TuioCursor*>::iterator iter;
  std::list <TuioCursor*>::iterator end = cursorList.end();
  int i;

  for (i = 0, iter = cursorList.begin(); i<cursor_count && iter!=end; iter++, i++) 
  {
    if (pstate[i] == 0) /* if still 0, then it was not updated, must fill it here */
    {
      TuioCursor* tcur = (*iter);

      int x = tcur->getScreenX(w);
      int y = tcur->getScreenY(h);

      if (use_client_coord)
        iupdrvScreenToClient(ih_canvas, &x, &y);

      px[i] = x;
      py[i] = y;
      pstate[i] = 'M';  /* mark as MOVE */
    }
  }
}

void IupTuioListener::updateCursorInfo(int *cursor_count, int* pid, int* px, int* py, int* pstate, int id, int x, int y, int state)
{
  int i;

  for(i = 0; i < *cursor_count; i++)
  {
    if (pid[i] == id)
    {
      px[i] = x;
      py[i] = y;
      pstate[i] = state;
      return;
    }
  }

  if (state == 'U')  /* UP - not in the cursorList, add it */
  {
    pid[i] = id;
    px[i] = x;
    py[i] = y;
    pstate[i] = state;
    (*cursor_count)++;
  }
}

int IupTuioListener::timer_action_cb(Ihandle *timer)
{
  IupTuioListener* listener = reinterpret_cast<IupTuioListener*>(IupGetAttribute(timer, "_IUP_TUIOLISTENER"));

  if (!listener->locked)
    return IUP_DEFAULT;

  std::list<TuioCursor*> cursorList = listener->client->getTuioCursors();
  int events_count = (int)listener->cursor_events.size();
  int cursor_count = (int)cursorList.size();
  int total_count = events_count+cursor_count;
  if (!total_count)
  {
    listener->locked = 0;
    listener->client->unlockCursorList();
    return IUP_DEFAULT;
  }

  int use_client_coord = 0;
  Ihandle* ih_canvas = IupGetAttributeHandle(listener->ih, "TARGETCANVAS");
  if (ih_canvas)
    use_client_coord = 1;
  else
    ih_canvas = listener->ih;

  IFniiis cb = (IFniiis)IupGetCallback(ih_canvas, "TOUCH_CB");
  IFniIIII mcb = (IFniIIII)IupGetCallback(ih_canvas, "MULTITOUCH_CB");

  int w, h, x, y;
  iupdrvGetFullSize(&w, &h);

  int *px=NULL, *py=NULL, *pid=NULL, *pstate=NULL;
  if (mcb)
  {
    px = new int[total_count];
    py = new int[total_count];
    pid = new int[total_count];
    pstate = new int[total_count];

    listener->initCursorInfo(cursorList, cursor_count, pid, pstate);
  }

  int min_id = -1;
  if (cb)
    min_id = listener->GetMainCursor(cursorList);

  for (int i = 0; i<events_count; i++) 
  {
    const iTuioCursorEnvent& evt = listener->cursor_events.front();

    const char* state = (evt.state=='D')? "DOWN": ((evt.state=='U')? "UP": "MOVE");
    x = (int)floor(evt.x*w+0.5f);
    y = (int)floor(evt.y*h+0.5f);

    if (use_client_coord)
      iupdrvScreenToClient(ih_canvas, &x, &y);

    if (cb)
    {
      if (min_id == evt.id)
        state = (evt.state=='D')? "DOWN-PRIMARY": ((evt.state=='U')? "UP-PRIMARY": "MOVE-PRIMARY");

      if (cb(ih_canvas, evt.id, x, y, (char*)state)==IUP_CLOSE)
        IupExitLoop();
    }

    if (mcb)
      listener->updateCursorInfo(&cursor_count, pid, px, py, pstate, evt.id, x, y, state[0]);

    listener->cursor_events.pop_front();
  }

  if (mcb)
  {
    listener->finishCursorInfo(cursorList, cursor_count, px, py, pstate, w, h, use_client_coord, ih_canvas);

    if (mcb(ih_canvas, cursor_count, pid, px, py, pstate)==IUP_CLOSE)
      IupExitLoop();
     
    delete[] px;
    delete[] py;
    delete[] pid;
    delete[] pstate;
  }

  listener->locked = 0;
  listener->client->unlockCursorList();
  return IUP_DEFAULT;
}

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

struct _IcontrolData 
{
  IupTuioListener* listener;
  TuioClient* client;
};

static int iTuioSetConnectAttrib(Ihandle* ih, const char* value)
{
  if (ih->data->client->isConnected())
  {
    IupSetAttribute(ih->data->listener->timer, "RUN", "NO");
    ih->data->client->disconnect();
  }

  if (iupStrEqualNoCase(value, "YES"))
  {
    ih->data->client->connect(false);
    IupSetAttribute(ih->data->listener->timer, "RUN", "YES");
  }
  else if (iupStrEqualNoCase(value, "LOCKED"))
  {
    ih->data->client->connect(true);
    IupSetAttribute(ih->data->listener->timer, "RUN", "YES");
  }
    
  return 0;
}

static char* iTuioGetConnectAttrib(Ihandle *ih)
{
  if (ih->data->client->isConnected())
    return (char*)"Yes";
  else
    return (char*)"No";
}

static int iTuioSetDebugAttrib(Ihandle* ih, const char* value)
{
  ih->data->listener->debug = iupStrBoolean(value);
  return 0;
}

static char* iTuioGetDebugAttrib(Ihandle *ih)
{
  if (ih->data->listener->debug)
    return (char*)"Yes";
  else
    return (char*)"No";
}

static int iTuioCreateMethod(Ihandle* ih, void** params)
{
  int port = 3333;
  if (params && params[0])
#if defined (WIN32) && defined (_M_X64)
    port = (int)(long long)(params[0]);
#else
    port = (int)(long)(params[0]); /* must cast to long first to avoid 64bit C++ compiler error */
#endif
  ih->data = iupALLOCCTRLDATA();
  
  ih->data->client = new TuioClient(port);
  ih->data->listener = new IupTuioListener(ih, ih->data->client);
  ih->data->client->addTuioListener(ih->data->listener);

  return IUP_NOERROR;
}

static void iTuioDestroyMethod(Ihandle* ih)
{
  IupDestroy(ih->data->listener->timer);
  delete ih->data->client;
  delete ih->data->listener;
}

Ihandle* IupTuioClient(int port)
{
  void *params[2];
  params[0] = (void*)port;
  params[1] = NULL;
  return IupCreatev("tuioclient", params);
}

static Iclass* iTuioNewClass(void)
{
  Iclass* ic = iupClassNew(NULL);

  ic->name = (char*)"tuioclient";
  ic->cons = (char*)"TuioClient";
  ic->format = (char*)"i";  /* (int) */
  ic->nativetype = IUP_TYPEVOID;
  ic->childtype = IUP_CHILDNONE;
  ic->is_interactive = 0;
  
  ic->New = iTuioNewClass;
  ic->Create = iTuioCreateMethod;
  ic->Destroy = iTuioDestroyMethod;

  /* Base Callbacks */
  iupBaseRegisterBaseCallbacks(ic);

  iupClassRegisterCallback(ic, "TOUCH_CB", "iiis");
  iupClassRegisterCallback(ic, "MULTITOUCH_CB", "iIIII");

  iupClassRegisterAttribute(ic, "CONNECT", iTuioGetConnectAttrib, iTuioSetConnectAttrib, NULL, NULL, IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "DEBUG", iTuioGetDebugAttrib, iTuioSetDebugAttrib, NULL, NULL, IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);

  return ic;
}

int IupTuioOpen(void)
{
  if (!IupIsOpened())
    return IUP_ERROR;

  if (IupGetGlobal("_IUP_TUIO_OPEN"))
    return IUP_OPENED;

  iupRegisterClass(iTuioNewClass());

  IupSetGlobal("_IUP_TUIO_OPEN", "1");
  return IUP_NOERROR;
}
