#include <string>
#include <algorithm>
#include <vdr/remote.h>
#include <vdr/status.h>
#include <vdr/plugin.h>
#include <vdr/cutter.h>
#include <vdr/menu.h>
#include <vdr/interface.h>
#include "replay_control.h"
#include "deluxe_config.h"
#include "menu_convert.h"
#include "i18n.h"

using std::string;

#define MODETIMEOUT       3 // seconds

cDeluxeReplayControl *cDeluxeReplayControl::currentReplayControl = NULL;
char *cDeluxeReplayControl::fileName = NULL;
char *cDeluxeReplayControl::title = NULL;

string GetRecordingChannelID(cRecording* recording);

cDeluxeReplayControl::cDeluxeReplayControl(void)
#ifndef VDRDEVEXTENSIONS
  #ifdef BIGPATCHVERSNUM
  :cDvbPlayerControl(fileName, &marks)
  #else
  :cDvbPlayerControl(fileName)
  #endif
#else
  #ifdef USE_JUMPPLAY
  :cDvbPlayerControl(fileName, &marks)
  #else
  :cDvbPlayerControl(fileName)
  #endif
#endif
{
  currentReplayControl = this;
  displayReplay = NULL;
  visible = modeOnly = shown = displayFrames = false;
  lastCurrent = lastTotal = -1;
  lastPlay = lastForward = false;
  lastTrack = NULL;
  lastSpeed = -2; // an invalid value
  timeoutShow = 0;
  timeSearchActive = false;
  marks.Load(fileName);
  cRecording Recording(fileName);
  cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true);
}

cDeluxeReplayControl::~cDeluxeReplayControl()
{
  Hide();
  cStatus::MsgReplaying(this, NULL, fileName, false);
  Stop();
  if (currentReplayControl == this)
     currentReplayControl = NULL;
  cRemote::CallPlugin("deluxe");
}

void cDeluxeReplayControl::SetRecording(const char *FileName, const char *Title)
{
  free(fileName);
  free(title);
  fileName = FileName ? strdup(FileName) : NULL;
  title = Title ? strdup(Title) : NULL;
}

const char *cDeluxeReplayControl::NowReplaying(void)
{
  return currentReplayControl ? fileName : NULL;
}

const char *cDeluxeReplayControl::LastReplayed(void)
{
  return fileName;
}

void cDeluxeReplayControl::ClearLastReplayed(const char *FileName)
{
  if (fileName && FileName && strcmp(fileName, FileName) == 0) {
     free(fileName);
     fileName = NULL;
     }
}

void cDeluxeReplayControl::ShowTimed(int Seconds)
{
  if (modeOnly)
     Hide();
  if (!visible) {
     shown = ShowProgress(true);
     timeoutShow = (shown && Seconds > 0) ? time(NULL) + Seconds : 0;
     }
}

void cDeluxeReplayControl::Show(void)
{
  ShowTimed();
}

void cDeluxeReplayControl::Hide(void)
{
  if (visible) {
     delete displayReplay;
     displayReplay = NULL;
#if APIVERSNUM >= 10500
     SetNeedsFastResponse(false);
#endif
     visible = false;
     modeOnly = false;
     lastPlay = lastForward = false;
     lastSpeed = -2; // an invalid value
     timeSearchActive = false;
     }
}

void cDeluxeReplayControl::ShowMode(void)
{
  if (visible || Setup.ShowReplayMode && !cOsd::IsOpen()) {
     bool Play, Forward;
     int Speed;
     if (GetReplayMode(Play, Forward, Speed) && (!visible || Play != lastPlay || Forward != lastForward || Speed != lastSpeed)) {
        bool NormalPlay = (Play && Speed == -1);

        if (!visible) {
           if (NormalPlay)
              return; // no need to do indicate ">" unless there was a different mode displayed before
           visible = modeOnly = true;
           displayReplay = Skins.Current()->DisplayReplay(modeOnly);
           }

        if (modeOnly && !timeoutShow && NormalPlay)
           timeoutShow = time(NULL) + MODETIMEOUT;
        displayReplay->SetMode(Play, Forward, Speed);
        lastPlay = Play;
        lastForward = Forward;
        lastSpeed = Speed;
        }
     }
}

