/*
 * targavfd plugin for VDR (C++)
 *
 * (C) 2010 Andreas Brachold <vdr07 AT deltab de>

 * This targavfd plugin is free software: you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License as published 
 * by the Free Software Foundation, version 3 of the License.
 *
 * See the files README and COPYING for details.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <stdint.h>

#include <vdr/tools.h>

#include "setup.h"
#include "ffont.h"
#include "vfd.h"

static const byte ICON_PLAY       = 0x00; //Play
static const byte ICON_PAUSE      = 0x01; //Pause
static const byte ICON_RECORD     = 0x02; //Record
static const byte ICON_MESSAGE    = 0x03; //Message symbol (without the inner @)
static const byte ICON_MSGAT      = 0x04; //Message @
static const byte ICON_MUTE       = 0x05; //Mute
static const byte ICON_WLAN1      = 0x06; //WLAN (tower base)
static const byte ICON_WLAN2      = 0x07; //WLAN strength (1 of 3)
static const byte ICON_WLAN3      = 0x08; //WLAN strength (2 of 3)
static const byte ICON_WLAN4      = 0x09; //WLAN strength (3 of 3)
static const byte ICON_VOLUME     = 0x0A; //Volume (the word)
static const byte ICON_VOL1       = 0x0B; //Volume level 1 of 14
static const byte ICON_VOL2       = 0x0C; //Volume level 2 of 14
static const byte ICON_VOL3       = 0x0D; //Volume level 3 of 14
static const byte ICON_VOL4       = 0x0E; //Volume level 4 of 14
static const byte ICON_VOL5       = 0x0F; //Volume level 5 of 14
static const byte ICON_VOL6       = 0x10; //Volume level 6 of 14
static const byte ICON_VOL7       = 0x11; //Volume level 7 of 14
static const byte ICON_VOL8       = 0x12; //Volume level 8 of 14
static const byte ICON_VOL9       = 0x13; //Volume level 9 of 14
static const byte ICON_VOL10      = 0x14; //Volume level 10 of 14
static const byte ICON_VOL11      = 0x15; //Volume level 11 of 14
static const byte ICON_VOL12      = 0x16; //Volume level 12 of 14
static const byte ICON_VOL13      = 0x17; //Volume level 13 of 14
static const byte ICON_VOL14      = 0x18; //Volume level 14 of 14

static const byte STATE_OFF       = 0x00; //Symbol off
static const byte STATE_ON        = 0x01; //Symbol on
static const byte STATE_ONHIGH    = 0x02; //Symbol on, high intensity, can only be used with the volume symbols

static const byte CMD_PREFIX      = 0x1b;
static const byte CMD_SETCLOCK    = 0x00; //Actualize the time of the display
static const byte CMD_SMALLCLOCK  = 0x01; //Display small clock on display
static const byte CMD_BIGCLOCK    = 0x02; //Display big clock on display
static const byte CMD_SETSYMBOL   = 0x30; //Enable or disable symbol
static const byte CMD_SETDIMM     = 0x40; //Set the dimming level of the display
static const byte CMD_RESET       = 0x50; //Reset all configuration data to default and clear
static const byte CMD_SETRAM      = 0x60; //Set the actual graphics RAM offset for next data write
static const byte CMD_SETPIXEL    = 0x70; //Write pixel data to RAM of the display
static const byte CMD_TEST1       = 0xf0; //Show vertical test pattern
static const byte CMD_TEST2       = 0xf1; //Show horizontal test pattern

static const byte TIME_12         = 0x00; //12 hours format
static const byte TIME_24         = 0x01; //24 hours format

static const byte BRIGHT_OFF      = 0x00; //Display off
static const byte BRIGHT_DIMM     = 0x01; //Display dimmed
static const byte BRIGHT_FULL     = 0x02; //Display full brightness

cVFDQueue::cVFDQueue() {
	hid = NULL;
  bInit = false;
}

cVFDQueue::~cVFDQueue() {
  cVFDQueue::close();
}

bool cVFDQueue::open()
{
  HIDInterfaceMatcher matcher = { 0x19c2, 0x6a11, NULL, NULL, 0 };
  hid_return ret;

  /* see include/debug.h for possible values */
  hid_set_debug(HID_DEBUG_NONE);
  hid_set_debug_stream(0);
  /* passed directly to libusb */
  hid_set_usb_debug(0);
  
  ret = hid_init();
  if (ret != HID_RET_SUCCESS) {
    esyslog("targaVFD: init - %s (%d)", hiderror(ret), ret);
    return false;
  }
  bInit = true;

  hid = hid_new_HIDInterface();
  if (hid == 0) {
    esyslog("targaVFD: hid_new_HIDInterface() failed, out of memory?\n");
    return false;
  }

  ret = hid_force_open(hid, 0, &matcher, 3);
  if (ret != HID_RET_SUCCESS) {
    esyslog("targaVFD: open - %s (%d)", hiderror(ret), ret);
    hid_close(hid);
    hid_delete_HIDInterface(&hid);
    hid = 0;
    return false;
  }

  while (!empty()) {
    pop();
  }
  //ret = hid_write_identification(stdout, hid);
  //if (ret != HID_RET_SUCCESS) {
  //  esyslog("targaVFD: write_identification %s (%d)", hiderror(ret), ret);
  //  return false;
  //}
  return true;
}

