/*
 * daap_menu.c: The menu implementations
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: daap_menu.c,v 1.42 2005/10/28 09:51:09 cm Exp $
 */

#include <strings.h>
#include "daap_config.h"
#include "daap_menu.h"
#include "daap_client.h"
#include "audio_player.h"

extern cDaapConfiguration oConfig;
extern cDaapClient oDAAPClient;

// --- cDaapMenu ---------------------------------------------------------------

cDaapMenu::cDaapMenu() : cOsdMenu(tr("DAAP")) {
  Set();
}

cDaapMenu::~cDaapMenu() {
}

void cDaapMenu::Set(void) {
  int i = 0;

  Clear();

  if (oDAAPClient.Hosts()->Count()) {
    for (i = 0; i < oDAAPClient.Hosts()->Count(); i++)
      Add(new cOsdItem(oDAAPClient.Hosts()->Item(i)->GetShare()));
  } else
    Add(new cOsdItem(tr("No hosts found")));
    
  SetHelp(NULL, NULL, tr("Refresh"), tr("Password"));
  SetStatus(NULL);

  Display();
}

eOSState cDaapMenu::ProcessKey(eKeys Key) {
  bool hadSubMenu = HasSubMenu();
  eOSState state = cOsdMenu::ProcessKey(Key);

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

    switch (Key) {
      case kOk:
        if (NULL != oDAAPClient.Hosts()->Item(Current())) {
          if (oDAAPClient.Hosts()->Item(Current())->isRunning()) {
            if (NULL == oDAAPClient.Hosts()->Item(Current())->Databases())
              SetStatus(tr("Share requires password"));
            else {
              SetStatus(tr("Loading database list"));
              cDaapMenuDatabases *po_menu = new cDaapMenuDatabases(oDAAPClient.Hosts()->Item(Current())->Databases()); 
              SetStatus(NULL);
              return AddSubMenu(po_menu);
            }
          } else
            SetStatus(tr("Host is not reachable"));
        }
        state = osContinue;
        break;
      case kYellow:
        Set();
        state = osContinue;
        break;
      case kBlue:
        if (NULL != oDAAPClient.Hosts()->Item(Current()))
          return AddSubMenu(new cDaapMenuPassword(oDAAPClient.Hosts()->Item(Current())));
        state = osContinue;
        break;
      case kBack:
        state = osBack;
        break;
      default:
        break;
    }
  }

  return state;
}

// --- cDaapMenuPassword -------------------------------------------------------

cDaapMenuPassword::cDaapMenuPassword(cDaapHost *host) : cOsdMenu(tr("Properties"), 12) {
  mpo_host = host;
  
  memset(m_password, 0x00, 129);
  strncpy(m_password, mpo_host->GetPassword(), 128);

  Set();
}

cDaapMenuPassword::~cDaapMenuPassword() {
}

void cDaapMenuPassword::Set(void) {
  Clear();

  Add(new cMenuEditStrItem(tr("Password"), m_password, 128, tr(" abcdefghijklmnopqrstuvwxyz0123456789-.#~")));

  Display();
}

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

  if (state == osUnknown && !HasSubMenu()) {
    switch (Key) {
      case kOk:
        mpo_host->SetPassword(m_password);
        state = osBack;
        break;
      default:
        break;
    }
  }

  return state;
}

// --- cDaapMenuDatabases ------------------------------------------------------

cDaapMenuDatabases::cDaapMenuDatabases(cDaapDatabases *dbs) : cOsdMenu(tr("Databases and Playlists")) {
  mpo_dbs = dbs;
  
  Set();
}

cDaapMenuDatabases::~cDaapMenuDatabases() {
}