bool cDeluxeReplayControl::ShowProgress(bool Initial)
{
  int Current, Total;
  if (GetIndex(Current, Total)) 
  {
      cRecording *Recording = Recordings.GetByName(cDeluxeReplayControl::LastReplayed());
      if (Recording)
      {
	  string trackinfo = Recording->Title();
	  cMark *m = marks.Get(Current);
	  if (m && m->Index() % 2)
	      m = marks.Prev(m);
	  if (!m) m = marks.GetPrev(Current);
	  if (m && m->comment)
	      trackinfo = cMenuConvert::FormatMark(m->comment);
	  SetRecording(Recording->FileName(), trackinfo.c_str());
     }
  }
  else
      return false;

  if (Total > 0) {
     if (!visible) {
        displayReplay = Skins.Current()->DisplayReplay(modeOnly);
        displayReplay->SetMarks(&marks);
#if APIVERSNUM >= 10500
        SetNeedsFastResponse(true);
#endif
        visible = true;
        }
        if (title)
           displayReplay->SetTitle(title);
        lastCurrent = lastTotal = -1;

     if (Total != lastTotal) {
        displayReplay->SetTotal(IndexToHMSF(Total));
        if (!Initial)
           displayReplay->Flush();
        }
     if (Current != lastCurrent || Total != lastTotal) {
        displayReplay->SetProgress(Current, Total);
        if (!Initial)
           displayReplay->Flush();
        displayReplay->SetCurrent(IndexToHMSF(Current, displayFrames));
        displayReplay->Flush();
        lastCurrent = Current;
        }
     lastTotal = Total;
     ShowMode();
     return true;
     }
  return false;
}

void cDeluxeReplayControl::TimeSearchDisplay(void)
{
  char buf[64];
  strcpy(buf, trVDR("Jump: "));
  int len = strlen(buf);
  char h10 = '0' + (timeSearchTime >> 24);
  char h1  = '0' + ((timeSearchTime & 0x00FF0000) >> 16);
  char m10 = '0' + ((timeSearchTime & 0x0000FF00) >> 8);
  char m1  = '0' + (timeSearchTime & 0x000000FF);
  char ch10 = timeSearchPos > 3 ? h10 : '-';
  char ch1  = timeSearchPos > 2 ? h1  : '-';
  char cm10 = timeSearchPos > 1 ? m10 : '-';
  char cm1  = timeSearchPos > 0 ? m1  : '-';
  sprintf(buf + len, "%c%c:%c%c", ch10, ch1, cm10, cm1);
  displayReplay->SetJump(buf);
}

void cDeluxeReplayControl::TimeSearchProcess(eKeys Key)
{
#define STAY_SECONDS_OFF_END 10
  int Seconds = (timeSearchTime >> 24) * 36000 + ((timeSearchTime & 0x00FF0000) >> 16) * 3600 + ((timeSearchTime & 0x0000FF00) >> 8) * 600 + (timeSearchTime & 0x000000FF) * 60;
  int Current = (lastCurrent / FRAMESPERSEC);
  int Total = (lastTotal / FRAMESPERSEC);
  switch (Key) {
    case k0 ... k9:
         if (timeSearchPos < 4) {
            timeSearchTime <<= 8;
            timeSearchTime |= Key - k0;
            timeSearchPos++;
            TimeSearchDisplay();
            }
         break;
    case kFastRew:
    case kLeft:
    case kFastFwd:
    case kRight: {
         int dir = ((Key == kRight || Key == kFastFwd) ? 1 : -1);
         if (dir > 0)
            Seconds = min(Total - Current - STAY_SECONDS_OFF_END, Seconds);
         SkipSeconds(Seconds * dir);
         timeSearchActive = false;
         }
         break;
    case kPlay:
    case kUp:
    case kPause:
    case kDown:
    case kOk:
         Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds);
         Goto(Seconds * FRAMESPERSEC, Key == kDown || Key == kPause || Key == kOk);
         timeSearchActive = false;
         break;
    default:
         timeSearchActive = false;
         break;
    }

  if (!timeSearchActive) {
     if (timeSearchHide)
        Hide();
     else
        displayReplay->SetJump(NULL);
     ShowMode();
     }
}

