/*
 * MP3/MPlayer plugin to VDR (C++)
 *
 * (C) 2001-2005 Stefan Huelswitt <s.huelswitt@gmx.de>
 *
 * This code is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This code is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 */

#include <stdlib.h>
#include <getopt.h>
#include <strings.h>
#include <typeinfo>

#include <vdr/menuitems.h>
#include <vdr/status.h>
#include <vdr/plugin.h>
#include <vdr/osd.h>
#include <vdr/interface.h>
#include <vdr/skins.h>
#include <vdr/menu.h>

#include "common.h"
#include "setup.h"
#include "setup-mp3.h"
#include "data-mp3.h"
#include "player-mp3.h"
#include "menu.h"
#include "menu-async.h"
#include "decoder.h"
#include "decoder-mp3.h"
#include "i18n.h"
#include "version.h"



#ifdef DEBUG
#include <mad.h>
#endif

#include "symbols/shuffle.xpm"
#include "symbols/loop.xpm"
#include "symbols/copy.xpm"

//cFileSources MP3Sources;

// --- cMenuSetupMP3 --------------------------------------------------------

class cMenuSetupMP3 : public cMenuSetupPage {
private:
  //
  const char *cddb[3], *scan[3], *bgr[3];
  const char *aout[AUDIOOUTMODES];
  int amode, amodes[AUDIOOUTMODES];
  const char *themes[eMP3ThemeMaxNumber];
protected:
  virtual void Store(void);
public:
  cMenuSetupMP3(void);
  };

cMenuSetupMP3::cMenuSetupMP3(void)
{
  static const char allowed[] = { "abcdefghijklmnopqrstuvwxyz0123456789-_" };
  int numModes=0;
  aout[numModes]=tr("DVB"); amodes[numModes]=AUDIOOUTMODE_DVB; numModes++;
#ifdef WITH_OSS
  aout[numModes]=tr("OSS"); amodes[numModes]=AUDIOOUTMODE_OSS; numModes++;
#endif
  amode=0;
  for(int i=0; i<numModes; i++)
    if(amodes[i]==MP3Setup.AudioOutMode) { amode=i; break; }

  themes[eMP3ThemeMoronimoNG]     = ("MoronimoNG");
  themes[eMP3ThemeOpaque]       = ("Opaque");
  themes[eMP3ThemeTranscluent]  = ("Transcluent");
  themes[eMP3ThemeEnigma]       = ("Enigma");
  themes[eMP3ThemeDeepBlue]   	= ("DeepBlue");

  SetSection(tr("MP3"));
  Add(new cMenuEditStraItem(tr("Setup.MP3$Theme"),                 &MP3Setup.osdtheme, eMP3ThemeMaxNumber, themes));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Scroll through playlist"),    &MP3Setup.CanScroll));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Skip with green/yellow Key"), &MP3Setup.CanSkip));
  Add(new cMenuEditIntItem( tr("Setup.MP3$Skip interval"),              &MP3Setup.Skiptime,1,360));
  Add(new cMenuEditIntItem( tr("Setup.MP3$Jump interval (FFW/FREW)"),   &MP3Setup.Jumptime,1,360));
  Add(new cMenuEditStrItem( tr("Setup.MP3$Where to copy tracks"),       MP3Setup.CopyDir,255,allowed));
  Add(new cMenuEditStrItem( tr("Setup.MP3$Where to record streams"),    MP3Setup.RecordDir,255,allowed));
  Add(new cMenuEditIntItem(tr("Setup.MP3$OSD Offset X"),                &MP3Setup.OSDoffsetx,-50 ,50));
  Add(new cMenuEditIntItem(tr("Setup.MP3$OSD Offset Y"),                &MP3Setup.OSDoffsety,-50 ,50));
  Add(new cMenuEditIntItem(tr("Setup.MP3$Display tracks"),              &MP3Setup.Rowcount,4 ,9));
  Add(new cMenuEditStraItem(tr("Setup.MP3$Audio output mode"),     &amode,numModes,aout));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Audio mode"),            &MP3Setup.AudioMode, tr("Round"), tr("Dither")));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Use 48kHz mode only"),   &MP3Setup.Only48kHz));
  Add(new cMenuEditIntItem( tr("Setup.MP3$Display mode"),          &MP3Setup.DisplayMode, 1, 3));
  bgr[0]=tr("Black");
  bgr[1]=tr("Live");
  Add(new cMenuEditStraItem(tr("Setup.MP3$Background mode"),       &MP3Setup.BackgrMode, 2, bgr));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Initial loop mode"),     &MP3Setup.InitLoopMode));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Initial shuffle mode"),  &MP3Setup.InitShuffleMode));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Abort player at end of list"),&MP3Setup.AbortAtEOL));
  scan[0]=tr("disabled");
  scan[1]=tr("ID3 only");
  scan[2]=tr("ID3 & Level");
  Add(new cMenuEditStraItem(tr("Setup.MP3$Background scan"),       &MP3Setup.BgrScan, 3, scan));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Editor display mode"),   &MP3Setup.EditorMode, tr("Filenames"), tr("ID3 names")));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Mainmenu mode"),         &MP3Setup.MenuMode, tr("Playlists"), tr("Browser")));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Keep selection menu"),   &MP3Setup.KeepSelect));
  Add(new cMenuEditBoolItem(tr("Hide mainmenu entry"),             &MP3Setup.HideMainMenu));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Exit stop playback"),    &MP3Setup.ExitClose));
  Add(new cMenuEditIntItem( tr("Setup.MP3$Normalizer level"),      &MP3Setup.TargetLevel, 0, MAX_TARGET_LEVEL));
  Add(new cMenuEditIntItem( tr("Setup.MP3$Limiter level"),         &MP3Setup.LimiterLevel, MIN_LIMITER_LEVEL, 100));
  Add(new cMenuEditBoolItem(tr("Setup.MP3$Use HTTP proxy"),        &MP3Setup.UseProxy));
  Add(new cMenuEditStrItem( tr("Setup.MP3$HTTP proxy host"),       MP3Setup.ProxyHost,MAX_HOSTNAME,allowed));
  Add(new cMenuEditIntItem( tr("Setup.MP3$HTTP proxy port"),       &MP3Setup.ProxyPort,1,65535));
  cddb[0]=tr("disabled");
  cddb[1]=tr("local only");
  cddb[2]=tr("local&remote");
  Add(new cMenuEditStraItem(tr("Setup.MP3$CDDB for CD-Audio"),     &MP3Setup.UseCddb,3,cddb));
  Add(new cMenuEditStrItem( tr("Setup.MP3$CDDB server"),           MP3Setup.CddbHost,MAX_HOSTNAME,allowed));
  Add(new cMenuEditIntItem( tr("Setup.MP3$CDDB port"),             &MP3Setup.CddbPort,1,65535));
}

void cMenuSetupMP3::Store(void)
{
  MP3Setup.AudioOutMode=amodes[amode];

  SetupStore("Theme",            MP3Setup.osdtheme);
  SetupStore("InitLoopMode",     MP3Setup.InitLoopMode   );
  SetupStore("InitShuffleMode",  MP3Setup.InitShuffleMode);
  SetupStore("AudioMode",        MP3Setup.AudioMode      );
  SetupStore("AudioOutMode",     MP3Setup.AudioOutMode   );
  SetupStore("BgrScan",          MP3Setup.BgrScan        );
  SetupStore("EditorMode",       MP3Setup.EditorMode     );
  SetupStore("DisplayMode",      MP3Setup.DisplayMode    );
  SetupStore("BackgrMode",       MP3Setup.BackgrMode     );
  SetupStore("MenuMode",         MP3Setup.MenuMode       );
  SetupStore("TargetLevel",      MP3Setup.TargetLevel    );
  SetupStore("LimiterLevel",     MP3Setup.LimiterLevel   );
  SetupStore("Only48kHz",        MP3Setup.Only48kHz      );
  SetupStore("UseProxy",         MP3Setup.UseProxy       );
  SetupStore("ProxyHost",        MP3Setup.ProxyHost      );
  SetupStore("ProxyPort",        MP3Setup.ProxyPort      );
  SetupStore("UseCddb",          MP3Setup.UseCddb        );
  SetupStore("CddbHost",         MP3Setup.CddbHost       );
  SetupStore("CddbPort",         MP3Setup.CddbPort       );
  SetupStore("AbortAtEOL",       MP3Setup.AbortAtEOL     );
  SetupStore("HideMainMenu",     MP3Setup.HideMainMenu   );
  SetupStore("KeepSelect",       MP3Setup.KeepSelect     );
  SetupStore("ExitClose",        MP3Setup.ExitClose      );
  SetupStore("CanScroll",        MP3Setup.CanScroll   );
  SetupStore("CanSkip",          MP3Setup.CanSkip   );
  SetupStore("Skiptime",         MP3Setup.Skiptime   );
  SetupStore("Jumptime",         MP3Setup.Jumptime   );
  SetupStore("CopyDir",          MP3Setup.CopyDir );
  SetupStore("RecordDir",        MP3Setup.RecordDir );
  SetupStore("OSDoffsetx",       MP3Setup.OSDoffsetx   );
  SetupStore("OSDoffsety",       MP3Setup.OSDoffsety   );
  SetupStore("Rowcount",         MP3Setup.Rowcount   );
}

