/*
 * frontend.c:
 *
 * See the main source file 'xineliboutput.c' for copyright information and
 * how to reach the author.
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <vdr/config.h>
#include <vdr/tools.h>
#include <vdr/plugin.h>

#include "logdefs.h"
#include "config.h"
#include "frontend.h"

#include "tools/pes.h"
#include "tools/general_remote.h"

//#define LOG_CONTROL_MESSAGES
//#define XINELIBOUTPUT_LOG_KEYS

//----------------------------- cXinelibThread --------------------------------

//
// keyboard control handler
//

/*static*/
void cXinelibThread::KeypressHandler(char *keymap, char *key, 
				     bool repeat, bool release)
{
#ifdef XINELIBOUTPUT_LOG_KEYS
  static FILE *flog = fopen("/video/keys.log","w");
  fprintf(flog,"KEY %s %s %d %d\n",keymap,key,repeat,release);fflush(flog);
#endif

  TRACE("keypress_handler: " << (keymap?keymap:"") << " " << key);

  if(!xc.use_x_keyboard || !key)
    return;

  if(keymap) {
    cRemote *item = Remotes.First();
    while(item) {
      if(!strcmp(item->Name(), keymap)) {
	// dirty... but only way to support learning ...
	((cGeneralRemote*)item)->Put(key, repeat, release);
	return;
      }
      item = Remotes.Next(item);
    }
    cGeneralRemote *r = new cGeneralRemote(keymap);
    if(*key)
      r->Put(key, repeat, release);
  } else {
    cRemote::Put(cKey::FromString(key));
  }
}

cXinelibThread::cXinelibThread(const char *Description) : cThread(Description)
{
  TRACEF("cXinelibThread::cXinelibThread");

  m_bStopThread = false;
  m_bReady = false;
  m_bIsFinished = false;
  m_bNoVideo = true;
  m_bLiveMode = false;
  m_StreamPos = 0;
  m_bEndOfStreamReached = false;
  m_bPlayingFile = false;
  m_FileName = NULL;
}

cXinelibThread::~cXinelibThread() 
{
  TRACEF("cXinelibThread::~cXinelibThread");

  if(Active())
    Cancel();
  if(m_FileName)
    free(m_FileName);
}

//
// Thread control
//

void cXinelibThread::Start(void) 
{
  TRACEF("cXinelibThread::Start");

  cThread::Start(); 
}

void cXinelibThread::Stop(void)  
{ 
  TRACEF("cXinelibThread::Stop");

  SetStopSignal(); 

  //if(Active()) 
  Cancel(5); 
}

void cXinelibThread::SetStopSignal(void) 
{
  TRACEF("cXinelibThread::SetStopSignal");

  LOCK_THREAD;
  m_bStopThread = true;
}

bool cXinelibThread::GetStopSignal(void) 
{
  TRACEF("cXinelibThread::GetStopSignal");

  LOCK_THREAD;
  return m_bStopThread;
}

bool cXinelibThread::IsReady(void)
{
  LOCK_THREAD;
  return m_bReady;
}

bool cXinelibThread::IsFinished(void)
{
  LOCK_THREAD;
  return m_bIsFinished;
}

//
// Playback control
//

void cXinelibThread::SetVolume(int NewVolume)
{
  Xine_Control("VOLUME", NewVolume * 100 / 255);
}

void cXinelibThread::TrickSpeed(int Speed)
{
  TRACEF("cXinelibThread::TrickSpeed");

  Xine_Control("TRICKSPEED", Speed);
}

void cXinelibThread::SetLiveMode(bool LiveModeOn)
{
  TRACEF("cXinelibThread::SetLiveMode");

  Lock();
  if(m_bLiveMode == LiveModeOn) {
    Unlock();
    return;
  }
  m_bLiveMode = LiveModeOn;
  Unlock();

  Xine_Control("LIVE", m_bLiveMode ? 1 : 0);
  Xine_Sync();
}

void cXinelibThread::SetNoVideo(bool bVal)
{
  TRACEF("cXinelibThread::SetNoVideo");

  Lock();
  if(m_bNoVideo == bVal) {
    Unlock();
    return;
  }
  m_bNoVideo = bVal;
  Unlock();

  Xine_Control("NOVIDEO", m_bNoVideo ? 1 : 0);

  if(m_bNoVideo && strcmp(xc.audio_visualization, "none")) {
    ConfigurePostprocessing(xc.audio_visualization, true, NULL);
  } else {
    ConfigurePostprocessing("AudioVisualization", false, NULL);
  }
}

