/*
 * audio_player.c: The audio player and control interface
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: audio_player.c,v 1.5 2005/12/23 09:24:48 cm Exp $
 */

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

#if defined(PLUGIN_DAAP)
#  include "daap_config.h"
#  include "daap_database.h"
#elif defined(PLUGIN_CDDA)
#  include "cdda_conf.h"
#endif

#include "audio_player.h"
#include "audio_encap.h"

// --- cAudioPlayer ------------------------------------------------------------

#if defined(PLUGIN_DAAP)
cAudioPlayer::cAudioPlayer(cAudioPlaylist *playlist) : cPlayer(cDaapConfiguration::GetInstance().isAudioOnly() ? pmAudioOnly : pmAudioOnlyBlack), cThread("DAAP Player"),
#elif defined(PLUGIN_CDDA)
cAudioPlayer::cAudioPlayer(cAudioPlaylist *playlist) : cPlayer(cCddaConfiguration::GetInstance().isAudioOnly() ? pmAudioOnly : pmAudioOnlyBlack), cThread("CDDA Player"),
#endif
  m_indexTrack(true), m_curFrame(INT_MAX), m_maxFrames(INT_MAX), m_playMode(pmNone),
  mpo_readFrame(NULL), mpo_playFrame(NULL), mpo_ringBufferFrame(NULL),
  mpo_decoder(NULL), mpo_playlist(playlist) {

  mpo_ringBufferFrame = new cRingBufferFrame(PLAYERBUFSIZE);

  cDevice::PrimaryDevice()->SetCurrentAudioTrack(ttAudio);
}

cAudioPlayer::~cAudioPlayer() {
  Detach();

  delete mpo_ringBufferFrame;
}