void cVFDQueue::close() {
  hid_return ret;
  if (hid != 0) {
    ret = hid_close(hid);
    if (ret != HID_RET_SUCCESS) {
      esyslog("targaVFD: close - %s (%d)", hiderror(ret), ret);
    }

    hid_delete_HIDInterface(&hid);
    hid = 0;
  }
  if(bInit) {
    ret = hid_cleanup();
    if (ret != HID_RET_SUCCESS) {
      esyslog("targaVFD: cleanup - %s (%d)", hiderror(ret), ret);
    }
    bInit = false;
  }
}

void cVFDQueue::QueueCmd(const byte & cmd) {
  this->push(CMD_PREFIX);
  this->push(cmd);
}

void cVFDQueue::QueueData(const byte & data) {
  this->push(data);
}

bool cVFDQueue::QueueFlush() {

  if(empty())
    return true;
  if(!isopen()) {
    return false;
  }

  int const PATH_OUT[1] = { 0xff7f0004 };
  char buf[64];
  hid_return ret;

  while (!empty()) {
    buf[0] = (char) std::min((size_t)63,size());
    for(unsigned int i = 0;i < 63 && !empty();++i) {
      buf[i+1] = (char) front(); //the first element in the queue
      pop();              //remove the first element of the queue
    }
    ret = hid_set_output_report(hid, PATH_OUT, sizeof(PATH_OUT), buf, (buf[0] + 1));
    if (ret != HID_RET_SUCCESS) {
      esyslog("targaVFD: set_output_report - %s (%d)", hiderror(ret), ret);
      while (!empty()) {
        pop();
      }
      cVFDQueue::close();
      return false;
    }
  }
  return true;
}