void cDaapMenuDatabases::Set(void) {
  char *p_str = NULL;
  cOsdItem *p_osdItem = NULL;

  Clear();

  for (int i = 0; i < mpo_dbs->Count(); i++) {
    asprintf(&p_str, "%s (%s)", tr("Database"),mpo_dbs->Item(i)->GetName()); 
    Add(new cDaapMenuItem(p_str, mpo_dbs->Item(i)));
    free(p_str);

    asprintf(&p_str, "%s ------------------------------------", tr("Playlists"));
    p_osdItem = new cOsdItem(p_str);
    p_osdItem->SetSelectable(false);
    Add(p_osdItem);
    free(p_str);
    for (int j = 0; j < mpo_dbs->Item(i)->Playlists()->Count(); j++) {
      asprintf(&p_str, "%s (%s)", tr("Playlist"), mpo_dbs->Item(i)->Playlists()->Item(j)->GetName());
      Add(new cDaapMenuItem(p_str, mpo_dbs->Item(i)->Playlists()->Item(j)));
      free(p_str);
    }
  }

  if (0 == mpo_dbs->Count())
    Add(new cDaapMenuItem(tr("No databases available")));

  SetCurrentHelp();

  Display();
}

eOSState cDaapMenuDatabases::ProcessKey(eKeys Key) {
  bool hadSubMenu = HasSubMenu();
  cDaapMenuItem *p_mi = NULL;
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (! hadSubMenu && kNone != Key) {
    SetStatus(NULL);

    p_mi = (cDaapMenuItem *)Get(Current());
    switch (Key) {
      case kOk:
        if (NULL != p_mi->GetPlaylist()) {
          return AddSubMenu(new cDaapMenuPlaylist(p_mi->GetPlaylist()));
        } else if (NULL != p_mi->GetDatabase()) {
          SetStatus(tr("Loading database"));
          cDaapMenuDatabase *po_menu = new cDaapMenuDatabase(p_mi->GetDatabase()); 
          SetStatus(NULL);
          return AddSubMenu(po_menu);
        }
        break;
      case kYellow:
        if (NULL != p_mi->GetPlaylist() && p_mi->GetPlaylist()->isWriteable()) {
          if (! p_mi->GetPlaylist()->isLocked())
            p_mi->GetPlaylist()->Clear();
          else
            SetStatus(tr("Cannot clear playlist while playing"));
        }
        break;
      default:
        break;
    }
    SetCurrentHelp();
  }

  return state;
}

void cDaapMenuDatabases::SetCurrentHelp(void) {
  SetHelp(NULL, NULL, NULL, NULL);

  if (0 <= Current() && Current() < Count()) {
    cDaapMenuItem *p_mi = (cDaapMenuItem *)Get(Current());

    if (NULL != p_mi->GetPlaylist() && p_mi->GetPlaylist()->isWriteable())
      SetHelp(NULL, NULL, tr("Clear"), NULL);
  }
}

// --- cDaapMenuDatabase -------------------------------------------------------

cDaapMenuDatabase::cDaapMenuDatabase(cDaapDatabase *db) : cOsdMenu(tr("Database"), 15, 25) {
  mpo_db = db;
  m_viewField = dfArtist;

  Set();
}

cDaapMenuDatabase::~cDaapMenuDatabase() {
  // are we have a copy of the cDaapDatabase master ?
  if (mpo_db->isCopy())
    delete mpo_db;
}

void cDaapMenuDatabase::Set(void) {
  char *p_str = NULL;
  const char *p_key = NULL;

  Clear();

  if (dfArtist == m_viewField)
    p_key = tr("Artist");
  else if (dfAlbum == m_viewField)
    p_key = tr("Album");
  else if (dfGenre == m_viewField)
    p_key = tr("Genre");

  mpo_db->Sort(m_viewField);

  for (int i = 0; i < mpo_db->Count(); i++) {
    const char *ptr_field1 = NULL;
    char field3[6];

    if (NULL == (ptr_field1 = mpo_db->Item(i)->GetValue(m_viewField)))
      ptr_field1 = "-";

    memset(field3, 0x00, 6);
    if (0 == mpo_db->Item(i)->GetSongUserRating())
      memset(field3, '-', 5);
    else
      for (int j = 0; j < mpo_db->Item(i)->GetSongUserRating() / 20 && j < 6; j++)
        field3[j] = '+';

    asprintf(&p_str, "%s\t %s\t %s", ptr_field1,
                                     mpo_db->Item(i)->GetName(),
                                     field3); 
    Add(new cDaapMenuItem(p_str, mpo_db->Item(i)));
    free(p_str);
  }

  if (0 == mpo_db->Count())
    Add(new cOsdItem(tr("No tracks available")));

  SetHelp(tr("Commands"), p_key, tr("Filter"), tr("Details"));

  Display();
}