// --- cAsyncStatus ------------------------------------------------------------

cAsyncStatus asyncStatus;

cAsyncStatus::cAsyncStatus(void)
{
  text=0;
  changed=false;
}

cAsyncStatus::~cAsyncStatus()
{
  free((void *)text);
}

void cAsyncStatus::Set(const char *Text)
{
  Lock();
  free((void *)text);
  text=Text ? strdup(Text) : 0;
  changed=true;
  Unlock();
}

const char *cAsyncStatus::Begin(void)
{
  Lock();
  return text;
}

void cAsyncStatus::Finish(void)
{
  changed=false;
  Unlock();
}

// --- cMP3Control --------------------------------------------------------

#define eDvbColor int
#define MAXROWS 120
#define INLINE

class cMP3Control : public cControl {
private:
  cOsd *osd;
  std::string imagefile;
  const cFont *font;
  int fw, fh;
  //
  cMP3Player *player;
  bool showtrans, visible, statusActive, copyfile, recordstream, pressedOk, flush;
  time_t timeoutShow, greentime, oktime;
  int lastkeytime, num, number, tracknr, oldtracknr;
  bool selecting, selecthide;
  //
  cMP3PlayInfo *lastMode;
  time_t fliptime, listtime;
  int hashlist[MAXROWS];
  int flip, flipint, top, rows, lh;
  int lastIndex, lastTotal, lastTop;
  int framesPerSecond;
  //
  static cBitmap bmShuffle , bmLoop, bmCopy;
  //
  bool jumpactive, jumphide, jumpsecs;
  int jumpmm;
  //
  int clrBackground;
  int clrBackground2;
  int clrNormalText;
  int clrTracklistText;
  int clrListItemActiveBackground;
  int clrListItemActiveText;
  int clrProgress;
  int clrProgressBackground;
  //
  void ShowTimed(int Seconds=0);
  void ShowProgress(bool open=false);
  void ShowStatus(bool force);
  void HideStatus(void);
  void DisplayInfo(const char *s=0);
  void JumpDisplay(void);
  void JumpProcess(eKeys Key);
  void Jump(void);
  void Stop(void);
  void CopyFile(void);
  void StartRecord(void);
  void StopRecord(void);
  void LoadCover(void);
  inline void Flush(void);
public:
  cMP3Control(void);
  virtual ~cMP3Control();
  virtual eOSState ProcessKey(eKeys Key);
  virtual void Show(void) { ShowTimed(); }
  virtual void Hide(void);
  bool Visible(void) { return visible; }
  static bool SetPlayList(cPlayList *plist);
  };

cMP3Control::cMP3Control(void)
:cControl(player=new cMP3Player)
{
  showtrans=visible=selecting=selecthide=jumpactive=jumphide=statusActive=copyfile=recordstream=pressedOk=false;
  timeoutShow=greentime=oktime=0;
  lastkeytime=number=0;
  lastMode=0;
  framesPerSecond=SecondsToFrames(1);
  if(!osd) osd=0;
  font=cFont::GetFont(fontOsd);
  cStatus::MsgReplaying(this,"MP3ng",0,true);
  
  
  clrBackground			= mp3Theme[MP3Setup.osdtheme].clrBackground;
  clrBackground2		= mp3Theme[MP3Setup.osdtheme].clrBackground2;
  clrNormalText			= mp3Theme[MP3Setup.osdtheme].clrNormalText;
  clrTracklistText		= mp3Theme[MP3Setup.osdtheme].clrTracklistText;
  clrListItemActiveBackground	= mp3Theme[MP3Setup.osdtheme].clrListItemActiveBackground;
  clrListItemActiveText		= mp3Theme[MP3Setup.osdtheme].clrListItemActiveText;
  clrProgress			= mp3Theme[MP3Setup.osdtheme].clrProgress;
  clrProgressBackground		= mp3Theme[MP3Setup.osdtheme].clrProgressBackground;
}

cBitmap cMP3Control::bmShuffle(shuffle_xpm);
cBitmap cMP3Control::bmLoop(loop_xpm);
cBitmap cMP3Control::bmCopy(copy_xpm);

cMP3Control::~cMP3Control()
{
  delete lastMode;
  Hide();
  Stop();
}

void cMP3Control::Stop(void)
{
  cStatus::MsgReplaying(this,0,0,false);
  delete player; player=0;
  mgr->Halt();
  mgr->Flush(); //XXX remove later
}

bool cMP3Control::SetPlayList(cPlayList *plist)
{
  bool res;
  cControl *control=cControl::Control();
  // is there a running MP3 player?
  if(control && typeid(*control)==typeid(cMP3Control)) {
    // add songs to running playlist
    mgr->Add(plist);
    res=true;
    }
  else {
    mgr->Flush();
    mgr->Add(plist);
    cControl::Launch(new cMP3Control);
    res=false;
    }
  delete plist;
  return res;
}

void cMP3Control::ShowTimed(int Seconds)
{
//  if(!visible) {
//    ShowProgress(true);
    if(Seconds>0) timeoutShow=time(0)+Seconds;
//    }
}

void cMP3Control::Hide(void)
{
  HideStatus();
  if(visible) {
    delete osd; osd=0;
    needsFastResponse=visible=false;
    }
}

void cMP3Control::ShowStatus(bool force)
{
  if((asyncStatus.Changed() || (force && !statusActive)) && !jumpactive) {
    const char *text=asyncStatus.Begin();
    if(text) {
         if(!statusActive) { osd->SaveRegion( 5*fw, lh + 2*fh, Setup.OSDWidth -7*fw -5*fh -fh/2, lh + 3*fh); }
          osd->DrawText(5*fw , lh + 2*fh, text, clrTracklistText, clrBackground2, font, Setup.OSDWidth - 7*fw -5*fh -fh/2, fh, taCenter);
        osd->Flush();
      statusActive=true;
      }
    else
      HideStatus();
    asyncStatus.Finish();
    }
}


void cMP3Control::HideStatus(void)
{
  if(statusActive) {
      osd->RestoreRegion();
      osd->Flush();
      }
  statusActive=false;
}

void cMP3Control::LoadCover(void)
{

  if (coverpicture < " " )
    printf("mp3ng: DEBUG: no CoverImage");
  else {
	font=cFont::GetFont(fontOsd);

        fw=font->Width(' ');
        fh=font->Height();

#ifdef HAVE_MAGICK
      cBitmap *b = LoadMagick(coverpicture, 4*fh +2, 4*fh -5, 15, true);
#else
#ifdef HAVE_IMLIB2   
      cBitmap *b = LoadImlib(coverpicture,  4*fh  +2, 4*fh -5, 15, true);
#else
//Make no sense
      cBitmap *b = LoadXpm(coverpicture);
#endif
#endif      

      if (b) {
        osd->DrawRectangle(Setup.OSDWidth - 4*fh -4*fw , lh, Setup.OSDWidth -3*fw -1, lh + 4*fh + fh/2 -1, clrBackground2);
	usleep(80000);
        osd->DrawBitmap(Setup.OSDWidth    - 4*fh -3*fw , lh +fw, *b, clrTransparent, clrTransparent, true);
        delete b;
	}
// Transfer to graphtft
//      PropagateImage(coverpicture);

      cPlugin *graphtft=cPluginManager::GetPlugin("graphtft");
      if(graphtft) graphtft->SetupParse("CoverImage", coverpicture);

  }
  CanLoadCover = false; 
}

void cMP3Control::Flush(void)
{
  if(osd) osd->Flush();
}