void cAudioPlayer::Action() {
  bool skipTrack = false;
  uint8_t *b = NULL, *data = new uint8_t[MAX_FRAME_PAYLOAD];
  int bc = 0;

  cEncapsulator oEncapsulator;
  cAudioDecoders oDecoders;

  oEncapsulator.SetPesParameters(0xBD, 0x8700);

#ifdef DEBUG
  printf("%.16s (%d) AudioPlayer starting\n", __FILE__, __LINE__);
#endif

  if (-1 == mpo_playlist->GetCurrent())
    mpo_playlist->SetCurrent(0);

  mpo_playlist->Lock();

  while (Running() && pmStopped != m_playMode && (-1 != mpo_playlist->GetCurrent() || 0 != mpo_ringBufferFrame->Available() || !DeviceFlush(100))) {
    if (NULL == mpo_decoder && -1 != mpo_playlist->GetCurrent()) {
#ifndef WITHOUT_PRELOADER
      if (mo_preloader.isEmpty())
        mo_preloader.SetItem(mpo_playlist->Current());

      if (mo_preloader.isReady()) {
        const cMediaDatabaseBlob *po_data = mo_preloader.GetData();

        if (NULL != po_data) {
          mpo_decoder = oDecoders.Get(mpo_playlist->Current()->GetFormat(), po_data);
#ifdef DEBUG
          printf("%.16s (%d) new decoder\n", __FILE__, __LINE__);
#endif
        } else
          skipTrack = true;

        mo_preloader.Clear();
        if (mpo_playlist->GetCurrent()+1 < mpo_playlist->Count())
          mo_preloader.SetItem(mpo_playlist->Item(mpo_playlist->GetCurrent()+1));
      } else
        cCondWait::SleepMs(100);
#else // WITHOUT_PRELOADER
#if defined(PLUGIN_DAAP)
      const cMediaDatabaseBlob *po_data = mpo_playlist->Current()->GetData();
#elif defined(PLUGIN_CDDA)
      cCddaTrack *po_data = mpo_playlist->Current()->GetData();
#endif
      if (NULL != po_data)
        mpo_decoder = oDecoders.Get(mpo_playlist->Current()->GetFormat(), po_data);
      else
        skipTrack = true;
#endif // WITHOUT_PRELOADER
    }

    cPoller oPoller;
    if (DevicePoll(oPoller, 100)) {
      LOCK_THREAD;

      if (NULL == mpo_readFrame && NULL != mpo_decoder) {
        int len = 0;
        len = mpo_decoder->Read(data, MAX_FRAME_PAYLOAD);

        if (0 != len) {
          oEncapsulator.SetPcmParameters(mpo_decoder->GetChannels(),
                                         mpo_decoder->GetSampleRate());
          bc = oEncapsulator.Data((uint8_t *)data, len);
          bc = oEncapsulator.ToPcm((uint8_t **)&b);
          bc = oEncapsulator.ToPes((uint8_t **)&b);

          mpo_readFrame = new cFrame(b, bc, ftUnknown, MSecondsToFrames(mpo_decoder->GetCurrentTime()));
          b = NULL;
          bc = 0;
        } else
          skipTrack = true;
      }

      if (NULL != mpo_readFrame) {
        if (mpo_ringBufferFrame->Put(mpo_readFrame))
          mpo_readFrame = NULL;
      }

      if (NULL == mpo_playFrame) {
        mpo_playFrame = mpo_ringBufferFrame->Get();
        b = NULL;
        bc = 0;
      }

      if (NULL != mpo_playFrame) {
        if (!b) {
          b = mpo_playFrame->Data();
          bc = mpo_playFrame->Count();
          // m_curFrame will be bigger than Index(), when we start playing the
          // new song
          if (m_curFrame > mpo_playFrame->Index())
            m_maxFrames = MSecondsToFrames(mpo_decoder->GetTime());
          m_curFrame = mpo_playFrame->Index();
        }
        if (b) {
          int i = 0;
          i = PlayPes(b, bc, false);
          if (0 < i) {
            b += i;
            bc -= i;
          }
        }
        if (0 == bc) {
          mpo_ringBufferFrame->Drop(mpo_playFrame);
          mpo_playFrame = NULL;
          b = NULL;
        }
      }
    } // if (DevicePoll ...

    // skipTrack when we've got the last bytes from our decoder or we cannot
    // get data from the current one
    if (skipTrack) {
      LOCK_THREAD;

      skipTrack = false;

      if (NULL != mpo_decoder && ! mpo_decoder->Active()) {
        DELETENULL(mpo_decoder);
#ifdef DEBUG
        printf("%.16s (%d) delete decoder\n", __FILE__, __LINE__);
#endif
      }

      // we have reached the end of the current track / the current cant be read
      if (NULL == mpo_decoder && -1 != mpo_playlist->GetCurrent()) {
        int next = mpo_playlist->GetCurrent() + 1;

        if (next == mpo_playlist->Count())
          next = -1;
        mpo_playlist->SetCurrent(next);
      }
    }

    if (pmNone == m_playMode && m_curFrame > 12) // buffer some data
      SetPlayMode(pmBuffered);

    WaitPlayMode(pmPaused, false, pmPlay);
  } // while (Running())

  SetPlayMode(pmStopped);

  DELETENULL(mpo_decoder);
#ifndef WITHOUT_PRELOADER
  mo_preloader.Clear();
#endif
  mpo_playlist->Unlock();

  delete[] data;

#ifdef DEBUG
  printf("%.16s (%d) AudioPlayer stopped\n", __FILE__, __LINE__);
#endif
}

void cAudioPlayer::Activate(bool On) {
#ifdef DEBUG
  printf("%.16s (%d) On=%s\n", __FILE__, __LINE__, On ? "true" : "false");
#endif
  if (On) {
    Start();
    WaitPlayMode(pmPlay, true, pmBuffered);
    Play();
  } else if (Active()) {
    Cancel(3);
  }
}

void cAudioPlayer::WaitPlayMode(ePlayMode test, bool negate, ePlayMode wait) {
  mo_playerMutex.Lock();
    // negate == false: test == m_playMode
    // negate == true : test != m_playMode
    if ((test == m_playMode) ^ negate) {
      while (m_playMode != wait && m_playMode != pmStopped) {
        mo_playerCond.Wait(mo_playerMutex);
      }
    }
  mo_playerMutex.Unlock();
}

void cAudioPlayer::SetPlayMode(ePlayMode mode) {
  mo_playerMutex.Lock();
    m_playMode = mode;
    mo_playerCond.Broadcast();
  mo_playerMutex.Unlock();
}

