/*
 * txtsubs.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */

//#define TRACE(x...) isyslog(x)
#define TRACE(x...) 

// --- cSubLine --------------------------------------------------------------

#include <vdr/tools.h>

class cSubLine : public cListObject {
  public:
    int64_t Start;
    int64_t Stop;
    cString Text;

    cSubLine(int64_t _Start, int64_t _Stop, char *_Text) : 
      Start(_Start), Stop(_Stop), Text(_Text) 
      {};
};

// --- cTxtSubsContainer -----------------------------------------------------

class cTxtSubsContainer : public cList<cSubLine> {
  private:
    cSubLine *Iter;

  protected:
    virtual void Parse(const char *line) = 0;

  public:
    cTxtSubsContainer() : Iter(NULL) {};
    bool Read(const char *file)
    {
      FILE *fp = fopen(file, "rt");
      if(!fp)
	return false;
      cReadLine r;
      char *line;
      while(NULL!=(line = r.Read(fp))) {
	TRACE("[txtsubs] %s", line);
	Parse(line);
      }
      isyslog("[txtsubs] %d subtitles loaded", Count());
      return true;
    }
    cSubLine *GetNext(int64_t now)
    {
      if(!Iter)
	Iter = First();
      if(!Iter)
	return NULL; /* empty list ... */
      while(now < Iter->Start && Iter->Prev())
	Iter = Prev(Iter);
      while(now >= Iter->Start && Iter->Next())
	Iter = Next(Iter);
      return Iter;
    }
};

// --- cTxtSubs_SubFile -------------------------------------------------------

class cTxtSubs_SubFile : public cTxtSubsContainer
{
  private:
    bool FirstLine;
    int  FrameRate;  /* 1000 * fps */

  protected:
    virtual void Parse(const char *line)
    {      
      if(FirstLine) {
	int a, b;
	FirstLine = false;
	if(2 == sscanf(line, "{1}{1}%d.%d", &a, &b)) {
	  FrameRate = 1000*a+b;
	  isyslog("[txtsubs] Subtitle frame rate: %d.%03d", a, b);
	  return;
	} else {
	  FrameRate = 25000;
	}
      } 
      if(!FirstLine) {      
	if(*line != '{')
	  return;
	char buf[1024];
	int s, e;
	if(3 == sscanf(line, "{%d}{%d}%[^\r\n]", &s, &e, buf)) {
	  int64_t start = s, end = e;
	  char *pt;
	  while(NULL != (pt=strchr(buf, '|')))
	    *pt = '\n';
	  // input values are frames from beginning

	  // 25 fps --> ms/frame
	  //start = 1000LL * start / 25LL;
	  //end   = 1000LL * end   / 25LL;

	  // 23.976 fps --> ms/frame
	  //start = 1000LL*1000LL * start / 23976LL;
	  //end   = 1000LL*1000LL * end   / 23976LL;
	  
	  // frame rate should be given in file
	  start = 1000LL*1000LL * start / (int64_t)(FrameRate);
	  end   = 1000LL*1000LL * end   / (int64_t)(FrameRate);
	  
	  Add(new cSubLine(start, end, buf));
	}
      }
    }

  public:
    cTxtSubs_SubFile() : cTxtSubsContainer(), FirstLine(true) {};
};

// --- cTxtSubsDecoder -------------------------------------------------------

#include <vdr/thread.h>
#include <vdr/device.h>
#include <vdr/osd.h>
#include <vdr/recording.h>

#include <vdr/osdcontroller.h> // vdr-subtitles plugin non-interactive OSD patch
using namespace NonInteractiveOsdPatch;


class cTxtSubsDecoder : public cThread, public cOsdListener 
{
  private:
    cTxtSubsContainer *Subs;
    cOsd *m_Osd;
    char *RecordingName; 
    const char *CurrentText;
    bool Showing;
    bool HasOsd;

  protected:

    virtual void Show(void) 
    {
      LOCK_THREAD;
      HasOsd = true;
      Draw();
    }

    virtual void Hide(void)
    {
      LOCK_THREAD;
      HasOsd = false;
      if(m_Osd) {
	delete m_Osd;
	m_Osd = NULL;
      }
    }