eOSState cDaapMenuDatabase::ProcessKey(eKeys Key) {
  bool hadSubMenu = HasSubMenu();
  bool dirty = false;
  char k = 'W';
  const char *p_key = NULL;
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (! hadSubMenu && 0 != mpo_db->Count() && kNone != Key) {
    SetStatus(NULL);

    cDaapMenuItem *po_song = (cDaapMenuItem *)Get(Current());
    cDaapDatabaseItem *po_dbi = NULL;
    cDaapPlaylistItem *po_pli = NULL;

    if (NULL != po_song->GetDatabaseItem())
      po_dbi = po_song->GetDatabaseItem();

    if (NULL != po_dbi) {
      switch (Key) {
        case kOk:
          state = osContinue;
          po_pli = new cDaapPlaylistItem(po_dbi->Database()->Playlists()->Item(pInstant),
                                         po_dbi->GetId());

          if (po_dbi->Database()->Playlists()->Item(pInstant)->Clear()) {
            if (NULL == po_dbi->Database()->Playlists()->Item(pInstant)->Add(po_pli))
              SetStatus(tr("Cannot add track(s) to playlist"));
            else {
              cControl::Launch(new cAudioControl(po_dbi->Database()->Playlists()->Item(pInstant)));
              state = osEnd;
            }
          } else
            SetStatus(tr("Cannot start new player while another one is active"));
          break;
        case kRed:
          return AddSubMenu(new cDaapMenuCommands(mpo_db, po_dbi));
          break;
        case kGreen:
          if (dfArtist == m_viewField)
            m_viewField = dfAlbum;
          else if (dfAlbum == m_viewField)
            m_viewField = dfGenre;
          else if (dfGenre == m_viewField)
            m_viewField = dfArtist;
          dirty = true;
          state = osContinue;
          break;
        case kYellow:
          p_key = po_dbi->GetValue(m_viewField);
          return AddSubMenu(new cDaapMenuDatabase(mpo_db->Filter(m_viewField, p_key)));
          break;
        case kBlue:
          return AddSubMenu(new cDaapMenuSong(po_dbi));
          break;
        case k0: // jump to top
          SetCurrent(Get(0));
          Display();
          state = osContinue;
          break;
        case k1: // 0123456789 ; 'W' - 16 ... = '0'
          k -= 17;
        case k2: // ABC ; 'W' - 3 ... = 'A'
          k -= 3;
        case k3: // DEF ; 'W' - 3 ... = 'D'
          k -= 3;
        case k4: // GHI ; 'W' - 3 ... = 'G'
          k -= 3;
        case k5: // JKL ; 'W' - 3 ... = 'J'
          k -= 3;
        case k6: // MNO ; 'W' - 3 ... = 'M'
          k -= 3;
        case k7: // PQRS ; 'W' - 4 ... = 'P'
          k -= 4;
        case k8: // TUV ; 'W' - 3 = 'T'
          k -= 3;
        case k9: // WXYZ
          SetCurrent(Get(mpo_db->FindIndexByChar(m_viewField, k, false)));
          Display();
          state = osContinue;
          break;
        default:
          break;
      }
    }
  }

  if (dirty)
    Set();

  return state;
}

// --- cDaapMenuPlaylist -------------------------------------------------------

cDaapMenuPlaylist::cDaapMenuPlaylist(cDaapPlaylist *pl) : cOsdMenu(tr("Playlist"), 15, 29) {
  mpo_pl = pl;

  Set();
}

cDaapMenuPlaylist::~cDaapMenuPlaylist() {
}