void cDeluxeReplayControl::TimeSearch(void)
{
  timeSearchTime = timeSearchPos = 0;
  timeSearchHide = false;
  if (modeOnly)
     Hide();
  if (!visible) {
     Show();
     if (visible)
        timeSearchHide = true;
     else
        return;
     }
  timeoutShow = 0;
  TimeSearchDisplay();
  timeSearchActive = true;
}

void cDeluxeReplayControl::MarkToggle(void)
{
  int Current, Total;
  if (GetIndex(Current, Total, true)) {
     cMark *m = marks.Get(Current);
     lastCurrent = -1; // triggers redisplay
     if (m)
        marks.Del(m);
     else {
        marks.Add(Current);
        ShowTimed(2);
        bool Play, Forward;
        int Speed;
        if (GetReplayMode(Play, Forward, Speed) && !Play)
           Goto(Current, true);
        }
     marks.Save();
     }
}

void cDeluxeReplayControl::TrackCut()
{
  int Current, Total;
  if (GetIndex(Current, Total)) 
  {
      cRecording *Recording = Recordings.GetByName(cDeluxeReplayControl::LastReplayed());
      if (Recording)
      {
	  string trackinfo = Recording->Title();
	  cMark *mStart = NULL;
	  cMark *mEnd = NULL;
	  cMark *m = marks.Get(Current);
	  if (m && m->Index() % 2)
	  {
	      mEnd = m;
	      mStart = marks.Prev(m);
	  }
	  if (!m) 
	  {
	      mStart = marks.GetPrev(Current);
	      mEnd = marks.GetNext(Current);
	  }
	  if (mStart && mStart->comment && mEnd && mEnd->comment == NULL)
	  {
	      Hide();
	      if (!Interface->Confirm(tr("Remove track?"))) return;
	      marks.Del(mStart);
	      marks.Del(mEnd);	      
	      marks.Save();
	  }
     }
  }
}

void cDeluxeReplayControl::MarkJump(bool Forward)
{
  if (marks.Count()) {
     int Current, Total;
     if (GetIndex(Current, Total)) {
        cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current);
        if (m) {
           Goto(m->position, true);
           displayFrames = true;
           }
        }
     }
}

void cDeluxeReplayControl::MarkMove(bool Forward, int Frames)
{
  int Current, Total;
  if (GetIndex(Current, Total)) {
     cMark *m = marks.Get(Current);
     if (m) {
        displayFrames = true;
        int p = SkipFrames(Forward ? Frames : -Frames);
        cMark *m2;
        if (Forward) {
           if ((m2 = marks.Next(m)) != NULL && m2->position <= p)
              return;
           }
        else {
           if ((m2 = marks.Prev(m)) != NULL && m2->position >= p)
              return;
           }
        Goto(m->position = p, true);
        marks.Save();
        }
     }
}

void cDeluxeReplayControl::EditTest(void)
{
  int Current, Total;
  if (GetIndex(Current, Total)) {
     cMark *m = marks.Get(Current);
     if (!m)
        m = marks.GetNext(Current);
     if (m) {
        if ((m->Index() & 0x01) != 0)
           m = marks.Next(m);
        if (m) {
           Goto(m->position - SecondsToFrames(3));
           Play();
           }
        }
     }
}