void cMP3Control::ShowProgress(bool open)
{
  int index, total;
  int x0, x1, y0, y1, y2, y5, y4, y3;

  if(player->GetIndex(index,total) && total>=0) {

    if(!cOsd::IsOpen() && !visible && !open) {
	open=true;
        }

	font=cFont::GetFont(fontOsd);

        fw=font->Width(' ');
        fh=font->Height();
	
	x0 = 0;
	x1 = Setup.OSDWidth;
	y0 = 0;
	y1 = fh;
	y2 = y1 + fh;
	y5 = Setup.OSDHeight;
	y4 = y5 -fh;
	y3 = y4 - fh;

        rows = MP3Setup.Rowcount;
	lh   = 4*fh +(rows * fh) + fh/2; //11*fh + fh/2 

    if(!visible && open) {

        HideStatus();

        osd=cOsdProvider::NewOsd(Setup.OSDLeft + MP3Setup.OSDoffsetx, Setup.OSDTop + MP3Setup.OSDoffsety);
        if(!osd) return;
			 // playlist , symbols , track-titel , progress


        tArea Area[] = { { 0         ,  0     , x1 -1        ,  3*fh -1 , 2 },        // border top
			 { 0         ,  3*fh  , x1 -1        ,  lh   -1 , 2 },        // tracklist
			 { 0               , lh          , x1 - 4*fh - 4*fw -1 , lh + 4*fh + fh/2 -1 , 2 },        // Info
			 { x1 -4*fh -4*fw  , lh          , x1 - 3*fw -1        , lh + 4*fh + fh/2 -1 , 4 },        // Cover
			 { x1 -3*fw        , lh          , x1 - 1              , lh + 4*fh + fh/2 -1 , 4 },        // Coverright
			 { 0       , lh + 4*fh + fh/2    , x1 -1               , lh + 6*fh + fh/2 -1 , 2 },        // Progress
	                 }; 

			eOsdError result = osd->CanHandleAreas(Area, sizeof(Area) / sizeof(tArea));
	        	if (result == oeOk) {
			    osd->SetAreas(Area, sizeof(Area) / sizeof(tArea));
			    }
			else  {
			    const char *errormsg = NULL;
			    switch (result) {
				    case oeTooManyAreas:
			        	errormsg = "Too many OSD areas"; break;
				    case oeTooManyColors:
			        	errormsg = "Too many colors"; break;
				    case oeBppNotSupported:
			        	errormsg = "Depth not supported"; break;	    
				    case oeAreasOverlap:
		    	        	errormsg = "Areas are overlapped"; break;
				    case oeWrongAlignment:
			        	errormsg = "Areas not correctly aligned"; break;
				    case oeOutOfMemory:
			        	errormsg = "OSD memory overflow"; break;
				    case oeUnknown:
			        	errormsg = "Unknown OSD error"; break;
				    default:
				    break;
			   }	    
			    esyslog("mp3ng: ERROR! OSD open failed! Can't handle areas (%d)-%s\n", result, errormsg);
			    return;
			}    

//top
      osd->DrawRectangle(0, 0, x1 - 1, fh, clrListItemActiveBackground);         // border top
      osd->DrawEllipse(0, 0, fh/2 , fh/2,  clrTransparent, -2);
      osd->DrawEllipse(x1 -1 - fh/2, 0, x1 -1 , fh/2, clrTransparent, -1);
      font=cFont::GetFont(fontSml);
      osd->DrawText( 3*fw, 0, "- VDR-MUSICPLAYER -", clrNormalText, clrListItemActiveBackground, font, x1 - (6*fw), fh, taCenter);
      font=cFont::GetFont(fontOsd);
  
      osd->DrawRectangle(0, fh + 1,    x1 -1 , 3*fh -1  ,clrBackground);
      osd->DrawText( 8*fw, 2*fh - fw, "TRACKLIST :", clrNormalText, clrBackground, font, 40*fw, fh, taLeft);

      osd->DrawEllipse(0, fh + 1, fh/2 , fh + 1 + fh/2, clrListItemActiveBackground, -2);
      osd->DrawEllipse(x1 - 1 - fh/2, fh + 1, x1 - 1 , fh + 1 + fh/2, clrListItemActiveBackground, -1);
//tracklist
      osd->DrawRectangle(0, 3*fh  ,  x1 -1 , lh -1 ,clrBackground);
      osd->DrawRectangle(3*fw, 3*fh ,  x1 - 3*fw , lh - fh/2 -1 ,clrBackground2);
      osd->DrawEllipse(3*fw  , 3*fh , 3*fw + fh/2 , 3*fh  + fh/2, clrBackground, -2);
      osd->DrawEllipse(x1 - 3*fw - fh/2, 3*fh, x1 - 3*fw , 3*fh + fh/2, clrBackground, -1);

      osd->DrawEllipse(3*fw             , lh -fh -1, 3*fw + fh/2 , lh - fh/2 -1, clrBackground, -3);
      osd->DrawEllipse(x1 - 3*fw - fh/2 , lh -fh -1, x1 - 3*fw   , lh - fh/2 -1, clrBackground, -4);

//info
      osd->DrawRectangle(0              , lh, x1 -4*fh -4*fw -1, lh + 4*fh +fh/2 -1 ,clrBackground);        // Info
      osd->DrawRectangle(x1 -4*fh -4*fw , lh, x1 -3*fw -1      , lh + 4*fh +fh/2 -1 ,clrBackground2); // Cover        // Cover
      osd->DrawRectangle(x1 -3*fw       , lh, x1 -1            , lh + 4*fh +fh/2 -1 ,clrBackground); // Cover        // Cover
      

      osd->DrawText( 8*fw, lh + fh/2 - fw, "NOW PLAYING :", clrNormalText, clrBackground, font, (46*fw), fh, taLeft);
      osd->DrawRectangle(3*fw, lh + fh + fh/2 , x1 -5*fh -1 - fh/2 , lh + 4*fh + fh/2 -1, clrBackground2);        // Info
      osd->DrawEllipse(3*fw  , lh + fh + fh/2 , 3*fw + fh/2        , lh + 2*fh, clrBackground, -2);
      osd->DrawEllipse(x1 -5*fh -1 - fh, lh + fh + fh/2, x1 -5*fh -1 -fh/2 , lh + 2*fh, clrBackground, -1);


//progressbar
      osd->DrawRectangle(0, lh + 4*fh + fh/2 , x1 -1 , lh + 6*fh + fh/2 -1  ,clrBackground);        // Progress
      osd->DrawRectangle(3*fw, lh + 5*fh, x1 - 3*fw , lh + 6*fh ,clrBackground2);        // Progress
      osd->DrawEllipse(3*fw, lh + 5*fh + fh/2, 3*fw + fh/2 , lh + 6*fh, clrBackground, -3);
      osd->DrawEllipse(x1 - 3*fw - fh/2, lh + 5*fh + fh/2 , x1  - 3*fw , lh + 6*fh, clrBackground, -4);

      LoadCover();
            
      osd->Flush();

      ShowStatus(true);
      needsFastResponse=visible=true;
      fliptime=listtime=0; flipint=0; flip=-1; top=lastTop=-1; lastIndex=lastTotal=-1;
      delete lastMode; lastMode=0;

      }

    
    cMP3PlayInfo *mode=new cMP3PlayInfo;
    bool valid=mgr->Info(-1,mode);
    bool changed=(!lastMode || mode->Hash!=lastMode->Hash);
    char buf[256];

    if(pressedOk) changed = true;

    if(valid) { // send progress to status monitor
      if(changed || mode->Loop!=lastMode->Loop || mode->Shuffle!=lastMode->Shuffle) {
        snprintf(buf,sizeof(buf),mode->Artist[0]?"[%c%c] (%d/%d) %s - %s":"[%c%c] (%d/%d) %s",
                  mode->Loop?'L':'.',mode->Shuffle?'S':'.',mode->Num,mode->MaxNum,mode->Title,mode->Artist);
        cStatus::MsgReplaying(this,buf,mode->Filename[0]?mode->Filename:0,true);
        }
      }

    if(visible) { // refresh the OSD progress display
      flush=false;

  if (CanLoadCover) {
     osd->DrawRectangle(x1 - 4*fh -4*fw , lh, x1 -3*fw -1, lh + 4*fh + fh/2 -1, clrTransparent);
     LoadCover();
     flush = true;
  }   


        if(!selecting && changed && !statusActive) {
          snprintf(buf,sizeof(buf),"%s: %i %s %i",tr("Track"),mode->Num,tr("of"),mode->MaxNum);
      	  font=cFont::GetFont(fontSml);
      	  osd->DrawText( 5*fw, lh + 3*fh + fh/2, buf, clrTracklistText, clrBackground2, font, 37 * fw, fh, taLeft);
      	  font=cFont::GetFont(fontOsd);
          flush=true;
          }
        
            if(!lastMode || mode->Loop!=lastMode->Loop) {
	     if(mode->Loop)  osd->DrawBitmap(x1 - 5*fh - 82 -fh/2, lh + fh/2 -fw, bmLoop, clrBackground, clrNormalText);
    	     else osd->DrawBitmap(x1 - 5*fh - 82 -fh/2, lh + fh/2 -fw, bmLoop, clrBackground, clrBackground2);
             flush=true;
	    }

            if(!lastMode || mode->Shuffle!=lastMode->Shuffle) {
	     if(mode->Shuffle)  osd->DrawBitmap(x1 - 5*fh - 54 -fh/2, lh + fh/2 -fw, bmShuffle, clrBackground, clrNormalText);
    	     else osd->DrawBitmap(x1 - 5*fh - 54 -fh/2, lh + fh/2 -fw, bmShuffle, clrBackground, clrBackground2);
             flush=true;
	    }


	    if (copyfile)  {
	     copyfile = false;
	     osd->DrawBitmap(x1 - 5*fh - 26 -fh/2, lh + fh/2 -fw, bmCopy, clrBackground, clrNormalText);
	     flush=true; }
	    else {
	     osd->DrawBitmap(x1 - 5*fh - 26 -fh/2, lh + fh/2 -fw, bmCopy, clrBackground, clrBackground2);
             flush=true;
            }
	    
	    if (recordstream) {
	     osd->DrawText(8*fw, lh + fh/2 -fw, "NOW RECORD STREAM:", clrNormalText, clrBackground, font, (46*fw), fh, taLeft);
	     flush=true;
	     }
	    else {
	      if(!player->IsStream())
	        osd->DrawText(8*fw, lh + fh/2 -fw, "NOW PLAYING:", clrNormalText, clrBackground, font, (46*fw), fh, taLeft);
	      else	
	        osd->DrawText(8*fw, lh + fh/2 -fw, "NOW PLAYING STREAM:", clrNormalText, clrBackground, font, (46*fw), fh, taLeft);
	     flush=true;
	    } 
	    
	if(!player->IsStream()) {
          index/=framesPerSecond; total/=framesPerSecond;
          if(index!=lastIndex || total!=lastTotal) {
            if(total>0) {
	       osd->DrawRectangle( 7*fw, lh + 5*fh, Setup.OSDWidth - 7*fw, lh + 6*fh, clrBackground2);
               osd->DrawEllipse(8*fw, lh + 5*fh + fh/2 -fw, 9*fw-1, lh + 5*fh + fh/2 +fw, clrProgress, 7);
               cProgressBar ProgressBar(x1 -18*fw, 2*fw +1, index, total, clrProgress, clrProgressBackground);
	       osd->DrawBitmap(9*fw , lh + 5*fh + fh/2 -fw, ProgressBar);
	       osd->DrawEllipse(x1 -9*fw, lh + 5*fh +fh/2 -fw, x1 - 8*fw -1 , lh + 5*fh + fh/2 +fw,  clrProgressBackground,5);
            }
    	    snprintf(buf,sizeof(buf),total?"%s: %02d:%02d %s %02d:%02d":"%s: %02d:%02d ",tr("Time"),index/60,index%60,tr("of"),total/60,total%60);
            font=cFont::GetFont(fontSml);
    	    osd->DrawText( x1 - 5*fh-34*fw - fh/2 - 3*fw, lh + 3*fh + fh/2, buf, clrTracklistText, clrBackground2, font, 35 * fw, fh, taRight);
    	    font=cFont::GetFont(fontOsd);
            flush=true;
          }
        } 
        else
	  osd->DrawText(7*fw, lh + 5*fh , "NETSTREAM", clrProgressBackground, clrBackground2, font, x1 -14*fw , fh, taCenter);

      if(!jumpactive) {
        bool doflip=false;    
        if(!valid || changed) {
          fliptime=time(0); flip=0;
	  doflip=true;
	  }
        else if(time(0)>fliptime+flipint) {
	  fliptime=time(0);
	  flip++; if(flip>=MP3Setup.DisplayMode) flip=0;
          doflip=true;
	  }
        if(doflip) {
          buf[0]=0;
          switch(flip) {
	    default:
	      flip=0;
	      // fall through
	    case 0:
	      snprintf(buf,sizeof(buf),mode->Artist[0]?"%s - %s":"%s%s",mode->Title,mode->Artist);
	      flipint=6;
	      break;
	    case 1:
              if(mode->Album[0]) {
      	        snprintf(buf,sizeof(buf),mode->Year>0?"%s: %s (%d)":"Album: %s",tr("Album"),mode->Album,mode->Year);
	        flipint=4;
	        }
              else fliptime=0;
              break;
	    case 2:
              if(mode->MaxBitrate>0)
                snprintf(buf,sizeof(buf),"%.1f kHz, %d-%d kbps, %s",mode->SampleFreq/1000.0,mode->Bitrate/1000,mode->MaxBitrate/1000,mode->SMode);
              else
                snprintf(buf,sizeof(buf),"%.1f kHz, %d kbps, %s",mode->SampleFreq/1000.0,mode->Bitrate/1000,mode->SMode);
	      flipint=3;
	      break;
	    }
          if(buf[0]) {
              if(!statusActive) {
                DisplayInfo(buf);
                flush=true;
                }
            }
          }
        }

        bool all=(top!=lastTop || changed);
        if(all || time(0)>listtime+2) {
          num=(top>0 && mode->Num==lastMode->Num) ? top : mode->Num - rows/2;
          if(num+rows>mode->MaxNum) num=mode->MaxNum-rows+1;
          if(num<1) num=1;
          top=num;
          for(int i=0 ; i<rows && i<MAXROWS && num<=mode->MaxNum ; i++,num++) {
            cMP3PlayInfo pi;
            mgr->Info(num,&pi); if(!pi.Title[0]) break;
            snprintf(buf,sizeof(buf),pi.Artist[0]?"%d.\t    %s - %s":"%d.\t    %s",num,pi.Title,pi.Artist);
            eDvbColor fg=clrTracklistText, bg=clrBackground2;
            int hash=MakeHash(buf);

	    if(scrollmode) {
                if(num==mode->Num) {
		    tracknr = num;
//dsyslog("SCROLL");
		    bg=clrListItemActiveText;
		    fg=clrListItemActiveBackground;
		    hash=(hash^77) + 23;
		}
	    }	
	    else  {
                if(num==mode->Num) {
		    oldtracknr = num;
//dsyslog("NORMAL");
		    bg=clrListItemActiveBackground;
		    fg=clrListItemActiveText;
		    hash=(hash^77) + 23;
		}
	    }

            if(all || hash!=hashlist[i]) {
              char *s=rindex(buf,'\t');
              if(s) {
                *s++=0;
	            font=cFont::GetFont(fontSml);
    		    osd->DrawEllipse( 5*fw , 3*fh + (fh/2) + (i*fh) , (5*fw)+(fh/2) , 3*fh + (fh/2)+ (i*fh)+fh , bg , 7);
          	    osd->DrawText( (5*fw) + (fh/2)+1 , (3*fh) + (fh/2) + (i*fh) , buf , fg , bg , font , 10*fw , fh , taRight);
           	    osd->DrawText( (5*fw) + (10*fw)+(fh/2)+1 , 3*fh + (fh/2) + (i*fh) , s , fg , bg  , font , x1 - (20*fw) -fh , fh , taLeft);
	            osd->DrawEllipse( x1 - (5*fw)-fh/2, 3*fh + (fh/2) + (i*fh) , x1 - (5*fw), 3*fh + (fh/2) +(i*fh) + fh, bg , 5);
	            font=cFont::GetFont(fontOsd);
                }
		  else {
	            osd->DrawText( 5*fw , 3*fh + (fh/2) + (i*fh) , buf, fg, bg, font, x1 - (10*fw), fh , taLeft);
		  }

              flush=true;
              hashlist[i]=hash;
              }
            }
          listtime=time(0); lastTop=top;
          }

      if(flush) Flush();
      } 

    lastIndex=index; lastTotal=total;
    delete lastMode; lastMode=mode;
    pressedOk=false;
    }
}