const char *cVFDQueue::hiderror(hid_return ret) const
{
  switch(ret) {
    case HID_RET_SUCCESS:
      return "success";
    case HID_RET_INVALID_PARAMETER:
      return "invalid parameter";
    case HID_RET_NOT_INITIALISED:
      return "not initialized";
    case HID_RET_ALREADY_INITIALISED:
      return "hid_init() already called";
    case HID_RET_FAIL_FIND_BUSSES:
      return "failed to find any USB busses";
    case HID_RET_FAIL_FIND_DEVICES:
      return "failed to find any USB devices";
    case HID_RET_FAIL_OPEN_DEVICE:
      return "failed to open device";
    case HID_RET_DEVICE_NOT_FOUND:
      return "device not found";
    case HID_RET_DEVICE_NOT_OPENED:
      return "device not yet opened";
    case HID_RET_DEVICE_ALREADY_OPENED:
      return "device already opened";
    case HID_RET_FAIL_CLOSE_DEVICE:
      return "could not close device";
    case HID_RET_FAIL_CLAIM_IFACE:
      return "failed to claim interface; is another driver using it?";
    case HID_RET_FAIL_DETACH_DRIVER:
      return "failed to detach kernel driver";
    case HID_RET_NOT_HID_DEVICE:
      return "not recognized as a HID device";
    case HID_RET_HID_DESC_SHORT:
      return "HID interface descriptor too short";
    case HID_RET_REPORT_DESC_SHORT:
      return "HID report descriptor too short";
    case HID_RET_REPORT_DESC_LONG:
      return "HID report descriptor too long";
    case HID_RET_FAIL_ALLOC:
      return "failed to allocate memory";
    case HID_RET_OUT_OF_SPACE:
      return "no space left in buffer";
    case HID_RET_FAIL_SET_REPORT:
      return "failed to set report";
    case HID_RET_FAIL_GET_REPORT:
      return "failed to get report";
    case HID_RET_FAIL_INT_READ:
      return "interrupt read failed";
    case HID_RET_NOT_FOUND:
      return "not found";
#ifdef HID_RET_TIMEOUT
    case HID_RET_TIMEOUT:
      return "timeout";
#endif
  }
  return "unknown error";
}

cVFD::cVFD() 
{
	this->pFont = NULL;
	this->lastIconState = 0;
}

cVFD::~cVFD() {
  this->close();
}


/**
 * Initialize the driver.
 * \retval 0	   Success.
 * \retval <0	  Error.
 */
bool cVFD::open()
{
  if(!SetFont(theSetup.m_szFont, 
              theSetup.m_nRenderMode == eRenderMode_DualLine, 
              theSetup.m_nBigFontHeight, 
              theSetup.m_nSmallFontHeight)) {
		return false;
  }
  if(!cVFDQueue::open()) {
		return false;
  }

	isyslog("targaVFD: open Device successful");

	/* Make sure the frame buffer is there... */
	this->framebuf = new cVFDBitmap(theSetup.m_cWidth,theSetup.m_cHeight);
	if (this->framebuf == NULL) {
		esyslog("targaVFD: unable to allocate framebuffer");
		return false;
	}
    m_iSizeYb = ((theSetup.m_cHeight + 7) / 8);

	/* Make sure the framebuffer backing store is there... */
	this->backingstore = new unsigned char[theSetup.m_cWidth * m_iSizeYb];
	if (this->backingstore == NULL) {
		esyslog("targaVFD: unable to create framebuffer backing store");
		return false;
	}

	this->lastIconState = 0;

  QueueCmd(CMD_RESET);
	//Brightness(theSetup.m_nBrightness);
  if(QueueFlush()) {
  	dsyslog("targaVFD: init() done");
  	return true;
  }
  return false;
}

/*
 * turning display off
 */
bool cVFD::SendCmdShutdown() {
  QueueCmd(CMD_RESET);
	return QueueFlush();
}

inline byte toBCD(int x){ 
 return (byte)(((x) / 10 * 16) + ((x) % 10));
}

/*
 * Show the big clock. We need to set it to the current time, then it just
 * keeps counting automatically.
 */
bool cVFD::SendCmdClock() {

  time_t tt;
  struct tm l;

  tt = time(NULL);
  localtime_r(&tt, &l);

  // Set time
  QueueCmd(CMD_SETCLOCK);
  QueueData(toBCD(l.tm_min));
  QueueData(toBCD(l.tm_hour));

  // Show it
  QueueCmd(CMD_BIGCLOCK);
  QueueData(TIME_24);
  return QueueFlush();
}

/**
 * Close the driver (do necessary clean-up).
 */
void cVFD::close()
{
  cVFDQueue::close();

  if(pFont) {
    delete pFont;
    pFont = NULL;
  }
  if(framebuf) {
    delete framebuf;
    framebuf = NULL;
  }
  if(backingstore) {
    delete backingstore;
    backingstore = NULL;
  }

	dsyslog("targaVFD: close() done");
}