cTrack *cDeluxeReplayControl::GetCurrentTrack(void)
{
    int offset = DeluxeConfig.offset;
    cRecording *Recording = Recordings.GetByName(cDeluxeReplayControl::LastReplayed());
    if (!Recording) return NULL;
    string channelID = GetRecordingChannelID(Recording);

    int Current, Total;
    if (GetIndex(Current, Total)) 
    {
	cIndexFile index(Recording->FileName(), false);
	for(cTrack* track = Tracks.First(); track; track = Tracks.Next(track))
	{
	    if (track->channelID != channelID) continue;
	    int indexIStart = index.GetNextIFrame((track->starttime - Recording->start + offset) * FRAMESPERSEC, true);
	    int indexIStop = index.GetNextIFrame((track->starttime + track->duration - Recording->start + offset) * FRAMESPERSEC, false);
	    if (indexIStart <= Current &&  indexIStop >= Current)
		return track;
	}
	return NULL;
    }
    else 
	return NULL;
}

cOsdObject *cDeluxeReplayControl::GetInfo(void)
{
    cTrack* track = GetCurrentTrack();
    if (!track) return NULL;

    string text;
    text += tr("Artist") + string(": ") + track->artist + string("\n");
    text += tr("Title") + string(": ") + track->title + string("\n");
    char szYear[5] = "";
    sprintf(szYear, "%04d", track->year);
    text += tr("Year") + string(": ") + string(szYear) + string("\n");
    return new cMenuText(tr("Track info"), text.c_str());
}

eOSState cDeluxeReplayControl::ProcessKey(eKeys Key)
{
  if (!Active())
     return osEnd;
  if (Key == k5)
  {
      cRemote::Put(kInfo);
      return osContinue;
  }
  if (visible) {
     if (timeoutShow && time(NULL) > timeoutShow) {
        Hide();
        ShowMode();
        timeoutShow = 0;
        }
     else if (modeOnly)
        ShowMode();
     else
        shown = ShowProgress(!shown) || shown;
     }
  bool DisplayedFrames = displayFrames;
  displayFrames = false;
  if (timeSearchActive && Key != kNone) {
     TimeSearchProcess(Key);
     return osContinue;
     }
  bool DoShowMode = true;
  switch (Key) {
    // Positioning:
#ifdef JUMPONUP
    case kUp: MarkJump(true); break;
#else
    case kUp: 
#endif
    case kPlay:      Play(); break;
    case kPause:
    case kDown:    Pause(); break;
    case kFastRew|k_Release:
    case kLeft|k_Release:
                   if (Setup.MultiSpeedMode) break;
    case kFastRew:
    case kLeft:    Backward(); break;
    case kFastFwd|k_Release:
    case kRight|k_Release:
                   if (Setup.MultiSpeedMode) break;
    case kFastFwd:
    case kRight:   Forward(); break;
    case kRed:     TimeSearch(); break;
    case kGreen|k_Repeat:
    case kGreen:   SkipSeconds(-60); break;
    case kYellow|k_Repeat:
    case kYellow:  SkipSeconds( 60); break;
    case kStop:
    case kBlue:    Hide();
                   Stop();
                   return osEnd;
    default: {
      DoShowMode = false;
      switch (Key) {
        // Editing:
        case kMarkToggle:      MarkToggle(); break;
        case kPrev|k_Repeat:
        case kPrev:
        case kMarkJumpBack|k_Repeat:
        case kMarkJumpBack:    MarkJump(false); break;
        case kNext|k_Repeat:
        case kNext:
        case kMarkJumpForward|k_Repeat:
        case kMarkJumpForward: MarkJump(true); break;
        case kMarkMoveBack|k_Repeat:
        case kMarkMoveBack:    MarkMove(false); break;
        case kMarkMoveForward|k_Repeat:
        case kMarkMoveForward: MarkMove(true); break;
        case k1|k_Repeat:
        case k1: MarkMove(false, 50); break; 
        case k3|k_Repeat:
        case k3: MarkMove(true, 50); break; 
        case kEditCut:         TrackCut(); break;
        case kEditTest:        EditTest(); break;
        default: {
          displayFrames = DisplayedFrames;
          switch (Key) {
            // Menu control:
            case kOk:      if (visible && !modeOnly) {
                              Hide();
                              DoShowMode = true;
                              }
                           else
                              Show();
                           break;
            case kBack:    return osRecordings;
            default:       return osUnknown;
            }
          }
        }
      }
    }
  if (DoShowMode)
     ShowMode();
  return osContinue;
}