void cXinelibThread::AudioStreamChanged(bool ac3, int StreamId)
{
  TRACEF("cXinelibThread::AudioStreamChanged");
  if(ac3)
    Xine_Control("NEWAUDIOSTREAM AC3");
  else
    Xine_Control("NEWAUDIOSTREAM", StreamId);
}

void cXinelibThread::SpuStreamChanged(int StreamId)
{
  TRACEF("cXinelibThread::SpuStreamChanged");
  Xine_Control("NEWSPUSTREAM", StreamId);
}

void cXinelibThread::Clear(void)
{
  TRACEF("cXinelibThread::Clear");

  Lock();
  int tmp = m_StreamPos;
  Unlock();
  Xine_Control("DISCARD", tmp);
}

bool cXinelibThread::Flush(int TimeoutMs) 
{
  TRACEF("cXinelibThread::Flush");

  return Xine_Control("FLUSH", TimeoutMs) <= 0;
}

bool cXinelibThread::Poll(cPoller& Poller, int TimeoutMs) 
{
  TRACEF("cXinelibThread::Poll");

  int n = Xine_Control("POLL", TimeoutMs);

  if(n>0)
    return true;

  return false; // Poller.Poll(TimeoutMs);
}

//
// Data transfer
//

int cXinelibThread::Play_PES(const uchar *data, int len)
{
  Lock();
  m_StreamPos += len;
  /*m_bEndOfStreamReached = false;*/
  Unlock();
  return len;
}

//
// Stream conversions
//

// Convert MPEG1 PES headers to MPEG2 PES headers

int cXinelibThread::Play_Mpeg1_PES(const uchar *data1, int len)
{
  if(!data1[0] && !data1[1] && data1[2] == 0x01 && len>7 && /* header sync bytes */
     ( VIDEO_STREAM == (data1[3] & ~VIDEO_STREAM_MASK) ||   /*   video stream */
       AUDIO_STREAM == (data1[3] & ~AUDIO_STREAM_MASK) ||   /*   audio stream */
       PRIVATE_STREAM1 == data1[3]) &&                      /*   private stream 1 */
     ((data1[6] & 0xC0) != 0x80) &&                         /* really mpeg1 pes */
     (len == ((data1[4]<<8) | data1[5]) + 6)) {             /* whole PES packet and nothing else */
    uchar *data2 = new uchar[len+64];
    int i1=0, i2=0, r=0;
    
    data2[i2++]=data1[i1++];  // 00 (sync)  
    data2[i2++]=data1[i1++];  // 00 (sync)
    data2[i2++]=data1[i1++];  // 01 (sync)
    data2[i2++]=data1[i1++];  // stream ID
    data2[i2++]=data1[i1++];  // len hi
    data2[i2++]=data1[i1++];  // len lo
    
    // skip stuffing
    while ((data1[i1] & 0x80) == 0x80) 
      i1++;

    if ((data1[i1] & 0xc0) == 0x40) {
      // skip STD_buffer_scale, STD_buffer_size
      i1 += 2;
    }
    
    if(len<i1+5) return len;
    
    data2[i2++] = 0x80;
    
    if ((data1[i1] & 0xf0) == 0x20) { 
      /* PTS */
      data2[i2++] = 0x80;
      data2[i2++] = 5;
      data2[i2++] = data1[i1++] & 0x0E;
      data2[i2++] = data1[i1++] & 0xFF;
      data2[i2++] = data1[i1++] & 0xFE;
      data2[i2++] = data1[i1++] & 0xFF;
      data2[i2++] = data1[i1++] & 0xFE;
    }
    else if ((data1[i1] & 0xf0) == 0x30) { 
      /* PTS & DTS */
      data2[i2++] = 0x80|0x40;
      data2[i2++] = 10;
      data2[i2++] = data1[i1++] & 0x0E;
      data2[i2++] = data1[i1++] & 0xFF;
      data2[i2++] = data1[i1++] & 0xFE;
      data2[i2++] = data1[i1++] & 0xFF;
      data2[i2++] = data1[i1++] & 0xFE;
      
      data2[i2++] = data1[i1++] & 0x0E;
      data2[i2++] = data1[i1++] & 0xFF;
      data2[i2++] = data1[i1++] & 0xFE;
      data2[i2++] = data1[i1++] & 0xFF;
      data2[i2++] = data1[i1++] & 0xFE;
    } else {
      i1++;
      data2[i2++] = 0; /* no pts, no dts */
      data2[i2++] = 0; /* header len */
    }
    
    int newlen = ((data1[4]<<8) | data1[5]) + (i2-i1), loops=0;
    data2[4] = ((newlen)&0xff00)>>8;
    data2[5] = ((newlen)&0xff);
    if(len-i1 > 0) {
      memcpy(data2+i2, data1+i1, len-i1);
      cPoller p;
      while(!Poll(p,100) && loops++ < 10) {
	LOGDBG("Play_Mpeg1_PES: poll failed");
      }
      r = Play_PES(data2,newlen+6);
    } 
  
    delete data2;
    return r==newlen+6 ? ((data1[4]<<8)|data1[5])+6 : 0;
  }
  return len; // nothing useful found ...
}