void cMP3Control::DisplayInfo(const char *s)
{
  if(osd) {
    if(s) {
      	osd->DrawText( 5*fw, lh + 2*fh , s, clrTracklistText, clrBackground2, font, Setup.OSDWidth - 7*fw - 5*fh -fh/2, fh, taLeft);
    }
  else {
      	osd->DrawText( 5*fw, lh + 2*fh , s, clrBackground2, clrBackground2, font, Setup.OSDWidth - 7*fw - 5*fh -fh/2, fh, taLeft);
       }
  }
}


void cMP3Control::JumpDisplay(void)
{
  char buf[64];
  const char *j=tr("Jump: "), u=jumpsecs?'s':'m';
  if(!jumpmm) sprintf(buf,"%s- %c",  j,u);
  else        sprintf(buf,"%s%d- %c",j,jumpmm,u);
    DisplayInfo(buf);
}




void cMP3Control::JumpProcess(eKeys Key)
{
 int n=Key-k0, d=jumpsecs?1:60;
  switch (Key) {
    case k0 ... k9:
      if(jumpmm*10+n <= lastTotal/d) jumpmm=jumpmm*10+n;
      JumpDisplay();
      break;
    case kBlue:
      jumpsecs=!jumpsecs;
      JumpDisplay();
      break;
    case kPlay:
    case kUp:
      jumpmm-=lastIndex/d;
      // fall through
    case kFastRew:
    case kFastFwd:
    case kLeft:
    case kRight:
      player->SkipSeconds(jumpmm*d * ((Key==kLeft || Key==kFastRew) ? -1:1));
      // fall through
    default:
      jumpactive=false;
      break;
    }

  if(!jumpactive) {
    if(jumphide) Hide();
    }
}

