
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <pthread.h>

#include <vdr/osd.h>
#include <vdr/osdbase.h>
#include <vdr/thread.h>
#include <vdr/font.h>

#include "ttxtsubsglobals.h"
#include "ttxtsubsdisplay.h"
#include "utils.h"

// pageState
enum {
  invalid = 0,
  collecting,
  interimshow,
  finished
};

// --------------------

class cOSDSelfMemory {
 public:
  cOSDSelfMemory(void) : mActive(0) {}
  ~cOSDSelfMemory() {}
  
  void SetActive(int a)
    {
      cMutexLock lock(&mLock);
      mActive = a;
      if(a)
	mThr = pthread_self();
    }
  int IsMe(void)
    {
      cMutexLock lock(&mLock);
      pthread_t thr = pthread_self();
      if(mActive && pthread_equal(thr, mThr))
	return 1;
      else
	return 0;
    }

 private:
  cMutex mLock;
  int mActive;
  pthread_t mThr;
};

class cOSDSelfMemoryLock {
 public:
  cOSDSelfMemoryLock(cOSDSelfMemory *m) : mMem(m) { mMem->SetActive(1); }
  ~cOSDSelfMemoryLock() { mMem->SetActive(0); }
  
 private:
  cOSDSelfMemory *mMem;
};

static cOSDSelfMemory gSelfMem;

// --------------------

cTtxtSubsDisplay::cTtxtSubsDisplay(void)
  :
  mPageState(invalid),
  mMag(0),
  mNo(0),
  mDoDisplay(0),
  mOsd(NULL),
  mOsdLock(),
  mLastDataTime(NULL)
{
  memset(&page.data, 0, sizeof(page.data));
  mLastDataTime = (struct timeval *) calloc(1, sizeof(*mLastDataTime));
}


cTtxtSubsDisplay::~cTtxtSubsDisplay(void)
{
  if(mLastDataTime)
    free(mLastDataTime);
  if(mOsd) {
    cOsd* tmp = mOsd;
    mOsd = NULL;
    delete tmp;
  }
}


void cTtxtSubsDisplay::SetPage(int Pageno)  // Pageno is 0x000 to 0x799
{
  mMag = (Pageno >> 8) & 0xF;
  mNo = Pageno & 0xFF;

  Clear();
}


void cTtxtSubsDisplay::Hide(void)
{
  if(gSelfMem.IsMe()) {
    //dprint("cTtxtSubsDisplay::Hide - Ignoring self induced hide!\n");
    return;
  }

  //dprint("cTtxtSubsDisplay::Hide\n");
  cMutexLock lock(&mOsdLock);
  mDoDisplay = 0;
  ClearOSD();
}


void cTtxtSubsDisplay::Show(void)
{
  if(gSelfMem.IsMe()) {
    //dprint("cTtxtSubsDisplay::Show - Ignoring self induced show!\n");
    return;
  }

  //dprint("cTtxtSubsDisplay::Show\n");
  cMutexLock lock(&mOsdLock);
  mDoDisplay = 1;
  ShowOSD();
}


void cTtxtSubsDisplay::Clear(void)
{
  mPageState = invalid;
  ClearOSD();
}


