/*
 * cdda_menu.c: The menu implementations
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: cdda_menu.c,v 1.22 2005/12/22 11:11:14 cm Exp $
 */

#include <vdr/tools.h>
#include "audio_player.h"
#include "cdda_conf.h"
#include "cdda_menu.h"
#include "cdda_cddb.h"

// --- cCddaMenu ---------------------------------------------------------------

cCddaMenu::cCddaMenu() : cOsdMenu(tr("CD Player"), 3, 13, 23, 2),
  m_listType(ltAll), mpo_playlist(NULL), o_device(cCddaDevice::GetInstance()) {

  Initialize();
  Set();

  o_device.Lock();
}

cCddaMenu::~cCddaMenu() {
  delete mpo_playlist;

  o_device.Unlock();
  if (! o_device.isLocked())
    o_device.Close();
}

void cCddaMenu::Initialize(void) {
  Skins.Message(mtStatus, tr("Loading disc information"));

  if (! o_device.isOpen()) {
    o_device.Open(cCddaConfiguration::GetInstance().GetDevice());

    if (o_device.isOpen() && o_device.Disc()->isAudio())
      cCddaCddb::GetInstance().Query(&o_device);
  }

  if (NULL == mpo_playlist && o_device.isOpen() && o_device.Disc()->isAudio())
    mpo_playlist = new cAudioPlaylist(o_device.Disc(), "Instant", 0, true);
}

void cCddaMenu::LoadPlaylist(int title) {
  cAudioTune *po_tune = NULL;

  switch (m_listType) {
    case ltSingle:
      po_tune = new cAudioTune(mpo_playlist, title);
      if (NULL == mpo_playlist->Add(po_tune))
        delete po_tune;
      break;
    case ltAll:
      for (unsigned int i = o_device.Disc()->Tracks()->GetFirst(); i <= o_device.Disc()->Tracks()->GetFirst() + o_device.Disc()->Tracks()->Count() - 1; i++) {
        po_tune = new cAudioTune(mpo_playlist, i);
        if (NULL == mpo_playlist->Add(po_tune))
          delete po_tune;
      }
      break;
    case ltProgram:
    default:
      break;
  }
  mpo_playlist->SetCurrent(mpo_playlist->Contains(title));
}

void cCddaMenu::Set() {
  int current = Current();

  Clear();

  if (o_device.isOpen() && o_device.Disc()->isAudio()) {
    cCddaTracks *po_tracks = o_device.Disc()->Tracks();
    unsigned int last = po_tracks->GetFirst() + po_tracks->Count() - 1;
    for (unsigned int i = po_tracks->GetFirst(); i <= last; i++) {
      char *p_text = NULL;
      const char *p_performer = po_tracks->Item(i)->Tags()->Current()->GetPerformer();
      const char *p_title = po_tracks->Item(i)->Tags()->Current()->GetTitle();
      int sec = DataSizeToSeconds(po_tracks->Item(i)->GetSize()) % 60;
      int min = DataSizeToSeconds(po_tracks->Item(i)->GetSize()) / 60;
      asprintf(&p_text, "%2d\t%s\t %s\t %s\t%2d:%02d",
               i,
               p_performer ? p_performer : tr("Artist"),
               p_title ? p_title : tr("Track"),
               -1 < mpo_playlist->Contains(i) ? "*" : "",
               min,
               sec);
      Add(new cOsdItem(p_text));
      free(p_text);
    }
    SetHelp(tr("Commands"),
            ltAll == m_listType ? tr("All") : (ltSingle == m_listType ? tr("Single") : tr("Program")),
            tr("Eject"),
            tr("Information"));
  } else {
    cOsdItem *po_OsdItem = new cOsdItem(tr("No music cd in your drive"));
    po_OsdItem->SetSelectable(false);
    Add(po_OsdItem);
    SetHelp(NULL, NULL, tr("Refresh"), NULL);
  }

  SetCurrent(Get(current));

  Display();
}

