/**************************************************************************/
/*                                                                        */
/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com)          */
/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>  */
/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de>                 */
/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de>                */
/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/
/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com)           */
/*                                                                        */
/* NXCOMPSHAD, NX protocol compression and NX extensions to this software */
/* are copyright of the aforementioned persons and companies.             */
/*                                                                        */
/* Redistribution and use of the present software is allowed according    */
/* to terms specified in the file LICENSE which comes in the source       */
/* distribution.                                                          */
/*                                                                        */
/* All rights reserved.                                                   */
/*                                                                        */
/* NOTE: This software has received contributions from various other      */
/* contributors, only the core maintainers and supporters are listed as   */
/* copyright holders. Please contact us, if you feel you should be listed */
/* as copyright holder, as well.                                          */
/*                                                                        */
/**************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#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 (mirror_ == 1)
  {
    int result = mirrorChanges_;

    mirrorChanges_ = 0;

    return result;
  }

  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", "Analyzing 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,
                       static_cast<short>(curLine),
                       static_cast<unsigned short>(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, curWorkLine, left, right;

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

    start = curWorkLine;
    last = curWorkLine;

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

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

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

      if ((curWorkLine - last > minSliceHeight_) ||
          (last - start > maxSliceHeight_))
      {
        XRectangle rect = {static_cast<short>(left),
                           static_cast<short>(start),
                           static_cast<unsigned short>(right - left + 1),
                           static_cast<unsigned short>(last - start + 1)};

        XUnionRectWithRegion(&rect, lastUpdatedRegion_, lastUpdatedRegion_);

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

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

      last = curWorkLine;

      curWorkLine++;
    }

    //
    // Send last block.
    //

    if (last >= start)
    {
      XRectangle rect = {static_cast<short>(left),
                         static_cast<short>(start),
                         static_cast<unsigned short>(right - left + 1),
                         static_cast<unsigned short>(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_];
  }
}