bool cAudioPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame) {
  Current = m_curFrame;

  if (m_indexTrack)
    Total = m_maxFrames;
  else {
    for (int i = 0; i < mpo_playlist->Count(); i++) {
#if defined(PLUGIN_DAAP)
      int index = mpo_playlist->Database()->FindIndex(dfId, mpo_playlist->Item(i)->GetSongId());
      int n = int(MSecondsToFrames(NULL != mpo_playlist->Database()->Item(index) ?
                                   mpo_playlist->Database()->Item(index)->GetSongTime() : 0));
#elif defined(PLUGIN_CDDA)
      int n = int(DataSizeToFrames(mpo_playlist->Disc()->Tracks()->Item(mpo_playlist->Item(i)->GetSongId())->GetSize()));
#endif

      if (i < mpo_playlist->GetCurrent())
        Current += n;

      Total += n;
    }
  }

  return Total >= 0;
}

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

  return true;
}

void cAudioPlayer::Clear(void) {
  LOCK_THREAD;
  SetPlayMode(pmNone);
  mpo_ringBufferFrame->Clear();
  mpo_readFrame = NULL;
  mpo_playFrame = NULL;
  DeviceClear();
}

void cAudioPlayer::Reset(void) {
  LOCK_THREAD;
#ifndef WITHOUT_PRELOADER
  mo_preloader.Clear();
#endif
  DELETENULL(mpo_decoder);
  Clear();
}

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

void cAudioPlayer::Pause(void) {
  if (pmPaused == m_playMode) {
    Play();
  } else {
    SetPlayMode(pmPaused);
    DeviceFreeze();
  }
}

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

void cAudioPlayer::Jump(int number) {
  {
    LOCK_THREAD;

    if (0 <= number && number < mpo_playlist->Count())
      mpo_playlist->SetCurrent(number);

    Reset();
  }

  WaitPlayMode(pmPlay, true, pmBuffered);
  Play();
}

