/*
 * cdda_player.c: The audio player and control interface
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: cdda_player.c,v 1.1.1.5 2005/06/27 13:34:02 cm Exp $
 */

#include <vdr/status.h>
#include "cdda_player.h"

extern cCddaConfiguration oConfig;
extern cCddaDevice *po_Device;

// --- cCddaPlayer ------------------------------------------------------------

cCddaPlayer::cCddaPlayer(cCddaPlaylist *playlist) : cPlayer(oConfig.isAudioOnly() ? pmAudioOnly : pmAudioOnlyBlack), cThread("CDDA Player") {
  currentFrame = 0;
  playMode = pmNone;
  timeMode = dtTrack;
  running = false;
  active = true;
  po_readFrame = NULL;
  po_playFrame = NULL;

  po_playlist = playlist;
  po_playlist->RefIncr();

  po_ringBufferFrame = new cRingBufferFrame(PLAYERBUFSIZE);

  cDevice::PrimaryDevice()->SetCurrentAudioTrack(ttAudio);
  po_Device->RefIncr();
  po_tracks = po_Device->Disc()->Tracks();
}

cCddaPlayer::~cCddaPlayer() {
  Detach();

  po_playlist->RefDecr();
  if (0 == po_playlist->RefCount()) {
    po_playlist->Clear();
    DELETENULL(po_playlist);
  }

  po_Device->RefDecr();
  if (0 == po_Device->RefCount()) {
    po_Device->Close();
    DELETENULL(po_Device);
  }
  delete po_ringBufferFrame;
}

void cCddaPlayer::Action() {
  unsigned char *b = NULL;
  int bc = 0, l = 0;
  long readPosition = 0;
  cEncapsulator oEncapsulator;
  cDataMangler oMangler;

  oEncapsulator.SetPesParameters(0xBD, 0x8700);
  // Audio CD with more or less than two channels ?
  oEncapsulator.SetPcmParameters(2, 44100);

  running = true;
  while (running && (NULL != po_playlist->Next() || 0 != po_ringBufferFrame->Available() || !DeviceFlush(100) || CDDA_EOF != readPosition)) {
    cPoller oPoller;
    if (DevicePoll(oPoller, 100)) {
      LOCK_THREAD;

      if (NULL == po_readFrame) {
        if (po_tracks->Item(po_playlist->GetCurrent()->GetKey())->isAudio())
          b = po_tracks->Item(po_playlist->GetCurrent()->GetKey())->Read(l, readPosition);

        if (NULL == b) {
          if (NULL != po_playlist->Next(NULL, true))
            po_tracks->Item(po_playlist->GetCurrent()->GetKey())->Seek(0, CDDA_SEEK_SET);
        } else {
          oMangler.Swap16(b, l);
          bc = oEncapsulator.Data((uint8_t *)b, l);
          bc = oEncapsulator.ToPcm((uint8_t **)&b);
          bc = oEncapsulator.ToPes((uint8_t **)&b);

          po_readFrame = new cFrame(b, bc, ftUnknown, POSITIONTOFRAMES(readPosition));
          b = NULL;
          bc = 0;
        }
      }

      if (NULL != po_readFrame) {
        if (po_ringBufferFrame->Put(po_readFrame))
          po_readFrame = NULL;
      }

      if (NULL == po_playFrame) {
        po_playFrame = po_ringBufferFrame->Get();
        b = NULL;
        bc = 0;
      }

      if (NULL != po_playFrame) {
        if (!b) {
          b = po_playFrame->Data();
          bc = po_playFrame->Count();
        }
        if (b) {
          int i = 0;
          i = PlayPes(b, bc, false);
          if (0 < i) {
            b += i;
            bc -= i;
          }
        }
        if (0 == bc) {
          currentFrame = po_playFrame->Index();
          po_ringBufferFrame->Drop(po_playFrame);
          po_playFrame = NULL;
          b = NULL;
        }
      }
    } // if (DevicePoll ...
    if (pmNone == playMode && currentFrame > 3) // some data needs to be buffered
      SetPlayMode(pmBuffered);

    o_playerMutex.Lock();
      if (pmPaused == playMode)
        WaitPlayMode(pmPlay);
    o_playerMutex.Unlock();
  } // while (running)
  running = active = false;
}

void cCddaPlayer::Activate(bool On) {
  if (On) {
    Start();
    o_playerMutex.Lock();
      if (pmPlay != playMode) {
        WaitPlayMode(pmBuffered);
      }
    o_playerMutex.Unlock();
    Play();
  } else if (active) {
    running = false;
    Cancel(3);
    active = false;
  }
}

void cCddaPlayer::WaitPlayMode(ePlayMode mode) {
  while (playMode != mode) {
    o_playerCond.Wait(o_playerMutex);
  }
}

void cCddaPlayer::SetPlayMode(ePlayMode mode) {
  o_playerMutex.Lock();
    playMode = mode;
    o_playerCond.Broadcast();
  o_playerMutex.Unlock();
}