    void Draw(void) 
    {
      LOCK_THREAD;

      if(!CurrentText || !HasOsd)
	return;

      if(!m_Osd) {
	m_Osd = cOsdProvider::NewOsd( 0, 400, true );

	if(!m_Osd) {
	  dsyslog("TXT Subs: No OSD - subtitles not shown\n");
	  return;
	}
      }

      tArea area = {0, 0, 710, 200, 2};
      if(m_Osd->CanHandleAreas(&area, 1) != oeOk) {
	dsyslog("TXT Subs: OSD can't handle areas - subtitles not shown\n");
	return;
      }
      m_Osd->SetAreas(&area, 1);

      m_Osd->DrawRectangle(0, 0, 710, 100, clrTransparent);

      char *t0 = strdup(CurrentText), *t = t0, *n;
      int line = 0;
      do {
	n = strchr(t, '\n');
	if(n) *n++ = 0;
	m_Osd->DrawText(100, 10+line*40, t, clrWhite, clrBlack, 
			cFont::GetFont(fontOsd), 0, 0, taCenter);
	line++;
      } while(NULL!=(t=n));
      free(t0);

      m_Osd->Flush();
      Showing = true;
    }

    void Clear(void)
    {
      LOCK_THREAD;
      if(m_Osd) {
	delete m_Osd;
	m_Osd = NULL;
      }
      Showing = false;
      if(HasOsd)
	m_Osd = cOsdProvider::NewOsd( 0, 400, true );
    }

    virtual void Action()
    {
      cCondWait::SleepMs(1000);

      int64_t TimeBase = cDevice::PrimaryDevice()->GetSTC() / 90ULL;
      TimeBase -= 1000ULL;
      cSubLine *CurrentLine = Subs->GetNext(0ULL);;

      {
	cResumeFile res(RecordingName);
	int r = res.Read();
	if(r<0) r = 0;
	/* frames from beginning */
	isyslog("[txtsubs] replay starts at frame %d", r);
	TimeBase -= r * 1000 / 25;
      }

      while(Running()) {
	int64_t Now = cDevice::PrimaryDevice()->GetSTC() / 90ULL;
	if(TimeBase < 0)
	  TimeBase = Now;
	int64_t FromStart = (0x1ffffffffULL + Now - TimeBase) & 0x1ffffffffULL;

	if(CurrentLine && 
	   CurrentLine->Start <= FromStart && 
	   CurrentLine->Stop > FromStart) {
	  LOCK_THREAD;
	  if(!Showing) {
	    TRACE("[txtsubs] %s", *(CurrentLine->Text));
            CurrentText = CurrentLine->Text;
	    Draw();
	  }
	} else {
	  {
	    LOCK_THREAD;
	    if(Showing) {
	      Clear();
	      TRACE("[txtsubs] HIDE");
	    }
	    CurrentText = NULL;
	  }

	  CurrentLine = Subs->GetNext(FromStart);
	  if(CurrentLine && (CurrentLine->Start >= (FromStart + 1100LL)))
	    cCondWait::SleepMs(1000);

	  TRACE("[txtsubs] now=%d next=%d..%d", (int)FromStart,
		CurrentLine ? (int)CurrentLine->Start : -1, 
		CurrentLine ? (int)CurrentLine->Stop : -1);
	}

	cCondWait::SleepMs(100);
      }

      Clear();
    }

  public:
    cTxtSubsDecoder(cTxtSubsContainer *_Subs, const char *_RecordingName) 
    {
      m_Osd = NULL; 
      CurrentText = NULL;
      Subs = _Subs; 
      RecordingName = strdup(_RecordingName);
      Showing = false;
      HasOsd = false;

      Start();

      OsdController.Subscribe(950, this);
    }

    ~cTxtSubsDecoder()
    {
      OsdController.Unsubscribe(this);
      Cancel(3);
      Clear();
      free(RecordingName);
    };
};

// --- cTxtSubsMonitor -------------------------------------------------------

#include <vdr/status.h>
#include <vdr/recording.h>

class cTxtSubsMonitor : public cStatus
{
    cTxtSubsDecoder *Decoder;