// Pack elementary MPEG stream to PES

bool cXinelibThread::Play_Mpeg2_ES(const uchar *data, int len, int streamID)
{
  uchar hdr[] = {0x00,0x00,0x01,0xe0,0x00,0x00,0x80,0,0}; /* mpeg2 */
  int todo = len, done = 0, hdrlen = 9/*sizeof(hdr)*/;
  uchar *frame = new uchar[PES_CHUNK_SIZE+32];

  hdr[3] = (uchar)streamID;
  while(todo) {
    int blocklen = todo;
    if(blocklen > ((PES_CHUNK_SIZE - hdrlen) & 0xfffc))
      blocklen = (PES_CHUNK_SIZE - hdrlen) & 0xfffc;
    hdr[4] = ((blocklen+3)&0xff00)>>8;
    hdr[5] = ((blocklen+3)&0xff);

    memcpy(frame, hdr, hdrlen);
    memcpy(frame+hdrlen, data+done, blocklen);

    done += blocklen;
    todo -= blocklen;

    cPoller p;
#if 1
    Poll(p, 100);
#else
    int loops=0;
    while(!Poll(p,100) && loops++ < 10) {
      LOGDBG("Play_ES: Poll Failed");
    }
#endif

    if(blocklen+hdrlen != Play_PES(frame,blocklen+hdrlen)) {      
      delete frame;
      return false;
    }
  }

  // append sequence end code to video 
  if((streamID & 0xF0) == 0xE0) { 
    frame[4]  = 0x00;
    frame[5]  = 0x07;
    frame[6]  = 0x80;
    frame[7]  = 0x00;
    frame[8]  = 0x00;
    frame[9]  = 0x00;
    frame[10] = 0x00;
    frame[11] = 0x01;
    frame[12] = 0xB7;
    Play_PES(frame, 13);
  }

  delete frame;
  return true;
}

//
// Built-in still images
//

bool cXinelibThread::BlankDisplay(void)
{
  TRACEF("cXinelibThread::BlankDisplay");

  extern unsigned char v_mpg_black[];     // black_720x576.c
  extern int v_mpg_black_length;

  bool r = Play_Mpeg2_ES(v_mpg_black, v_mpg_black_length, VIDEO_STREAM);
  for(int i=0; i<5 && !Flush(100); i++)
    ;
  return r;
}

bool cXinelibThread::LogoDisplay(void)
{
  TRACEF("cXinelibThread::LogoDisplay");

  extern unsigned char v_mpg_vdrlogo[];     // vdrlogo_720x576.c
  extern int v_mpg_vdrlogo_length;

  bool r = Play_Mpeg2_ES(v_mpg_vdrlogo, v_mpg_vdrlogo_length, VIDEO_STREAM);
  for(int i=0; i<5 && !Flush(100); i++)
    ;
  return r;
}

bool cXinelibThread::NoSignalDisplay(void)
{
  TRACEF("cXinelibThread::NoSignalDisplay");

  extern unsigned char v_mpg_nosignal[];     // nosignal_720x576.c
  extern int v_mpg_nosignal_length;

  bool r = Play_Mpeg2_ES(v_mpg_nosignal, v_mpg_nosignal_length, VIDEO_STREAM);
  for(int i=0; i<5 && !Flush(100); i++)
    ;
  return r;
}

//
// Xine Control
//

int cXinelibThread::Xine_Control(const char *cmd, int p1)
{
  char buf[128];
  sprintf(buf, "%s %d", cmd, p1);
  return Xine_Control(buf);
}