void cAudioPlayer::SkipTrack(int offset) {
  int next = mpo_playlist->GetCurrent() + offset;

#if defined(PLUGIN_DAAP)
  if (FramesToSeconds(m_curFrame) > (unsigned int)cDaapConfiguration::GetInstance().GetSkipBackMargin() &&
#elif defined(PLUGIN_CDDA)
  if (FramesToSeconds(m_curFrame) > (unsigned int)cCddaConfiguration::GetInstance().GetSkipBackMargin() &&
#endif
      next == mpo_playlist->GetCurrent() - 1) {
    {
      LOCK_THREAD;

      mpo_decoder->Seek(0, dsSet);
      Clear();
    }

    WaitPlayMode(pmPlay, true, pmBuffered);
    Play();
  } else
    Jump(next);
}

void cAudioPlayer::SkipSeconds(int seconds) {
  {
    LOCK_THREAD;

    mpo_decoder->Seek(seconds * long(mpo_decoder->GetAvgBitRate()) / 8, dsCurrent);
    Clear();
  }

  WaitPlayMode(pmPlay, true, pmBuffered);
  Play();
}

void cAudioPlayer::ToggleIndex(void) {
  m_indexTrack = !m_indexTrack;
}

// --- cAudioControl -----------------------------------------------------------

cAudioControl::cAudioControl(cAudioPlaylist *playlist) : cControl(player = new cAudioPlayer(playlist)),
  mp_replayDescription(NULL), m_curSongId(-1), mpo_playlist(playlist),
  mpo_mnuReplay(NULL), mpo_mnuPlaylist(NULL) {
  // p_replayDescription _must_ be initialized !
  mp_replayDescription = strdup("");

#if defined(PLUGIN_CDDA)
  cCddaDevice::GetInstance().Lock();
#endif
}

cAudioControl::~cAudioControl() {
#if VDRVERSNUM >= 10338
  cStatus::MsgReplaying(this, "cdda", NULL, false);
#else
  cStatus::MsgReplaying(this, NULL);
#endif
  free(mp_replayDescription);

  delete player;

  delete mpo_mnuReplay;
  delete mpo_mnuPlaylist;

#if defined(PLUGIN_CDDA)
  // _must_ be deleted _after_ player
  delete mpo_playlist;

  cCddaDevice::GetInstance().Unlock();
  if (! cCddaDevice::GetInstance().isLocked())
    cCddaDevice::GetInstance().Close();
#endif
}

void cAudioControl::Hide(void) {
  if (NULL != mpo_mnuReplay)
    DELETENULL(mpo_mnuReplay);

  if (NULL != mpo_mnuPlaylist)
    DELETENULL(mpo_mnuPlaylist);
}

// when called with number != 0, then the "jump to" string will be displayed
void cAudioControl::RefreshProgress(int number) {
  char *p_str = NULL;
  static const char *p_disc = NULL;
  static const char *p_album = NULL;
  static const char *p_artist = NULL;
  static const char *p_title = NULL;

  if (NULL != mpo_playlist->Current() && m_curSongId != mpo_playlist->Current()->GetSongId()) {
    m_curSongId = mpo_playlist->Current()->GetSongId();

#if defined(PLUGIN_DAAP)
    int index = mpo_playlist->Database()->FindIndex(dfId, m_curSongId);
    cMediaDatabaseItem *po_item = NULL;

#ifdef DEBUG
    if (-1 == index)
      printf("%.16s (%d) could not find index, how can this happen?\n", __FILE__, __LINE__);
#endif

    if (NULL != (po_item = mpo_playlist->Database()->Item(index))) {
      p_artist = po_item->GetSongArtist();
      p_title = po_item->GetName();
    } else
      p_artist = p_title = NULL;
#elif defined(PLUGIN_CDDA)
    p_disc = mpo_playlist->Disc()->Tags()->Current()->GetPerformer();
    p_album = mpo_playlist->Disc()->Tags()->Current()->GetTitle();

    cCddaTrack *po_track = mpo_playlist->Disc()->Tracks()->Item(m_curSongId);

    if (NULL != po_track) {
      p_artist = po_track->Tags()->Current()->GetPerformer();
      p_title = po_track->Tags()->Current()->GetTitle();
    } else
      p_artist = p_title = NULL;
#endif
  }

  if (NULL == p_disc)
    p_disc = tr("Disc");
  if (NULL == p_album)
    p_album = tr("Album");
  if (NULL == p_artist)
    p_artist = tr("Artist");
  if (NULL == p_title)
    p_title = tr("Title");

  if (0 == number) {
#if defined(PLUGIN_DAAP)
    asprintf(&p_str, "[daap] (%d/%d) %s - %s", mpo_playlist->GetCurrent()+1,
                                               mpo_playlist->Count(),
                                               p_artist, p_title);
  } else
    asprintf(&p_str, "[daap] %s %d-", tr("Jump to title"), number);
#elif defined(PLUGIN_CDDA)
    if (ivTrack == cCddaConfiguration::GetInstance().GetDisplayInfoView())
      asprintf(&p_str, "[cdda] (%d/%d) %s - %s", mpo_playlist->GetCurrent()+1,
                                                 mpo_playlist->Count(),
                                                 p_artist, p_title);
    else
      asprintf(&p_str, "[cdda] (%d/%d) %s - %s : %s - %s",
                       mpo_playlist->GetCurrent()+1,
                       mpo_playlist->Count(),
                       p_disc, p_album, p_artist, p_title);
  } else
    asprintf(&p_str, "[cdda] %s %d-", tr("Jump to title"), number);
#endif

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

    mp_replayDescription = p_str;
    p_str = NULL;

#if VDRVERSNUM >= 10338
    cStatus::MsgReplaying(this, "cdda", mp_replayDescription, true);
#else
    cStatus::MsgReplaying(this, mp_replayDescription);
#endif
  } else
    free(p_str);

  if (NULL != mpo_mnuReplay)
    ShowReplay();

  if (NULL != mpo_mnuPlaylist)
    ShowPlaylist(0 != number);
}

void cAudioControl::ShowReplay(void) {
  if (NULL != mpo_mnuReplay) {
    bool play, direct;
    int total = 0, current = 0, speed = 0;

    player->GetIndex(current, total, false);
    player->GetReplayMode(play, direct, speed);
    // Add 7 Bytes to suppress the "[...] " id
    mpo_mnuReplay->SetTitle(mp_replayDescription+7);
    mpo_mnuReplay->SetCurrent(IndexToHMSF(current));
    mpo_mnuReplay->SetTotal(IndexToHMSF(total));
    mpo_mnuReplay->SetProgress(current, total);
    mpo_mnuReplay->SetMode(play, direct, speed);
    mpo_mnuReplay->Flush();
  }
}