void cMP3Control::Jump(void)
{
  jumpmm=0; jumphide=jumpsecs=false;
  if(!visible) {
    ShowTimed(); if(!visible) return;
    jumphide=true;
    }
  JumpDisplay();
  jumpactive=true; fliptime=0; flip=-1;
}

void cMP3Control::CopyFile(void)
{
FILE *copyscript;
char *buffer;

  copyfile = true;
  osd->DrawBitmap(Setup.OSDWidth - 5*fh - 26 -fh/2, lh + fh/2 -fw, bmCopy, clrBackground, clrNormalText);
  Flush();
  asprintf(&buffer, "cp '%s' '%s'", Songname, MP3Setup.CopyDir);
  copyscript = popen(buffer, "r");
  dsyslog("copy '%s' to '%s'",Songname,MP3Setup.CopyDir);
  free(buffer);
  pclose(copyscript);
}

void cMP3Control::StartRecord(void)
{
  dsyslog("mp3ng: Started Recording and playback of Relaystream\n");
}  
  

void cMP3Control::StopRecord(void)
{
  dsyslog("mp3ng: Stopped Recording and Relaystream\n");
}  

eOSState cMP3Control::ProcessKey(eKeys Key)
{

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

  if(visible && timeoutShow && time(0)>timeoutShow) { Hide(); timeoutShow=0; }

  ShowProgress();
  ShowStatus(Key==kNone);

  if(jumpactive && Key!=kNone) { JumpProcess(Key); return osContinue; }

  switch(Key) {
    case kUp:
    case kUp|k_Repeat:
      if(!scrollmode && MP3Setup.CanScroll) {
        scrollmode = true;
      }	
//      if(!player->PrevCheck()) mgr->Prev();
      mgr->Prev();
      player->Play();
      break;
    case kDown:
    case kDown|k_Repeat:
      if(!scrollmode && MP3Setup.CanScroll) {
        scrollmode = true;
      }	
      mgr->Next();
      player->Play();
      break;
    case kLeft:
    case kLeft|k_Repeat:
        if(!scrollmode && MP3Setup.CanScroll) {
            scrollmode = true;
        }
        if(top>0) {
			top-=rows;
			top = top + rows/2;
			if(top<1) top=1;
			if(scrollmode) mgr->Goto(top);
	 }
        break;
    case kFastRew:
    case kFastRew|k_Repeat:
      if(!player->IsStream()) player->SkipSeconds(-MP3Setup.Jumptime);
      break;
    case kRight:
    case kRight|k_Repeat:
	if(!scrollmode && MP3Setup.CanScroll) {
            scrollmode = true;
        }
        if(top>0) { top+=rows; if(scrollmode) mgr->Goto(top + rows/2);}
        break;
    case kFastFwd:
    case kFastFwd|k_Repeat:
      if(!player->IsStream()) player->SkipSeconds(MP3Setup.Jumptime);
      break;
    case kPlay:
        if(lastMode) {
          if(time(0)>greentime) {
            if(lastMode->Loop || (!lastMode->Loop && !lastMode->Shuffle)) mgr->ToggleLoop();
            if(lastMode->Shuffle) mgr->ToggleShuffle();
            }
          else {
            if(!lastMode->Loop) mgr->ToggleLoop();
            else if(!lastMode->Shuffle) mgr->ToggleShuffle();
            else mgr->ToggleLoop();
            }
          greentime=time(0)+MULTI_TIMEOUT;
          }
      break;
    case kRed:
      if(!player->IsStream()) Jump();
      break;
    case kGreen:
    case kGreen|k_Repeat:
      if (MP3Setup.CanSkip) {
          if(!player->IsStream()) player->SkipSeconds(-MP3Setup.Skiptime); }
      else {
          if(scrollmode) scrollmode=false;
	  mgr->Prev();
      player->Play();
      }	
      break;
    case kYellow:
    case kYellow|k_Repeat:
      if (MP3Setup.CanSkip) {
	  if(!player->IsStream()) player->SkipSeconds(+MP3Setup.Skiptime); }
      else {
          if(scrollmode) scrollmode=false;
	  mgr->Next();
	  player->Play();
      }
          break;
    case kBlue:
      if (osd) {
	if(!player->IsStream())  CopyFile();
	 else {
	if (recordstream) {
	  printf("Recording stopped\n");
	  StopRecord();
	  recordstream = false;
	  }
	else {
	  printf("New Recording started\n");
	  StartRecord();
	  recordstream = true;
	  }   
        }
      }	 
      break;
    case kPause:
      if(!player->IsStream()) player->Pause();
      break;
    case kOk:
      if (scrollmode && MP3Setup.CanScroll) {
          if(tracknr>0) {
	    num = tracknr;
            scrollmode=false;
	    pressedOk = true;
	    mgr->Goto(num);
	    player->Play();
	    ShowProgress(true);
	    }
      }

      else {
   
      if (!showtrans) {
        showtrans = true;

	clrBackground			= 0x0A000000 ;
	clrBackground2			= 0x0A000000 ;
	clrNormalText			= 0x0A000000 ;
	clrTracklistText		= 0x0A000000 ;
	clrListItemActiveBackground	= 0x0A000000 ;
	clrListItemActiveText		= 0x0A000000 ;
	clrProgress			= 0x0A000000 ;
	clrProgressBackground		= 0x0A000000 ;
	Hide();
	ShowProgress(true);
        }
      else {
	showtrans = false;
	
	clrBackground			= mp3Theme[MP3Setup.osdtheme].clrBackground;
	clrBackground2			= mp3Theme[MP3Setup.osdtheme].clrBackground2;
	clrNormalText			= mp3Theme[MP3Setup.osdtheme].clrNormalText;
	clrTracklistText		= mp3Theme[MP3Setup.osdtheme].clrTracklistText;
	clrListItemActiveBackground	= mp3Theme[MP3Setup.osdtheme].clrListItemActiveBackground;
	clrListItemActiveText		= mp3Theme[MP3Setup.osdtheme].clrListItemActiveText;
	clrProgress			= mp3Theme[MP3Setup.osdtheme].clrProgress;
	clrProgressBackground		= mp3Theme[MP3Setup.osdtheme].clrProgressBackground;
	Hide();
	ShowProgress(true);
      	}
      printf("Transparent = %d\n", showtrans);
    }

      break;

    case kStop:
      Hide();
      Stop();
      return osEnd;

    case k0 ... k9:
      number=number*10+Key-k0;
      if(lastMode && number>0 && number<=lastMode->MaxNum) {
        if(!visible) { ShowTimed(); selecthide=true; }
        selecting=true; lastkeytime=time_ms();
        char buf[32];
        snprintf(buf,sizeof(buf),"%s: %d- %s %d",tr("Track"),number,tr("of"),lastMode->MaxNum);
        font=cFont::GetFont(fontSml);
      	osd->DrawText( 5*fw, lh + 3*fh + fh/2, buf, clrTracklistText, clrBackground2, font, 40 * fw , fh, taLeft);
        font=cFont::GetFont(fontOsd);
        Flush();
        break;
        }
      number=0; lastkeytime=0;
      // fall through
    case kNone:
      if(selecting && time_ms()-lastkeytime>SELECT_TIMEOUT) {
        if(number>0) { mgr->Goto(number); player->Play();  }
        if(selecthide) timeoutShow=time(0)+SELECTHIDE_TIMEOUT;
        if(lastMode) lastMode->Hash=-1;
        number=0; selecting=selecthide=false;
        }
      break;
    case kBack:
      if(scrollmode && MP3Setup.CanScroll) {
        mgr->Goto(oldtracknr);
        scrollmode = false;
        pressedOk = true;
        ShowProgress(true);
        }
      else {
            Hide();
	    cRemote::CallPlugin("mp3ng");
	      if (MP3Setup.ExitClose) 
                return (osBack);
	      else
	        return (osPlugin);	
        }
    default:
      return osUnknown;
    }
  return osContinue;
}

// --- cMenuID3Info ------------------------------------------------------------

