/**************************************************************************/
/*                                                                        */
/* Copyright (c) 2001, 2011 NoMachine, http://www.nomachine.com/.         */
/*                                                                        */
/* NXCOMPSHAD, NX protocol compression and NX extensions to this software */
/* are copyright of NoMachine. Redistribution and use of the present      */
/* software is allowed according to terms specified in the file LICENSE   */
/* which comes in the source distribution.                                */
/*                                                                        */
/* Check http://www.nomachine.com/licensing.html for applicability.       */
/*                                                                        */
/* NX and NoMachine are trademarks of Medialogic S.p.A.                   */
/*                                                                        */
/* All rights reserved.                                                   */
/*                                                                        */
/**************************************************************************/

#include <string.h>
#include <sys/time.h>

#define PANIC
#define WARNING
#undef  TEST
#undef  DEBUG

#include "Core.h"
#include "Logger.h"

const int CorePoller::maxSliceHeight_ = 20;
const int CorePoller::minSliceHeight_ = 3;

const char CorePoller::interlace_[] =
{
  0, 16,
  8, 24,
  4, 20, 12, 28,
  2, 18, 10, 26, 6, 22, 14, 30,
  1, 17,
  9, 25,
  5, 21, 13, 29,
  3, 19, 11, 27, 7, 23, 15, 31
};

CorePoller::CorePoller(Input *input, Display *display) : input_(input)
{
  logTrace("CorePoller::CorePoller");

  buffer_ = NULL;
  lastUpdatedRegion_ = NULL;
  lineStatus_ = NULL;
  linePriority_ = NULL;
  lefts_ = NULL;
  rights_ = NULL;
}

CorePoller::~CorePoller()
{
  logTrace("CorePoller::~CorePoller");

  if (buffer_ != NULL)
  {
    delete [] buffer_;

    buffer_ = NULL;
  }

  if (lastUpdatedRegion_ != NULL)
  {
    XDestroyRegion(lastUpdatedRegion_);

    lastUpdatedRegion_ = NULL;
  }

  if (lineStatus_ != NULL)
  {
    delete [] lineStatus_;

    lineStatus_ = NULL;
  }

  if (linePriority_ != NULL)
  {
    delete [] linePriority_;

    linePriority_ = NULL;
  }

  if (lefts_ != NULL)
  {
    delete [] lefts_;

    lefts_ = NULL;
  }

  if (rights_ != NULL)
  {
    delete [] rights_;

    rights_ = NULL;
  }
}

int CorePoller::init()
{
  logTrace("CorePoller::init");

  createFrameBuffer();

  if (buffer_ == NULL)
  {
    logError("CorePoller::init", ESET(ENOMEM));

    return -1;
  }

  logTest("CorePoller::init", "Allocated frame buffer at [%p] for [%d] bytes.",
            buffer_, bpl_ * height_);

  if (lastUpdatedRegion_ != NULL)
  {
    XDestroyRegion(lastUpdatedRegion_);

    lastUpdatedRegion_ = NULL;
  }

  lastUpdatedRegion_ = XCreateRegion();

  if (lineStatus_ != NULL)
  {
    delete[] lineStatus_;
  }

  lineStatus_ = new LineStatus[height_ + 1];

  if (lineStatus_ == NULL)
  {
    logError("CorePoller::init", ESET(ENOMEM));

    return -1;
  }

  //
  // We need this boundary element to
  // speed up the algo.
  //

  if (linePriority_ != NULL)
  {
    delete[] linePriority_;
  }

  linePriority_ = new int [height_ + 1];

  if (linePriority_ == NULL)
  {
    logError("CorePoller::init", ESET(ENOMEM));

    return -1;
  }

  for (unsigned int i = 0; i < height_; i++)
  {
    linePriority_[i] = HIGHEST_PRIORITY;
  }

  if (lefts_ != NULL)
  {
    delete[] lefts_;
  }

  lefts_ = new int [height_];

  if (rights_ != NULL)
  {
    delete[] rights_;
  }

  rights_ = new int [height_];

  for (unsigned int i = 0; i < height_; i++)
  {
    rights_[i] = lefts_[i] = 0;
  }

  return 1;
}