void cDaapMenuPlaylist::Set(void) {
  char *p_str = NULL;
  const char *p_artist = NULL;
  const char *p_name = NULL;
  int current = (-1 != Current() ? Current() : mpo_pl->GetCurrent());
  cDaapDatabaseItem *po_item = NULL;

  Clear();

  if (0 != mpo_pl->Count()) {
    mpo_pl->Database()->Sort(dfId);

    for (int i = 0; i < mpo_pl->Count(); i++) {
      int index = mpo_pl->Database()->FindIndexById(mpo_pl->Item(i)->GetSongId());

      if (-1 != index) {
        po_item = mpo_pl->Database()->Item(index);

        if (NULL != po_item) {
          if (NULL == (p_artist = po_item->GetSongArtist()))
            p_artist = tr("Artist");
          if (NULL == (p_name = po_item->GetName()))
            p_name = tr("Title");

          asprintf(&p_str, "%s\t %s\t %c", p_artist,
                                           p_name,
                                           mpo_pl->GetCurrent() == i ? '#' : ' ');
          Add(new cDaapMenuItem(p_str, po_item));
          free(p_str);
        }
      }
    }

    SetCurrent(Get(-1 != current ? current : 0));

  } else
    Add(new cDaapMenuItem(tr("No tracks available")));

  SetCurrentHelp();
  SetStatus(NULL);

  Display();
}

eOSState cDaapMenuPlaylist::ProcessKey(eKeys Key) {
  bool dirty = false, hadSubMenu = HasSubMenu();
  static bool moving = false;
  static int src = -1;
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (! hadSubMenu && 0 != mpo_pl->Count() && kNone != Key) {
    SetCurrentHelp();

    if (! moving)
      SetStatus(NULL);

    switch (Key) {
      case kOk:
        if (moving && mpo_pl->isWriteable()) {
          if (Current() != src) {
            if (-1 != mpo_pl->Move(Current(), src)) {
              dirty = true;
              SetStatus(NULL);
              state = osContinue;
            } else
              SetStatus(tr("Cannot modify title which is before the current played"));
          }

          src = -1;
          moving = false;
        } else {
          mpo_pl->SetCurrent(Current());
          cControl::Launch(new cAudioControl(mpo_pl));
          state = osEnd;
        }
        break;
      case kRed:
        if (! moving) {
          mpo_pl->SetCurrent(Current());
          cControl::Launch(new cAudioControl(mpo_pl));
          state = osEnd;
        } else
          state = osContinue;
        break;
      case kGreen:
        if (! moving && mpo_pl->isWriteable() &&
            (! mpo_pl->isLocked() || (mpo_pl->isLocked() && Current() > mpo_pl->GetCurrent()))) {
          src = Current();
          moving = true;
          SetStatus(tr("Up/Dn for new location - OK to move"));
        }
        state = osContinue;
        break;
      case kYellow:
        if (! moving && mpo_pl->isWriteable() &&
            (! mpo_pl->isLocked() || (mpo_pl->isLocked() && Current() > mpo_pl->GetCurrent()))) {
          if (-1 != mpo_pl->Delete(Current()))
            dirty = true;
        }
        state = osContinue;
        break;
      case kBlue:
        if (! moving) {
          cDaapMenuItem *po_song = (cDaapMenuItem *)Get(Current());
          if (NULL != po_song->GetDatabaseItem())
            return AddSubMenu(new cDaapMenuSong(po_song->GetDatabaseItem()));
        }
        state = osContinue;
        break;
      case kBack:
        src = -1;
        moving = false;
        SetStatus(NULL);
        state = osBack;
        break;
      default:
        break;
    }
  }

  if (dirty)
    Set();

  return state;
}

void cDaapMenuPlaylist::SetCurrentHelp(void) {
  SetHelp(NULL, NULL, NULL, NULL);

  if (0 <= Current() && Current() < Count()) {
    SetHelp(tr("Play"), NULL, NULL, tr("Details"));

    if (mpo_pl->isWriteable() &&
        (! mpo_pl->isLocked() || (mpo_pl->isLocked() && Current() > mpo_pl->GetCurrent())))
      SetHelp(tr("Play"), tr("Move"), tr("Delete"), tr("Details"));
  }
}