class cMenuID3Info : public cOsdMenu {
private:
  cOsdItem *Item(const char *name, const char *text);
  cOsdItem *Item(const char *name, const char *format, const float num);
  void Build(cSongInfo *info, const char *name);
public:
  cMenuID3Info(cSong *song);
  cMenuID3Info(cSongInfo *si, const char *name);
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuID3Info::cMenuID3Info(cSong *song)
:cOsdMenu(tr("ID3 information"),12)
{
  Build(song->Info(),song->Name());
}

cMenuID3Info::cMenuID3Info(cSongInfo *si, const char *name)
:cOsdMenu(tr("ID3 information"),12)
{
  Build(si,name);
}

void cMenuID3Info::Build(cSongInfo *si, const char *name)
{
  if(si) {
    Item(tr("Filename"),name);
    if(si->HasInfo() && si->Total>0) {
      char *buf=0;
      asprintf(&buf,"%02d:%02d",si->Total/60,si->Total%60);
      Item(tr("Length"),buf);
      free(buf);
      Item(tr("Title"),si->Title);
      Item(tr("Artist"),si->Artist);
      Item(tr("Album"),si->Album);
      Item(tr("Year"),0,(float)si->Year);
      Item(tr("Samplerate"),"%.1f kHz",si->SampleFreq/1000.0);
      Item(tr("Bitrate"),"%.f kbit/s",si->Bitrate/1000.0);
      Item(tr("Channels"),0,(float)si->Channels);
      }
    Display();
    }
}

cOsdItem *cMenuID3Info::Item(const char *name, const char *format, const float num)
{
  cOsdItem *item;
  if(num>=0.0) {
    char *buf=0;
    asprintf(&buf,format?format:"%.f",num);
    item=Item(name,buf);
    free(buf);
    }
  else item=Item(name,"");
  return item;
}

cOsdItem *cMenuID3Info::Item(const char *name, const char *text)
{
  char *buf=0;
  asprintf(&buf,"%s:\t%s",name,text?text:"");
  cOsdItem *item = new cOsdItem(buf,osBack);
  item->SetSelectable(false);
  free(buf);
  Add(item); return item;
}

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

  if(state==osUnknown) {
     switch(Key) {
       case kRed:
       case kGreen:
       case kYellow:
       case kBlue:   return osContinue;
       case kMenu:   return osEnd;
       default: break;
       }
     }
  return state;
}

// --- cMenuInstantBrowse -------------------------------------------------------

class cMenuInstantBrowse : public cMenuBrowse {
private:
  const char *selecttext, *alltext;
  virtual void SetButtons(void);
  virtual eOSState ID3Info(void);
public:
  cMenuInstantBrowse(cFileSource *Source, const char *Selecttext, const char *Alltext);
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuInstantBrowse::cMenuInstantBrowse(cFileSource *Source, const char *Selecttext, const char *Alltext)
:cMenuBrowse(Source,true,true,tr("Directory browser"))
{
  excl=excl_br; // defined in data-mp3.c
  selecttext=Selecttext; alltext=Alltext;
  SetButtons();
}

void cMenuInstantBrowse::SetButtons(void)
{
  SetHelp(selecttext, currentdir?tr("Parent"):0, currentdir?0:alltext, tr("ID3 info"));
  Display();
}

eOSState cMenuInstantBrowse::ID3Info(void)
{
  cFileObj *item=CurrentItem();
  if(item && item->Type()==otFile) {
    cSong *song=new cSong(item);
    cSongInfo *si;
    if(song && (si=song->Info())) {
      AddSubMenu(new cMenuID3Info(si,item->Path()));
      }
    delete song;
    }
  return osContinue;
}

eOSState cMenuInstantBrowse::ProcessKey(eKeys Key)
{
  eOSState state=cOsdMenu::ProcessKey(Key);
  if(state==osUnknown) {
     switch (Key) {
       case kYellow: lastselect=new cFileObj(source,0,0,otBase);
                     return osBack;
       default: break;
       }
     }
  if(state==osUnknown) state=cMenuBrowse::ProcessStdKey(Key,state);
  return state;
}

// --- cMenuPlayListItem -------------------------------------------------------

class cMenuPlayListItem : public cOsdItem {
  private:
  bool showID3;
  cSong *song;
public:
  cMenuPlayListItem(cSong *Song, bool showid3);
  cSong *Song(void) { return song; }
  virtual void Set(void);
  void Set(bool showid3);
  };

cMenuPlayListItem::cMenuPlayListItem(cSong *Song, bool showid3)
{
  song=Song;
  Set(showid3);
}

void cMenuPlayListItem::Set(bool showid3)
{
  showID3=showid3;
  Set();
}

void cMenuPlayListItem::Set(void)
{
  char *buffer=0;
  cSongInfo *si=song->Info(false);
  if(showID3 && !si) si=song->Info();
  if(showID3 && si && si->Title)
    asprintf(&buffer, "%d.\t%s%s%s",song->Index()+1,si->Title,si->Artist?" - ":"",si->Artist?si->Artist:"");
  else
    asprintf(&buffer, "%d.\t<%s>",song->Index()+1,song->Name());
  SetText(buffer,false);
}

// --- cMenuPlayList ------------------------------------------------------

class cMenuPlayList : public cOsdMenu {
private:
  cPlayList *playlist;
  bool browsing, showid3;
  void Buttons(void);
  void Refresh(bool all = false);
  void Add(void);
  virtual void Move(int From, int To);
  eOSState Remove(void);
  eOSState ShowID3(void);
  eOSState ID3Info(void);
public:
  cMenuPlayList(cPlayList *Playlist);
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuPlayList::cMenuPlayList(cPlayList *Playlist)
:cOsdMenu(tr("Playlist editor"),4)
{
  browsing=showid3=false;
  playlist=Playlist;
  if(MP3Setup.EditorMode) showid3=true;

  cSong *mp3 = playlist->First();
  while(mp3) {
    cOsdMenu::Add(new cMenuPlayListItem(mp3,showid3));
    mp3 = playlist->cList<cSong>::Next(mp3);
    }
  Buttons(); Display();
}

void cMenuPlayList::Buttons(void)
{
  SetHelp(tr("Add"), showid3?tr("Filenames"):tr("ID3 names"), tr("Remove"), tr("Mark"));
}

void cMenuPlayList::Refresh(bool all)
{
  cMenuPlayListItem *cur=(cMenuPlayListItem *)((all || Count()<2) ? First() : Get(Current()));
  while(cur) {
    cur->Set(showid3);
    cur=(cMenuPlayListItem *)Next(cur);
    }
}

void cMenuPlayList::Add(void)
{
  cFileObj *item=cMenuInstantBrowse::GetSelected();
  if(item) {
    Status(tr("Scanning directory..."));
    cInstantPlayList *newpl=new cInstantPlayList(item);
    if(newpl->Load()) {
      if(newpl->Count()) {
        if(newpl->Count()==1 || Interface->Confirm(tr("Add recursivly?"))) {
          cSong *mp3=newpl->First();
          while(mp3) {
            cSong *n=new cSong(mp3);
            if(Count()>0) {
              cMenuPlayListItem *current=(cMenuPlayListItem *)Get(Current());
              playlist->Add(n,current->Song());
              cOsdMenu::Add(new cMenuPlayListItem(n,showid3),true,current);
              }
            else {
              playlist->Add(n);
              cOsdMenu::Add(new cMenuPlayListItem(n,showid3),true);
              }
            mp3=newpl->cList<cSong>::Next(mp3);
            }
          playlist->Save();
          Refresh(); Display();
          }
        }
      else Error(tr("Empty directory!"));
      }
    else Error(tr("Error scanning directory!"));
    delete newpl;
    Status(0);
    }
}

void cMenuPlayList::Move(int From, int To)
{
  playlist->Move(From,To); playlist->Save();
  cOsdMenu::Move(From,To);
  Refresh(true); Display();
}

eOSState cMenuPlayList::ShowID3(void)
{
  showid3=!showid3;
  Buttons(); Refresh(true); Display();
  return osContinue;
}

eOSState cMenuPlayList::ID3Info(void)
{
  if(Count()>0) {
    cMenuPlayListItem *current = (cMenuPlayListItem *)Get(Current());
    AddSubMenu(new cMenuID3Info(current->Song()));
    }
  return osContinue;
}

eOSState cMenuPlayList::Remove(void)
{
  if(Count()>0) {
    cMenuPlayListItem *current = (cMenuPlayListItem *)Get(Current());
    if(Interface->Confirm(tr("Remove entry?"))) {
      playlist->Del(current->Song()); playlist->Save();
      cOsdMenu::Del(Current());
      Refresh(); Display();
      }
    }
  return osContinue;
}

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

  if(browsing && !HasSubMenu() && state==osContinue) { Add(); browsing=false; }