void cTtxtSubsDisplay::TtxtData(const uint8_t *Data, uint64_t sched_time)
{
  int mp;
  int mag; // X in ETSI EN 300 706
  int packet; // Y
  struct ttxt_data_field *d;

  // idle time - if we have been sitting on a half finished page for a while - show it
  if(Data == NULL && mPageState == collecting) {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    // add second diff to usecs
    tv.tv_usec += 1000000 * (tv.tv_sec - mLastDataTime->tv_sec);

    if((tv.tv_usec - mLastDataTime->tv_usec) > 500000) {
      //dprint("Interimshow!\n");
      mPageState = interimshow;
      ClearOSD();
      ShowOSD();
    }
  }

  if(Data == NULL)
    return;

  //print_line((char *) Data);

  d = (struct ttxt_data_field *) Data;

  mp = unham(invtab[d->mag_addr_ham[0]], invtab[d->mag_addr_ham[1]]);
  mag = mp & 0x7;
  packet = (mp >> 3) & 0x1f;

  //dprint("cTtxtSubsDisplay::TtxtData: mag:%d packet: %d\n", mag, packet); // XXX

  if(packet == 0) {
    int i, no;
    uint8_t fi[8]; // flags field inverted

    for(i = 0; i < 8; i++)
      fi[i] = invtab[d->data[i]];

    if(mag == mMag) { /* XXX: && ! magazine_serial */
      if(mPageState == collecting) {
	mPageState = finished;
	ClearOSD();
	ShowOSD();
      }
      if(mPageState == interimshow)
	mPageState = finished;
    }

    no = unham(fi[0], fi[1]);

    //dprint("cTtxtSubsDisplay::TtxtData: page:%d%02x, packet: %d\n", mag, no, packet); // XXX
    //print_line((char *) Data);

    if(mag == mMag && no == mNo) {
      //dprint("cTtxtSubsDisplay::TtxtData: page:%d%02x, packet: %d\n", mag, no, packet); // XXX
      page.mag = mag;
      page.no = no;
      page.flags = 0;
      page.national_charset = 0;
      
      if(fi[3] & 0x80) { // Erase Page
	page.flags |= erasepage;
	memset(&page.data, 0, sizeof(page.data)); // only if erasepage is set?
      }
      if(fi[5] & 0x20) // Newsflash
	page.flags |= newsflash;
      if(fi[5] & 0x80) // Subtitle
	page.flags |= subtitle;
      if(fi[6] & 0x02) // Suppress Header
	page.flags |= suppress_header;
      // if(fi[6] & 0x08) // Update Indicator
      // if(fi[6] & 0x20) // Interrupted Sequence
      if(fi[6] & 0x80) // Inhibit Display
	page.flags |= inhibit_display;
      // if(fi[7] & 0x02) // Magazine Serial
      
      page.national_charset = ((fi[7] & 0x80) >> 7) +
	((fi[7] & 0x20) >> 4) + ((fi[7] & 0x08) >> 1);
      
      if(mPageState != collecting) {
        int diff = sched_time - cTimeMs::Now();
        //printf("Got sched_time %llx, diff %d\n", sched_time, diff);
        if (diff > 10) cCondWait::SleepMs(diff);
      }

      mPageState = collecting;
      gettimeofday(mLastDataTime, NULL);

      for(i = 0; i < 32; i++)
	page.data[0][i] = invtab[d->data[i+8]];
    }
  } else if(mag == page.mag && packet < TTXT_DISPLAYABLE_ROWS && 
	    (mPageState == collecting || mPageState == interimshow)) {
    // mag == page.mag: The magazines can be sent interleaved
    int i;
    for(i = 0; i < 40; i++)
      page.data[packet][i] = invtab[d->data[i]];

    mPageState = collecting;
    gettimeofday(mLastDataTime, NULL);

    //dprint("row: %d ", packet);
    //dump_hex("", page.data[packet], 40);
  } else {
    // packets with national characters information: X/28/0 format 1, X/28/1, X/28/4, M/29/0 M/29/4, 
    if(packet == 28 || packet == 29) {
      //dprint("mag: %d, packet: %d, page: %02x, state: %d\n", page.mag, packet, page.no, mPageState);
    }
    
    //if(packet == 26) {
    //  dprint("mag: %d, packet: %d, page: %02x, state: %d\n", page.mag, packet, page.no, mPageState);
    //  dump_hex("", page.data[packet], 40);
    //}
  }
}


static char *
ttxt2la1(uint8_t *p, char *buf, int natopts)
{
  int i, j = 0, skip = 1;
  for(i = 0; i < 40; i++) {
    uint8_t c = p[i] & 0x7f;

    if(c == 0x0b) { // box begin
      skip = 0;
      if(j != 0) // if we have chars in buf
	buf[j++] = ' ';
      continue;
    }

    if(c == 0x0a) { // box end
      skip = 1;
      continue;
    }

    if(skip)
      continue;
    
    if(j == 0 && c == 20) // skip leading spaces
      continue;

    if(c >= 0x20) {
      buf[j++] = ttxt_laG0_la1_char(0, natopts, c);
    }
  }

  while(j > 0 && buf[j-1] == ' ') // strip extra spaces
    j--;

  buf[j++] = '\0';
  if(strlen(buf))
    return buf;
  else
    return NULL;
}

#if 0

#define TEST_CENTER 0
#if TEST_CENTER
#define SCREENLEFT 0
#define SCREENWIDTH 720
#else
#define SCREENLEFT 125
#endif

#define TEST_169 0
#if TEST_169
#define SCREENBOT 480
#else
#define SCREENBOT 540
#endif

#if 0
#define ROWINCR 45
#define ROWH 36
#define TEXTY 4
#else
#define ROWINCR 43
#define ROWH 34
#define TEXTY 3
#endif
#define TEXTX 15
//#define SCREENTOP (SCREENBOT - MAXTTXTROWS * ROWINCR)
#define SCREENTOP 100

#endif

enum {
  SCREENLEFT = 0,
  SCREENRIGHT = 719,
  SCREENTOP = 150,
  SCREENBOTTOM = 575,
  
  SIDEMARGIN = 125,
  BOTNORM = 540,
  BOTLETTERBOX = 482,

  ROWINCR = 43,
  ROWH = 34,
  TEXTY = 3,
  TEXTX = 15
};