eOSState cCddaMenu::ProcessKey(eKeys Key) {
  bool hadSubMenu = HasSubMenu();
  static unsigned int title = 0;
  static int ticks = 0;

  eOSState state = cOsdMenu::ProcessKey(Key);

  if (! hadSubMenu) {
    int current = Current();
    bool dirty = false;

    if (kNone != Key)
      SetStatus(NULL);

    if (kYellow != Key && o_device.isOpen() && o_device.Disc()->isAudio()) {
      switch (Key & ~k_Repeat) {
        case kOk:
          LoadPlaylist(current+1);
          cControl::Launch(new cAudioControl(mpo_playlist));
          mpo_playlist = NULL; // _must_ be set to NULL

          return osEnd;
          break;
        case kRed:
          return AddSubMenu(new cCddaMenuCommands(mpo_playlist,
                                                  ltProgram == m_listType ? current+1 : -1));
          break;
        case kGreen:
          if (ltAll == m_listType)
            m_listType = ltSingle;
          else if (ltSingle == m_listType)
            m_listType = ltProgram;
          else if (ltProgram == m_listType)
            m_listType = ltAll;

          mpo_playlist->Clear();
          dirty = true;
          state = osContinue;
          break;
        case kBlue:
          return AddSubMenu(new cCddaMenuTrackInfo(tr("Information"),
                                                   o_device.Disc(),
                                                   o_device.Disc()->Tracks()->Item(current+1)));
          break;
        case k0 ... k9:
          if (title * 10 + ((Key &  ~k_Repeat) - k0) <= o_device.Disc()->Tracks()->GetFirst() + o_device.Disc()->Tracks()->Count() - 1) {
            title = title * 10 + ((Key & ~k_Repeat) - k0);
            char *buffer;
            asprintf(&buffer, "%s %d-", tr("Jump to title"), title);
            SetStatus(buffer);
            free(buffer);
            state = osContinue;
          } else {
            LoadPlaylist(title);
            cControl::Launch(new cAudioControl(mpo_playlist));
            mpo_playlist = NULL; // _must_ be set to NULL
            title = ticks = 0;
            return osEnd;
          }
          break;
        default:
          if (0 != title)
            ticks++;

          if (3 == ticks) {
            LoadPlaylist(title);
            cControl::Launch(new cAudioControl(mpo_playlist));
            mpo_playlist = NULL; // _must_ be set to NULL
            title = ticks = 0;
            return osEnd;
          }
          break;
      } // switch (Key ...

      if (o_device.Disc()->Tags()->Current()->isDirty()) {
        o_device.Disc()->Tags()->Current()->SetDirty(false);
        dirty = true;
      }
    } else if (kYellow == Key) {
      if (o_device.isOpen()) {
        // check if device is already opened and nobody is accessing our o_device (e.g. the player)
        o_device.Unlock();
        if (! o_device.isLocked()) {
          DELETENULL(mpo_playlist);
          o_device.Eject();
          o_device.Close();
        } else
          SetStatus(tr("Cannot eject disc while playing"));
        o_device.Lock();
      } else
        Initialize();

      dirty = true;
      state = osContinue;
    }

    if (dirty)
      Set();
  } else if (hadSubMenu && ! HasSubMenu())
    Set();

  return state;
}

// --- cCddaMenuCommands -------------------------------------------------------

cCddaMenuCommands::cCddaMenuCommands(cAudioPlaylist *playlist, int track) : cOsdMenu(tr("Commands")),
  m_track(track), mpo_playlist(playlist) {
  Set();
}

cCddaMenuCommands::~cCddaMenuCommands() {
}

void cCddaMenuCommands::Set() {
  SetHasHotkeys();

  if (0 < m_track) {
    if (-1 == mpo_playlist->Contains(m_track))
      Add(new cOsdItem(hk(tr("Add to playlist")), osUser1));
    else
      Add(new cOsdItem(hk(tr("Remove from playlist")), osUser2));
  }

  Add(new cOsdItem(hk(tr("Next disc/track tag")), osUser3));

  if (cCddaConfiguration::GetInstance().allowCddbCache() &&
      NULL != cCddaConfiguration::GetInstance().GetCddbDir())
    Add(new cOsdItem(hk(tr("Delete current tag")), osUser4));
}