void cCddaPlayer::SetDisplayTimeMode(eDisplayTimeMode mode) {
  timeMode = mode;
}

bool cCddaPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame) {
  cCddaPlaylistItem *po_item = NULL;

  Current = currentFrame;
  Total = 0;

  switch (timeMode) {
    case dtTrack:
    case dtTrackElapsed:
      Total = POSITIONTOFRAMES(po_tracks->Item(po_playlist->GetCurrent()->GetKey())->GetLength());

      if (dtTrackElapsed == timeMode)
        Current = Total - currentFrame;
      break;
    case dtAll:
    case dtAllElapsed:
      for (po_item = po_playlist->First(); NULL != po_item; po_item = po_playlist->Next(po_item))
        Total += POSITIONTOFRAMES(po_tracks->Item(po_item->GetKey())->GetLength());

      for (po_item = po_playlist->First(); po_item != po_playlist->GetCurrent(); po_item = po_playlist->Next(po_item))
        Current += POSITIONTOFRAMES(po_tracks->Item(po_item->GetKey())->GetLength());

      if (dtAllElapsed == timeMode)
        Current = Total - Current;
      break;
  }

  return Total >= 0;
}

bool cCddaPlayer::GetReplayMode(bool &Play, bool &Forward, int &Speed) {
  Play = (pmPlay == playMode);
  Forward = true;
  Speed = -1;

  return true;
}

void cCddaPlayer::Clear(void) {
  LOCK_THREAD;
  SetPlayMode(pmNone);
  po_ringBufferFrame->Clear();
  po_readFrame = NULL;
  po_playFrame = NULL;
  DeviceClear();
}

void cCddaPlayer::Reset(void) {
  LOCK_THREAD;
  po_tracks->Item(po_playlist->GetCurrent()->GetKey())->Seek(0, CDDA_SEEK_SET);
  Clear();
  currentFrame = 0;
}

void cCddaPlayer::Play(void) {
  SetPlayMode(pmPlay);
  DevicePlay();
}

void cCddaPlayer::Pause(void) {
  if (pmPaused == playMode) {
    Play();
  } else {
    SetPlayMode(pmPaused);
    DeviceFreeze();
  }
}

void cCddaPlayer::Stop(void) {
  // SetPlayMode() must be called after Reset()
  Reset();
  SetPlayMode(pmStopped);
}

int cCddaPlayer::Jump(int index, bool force) {
  if (po_playlist->First()->Index() <= index && index < po_playlist->GetNumTracks() &&
     (index < po_playlist->GetCurrent()->Index() - 1 || currentFrame < (oConfig.GetSkipBackMargin() * 25) ||
     po_playlist->GetCurrent()->Index() < index || force))
    po_playlist->SetCurrent(index);

  Reset();
  o_playerMutex.Lock();
    if (pmPlay != playMode)
      WaitPlayMode(pmBuffered);
  o_playerMutex.Unlock();
  Play();
  return po_playlist->GetCurrent()->Index();
}

int cCddaPlayer::Skip(int offset) {
  return Jump(po_playlist->GetCurrent()->Index() + offset, false);
}

void cCddaPlayer::SkipSeconds(int seconds) {
  {
    LOCK_THREAD;
    po_tracks->Item(po_playlist->GetCurrent()->GetKey())->Seek(seconds * 176400, CDDA_SEEK_CUR);
    Clear();
  }
  o_playerMutex.Lock();
    if (pmPlay != playMode)
      WaitPlayMode(pmBuffered);
  o_playerMutex.Unlock();
  Play();
}

// --- cCddaControl -----------------------------------------------------------

cCddaControl::cCddaControl(cCddaPlaylist *playlist) : cControl(player = new cCddaPlayer(playlist)) {
  infoView = oConfig.GetDisplayInfoView();
  po_display = NULL;

  po_playlist = playlist;
  po_playlist->RefIncr();

  po_Device->RefIncr();

  // p_replayDescription _must_ be initialized !
  p_replayDescription = strdup("");
}

cCddaControl::~cCddaControl() {
  cStatus::MsgReplaying(this, NULL);
  free(p_replayDescription);

  DELETENULL(player);

  delete po_display;

  po_playlist->RefDecr();
  if (0 == po_playlist->RefCount()) {
    po_playlist->Clear();
    DELETENULL(po_playlist);
  }

  po_Device->RefDecr();
  if (0 == po_Device->RefCount()) {
    po_Device->Close();
    DELETENULL(po_Device);
  }
}

void cCddaControl::Hide(void) {
  DELETENULL(po_display);
}