int CorePoller::isChanged(int (*checkIfInputCallback)(void *), void *arg, int *suspended)
{
  logTrace("CorePoller::isChanged");

#if defined(__CYGWIN32__) || defined(WIN32)

  checkDesktop();

#endif

#if !defined(__CYGWIN32__) && !defined(WIN32)

  if (mirror_ == 1)
  {
    int result = mirrorChanges_;

    mirrorChanges_ = 0;

    return result;
  }

#endif

  logDebug("CorePoller:isChanged", "Going to use default polling algorithm.\n");

  //
  // In order to allow this function to
  // be suspended and resumed later, we
  // need to save these two status vars.
  // 

  static int idxIlace = 0;
  static int curLine = 0;


  const long timeout = 50;
  long oldTime;
  long newTime;
  struct timeval ts;

  gettimeofday(&ts, NULL);

  oldTime = ts.tv_sec * 1000 + ts.tv_usec / 1000;

  if (curLine == 0) // && idxIlace == 0 ?
  {
    for (unsigned int i = 0; i < height_; i++)
    {
      lineStatus_[i] = LINE_NOT_CHECKED;
    }
  }

  int foundChanges = 0;

  foundChanges = 0;

  int curIlace = interlace_[idxIlace];

  bool moveBackward = false;

  logDebug("CorePoller::isChanged", "Interlace index [%d] interlace [%d].", idxIlace, curIlace);

  for (; curLine < (int) height_; curLine++)
  {
    logDebug("CorePoller::isChanged", "Analizing line [%d] move backward [%d] status [%d] priority [%d].",
                curLine, moveBackward, lineStatus_[curIlace], linePriority_[curLine]);

    //
    // Ask the caller if the polling have to be suspended.
    //

    if ((*checkIfInputCallback)(arg) == 1)
    {
      *suspended = 1;

      break;
    }

    //
    // Suspend if too much time is elapsed.
    //

    gettimeofday(&ts, NULL);

    newTime = ts.tv_sec * 1000 + ts.tv_usec / 1000;

    if (newTime - oldTime >= timeout)
    {
      *suspended = 1;

      break;
    }

    oldTime = newTime;

    if (lineStatus_[curLine] != LINE_NOT_CHECKED)
    {
      continue;
    }

    if (moveBackward)
    {
      moveBackward = false;
    }
    else
    {
      switch (linePriority_[curLine])
      {
        case 1:
        case 29:
        {
          //
          // It was a priority,
          // but now it may not be.
          //
        }
        case 31:
        {
          //
          // Not a priority, still isn't.
          //

          linePriority_[curLine] = NOT_PRIORITY;

          break;
        }
        case 0:
        {
          //
          // Make it a priority.
          //

          linePriority_[curLine] = PRIORITY;

          break;
        }
        default:
        {
          linePriority_[curLine]--;

          break;
        }
      }

      if ((linePriority_[curLine] > PRIORITY) && ((curLine & 31) != curIlace))
      {
        continue;
      }
    }

    XRectangle rect = {0, curLine, width_, 1};

    char *buffer;

    logDebug("CorePoller::isChanged", "Checking line [%d].", curLine);

    if ((buffer = getRect(rect)) == NULL)
    {
      logDebug("CorePoller::isChanged", "Failed to retrieve line [%d].", curLine);

      return -1;
    }

    if (memcmp(buffer, buffer_ + curLine * bpl_, bpl_) == 0 || differ(buffer, rect) == 0)
    {
      logDebug("CorePoller::isChanged", "Data buffer didn't change.");

      lineStatus_[curLine] = LINE_NOT_CHANGED;

      continue;
    }

    rect.x = lefts_[rect.y];
    rect.width = rights_[rect.y] - lefts_[rect.y] + 1;

    update(buffer + rect.x * bpp_, rect);

    foundChanges = 1;

    lineStatus_[curLine] = LINE_HAS_CHANGED;

    //
    // Wake up the next line.
    //

    if (linePriority_[curLine + 1] > PRIORITY)
    {
      linePriority_[curLine + 1] = HIGHEST_PRIORITY;
    }

    //
    // Give this line priority.
    //

    linePriority_[curLine] = HIGHEST_PRIORITY;

    //
    // Wake up previous line.
    //

    if (curLine > 0 && lineStatus_[curLine - 1] == LINE_NOT_CHECKED)
    {
      moveBackward = true;
      curLine -= 2;
    }
  }

  //
  // Execution reached the end of loop.
  //

  if (curLine == (int) height_)
  {
    idxIlace = (idxIlace + 1) % 32;

    curLine = 0;
  }

  //
  // Create the region of changed pixels.
  //

  if (foundChanges)
  {
    int start, last, curLine, left, right;

    for (curLine = 0; curLine < (int) height_; curLine++)
    {
      if (lineStatus_[curLine] == LINE_HAS_CHANGED)
      {
        break;
      }
    }

    start = curLine;
    last = curLine;

    left = lefts_[curLine];
    right = rights_[curLine];
    curLine++;

    while (1)
    {
      for (; curLine < (int) height_; curLine++)
      {
        if (lineStatus_[curLine] == LINE_HAS_CHANGED)
        {
          break;
        }
      }

      if (curLine == (int) height_)
      {
        break;
      }

      if ((curLine - last > minSliceHeight_) || (last - start > maxSliceHeight_))
      {
        XRectangle rect = {left, start, right - left + 1, last - start + 1};

        XUnionRectWithRegion(&rect, lastUpdatedRegion_, lastUpdatedRegion_);

        start = curLine;
        left = lefts_[curLine];
        right = rights_[curLine];
      }
      else
      {
        if (lefts_[curLine] < left)
        {
          left = lefts_[curLine];
        }

        if (rights_[curLine] > right)
        {
          right = rights_[curLine];
        }
      }

      last = curLine;

      curLine++;
    }

    //
    // Send last block.
    //

    if (last >= start)
    {
      XRectangle rect = {left, start, right - left + 1, last - start + 1};

      XUnionRectWithRegion(&rect, lastUpdatedRegion_, lastUpdatedRegion_);
    }
  }

  return foundChanges;
}

