/*
 * 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.35 2005/10/26 19:04:53 cm Exp $
 */

#include <limits.h>
#include <vdr/status.h>
#include "daap_config.h"
#include "daap_database.h"
#include "audio_player.h"
#include "audio_encap.h"

extern cDaapConfiguration oConfig;

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

cAudioPlayer::cAudioPlayer(cAudioPlaylist *playlist) : cPlayer(oConfig.isAudioOnly() ? pmAudioOnly : pmAudioOnlyBlack), cThread("DAAP Player") {
  m_playMode = pmNone;
  mpo_readFrame = NULL;
  mpo_playFrame = NULL;
  mpo_decoder = NULL;
  m_curFrame = m_maxFrames = INT_MAX;

  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;

#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()) {
      if (o_preloader.isEmpty())
        o_preloader.SetItem(mpo_playlist->Current());

      if (o_preloader.isReady()) {
        const cMediaDatabaseBlob *po_blob = o_preloader.GetData();

        if (NULL != po_blob) {
          mpo_decoder = oDecoders.Get(mpo_playlist->Current()->GetFormat(), po_blob);

          oEncapsulator.SetPesParameters(0xBD, 0x8700);
          oEncapsulator.SetPcmParameters(mpo_playlist->Current()->GetChannels(),
                                           mpo_playlist->Current()->GetSampleRate());
#ifdef DEBUG
          printf("%.16s (%d) new decoder\n", __FILE__, __LINE__);
#endif
        } else
          skipTrack = true;

        o_preloader.Clear();
        if (mpo_playlist->GetCurrent()+1 < mpo_playlist->Count())
          o_preloader.SetItem(mpo_playlist->Item(mpo_playlist->GetCurrent()+1));
      } else
        cCondWait::SleepMs(100);
    }

    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) {
          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 data
          if (m_curFrame > mpo_playFrame->Index())
            m_maxFrames = MSECONDSTOFRAMES(mpo_playlist->Current()->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 the 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);
  o_preloader.Clear();
  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;
  Total = m_maxFrames;

  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;
  o_preloader.Clear();
  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 (FRAMESTOSECONDS(m_curFrame) > oConfig.GetSkipBackMargin() &&
      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();
}

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

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

cAudioControl::~cAudioControl() {
  cStatus::MsgReplaying(this, NULL, NULL, false);
  free(mp_replayDescription);

  DELETENULL(player);

  delete mpo_mnuReplay;
  delete mpo_mnuPlaylist;
}

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_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();

    int index = mpo_playlist->Database()->FindIndexById(m_curSongId);
    cDaapDatabaseItem *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;
  }

  if (NULL == p_artist)
    p_artist = tr("Artist");
  if (NULL == p_title)
    p_title = tr("Title");

  if (0 == number)
    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);

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

    mp_replayDescription = p_str;
    p_str = NULL;

    cStatus::MsgReplaying(this, mp_replayDescription, NULL, true);
  } 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_artist = tr("Artist");
      const char *p_title = tr("Title");
      int index = mpo_playlist->Database()->FindIndexById(mpo_playlist->Item(i)->GetSongId());
      cDaapDatabaseItem *p_dbi = mpo_playlist->Database()->Item(index);

      if (NULL != p_dbi) {
        if (NULL != p_dbi->GetSongArtist())
          p_artist = p_dbi->GetSongArtist();

        if (NULL != p_dbi->GetName())
          p_title = p_dbi->GetName();
      }

      asprintf(&p_str, "%d\t %s\t %s", i+1, p_artist, p_title);
      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 kGreen:
      player->SkipSeconds(-oConfig.GetSkipLength());
      break;
    case kYellow:
      player->SkipSeconds(oConfig.GetSkipLength());
      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;
}