static tColor
getcolor(int color)
{ 
  switch (color)
    {
    case 0:  return clrBlack;
    case 1:  return clrWhite;
    case 2:  return clrRed;
    case 3:  return clrGreen;
    case 4:  return clrYellow;
    case 5:  return clrMagenta;
    case 6:  return clrBlue;
    case 7:  return clrCyan;
    case 8:  return globals.customColor();
    case 9:  return clrTransparent;
    default: return clrGray50;
    }
  return clrGray50;
}

void cTtxtSubsDisplay::ShowOSD(void)
{
  int i, y;
  int rowcount = 0;
  char buf[TTXT_DISPLAYABLE_ROWS][41];
  int bottom = globals.bottomAdj() + (globals.bottomLB() ? BOTLETTERBOX : BOTNORM);
  tArea areas[MAXOSDAREAS];
  int numAreas = 0;

  cOSDSelfMemoryLock selfmem(&gSelfMem);
  cMutexLock lock(&mOsdLock);

  if(!mDoDisplay) {
    //dprint("NOT displaying subtitles because of other OSD activities!\n");
    return;
  }
  
  if(mPageState != interimshow && mPageState != finished) {
    //dprint("NOT displaying subtitles because page state: %d!\n", mPageState);
    return;
  }

  for(i = 1; i < 24; i++) {
    if(page.data[i][0] != 0) // anything on this line?
      if(ttxt2la1(page.data[i], buf[rowcount], page.national_charset))
	rowcount++;
  }

  if(mOsd) {
    cOsd* tmp = mOsd;
    mOsd = NULL;
    delete tmp;
  }

  if (cOsd::IsOpen()) {
     //dprint("NOT displaying subtitles because of other OSD activities!\n");
     return;
     }
  else {
     mOsd = cOsdProvider::NewOsd(SCREENLEFT, SCREENTOP);
     if(!mOsd) {
       //dprint("Error: cOsdProvider::NewOsd() returned NULL!\n");
       return;
       }
     }

  cFont::SetCode(I18nCharSets()[globals.i18nLanguage()]);
  if(rowcount > MAXTTXTROWS)
    rowcount = MAXTTXTROWS;
  y = bottom - SCREENTOP - ROWH - ((ROWINCR + globals.lineSpacing()) * (rowcount-1));
  for(i = 0; i < rowcount; i++) {
    int w = 0;
    int left = SIDEMARGIN;
    w = cFont::GetFont(fontOsd)->Width(buf[i]) + 2 * TEXTX;
    if(w % 4)
      w += 4 - (w % 4);
    switch(globals.textPos()) {
      case 1:
        left = (SCREENRIGHT - w) / 2;
        break;
      case 2:
        left = SCREENRIGHT - SIDEMARGIN - w;
        break;
      }
    tArea area = {left, y, left+w-1, y+ROWH-1, 2};
    areas[numAreas++] = area;
    y += (ROWINCR + globals.lineSpacing());
  }
  if (mOsd->CanHandleAreas(areas, numAreas) != oeOk) {
     cFont::SetCode(I18nCharSets()[Setup.OSDLanguage]);
     dprint("ttxtsubs: OSD Cannot handle areas (error code: %d) - try to enlarge the line spacing!\n", mOsd->CanHandleAreas(areas, numAreas));
     }
  else {
    mOsd->SetAreas(areas, numAreas);
    y = bottom - SCREENTOP - ROWH - ((ROWINCR + globals.lineSpacing()) * (rowcount-1));
    for(i = 0; i < rowcount; i++) {
      int w = 0;
      int left = SIDEMARGIN;
      w = cFont::GetFont(fontOsd)->Width(buf[i]) + 2 * TEXTX;
      if(w % 4)
        w += 4 - (w % 4);
      switch(globals.textPos()) {
        case 1:
          left = (SCREENRIGHT - w) / 2;
          break;
        case 2:
          left = SCREENRIGHT - SIDEMARGIN - w;
          break;
        }
      mOsd->DrawRectangle(left, y, left + w, y + ROWH, getcolor(globals.bgColor()));
      mOsd->DrawText(left + TEXTX, y + TEXTY, buf[i], getcolor(globals.fgColor()), getcolor(globals.bgColor()), cFont::GetFont(fontOsd));
      //dprint("%d/%d (%d,%d) (%d,%d): %s\n", i, rowcount-1, areas[i].x1, areas[i].y1, left + TEXTX, y + TEXTY, buf[i]);
      y += (ROWINCR + globals.lineSpacing());
      }
    cFont::SetCode(I18nCharSets()[Setup.OSDLanguage]);
    mOsd->Flush();
    }
}


void cTtxtSubsDisplay::ClearOSD(void)
{
  // dprint("\nClearOSD!\n");
  cOSDSelfMemoryLock selfmem(&gSelfMem);
  cMutexLock lock(&mOsdLock);

  if(mOsd) {
    cOsd* tmp = mOsd;
    mOsd = NULL;
    delete tmp;
  }
}