  public:
    cTxtSubsMonitor() : Decoder(NULL) {};
    ~cTxtSubsMonitor() { if(Decoder) delete Decoder; }

#if VDRVERSNUM < 10338
    virtual void Replaying(const cControl *Control, const char *Name)
    {
      bool On = (Name!=NULL && Control != NULL);
      char *FileName = NULL;
#else
    virtual void Replaying(const cControl *Control, const char *Name, 
			   const char *FileName, bool On)
    {
#endif
      if(!On) {
	if(Decoder)
	  delete Decoder;
	Decoder = NULL;

      } else {	
	cRecording *r = Recordings.First();
	while(r && strcmp(r->Name(), Name))
	  r = Recordings.Next(r);

	if(r) {
	  cReadDir d(r->FileName());
	  struct dirent *e;

	  dsyslog("[txtsubs] Searching subtitles from %s", r->FileName());

	  while ((e = d.Next()) != NULL) {
	    char *pos = strrchr(e->d_name,'.');
	    
	    if(pos && !strcasecmp(pos,".sub")) {
	      char *buffer;
	      asprintf(&buffer, "%s/%s", r->FileName(), e->d_name);
	      dsyslog("[txtsubs] %s", buffer);
	      cTxtSubsContainer *c = new cTxtSubs_SubFile;
	      if(c->Read(buffer)) {
		isyslog("[txtsubs] Using subtitle file %s", e->d_name);
		Decoder = new cTxtSubsDecoder(c, r->FileName());
		free(buffer);
		return;
	      }
	      free(buffer);	    
	    }
#if 0
	    if(pos && !strcasecmp(pos,".srt")) {
	      char *buffer;
	      asprintf(&buffer, "%s/%s", r->FileName(), e->d_name);
	      dsyslog("[txtsubs] %s", buffer);
	      cTxtSubsContainer *c = new cTxtSubs_SrtFile;
	      if(c->Read(buffer)) {
		isyslog("[txtsubs] Using subtitle file %s", e->d_name);
		Decoder = new cTxtSubsDecoder(c, r->FileName());
		free(buffer);
		return;
	      }
	      free(buffer);	    
	    }
#endif
	  }
	}
      }
    }
};

// --- cPluginTxtsubs --------------------------------------------------------

#include <vdr/plugin.h>

static const char *VERSION        = "0.1.1";
static const char *DESCRIPTION    = "Subtitle viewer for .sub subtitles";
static const char *MAINMENUENTRY  = NULL;//"Txtsubs";

class cPluginTxtsubs : public cPlugin {
private:
  // Add any member variables or functions you may need here.
  cTxtSubsMonitor *Monitor;

public:
  cPluginTxtsubs(void);
  virtual ~cPluginTxtsubs();
  virtual const char *Version(void) { return VERSION; }
  virtual const char *Description(void) { return DESCRIPTION; }
  virtual const char *CommandLineHelp(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Initialize(void);
  virtual bool Start(void);
  virtual void Stop(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
  virtual cOsdObject *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
  virtual bool Service(const char *Id, void *Data = NULL);
  virtual const char **SVDRPHelpPages(void);
  virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode);
  };

cPluginTxtsubs::cPluginTxtsubs(void)
{
  // Initialize any member variables here.
  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
  Monitor = NULL;
}

cPluginTxtsubs::~cPluginTxtsubs()
{
  // Clean up after yourself!
  if(Monitor)
    delete Monitor;
  Monitor = NULL;
}

const char *cPluginTxtsubs::CommandLineHelp(void)
{
  // Return a string that describes all known command line options.
  return NULL;
}

bool cPluginTxtsubs::ProcessArgs(int argc, char *argv[])
{
  // Implement command line argument processing here if applicable.
  return true;
}

bool cPluginTxtsubs::Initialize(void)
{
  // Initialize any background activities the plugin shall perform.
  return true;
}

bool cPluginTxtsubs::Start(void)
{
  // Start any background activities the plugin shall perform.
  if(!Monitor)
    Monitor = new cTxtSubsMonitor;
  return true;
}

void cPluginTxtsubs::Stop(void)
{
  // Stop any background activities the plugin shall perform.
  if(Monitor)
    delete Monitor;
  Monitor = NULL;
}

void cPluginTxtsubs::Housekeeping(void)
{
  // Perform any cleanup or other regular tasks.
}

cOsdObject *cPluginTxtsubs::MainMenuAction(void)
{
  // Perform the action when selected from the main VDR menu.
  return NULL;
}

cMenuSetupPage *cPluginTxtsubs::SetupMenu(void)
{
  // Return a setup menu in case the plugin supports one.
  return NULL;
}

bool cPluginTxtsubs::SetupParse(const char *Name, const char *Value)
{
  // Parse your own setup parameters and store their values.
  return false;
}

bool cPluginTxtsubs::Service(const char *Id, void *Data)
{
  // Handle custom service requests from other plugins
  return false;
}

const char **cPluginTxtsubs::SVDRPHelpPages(void)
{
  // Return help text for SVDRP commands this plugin implements
  return NULL;
}

cString cPluginTxtsubs::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
{
  // Process SVDRP commands this plugin implements
  return NULL;
}

VDRPLUGINCREATOR(cPluginTxtsubs); // Don't touch this!