void cAudioControl::ShowPlaylist(bool message) {
  if (NULL != mpo_mnuPlaylist) {
    mpo_mnuPlaylist->Clear();

    mpo_mnuPlaylist->SetTitle(tr("Playlist"));
    mpo_mnuPlaylist->SetTabs(4, 15);

    if (message)
      mpo_mnuPlaylist->SetMessage(mtInfo, mp_replayDescription+7);
    else
      mpo_mnuPlaylist->SetMessage(mtInfo, NULL);

    int first = 0 > mpo_playlist->GetCurrent() - 2 ?
                0 : mpo_playlist->GetCurrent() - 2;
    int last = first + mpo_mnuPlaylist->MaxItems() <= mpo_playlist->Count() ?
               first + mpo_mnuPlaylist->MaxItems() : mpo_playlist->Count();

    for (int i = first; i < last; i++) {
      char *p_str = NULL;
      const char *p_disc = NULL;
      const char *p_album = NULL;
      const char *p_artist = NULL;
      const char *p_title = NULL;
#if defined(PLUGIN_DAAP)
      int index = mpo_playlist->Database()->FindIndex(dfId, mpo_playlist->Item(i)->GetSongId());
      cMediaDatabaseItem *p_dbi = mpo_playlist->Database()->Item(index);

      if (NULL != p_dbi) {
          p_artist = p_dbi->GetSongArtist();
          p_title = p_dbi->GetName();
      }
#elif defined(PLUGIN_CDDA)
      p_disc = mpo_playlist->Disc()->Tags()->Current()->GetPerformer();
      p_album = mpo_playlist->Disc()->Tags()->Current()->GetTitle();

      cCddaTrack *po_track = mpo_playlist->Item(i)->GetData();

      if (NULL != po_track) {
          p_artist = po_track->Tags()->Current()->GetPerformer();
          p_title = po_track->Tags()->Current()->GetTitle();
      }
#endif
      if (NULL == p_disc)
        p_disc = tr("Disc");
      if (NULL == p_album)
        p_album = tr("Album");
      if (NULL == p_artist)
        p_artist = tr("Artist");
      if (NULL == p_title)
        p_title = tr("Title");

#if defined(PLUGIN_DAAP)
      asprintf(&p_str, "%d\t %s\t %s", i+1, p_artist, p_title);
#elif defined(PLUGIN_CDDA)
      if (ivTrack == cCddaConfiguration::GetInstance().GetDisplayInfoView())
        asprintf(&p_str, "%d\t %s\t %s", i+1, p_artist, p_title);
      else
        asprintf(&p_str, "%d\t %s %s\t %s %s", i+1, p_disc, p_album, p_artist, p_title);
#endif
      mpo_mnuPlaylist->SetItem(p_str, i - first, i == mpo_playlist->GetCurrent(), true);
      free(p_str);
    }
  }
}

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

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

  switch (Key & ~k_Repeat) {
    case kOk:
      if (NULL == mpo_mnuReplay) {
        Hide();
        mpo_mnuReplay = Skins.Current()->DisplayReplay(false);
      } else
        Hide();
      break;
    case kRed:
      player->ToggleIndex();
      break;
    case kGreen:
#if defined(PLUGIN_DAAP)
      player->SkipSeconds(-cDaapConfiguration::GetInstance().GetSkipLength());
#elif defined(PLUGIN_CDDA)
      player->SkipSeconds(-cCddaConfiguration::GetInstance().GetSkipLength());
#endif
      break;
    case kYellow:
#if defined(PLUGIN_DAAP)
      player->SkipSeconds(cDaapConfiguration::GetInstance().GetSkipLength());
#elif defined(PLUGIN_CDDA)
      player->SkipSeconds(cCddaConfiguration::GetInstance().GetSkipLength());
#endif
      break;
    case kBlue:
      if (NULL == mpo_mnuPlaylist) {
        Hide();
        mpo_mnuPlaylist = Skins.Current()->DisplayMenu();
        ShowPlaylist();
      } else
        Hide();
      break;
    case kUp:
    case kPlay:
      player->Play();
      break;
    case kDown:
    case kPause:
      player->Pause();
      break;
    case kLeft:
      player->SkipTrack(-1);
      break;
    case kRight:
      player->SkipTrack(+1);
      break;
    case kStop:
    case kBack:
      Hide();
      player->Stop();
      DELETENULL(player);
      state = osEnd;
      break;
    case k0...k9:
      if (title * 10 + ((Key &  ~k_Repeat) - k0) <= mpo_playlist->Count())
        title = title * 10 + ((Key & ~k_Repeat) - k0);
      else {
        player->Jump(title-1);
        title = ticks = 0;
      }
      break;
    default:
      if (0 != title)
        ticks++;

      if (3 == ticks) {
        player->Jump(title-1);
        title = ticks = 0;
      }
      break;
  }

  RefreshProgress(title);

  return state;
}