eOSState cCddaMenuCommands::ProcessKey(eKeys Key) {
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (kNone != Key)
    SetStatus(NULL);

  cAudioTune *po_tune = NULL;
  long id = 0;
  switch (state) {
    case osUser1:
      po_tune = new cAudioTune(mpo_playlist, m_track);
      if (NULL == mpo_playlist->Add(po_tune))
        delete po_tune;
      return osBack;
      break;
    case osUser2:
      mpo_playlist->Delete(mpo_playlist->Contains(m_track));
      return osBack;
      break;
    case osUser3:
      if (1 < mpo_playlist->Disc()->Tags()->Count()) {
        mpo_playlist->Disc()->Tags()->Next();
        mpo_playlist->Disc()->Tags()->Current()->SetDirty(true);
        return osBack;
      } else
        SetStatus(tr("Only one disc/track tag available"));
      state = osContinue;
      break;
    case osUser4:
      sscanf(mpo_playlist->Disc()->Tags()->Current()->GetDiscId(), "%08lx", &id);
      if (cCddaCddb::GetInstance().Delete(id, mpo_playlist->Disc()->Tags()->Current()->GetGenre())) {
        int idx = mpo_playlist->Disc()->Tags()->GetCurrent();

        mpo_playlist->Disc()->Tags()->Delete(idx);
        cCddaTracks *po_tracks = mpo_playlist->Disc()->Tracks();
        unsigned int last = po_tracks->GetFirst() + po_tracks->Count() - 1;
        for (unsigned int i = po_tracks->GetFirst(); i <= last; i++)
          po_tracks->Item(i)->Tags()->Delete(idx);

        mpo_playlist->Disc()->Tags()->Current()->SetDirty(true);

        return osBack;
      } else
        SetStatus(tr("Cannot delete current tag"));
      state = osContinue;
      break;
    default:
      break;
  }

  return state;
}

// --- cCddaMenuTrackInfo ------------------------------------------------------

cCddaMenuTrackInfo::cCddaMenuTrackInfo(const char *Title, cCddaDisc *disc, cCddaTrack *track) : cOsdMenu(Title, 15) {
  Set(disc, track);
}

cCddaMenuTrackInfo::~cCddaMenuTrackInfo() {
}

const char *cCddaMenuTrackInfo::ConCatStr(const char *desc, const char *text, bool indent) {
  static char str[81];

  memset(str, 0x00, 81);
  snprintf(str, 81, "%s%s:\t%s", indent ? "  " : "", desc, text ? text : "-");

  return str;
}

void cCddaMenuTrackInfo::Set(cCddaDisc *disc, cCddaTrack *track) {
  cOsdItem *po_item = NULL;

  Add(po_item = new cOsdItem(tr("Disc information")));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Artist"), disc->Tags()->Current()->GetPerformer(), true)));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Album"), disc->Tags()->Current()->GetTitle(), true)));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Disc ID"), disc->Tags()->Current()->GetDiscId(), true)));
  po_item->SetSelectable(false);

  Add(po_item = new cOsdItem(tr("Track information")));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Arranger"), track->Tags()->Current()->GetArranger(), true)));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Composer"), track->Tags()->Current()->GetComposer(), true)));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Songwriter"), track->Tags()->Current()->GetSongwriter(), true)));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Artist"), track->Tags()->Current()->GetPerformer(), true)));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Title"), track->Tags()->Current()->GetTitle(), true)));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Genre"), track->Tags()->Current()->GetGenre(), true)));
  po_item->SetSelectable(false);
  Add(po_item = new cOsdItem(ConCatStr(tr("Message"), track->Tags()->Current()->GetMessage(), true)));
  po_item->SetSelectable(false);

  Display();
}