  if(state==osUnknown) {
     switch(Key) {
       case kOk:     return ID3Info();
       case kRed:    browsing=true;
                     return AddSubMenu(new cMenuInstantBrowse(MP3Sources.GetSource(),tr("Add"),tr("Add all")));
       case kGreen:  return ShowID3();
       case kYellow: return Remove();
       case kBlue:   Mark(); return osContinue;
       case kMenu:   return osEnd;
       default: break;
       }
     }
  return state;
}

// --- cPlaylistRename --------------------------------------------------------

class cPlaylistRename : public cOsdMenu {
private:
  static char *newname;
  const char *oldname;
  char data[64];
public:
  cPlaylistRename(const char *Oldname);
  virtual eOSState ProcessKey(eKeys Key);
  static const char *GetNewname(void) { return newname; }
  };

char *cPlaylistRename::newname = NULL;

cPlaylistRename::cPlaylistRename(const char *Oldname)
:cOsdMenu(tr("Rename playlist"), 15)
{
  free(newname); newname=0;

  oldname=Oldname;
  char *buf=NULL;
  asprintf(&buf,"%s\t%s",tr("Old name:"),oldname);
  cOsdItem *old = new cOsdItem(buf,osContinue);
  old->SetSelectable(false);
  Add(old);
  free(buf);

  data[0]=0;
  Add(new cMenuEditStrItem( tr("New name"), data, sizeof(data)-1, tr(FileNameChars)),true);
}

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

  if (state == osUnknown) {
     switch (Key) {
       case kOk:     if(data[0] && strcmp(data,oldname)) newname=strdup(data);
                     return osBack;
       case kRed:
       case kGreen:
       case kYellow:
       case kBlue:   return osContinue;
       default: break;
       }
     }
  return state;
}

// --- cMenuMP3Item -----------------------------------------------------

class cMenuMP3Item : public cOsdItem {
  private:
  cPlayList *playlist;
  virtual void Set(void);
public:
  cMenuMP3Item(cPlayList *PlayList);
  cPlayList *List(void) { return playlist; }
  };

cMenuMP3Item::cMenuMP3Item(cPlayList *PlayList)
{
  playlist=PlayList;
  Set();
}

void cMenuMP3Item::Set(void)
{
  char *buffer=0;
  asprintf(&buffer," %s",playlist->BaseName());
  SetText(buffer,false);
}

// --- cMenuMP3 --------------------------------------------------------

class cMenuMP3 : public cOsdMenu {
private:
  cPlayLists *lists;
  bool renaming, sourcing, instanting;
  int buttonnum;
  eOSState Play(void);
  eOSState Edit(void);
  eOSState New(void);
  eOSState Delete(void);
  eOSState Rename(bool second);
  eOSState Source(bool second);
  eOSState Instant(bool second);
  void ScanLists(void);
  eOSState SetButtons(int num);
public:
  cMenuMP3(void);
  ~cMenuMP3(void);
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuMP3::cMenuMP3(void)
:cOsdMenu(tr("MP3ng"))
{
  renaming=sourcing=instanting=false;
  lists=new cPlayLists;
  ScanLists(); SetButtons(1);
  if(MP3Setup.MenuMode) Instant(false);
}

cMenuMP3::~cMenuMP3(void)
{
  delete lists;
}

eOSState cMenuMP3::SetButtons(int num)
{
  switch(num) {
    case 1:
      SetHelp(tr("Edit"), tr("Source"), tr("Browse"), ">>");
      break;
    case 2:
      SetHelp("<<", tr("New"), tr("Delete"), tr("Rename"));
      break;
    }
  buttonnum=num; Display();
  return osContinue;
}

void cMenuMP3::ScanLists(void)
{
  Clear();
  Status(tr("Scanning playlists..."));
  bool res=lists->Load(MP3Sources.GetSource());
  Status(0);
  if(res) {
    cPlayList *plist=lists->First();
    while(plist) {
      Add(new cMenuMP3Item(plist));
      plist=lists->Next(plist);
      }
    }
  else Error(tr("Error scanning playlists!"));
}

eOSState cMenuMP3::Delete(void)
{
  if(Count()>0) {
    if(Interface->Confirm(tr("Delete playlist?")) &&
       Interface->Confirm(tr("Are you sure?")) ) {
      cPlayList *plist = ((cMenuMP3Item *)Get(Current()))->List();
      if(plist->Delete()) {
        lists->Del(plist);
        cOsdMenu::Del(Current());
        Display();
        }
      else Error(tr("Error deleting playlist!"));
      }
    }
  return osContinue;
}

eOSState cMenuMP3::New(void)
{
  cPlayList *plist=new cPlayList(MP3Sources.GetSource(),0,0);
  char name[32];
  int i=0;
  do {
    if(i) sprintf(name,"%s%d",tr("unnamed"),i++);
    else { strcpy(name,tr("unnamed")); i++; }
    } while(plist->TestName(name));

  if(plist->Create(name)) {
    lists->Add(plist);
    Add(new cMenuMP3Item(plist), true);

    isyslog("mp3ng: playlist %s added", plist->Name());
    return AddSubMenu(new cMenuPlayList(plist));
    }
  Error(tr("Error creating playlist!"));
  delete plist;
  return osContinue;
}

eOSState cMenuMP3::Rename(bool second)
{
  if(HasSubMenu() || Count() == 0) return osContinue;

  cPlayList *plist = ((cMenuMP3Item *)Get(Current()))->List();
  if(!second) {
    renaming=true;
    return AddSubMenu(new cPlaylistRename(plist->BaseName()));
    }
  renaming=false;
  const char *newname=cPlaylistRename::GetNewname();
  if(newname) {
    if(plist->Rename(newname)) {
      RefreshCurrent();
      DisplayCurrent(true);
      }
    else Error(tr("Error renaming playlist!"));
    }
  return osContinue;
}

eOSState cMenuMP3::Edit(void)
{
  if(HasSubMenu() || Count() == 0) return osContinue;

  cPlayList *plist = ((cMenuMP3Item *)Get(Current()))->List();
  if(!plist->Load()) Error(tr("Error loading playlist!"));
  else if(!plist->IsWinAmp()) {
    isyslog("mp3ng: editing playlist %s", plist->Name());
    return AddSubMenu(new cMenuPlayList(plist));
    }
  else Error(tr("Can't edit a WinAmp playlist!"));
  return osContinue;
}

eOSState cMenuMP3::Play(void)
{
  if(HasSubMenu() || Count() == 0) return osContinue;

  Status(tr("Loading playlist..."));
  cPlayList *newpl=new cPlayList(((cMenuMP3Item *)Get(Current()))->List());
  if(newpl->Load() && newpl->Count()) {
    isyslog("mp3-MKIV: playback started with playlist %s", newpl->Name());
    cMP3Control::SetPlayList(newpl);
    if(MP3Setup.KeepSelect) { Status(0); return osContinue; }
    return osEnd;
    }
  Status(0);
  delete newpl;
  Error(tr("Error loading playlist!"));
  return osContinue;
}

eOSState cMenuMP3::Source(bool second)
{
  if(HasSubMenu()) return osContinue;

  if(!second) {
    sourcing=true;
    return AddSubMenu(new cMenuSource(&MP3Sources,tr("MP3 source")));
    }
  sourcing=false;
  cFileSource *src=cMenuSource::GetSelected();
  if(src) {
    MP3Sources.SetSource(src);
    ScanLists();
    Display();
    }
  return osContinue;
}

eOSState cMenuMP3::Instant(bool second)
{
  if(HasSubMenu()) return osContinue;

  if(!second) {
    instanting=true;
    return AddSubMenu(new cMenuInstantBrowse(MP3Sources.GetSource(),tr("Play"),tr("Play all")));
    }
  instanting=false;
  cFileObj *item=cMenuInstantBrowse::GetSelected();
  if(item) {
    Status(tr("Building playlist..."));
    cInstantPlayList *newpl = new cInstantPlayList(item);
    if(newpl->Load() && newpl->Count()) {
      isyslog("mp3: playback started with instant playlist %s", newpl->Name());
      cMP3Control::SetPlayList(newpl);
      if(MP3Setup.KeepSelect) { Status(0); return Instant(false); }
      return osEnd;
      }
    Status(0);
    delete newpl;
    Error(tr("Error building playlist!"));
    }
  return osContinue;
}

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

  if(!HasSubMenu() && state==osContinue) { // eval the return value from submenus
    if(renaming) return Rename(true);
    if(sourcing) return Source(true);
    if(instanting) return Instant(true);
    }

  if(state == osUnknown) {
    switch(Key) {
      case kOk:     return Play();
      case kRed:    return (buttonnum==1 ? Edit() : SetButtons(1)); 
      case kGreen:  return (buttonnum==1 ? Source(false) : New());
      case kYellow: return (buttonnum==1 ? Instant(false) : Delete());
      case kBlue:   return (buttonnum==1 ? SetButtons(2) : Rename(false));
      case kMenu:   return osEnd;
      default:      break;
      }
    }
  return state;
}

// --- PropagateImage ----------------------------------------------------------

//void PropagateImage(const char *image)
//{
//  cPlugin *graphtft=cPluginManager::GetPlugin("graphtft");
//  if(graphtft) graphtft->SetupParse("CoverImage", coverpicture);
//}

// --- cPluginMP3 --------------------------------------------------------------

static const char *VERSION        = PLUGIN_VERSION;
static const char *DESCRIPTION    = "Next Generation audio player";
static const char *MAINMENUENTRY  = "MP3-NextGen";