int cXinelibThread::Xine_Control(const char *cmd, int64_t p1)
{
#if 0
  return Xine_Control(cString::sprintf("%s %s", cmd, p1));
#endif
  char buf[128];
  sprintf(buf, "%s %lld", cmd, p1);
  return Xine_Control(buf);
}

int cXinelibThread::Xine_Control(const char *cmd, char *p1)
{
  char buf[128];
  sprintf(buf, "%s %s", cmd, p1);
  return Xine_Control(buf);
}

bool cXinelibThread::PlayFile(const char *FileName, int Position, 
			      bool LoopPlay)
{
  TRACEF("cXinelibThread::PlayFile");
  char buf[2048];
  m_bEndOfStreamReached = false;
  sprintf(buf, "PLAYFILE %s %d %s\r\n",
	  LoopPlay ? "Loop" : "", Position, FileName ? FileName : "");
  Xine_Control(buf);

  if(!FileName) {
    m_bPlayingFile = false;
    if(m_FileName)
      free(m_FileName);
  } else { 
    m_FileName = strdup(FileName);
    m_bPlayingFile = true; 
  }

  return !GetStopSignal();
}


//
// Configuration
//

int cXinelibThread::ConfigureOSD(bool prescale_osd, bool unscaled_osd) 
{
  char s[256];
  sprintf(s, "OSDSCALING %d", prescale_osd);
  if(!xc.prescale_osd_downscale)
    strcat(s, " NoDownscale");
  if(unscaled_osd)
    strcat(s, " UnscaledAlways");
  if(xc.unscaled_osd_opaque)
    strcat(s, " UnscaledOpaque");
  if(xc.unscaled_osd_lowresvideo)
    strcat(s, " UnscaledLowRes");

  return Xine_Control(s);
}

int cXinelibThread::ConfigurePostprocessing(char *deinterlace_method, int audio_delay, 
					    int audio_compression, int *audio_equalizer,
					    int audio_surround) 
{
  char tmp[255];
  int r = true;

  if(strcmp(deinterlace_method, "tvtime")) 
    r = ConfigurePostprocessing("tvtime", false, NULL) && r;

  r = Xine_Control("DEINTERLACE", deinterlace_method) && r;
  r = Xine_Control("AUDIODELAY", audio_delay) && r;
  r = Xine_Control("AUDIOCOMPRESSION", audio_compression) && r;
  r = Xine_Control("AUDIOSURROUND", audio_compression) && r;
  sprintf(tmp,"EQUALIZER %d %d %d %d %d %d %d %d %d %d",
	  audio_equalizer[0],audio_equalizer[1],
	  audio_equalizer[2],audio_equalizer[3],
	  audio_equalizer[4],audio_equalizer[5],
	  audio_equalizer[6],audio_equalizer[7],
	  audio_equalizer[8],audio_equalizer[9]);
  r = Xine_Control(tmp) && r;

  if(m_bNoVideo && strcmp(xc.audio_visualization, "none")) {
    //fe->post_open(fe, xc.audio_visualization, NULL);
    r = ConfigurePostprocessing(xc.audio_visualization, true, NULL) && r;
  } else {
    //fe->post_close(fe, NULL, 0);
    r = ConfigurePostprocessing("AudioVisualization", false, NULL) && r;
  }

  if(!strcmp(deinterlace_method, "tvtime")) 
    r = ConfigurePostprocessing("tvtime", true, xc.deinterlace_opts) && r;

  return r;
}

int cXinelibThread::ConfigurePostprocessing(char *name, bool on, char *args)
{
  char tmp[1024];  
  if(!name) name = "0";
  if(!args) args = "";
  if(on) {
    sprintf(tmp, "POST %s On %s", name, args);
    return Xine_Control(tmp);
  } else {
    // 0 - audio vis.
    // 1 - audio post
    // 2 - video post
    //return fe->post_close(fe, name, -1);
    sprintf(tmp, "POST %s Off", name);
    return Xine_Control(tmp);
  }
  return -1;
}

int cXinelibThread::ConfigureVideo(int hue, int saturation, 
				   int brightness, int contrast)
{
  char cmd[128];
  sprintf(cmd, "VIDEO_PROPERTIES %d %d %d %d", 
	  hue, saturation, brightness, contrast);
  return Xine_Control(cmd);
}

//
// Playback files
//

bool cXinelibThread::EndOfStreamReached(void) 
{
  LOCK_THREAD;
  bool r = m_bEndOfStreamReached;
  /*m_bEndOfStreamReached = false;*/
  return r;
}