// --- cDaapMenuSong -----------------------------------------------------------

cDaapMenuSong::cDaapMenuSong(cDaapDatabaseItem *item) : cOsdMenu(tr("Song"), 13) {
  Set(item);
}

cDaapMenuSong::~cDaapMenuSong() {
}

void cDaapMenuSong::Set(cDaapDatabaseItem *item) {
  char rating[6];
  Clear();

  AddSongItemStr(tr("Artist"), NULL != item->GetSongArtist() ? item->GetSongArtist() : "-");
  AddSongItemStr(tr("Album"), NULL != item->GetSongAlbum() ? item->GetSongAlbum() : "-");
  AddSongItemStr(tr("Name"), NULL != item->GetName() ? item->GetName() : "-");
  AddSongItemStr(tr("Genre"), NULL != item->GetSongGenre() ? item->GetSongGenre() : "-");
  AddSongItemInt(tr("Track number"), item->GetSongTrackNumber());
  AddSongItemInt(tr("Track count"), item->GetSongTrackCount());

  memset(rating, 0x00, 6);
  if (0 == item->GetSongUserRating())
    memset(rating, '-', 5);
  else
    for (int j = 0; j < item->GetSongUserRating() / 20 && j < 6; j++)
      rating[j] = '+';

  AddSongItemStr(tr("Rating"), rating);
  AddSongItemInt(tr("Year"), item->GetSongYear());
  AddSongItemInt(tr("BPM"), item->GetSongBeatsPerMinute());
  AddSongItemInt(tr("Bit Rate"), item->GetSongBitRate());
  AddSongItemInt(tr("Sample Rate"), item->GetSongSampleRate());
  AddSongItemInt(tr("Discount"), item->GetSongDiscount());
  AddSongItemInt(tr("Disc number"), item->GetSongDiscNumber());
  AddSongItemInt(tr("Size"), item->GetSongSize());

  char *p_time = NULL;
  asprintf(&p_time, "%d:%02d", item->GetSongTime() / 60000, (item->GetSongTime() % 60000) / 1000); 
  AddSongItemStr(tr("Time"), p_time);
  free(p_time);

  AddSongItemStr(tr("Format"), NULL != item->GetSongFormat() ? item->GetSongFormat() : "-");

  Display();
}

void cDaapMenuSong::AddSongItemStr(const char *description, const char *data) {
  char *p_str = NULL;

  if (NULL != description && NULL != data) {
    asprintf(&p_str, "%s:\t%s", description, data);

    Add(new cOsdItem(p_str));

    free(p_str);
  }
}

void cDaapMenuSong::AddSongItemInt(const char *description, int num) {
  char *p_str = NULL;
  
  if (NULL != description) {
    asprintf(&p_str, "%s:\t%d", description, num);

    Add(new cOsdItem(p_str));

    free(p_str);
  }
}

// --- cDaapMenuCommands -------------------------------------------------------

cDaapMenuCommands::cDaapMenuCommands(cDaapDatabase *db, cDaapDatabaseItem *current) : cOsdMenu(tr("Commands")){
  mpo_current = current;
  mpo_db = db;

  Set();
}

cDaapMenuCommands::~cDaapMenuCommands() {
}

void cDaapMenuCommands::Set(void) {
  Clear();

  SetHasHotkeys();

  Add(new cOsdItem(hk(tr("Add track"))));
  Add(new cOsdItem(hk(tr("Add all tracks"))));

  Display();
}