/**
 * Clear the screen.
 */
void cVFD::clear()
{
  if(framebuf)
    framebuf->clear();
}


/**
 * Flush cached bitmap data and submit changes rows to the Display.
 */

bool cVFD::flush(bool refreshAll)
{
  unsigned int n, x, yb;

  if (!backingstore || !framebuf)
      return false;

  const uchar* fb = framebuf->getBitmap();
  const unsigned int width = framebuf->Width();

  bool doRefresh = false;
  unsigned int minX = width;
  unsigned int maxX = 0;

  for (yb = 0; yb < m_iSizeYb; ++yb)
      for (x = 0; x < width; ++x)
      {
          n = x + (yb * width);
          if (*(fb + n) != *(backingstore + n))
          {
              *(backingstore + n) = *(fb + n);
              minX = min(minX, x);
              maxX = max(maxX, x + 1);
              doRefresh = true;
          }
      }

  if (refreshAll || doRefresh) {
    if (refreshAll) {
      minX = 0;
      maxX = width;
    }

    maxX = min(maxX, width);

	  unsigned int nData = (maxX-minX) * m_iSizeYb;
	  if(nData) {
	    // send data to display, controller
		QueueCmd(CMD_SETRAM);
		QueueData(minX*m_iSizeYb);
		QueueCmd(CMD_SETPIXEL);
		QueueData(nData);

    for (x = minX; x < maxX; ++x)
	    for (yb = 0; yb < m_iSizeYb; ++yb)
        {
            n = x + (yb * width);
            QueueData((*(backingstore + n)));
        }
		}
  }
  return QueueFlush();
}

/**
 * Print a string on the screen at position (x,y).
 * The upper-left corner is (1,1), the lower-right corner is (this->width, this->height).
 * \param x        Horizontal character position (column).
 * \param y        Vertical character position (row).
 * \param string   String that gets written.
 */
int cVFD::DrawText(int x, int y, const char* string)
{
  if(pFont && framebuf)
    return pFont->DrawText(framebuf, x, y, string, 1024);
  return -1;
}


/**
 * Sets the "icons state" for the device. We use this to control the icons
   * around the outside the display. 
 *
 * \param state    This symbols to display.
 */
void cVFD::icons(unsigned int state)
{
  for(unsigned i = 0; i <= 0x18; i++) {
    if((state & (1 << i)) != (lastIconState & (1 << i))) 
    { 
      QueueCmd(CMD_SETSYMBOL);
      QueueData(i);
      QueueData((state & (1 << i)) ? STATE_ON : STATE_OFF );
    }
  }  
  lastIconState = state;
}

/**
 * Sets the brightness of the display.
 *
 * \param nBrightness The value the brightness (0 = off
 *                  1 = half brightness; 2 = highest brightness).
 */
void cVFD::Brightness(int nBrightness)
{
	if (nBrightness < 0) {
		nBrightness = 0;
	} else if (nBrightness > 2) {
		nBrightness = 2;
	}
  this->QueueCmd(CMD_SETDIMM);
  this->QueueData((byte) (nBrightness));
}

bool cVFD::SetFont(const char *szFont, bool bTwoLineMode, int nBigFontHeight, int nSmallFontHeight) {

  cVFDFont* tmpFont = NULL;

  cString sFileName = cFont::GetFontFileName(szFont);
  if(!isempty(sFileName))
  {
    if (bTwoLineMode) {
      tmpFont = new cVFDFont(sFileName,nSmallFontHeight);
    } else {
      tmpFont = new cVFDFont(sFileName,nBigFontHeight);
    }
  } else {
		esyslog("targaVFD: unable to find font '%s'",szFont);
  }
  if(tmpFont) {
    if(pFont) {
      delete pFont;
    }
    pFont = tmpFont;
    return true;
  }
  return false;
}