class cPluginMp3 : public cPlugin {
public:
  cPluginMp3(void);
  virtual ~cPluginMp3();
  virtual const char *Version(void) { return VERSION; }
  virtual const char *Description(void) { return tr(DESCRIPTION); }
  virtual const char *CommandLineHelp(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Initialize(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void);
  virtual cOsdObject *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
  };

cPluginMp3::cPluginMp3(void)
{
  // Initialize any member varaiables here.
  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
}

cPluginMp3::~cPluginMp3()
{
  InfoCache.Save();
  delete mgr;
}

const char *cPluginMp3::CommandLineHelp(void)
{
  static char *help_str=0;
  
  free(help_str);    //                                     for easier orientation, this is column 80|
  asprintf(&help_str,"  -m CMD,   --mount=CMD    use CMD to mount/unmount/eject mp3 sources\n"
                     "                           (default: %s)\n"
                     "  -n CMD,   --network=CMD  execute CMD before & after network access\n"
                     "                           (default: %s)\n"
                     "  -C DIR,   --cache=DIR    store ID3 cache file in DIR\n"
                     "                           (default: video dir)\n"
                     "  -B DIR,   --cddb=DIR     search CDDB files in DIR\n"
                     "                           (default: %s)\n"
                     "  -D DIR,   --dsp=DIR      device for OSS output\n"
                     "                           (default: %s)\n",
                     mountscript,netscript?netscript:"none",
#ifdef HAVE_SNDFILE
                     cddbpath,
#else
                     "none",
#endif
#ifdef WITH_OSS
                     dspdevice
#else
                     "none"
#endif
                     );
  return help_str;
}

bool cPluginMp3::ProcessArgs(int argc, char *argv[])
{
  static struct option long_options[] = {
      { "mount",    required_argument, NULL, 'm' },
      { "network",  required_argument, NULL, 'n' },
      { "cddb",     required_argument, NULL, 'B' },
      { "dsp",      required_argument, NULL, 'D' },
      { "cache",    required_argument, NULL, 'C' },
      { NULL }
    };

  int c, option_index = 0;
  while((c=getopt_long(argc,argv,"c:i:m:n:B:C:D:",long_options,&option_index))!=-1) {
    switch (c) {
      case 'm': mountscript=optarg; break;
      case 'n': netscript=optarg; break;
      case 'C': cachedir=optarg; break;
      case 'B':
#ifdef HAVE_SNDFILE
                cddbpath=optarg; break;
#else
                fprintf(stderr, "mp3ng: libsndfile support has not been compiled in!\n"); return false;
#endif
      case 'D':
#ifdef WITH_OSS
                dspdevice=optarg; break;
#else
                fprintf(stderr, "mp3ng: OSS output has not been compiled in!\n"); return false;
#endif
      default:  return false;
      }
    }
  return true;
}

bool cPluginMp3::Initialize(void)
{
  if(!CheckVDRVersion(1,1,29,"mp3ng")) return false;
  i18n_name=Name();
  MP3Sources.Load(AddDirectory(ConfigDirectory(),"mp3sources.conf"));
  if(MP3Sources.Count()<1) {
     esyslog("ERROR: you should have defined at least one source in mp3sources.conf");
     fprintf(stderr,"No source(s) defined in mp3sources.conf\n");
     return false;
     }
  InfoCache.Load();
  RegisterI18n(Phrases);
  mgr=new cPlayManager;
  if(!mgr) {
    esyslog("ERROR: creating playmanager failed");
    fprintf(stderr,"Creating playmanager failed\n");
    return false;
    }
  d(printf("mp3ng: using %s\n",mad_version))
  d(printf("mp3ng: compiled with %s\n",MAD_VERSION))
  return true;
}

void cPluginMp3::Housekeeping(void)
{
  InfoCache.Save();
}

const char *cPluginMp3::MainMenuEntry(void)
{
  return MP3Setup.HideMainMenu ? 0 : tr(MAINMENUENTRY);
}

cOsdObject *cPluginMp3::MainMenuAction(void)
{
  return new cMenuMP3;
}

cMenuSetupPage *cPluginMp3::SetupMenu(void)
{
  return new cMenuSetupMP3;
}

bool cPluginMp3::SetupParse(const char *Name, const char *Value)
{
  if      (!strcasecmp(Name, "Theme"))            MP3Setup.osdtheme        = atoi(Value);
  else if (!strcasecmp(Name, "InitLoopMode"))     MP3Setup.InitLoopMode    = atoi(Value);
  else if (!strcasecmp(Name, "InitShuffleMode"))  MP3Setup.InitShuffleMode = atoi(Value);
  else if (!strcasecmp(Name, "AudioMode"))        MP3Setup.AudioMode       = atoi(Value);
  else if (!strcasecmp(Name, "BgrScan"))          MP3Setup.BgrScan         = atoi(Value);
  else if (!strcasecmp(Name, "EditorMode"))       MP3Setup.EditorMode      = atoi(Value);
  else if (!strcasecmp(Name, "DisplayMode"))      MP3Setup.DisplayMode     = atoi(Value);
  else if (!strcasecmp(Name, "BackgrMode"))       MP3Setup.BackgrMode      = atoi(Value);
  else if (!strcasecmp(Name, "MenuMode"))         MP3Setup.MenuMode        = atoi(Value);
  else if (!strcasecmp(Name, "TargetLevel"))      MP3Setup.TargetLevel     = atoi(Value);
  else if (!strcasecmp(Name, "LimiterLevel"))     MP3Setup.LimiterLevel    = atoi(Value);
  else if (!strcasecmp(Name, "Only48kHz"))        MP3Setup.Only48kHz       = atoi(Value);
  else if (!strcasecmp(Name, "UseProxy"))         MP3Setup.UseProxy        = atoi(Value);
  else if (!strcasecmp(Name, "ProxyHost"))        strn0cpy(MP3Setup.ProxyHost,Value,MAX_HOSTNAME);
  else if (!strcasecmp(Name, "ProxyPort"))        MP3Setup.ProxyPort       = atoi(Value);
  else if (!strcasecmp(Name, "UseCddb"))          MP3Setup.UseCddb         = atoi(Value);
  else if (!strcasecmp(Name, "CddbHost"))         strn0cpy(MP3Setup.CddbHost,Value,MAX_HOSTNAME);
  else if (!strcasecmp(Name, "CddbPort"))         MP3Setup.CddbPort        = atoi(Value);
  else if (!strcasecmp(Name, "AbortAtEOL"))       MP3Setup.AbortAtEOL      = atoi(Value);
  else if (!strcasecmp(Name, "AudioOutMode")) {
    MP3Setup.AudioOutMode = atoi(Value);
#ifndef WITH_OSS
    if(MP3Setup.AudioOutMode==AUDIOOUTMODE_OSS) {
      esyslog("WARNING: AudioOutMode OSS not supported, falling back to DVB");
      MP3Setup.AudioOutMode=AUDIOOUTMODE_DVB;
      }
#endif
    }
#if VDRVERSNUM >= 10307
  else if (!strcasecmp(Name, "ReplayDisplay"))      	MP3Setup.ReplayDisplay = atoi(Value);
#endif
  else if (!strcasecmp(Name, "HideMainMenu"))       	MP3Setup.HideMainMenu  = atoi(Value);
  else if (!strcasecmp(Name, "KeepSelect"))         	MP3Setup.KeepSelect    = atoi(Value);
  else if (!strcasecmp(Name, "ExitClose"))         	MP3Setup.ExitClose     = atoi(Value);
  else if (!strcasecmp(Name, "CanScroll"))         	MP3Setup.CanScroll                 = atoi(Value);
  else if (!strcasecmp(Name, "CanSkip"))           	MP3Setup.CanSkip                   = atoi(Value);
  else if (!strcasecmp(Name, "Skiptime"))          	MP3Setup.Skiptime                  = atoi(Value);
  else if (!strcasecmp(Name, "Jumptime"))          	MP3Setup.Jumptime                  = atoi(Value);
  else if (!strcasecmp(Name, "CopyDir"))           	strn0cpy(MP3Setup.CopyDir,Value,sizeof(MP3Setup.CopyDir));
  else if (!strcasecmp(Name, "RecordDir"))           	strn0cpy(MP3Setup.RecordDir,Value,sizeof(MP3Setup.RecordDir));
  else if (!strcasecmp(Name, "OSDoffsetx"))        	MP3Setup.OSDoffsetx                = atoi(Value);
  else if (!strcasecmp(Name, "OSDoffsety"))        	MP3Setup.OSDoffsety                = atoi(Value);
  else if (!strcasecmp(Name, "Rowcount"))        	MP3Setup.Rowcount                  = atoi(Value);

  else return false;
  return true;
}


VDRPLUGINCREATOR(cPluginMp3); // Don't touch this!