eOSState cDaapMenuCommands::ProcessKey(eKeys Key) {
  eOSState state = cOsdMenu::ProcessKey(Key);
  cDaapPlaylistItem *po_pli = NULL;

  if (osUnknown == state && ! HasSubMenu()) {
    if (kNone != Key)
      SetStatus(NULL);

    switch (Key) {
      case kOk:
        if (Current() == 0) {
          po_pli = new cDaapPlaylistItem(mpo_db->Playlists()->Item(pInstant),
                                         mpo_current->GetId());
          if (NULL == mpo_db->Playlists()->Item(pInstant)->Add(po_pli)) {
            delete po_pli;
            SetStatus(tr("Cannot add track(s) to playlist"));
          }
        } else if (Current() == 1) {
          for (int i = 0; i < mpo_db->Count(); i++) {
            po_pli = new cDaapPlaylistItem(mpo_db->Playlists()->Item(pInstant),
                                           mpo_db->Item(i)->GetId());
            if (NULL == mpo_db->Playlists()->Item(pInstant)->Add(po_pli)) {
              delete po_pli;
              SetStatus(tr("Cannot add track(s) to playlist"));
            }
          }
        }
        state = osBack;
        break;
      default:
        break;
    }
  }

  return state;
}

// --- cDaapMenuItem -----------------------------------------------------------

cDaapMenuItem::cDaapMenuItem(const char *Text, cDaapDatabaseItem *item, eOSState State) : cOsdItem(Text, State),
  m_type(miItem), mpo_dbitem(item) {
}

cDaapMenuItem::cDaapMenuItem(const char *Text, cDaapPlaylist *playlist, eOSState State) : cOsdItem(Text, State),
  m_type(miPlaylist), mpo_pl(playlist) {
}

cDaapMenuItem::cDaapMenuItem(const char *Text, cDaapDatabase *database, eOSState State) : cOsdItem(Text, State),
  m_type(miDatabase), mpo_db(database) {
}

cDaapMenuItem::cDaapMenuItem(const char *Text, eOSState State) : cOsdItem(Text, State),
  m_type(miNone), mpo_object(NULL) {
}

cDaapMenuItem::~cDaapMenuItem() {
}

cDaapDatabaseItem *cDaapMenuItem::GetDatabaseItem(void) const {
  if (miItem == m_type)
    return mpo_dbitem;

  return NULL;
}

cDaapDatabase *cDaapMenuItem::GetDatabase(void) const {
  if (miDatabase == m_type)
    return mpo_db;

  return NULL;
}

cDaapPlaylist *cDaapMenuItem::GetPlaylist(void) const {
  if (miPlaylist == m_type)
    return mpo_pl;

  return NULL;
}

// --- cDaapMenuSetup ----------------------------------------------------------

cDaapMenuSetup::cDaapMenuSetup() {
  m_menuEntry = oConfig.allowMenuEntry();
  m_audioOnly = oConfig.isAudioOnly();
  m_skipBackMargin = oConfig.GetSkipBackMargin();
  m_skipLength = oConfig.GetSkipLength();

  memset(m_instantPlaylistName, 0x00, 21);
  strncpy(m_instantPlaylistName, oConfig.GetInstantPlaylistName(), 20);

  Add(new cMenuEditBoolItem(tr("Mainmenu entry"), &m_menuEntry, tr("hide"), tr("show")));
  Add(new cMenuEditBoolItem(tr("Background"), &m_audioOnly, tr("black"), tr("live")));
  Add(new cMenuEditIntItem(tr("Skip length (s)"), &m_skipLength, 5, 20));
  Add(new cMenuEditIntItem(tr("Skip back margin (s)"), &m_skipBackMargin, 5, 15));
  Add(new cMenuEditStrItem(tr("Instant playlist name"), m_instantPlaylistName, 20, tr(PlaylistNameChars)));
}

void cDaapMenuSetup::Store(void) {
  SetupStore("menuEntry", oConfig.SetMenuEntry(m_menuEntry));
  SetupStore("audioOnly", oConfig.SetAudioOnly(m_audioOnly));
  SetupStore("skipBackMargin", oConfig.SetSkipBackMargin(m_skipBackMargin));
  SetupStore("skipLength", oConfig.SetSkipLength(m_skipLength));
  SetupStore("instantPlaylistName", oConfig.SetInstantPlaylistName(m_instantPlaylistName));
}