// --- cCddaSetupMenu ----------------------------------------------------------
cCddaSetupMenu::cCddaSetupMenu() {
  m_newMenuEntry = cCddaConfiguration::GetInstance().allowMenuEntry();
  m_newSkipLength = cCddaConfiguration::GetInstance().GetSkipLength();
  m_newAudioOnly = cCddaConfiguration::GetInstance().isAudioOnly();
  m_newSkipBackMargin = cCddaConfiguration::GetInstance().GetSkipBackMargin();
  m_newAutoReplay = cCddaConfiguration::GetInstance().isAutoReplay();
  m_newCddbPriority = cCddaConfiguration::GetInstance().GetCddbPriority();
  mp_newCddbServerFqdn = cCddaConfiguration::GetInstance().GetCddbServerFqdn();
  m_newCddbServerPort = cCddaConfiguration::GetInstance().GetCddbServerPort();
  m_newCddbCache = cCddaConfiguration::GetInstance().allowCddbCache();
  m_newInfoView = cCddaConfiguration::GetInstance().GetDisplayInfoView();

  m_cddbPrioTxt[0] = tr("off");
  m_cddbPrioTxt[1] = tr("no cd-text");
  m_cddbPrioTxt[2] = tr("cache only");
  m_cddbPrioTxt[3] = tr("always");

  Add(new cMenuEditBoolItem(tr("Mainmenu entry"), &m_newMenuEntry, tr("hide"), tr("show")));
  Add(new cMenuEditBoolItem(tr("Information view"), &m_newInfoView, tr("Track"), tr("All")));
  Add(new cMenuEditIntItem(tr("Skip length (s)"), &m_newSkipLength, 1, 15));
  Add(new cMenuEditIntItem(tr("Skip back margin (s)"), &m_newSkipBackMargin, 1, 15));
  Add(new cMenuEditBoolItem(tr("Background"), &m_newAudioOnly, tr("black"), tr("live")));
  Add(new cMenuEditBoolItem(tr("Autostart replay"), &m_newAutoReplay));
  Add(new cMenuEditStraItem(tr("CDDB Priority"), &m_newCddbPriority, 4, m_cddbPrioTxt));
  Add(new cMenuEditStrItem(tr("CDDB Server FQDN"), mp_newCddbServerFqdn, 32, "abcdefghijklmnopqrstuvxyz.-"));
  Add(new cMenuEditIntItem(tr("CDDB Server Port"), &m_newCddbServerPort, 1, 65535));
  Add(new cMenuEditBoolItem(tr("CDDB Cache"), &m_newCddbCache, tr("off"), tr("on")));
}

void cCddaSetupMenu::Store(void) {
  SetupStore("menuEntry", cCddaConfiguration::GetInstance().SetMenuEntry(m_newMenuEntry));
  SetupStore("skipSeconds", cCddaConfiguration::GetInstance().SetSkipLength(m_newSkipLength));
  SetupStore("skipBackMargin", cCddaConfiguration::GetInstance().SetSkipBackMargin(m_newSkipBackMargin));
  SetupStore("audioOnly", cCddaConfiguration::GetInstance().SetAudioOnly(m_newAudioOnly));
  SetupStore("autoReplay", cCddaConfiguration::GetInstance().SetAutoReplay(m_newAutoReplay));
  SetupStore("cddbPriority", cCddaConfiguration::GetInstance().SetCddbPriority((eCddbPriority)m_newCddbPriority));
  SetupStore("cddbServerFqdn", cCddaConfiguration::GetInstance().SetCddbServerFqdn(mp_newCddbServerFqdn));
  SetupStore("cddbServerPort", cCddaConfiguration::GetInstance().SetCddbServerPort(m_newCddbServerPort));
  SetupStore("cddbCache", cCddaConfiguration::GetInstance().SetCddbCache(m_newCddbCache));
  SetupStore("infoView", cCddaConfiguration::GetInstance().SetDisplayInfoView((eDisplayInformationView)m_newInfoView));
}