int CorePoller::differ(char *buffer, XRectangle r)
{
  logTrace("CorePoller::differ");

  int bpl = bpp_ * r.width;
  int i;
  char *pBuf;
  char *pFb;

  pBuf = (buffer);
  pFb = (buffer_ + r.x + r.y * bpl_);

  for (i = 0; i < bpl; i++)
  {
    if (*pFb++ != *pBuf++)
    {
      lefts_[r.y] = i / bpp_;

      break;
    }
  }

  if (i == bpl)
  {
    return 0;
  }

  pBuf = (buffer) + bpl - 1;
  pFb = (buffer_ + r.x + r.y * bpl_) + bpl - 1;

  int j = i - 1;

  for (i = bpl - 1; i > j; i--)
  {
    if (*pFb-- != *pBuf--)
    {
      rights_[r.y] = i / bpp_;

      break;
    }
  }

  return 1;
}

void CorePoller::update(char *src, XRectangle r)
{
  logTrace("CorePoller::update");

  char *dst = buffer_ + r.x * bpp_ + r.y * bpl_;
  int bpl = bpp_ * r.width;

  for (unsigned int i = 0; i < r.height; i++)
  {
    if(((r.x * bpp_ + r.y * bpl_) + bpl) > (bpl_ * height_))
    {
      //
      // Out of bounds. Maybe a resize is going on.
      //

      continue;
    }

    memcpy(dst, src, bpl);

    src += bpl;

    dst += bpl_;
  }
}

void CorePoller::handleEvent(Display *display, XEvent *event)
{
  logTrace("CorePoller::handleEvent");

  switch (event -> type)
  {
    case KeyPress:
    case KeyRelease:
    {
      handleKeyboardEvent(display, event);
      break;
    }
    case ButtonPress:
    case ButtonRelease:
    case MotionNotify:
    {
      handleMouseEvent(display, event);
      break;
    }
    default:
    {
      logTest("CorePoller::handleEvent", "Handling unexpected event [%d] from display [%p].",
                  event -> type, display);
      break;
    }
  }
}

void CorePoller::handleWebKeyEvent(KeySym keysym, Bool isKeyPress)
{
  logTrace("CorePoller::handleWebKeyEvent");

  handleWebKeyboardEvent(keysym, isKeyPress);
}

void CorePoller::handleInput()
{
  while (input_ -> checkIfEvent())
  {
    Display *display = input_ -> currentDisplay();
    XEvent *event = input_ -> popEvent();

    handleEvent(display, event);

    delete event;
  }
}

void CorePoller::createFrameBuffer()
{
  logTrace("CorePoller::createFrameBuffer");

  if (buffer_ == NULL)
  {
    buffer_ = new char[bpl_ * height_];
  }
}