void cCddaControl::ShowJump(int key) {
  bool play, direct;
  int total = 0, current = 0, speed = 0;
  char *p_str = NULL;

  asprintf(&p_str, "%s %d-", tr("Jump to"), key);
  if (0 != strcmp(p_replayDescription, p_str)) {
    free(p_replayDescription);

    p_replayDescription = p_str;
    p_str = NULL;

    cStatus::MsgReplaying(this, p_replayDescription);
  } else
    free(p_str);

  if (NULL != po_display) {
    player->GetIndex(current, total, false);
    player->GetReplayMode(play, direct, speed);
    po_display->SetTitle(p_replayDescription);
    po_display->SetCurrent(IndexToHMSF(current));
    po_display->SetTotal(IndexToHMSF(total));
    po_display->SetProgress(current, total);
    po_display->SetMode(play, direct, speed);
    po_display->Flush();
  }
}

void cCddaControl::ShowProgress(void) {
  bool play, direct;
  int total = 0, current = 0, speed = 0;
  cCddaTrack *po_track = NULL;
  char *p_str = NULL;

  po_track = po_Device->Disc()->Tracks()->Item(po_playlist->GetCurrent()->GetKey());
  if (ivAll == infoView) {
    asprintf(&p_str, "[audiocd] (%d/%d) %s - %s : %s - %s",
                    po_playlist->GetCurrent()->Index()+1,
                    po_playlist->GetNumTracks(),
                    po_Device->Disc()->Cdtext()->GetPerformer() ? po_Device->Disc()->Cdtext()->GetPerformer() : tr("Disc"),
                    po_Device->Disc()->Cdtext()->GetTitle() ? po_Device->Disc()->Cdtext()->GetTitle() : tr("Album"),
                    po_track->Cdtext()->GetPerformer() ? po_track->Cdtext()->GetPerformer() : tr("Artist"),
                    po_track->Cdtext()->GetTitle() ? po_track->Cdtext()->GetTitle() : tr("Track"));
  } else {
    asprintf(&p_str, "[audiocd] (%d/%d) %s - %s",
                    po_playlist->GetCurrent()->Index()+1,
                    po_playlist->GetNumTracks(),
                    po_track->Cdtext()->GetPerformer() ? po_track->Cdtext()->GetPerformer() : tr("Artist"),
                    po_track->Cdtext()->GetTitle() ? po_track->Cdtext()->GetTitle() : tr("Track"));
  }
  po_track = NULL;

  if (0 != strcmp(p_replayDescription, p_str)) {
    free(p_replayDescription);

    p_replayDescription = p_str;
    p_str = NULL;

    cStatus::MsgReplaying(this, p_replayDescription);
  } else
    free(p_str);

  if (NULL != po_display) {
    player->GetIndex(current, total, false);
    player->GetReplayMode(play, direct, speed);
    // Add 10 Bytes to suppress [...] in string
    po_display->SetTitle(p_replayDescription+10);
    po_display->SetCurrent(IndexToHMSF(current));
    po_display->SetTotal(IndexToHMSF(total));
    po_display->SetProgress(current, total);
    po_display->SetMode(play, direct, speed);
    po_display->Flush();
  }
}

eOSState cCddaControl::ProcessKey(eKeys Key) {
  static int track = 0;
  static int ticks = 0;
  eOSState state = osContinue;

  if (! player->isActive()) {
    return osEnd;
  }

  if (ticks)
    ticks++;

  if (5 == ticks) {
    player->Jump(track-1, true);
    track = ticks = 0;
  }

  switch (Key & ~k_Repeat) {
    case kOk:
      if (NULL == po_display)
        po_display = Skins.Current()->DisplayReplay(false);
      else
        Hide();
      break;
    case kPlay:
    case kUp:
      player->Play();
      break;
    case kPause:
    case kDown:
      player->Pause();
      break;
    case kFastRew:
    case kLeft:
      player->Skip(-1);
      break;
    case kFastFwd:
    case kRight:
      player->Skip(1);
      break;
    case kRed:
      switch (player->GetDisplayTimeMode()) {
        case dtTrack:
          player->SetDisplayTimeMode(dtTrackElapsed);
          break;
        case dtTrackElapsed:
          player->SetDisplayTimeMode(dtAll);
          break;
        case dtAll:
          player->SetDisplayTimeMode(dtAllElapsed);
          break;
        case dtAllElapsed:
          player->SetDisplayTimeMode(dtTrack);
          break;
      }
      break;
    case kGreen:
    case kGreen | k_Repeat:
      player->SkipSeconds(-(oConfig.GetSkipSeconds()));
      break;
    case kYellow:
    case kYellow | k_Repeat:
      player->SkipSeconds(oConfig.GetSkipSeconds());
      break;
    case kBlue:
      infoView = ivAll == infoView ? ivTrack : ivAll;
      break;
    case kStop:
    case kBack:
      Hide();
      player->Stop();
      DELETENULL(player);
      state = osEnd;
      break;
    case k0 ... k9:
      if (! ticks)
        ticks++;

      if (! track)
        track = (Key & ~k_Repeat) - k0;
      else {
        track = track*10 + (Key & ~k_Repeat) - k0;
        player->Jump(track-1, true);
        track = ticks = 0;
      }
      break;
    default:
      break;
  }

  if (track)
    ShowJump(track);
  else
    ShowProgress();

  return state;
}
