/*
 * Karaoke plugin to VDR (C++)
 *
 * 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-karaoke.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"

#include "karaoke.h"
#include "song.h"

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

// --- 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(  );
}

// --- CKaraokeControl
// --------------------------------------------------------

CKaraokeControl::CKaraokeControl( 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, "karaoke", 0, true );

  printf( "CKaraokeControl::ctor()\n" );

  clrBackground = mp3Theme[KaraokeSetup.osdtheme].clrBackground;
  clrBackground2 = mp3Theme[KaraokeSetup.osdtheme].clrBackground2;
  clrNormalText = mp3Theme[KaraokeSetup.osdtheme].clrNormalText;
  clrTracklistText = mp3Theme[KaraokeSetup.osdtheme].clrTracklistText;
  clrListItemActiveBackground =
    mp3Theme[KaraokeSetup.osdtheme].clrListItemActiveBackground;
  clrListItemActiveText =
    mp3Theme[KaraokeSetup.osdtheme].clrListItemActiveText;
  clrProgress = mp3Theme[KaraokeSetup.osdtheme].clrProgress;
  clrProgressBackground =
    mp3Theme[KaraokeSetup.osdtheme].clrProgressBackground;
}

cBitmap CKaraokeControl::bmShuffle( shuffle_xpm );
cBitmap CKaraokeControl::bmLoop( loop_xpm );
cBitmap CKaraokeControl::bmCopy( copy_xpm );
CKaraokeControl::~CKaraokeControl(  )
{
  printf( "CKaraokeControl::dtor()\n" );

  delete lastMode;
  Hide(  );
  Stop(  );
}
void CKaraokeControl::Stop( void )
{
  cStatus::MsgReplaying( this, 0, 0, false );
  delete player;
  player = 0;
  printf( "CKaraokeControl::Stop()\n" );
  mgr->Halt(  );
  mgr->Flush(  );               // XXX remove later
}

bool CKaraokeControl::SetPlayList( cPlayList * plist )
{
  bool res;
  cControl *control = cControl::Control(  );
  // is there a running MP3 player?
  if( control && typeid( *control ) == typeid( CKaraokeControl ) )
  {
    // add songs to running playlist
    mgr->Add( plist );
    res = true;
  }
  else
  {
    mgr->Flush(  );
    mgr->Add( plist );
    cControl::Launch( new CKaraokeControl );
    res = false;
  }
  delete plist;
  return res;
}
void CKaraokeControl::ShowTimed( int Seconds )
{
  // if(!visible) {
  // ShowProgress(true);
  if( Seconds > 0 )
    timeoutShow = time( 0 ) + Seconds;
  // }
}
void CKaraokeControl::Hide( void )
{
  HideStatus(  );
  if( visible )
  {
    delete osd;
    osd = 0;
    visible = false;
#if APIVERSNUM >= 10500
    SetNeedsFastResponse(false);
    #else
    needsFastResponse =  false;
#endif    
  }
}
void CKaraokeControl::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 CKaraokeControl::HideStatus( void )
{
  if( statusActive )
  {
    osd->RestoreRegion(  );
    osd->Flush(  );
  }
  statusActive = false;
}
void CKaraokeControl::LoadCover( void )
{
  if( coverpicture < " " )
    printf( "karaoke: 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 CKaraokeControl::Flush( void )
{
  if( osd )
    osd->Flush(  );
}


extern int cptTimeOut;
extern int Refreshthread;
extern int stopThread;

extern void *refreshThread( void *ptr );

void CKaraokeControl::ShowProgress( bool open )
{
  pthread_t thread1;
  cptTimeOut = 0;

  if( !visible )
  {
    visible = 1;
    stopThread = 0;
    if( Refreshthread == 0 )
    {
      printf( "Creating refresh thread\n" );
      Refreshthread = 1;
      pthread_create( &thread1, NULL, refreshThread, ( void * ) this );

    }
  }
}

void CKaraokeControl::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 CKaraokeControl::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 CKaraokeControl::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 CKaraokeControl::Jump( void )
{
  jumpmm = 0;
  jumphide = jumpsecs = false;
  if( !visible )
  {
    ShowTimed(  );
    if( !visible )
      return;
    jumphide = true;
  }
  JumpDisplay(  );
  jumpactive = true;
  fliptime = 0;
  flip = -1;
}
void CKaraokeControl::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, KaraokeSetup.CopyDir );
  copyscript = popen( buffer, "r" );
  dsyslog( "copy '%s' to '%s'", Songname, KaraokeSetup.CopyDir );
  free( buffer );
  pclose( copyscript );
}
void CKaraokeControl::StartRecord( void )
{
  dsyslog( "karaoke: Started Recording and playback of Relaystream\n" );
}

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

eOSState CKaraokeControl::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 && KaraokeSetup.CanScroll )
    {
      scrollmode = true;
    }
    // if(!player->PrevCheck()) mgr->Prev();
    mgr->Prev(  );
    player->Play(  );
    break;
  case kDown:
  case kDown | k_Repeat:
    if( !scrollmode && KaraokeSetup.CanScroll )
    {
      scrollmode = true;
    }
    mgr->Next(  );
    player->Play(  );
    break;
  case kLeft:
  case kLeft | k_Repeat:
    if( !scrollmode && KaraokeSetup.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( -KaraokeSetup.Jumptime );
    break;
  case kRight:
  case kRight | k_Repeat:
    if( !scrollmode && KaraokeSetup.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( KaraokeSetup.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( KaraokeSetup.CanSkip )
    {
      if( !player->IsStream(  ) )
        player->SkipSeconds( -KaraokeSetup.Skiptime );
    }
    else
    {
      if( scrollmode )
        scrollmode = false;
      mgr->Prev(  );
      player->Play(  );
    }
    break;
  case kYellow:
  case kYellow | k_Repeat:
    if( KaraokeSetup.CanSkip )
    {
      if( !player->IsStream(  ) )
        player->SkipSeconds( +KaraokeSetup.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 && KaraokeSetup.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[KaraokeSetup.osdtheme].clrBackground;
        clrBackground2 = mp3Theme[KaraokeSetup.osdtheme].clrBackground2;
        clrNormalText = mp3Theme[KaraokeSetup.osdtheme].clrNormalText;
        clrTracklistText = mp3Theme[KaraokeSetup.osdtheme].clrTracklistText;
        clrListItemActiveBackground =
          mp3Theme[KaraokeSetup.osdtheme].clrListItemActiveBackground;
        clrListItemActiveText =
          mp3Theme[KaraokeSetup.osdtheme].clrListItemActiveText;
        clrProgress = mp3Theme[KaraokeSetup.osdtheme].clrProgress;
        clrProgressBackground =
          mp3Theme[KaraokeSetup.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 && KaraokeSetup.CanScroll )
    {
      mgr->Goto( oldtracknr );
      scrollmode = false;
      pressedOk = true;
      ShowProgress( true );
    }
    else
    {
      Hide(  );
      cRemote::CallPlugin( "karaoke" );
      if( KaraokeSetup.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( KaraokeSetup.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( KaraokeSources.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;
}

// --- cMenuKaraokeItem -----------------------------------------------------
class cMenuKaraokeItem:public cOsdItem
{
private:
  cPlayList * playlist;
  virtual void Set( void );
public:
  cMenuKaraokeItem( cPlayList * PlayList );
  cPlayList *List( void )
  {
    return playlist;
  }
};
cMenuKaraokeItem::cMenuKaraokeItem( cPlayList * PlayList )
{
  playlist = PlayList;
  Set(  );
}
void cMenuKaraokeItem::Set( void )
{
  char *buffer = 0;
  asprintf( &buffer, " %s", playlist->BaseName(  ) );
  SetText( buffer, false );
}

// --- cMenuKaraoke --------------------------------------------------------
class cMenuKaraoke: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:
  cMenuKaraoke( void );
   ~cMenuKaraoke( void );
  virtual eOSState ProcessKey( eKeys Key );
};
cMenuKaraoke::cMenuKaraoke( void ):cOsdMenu( tr( "karaoke" ) )
{
  renaming = sourcing = instanting = false;
  lists = new cPlayLists;
  ScanLists(  );
  SetButtons( 1 );
  if( KaraokeSetup.MenuMode )
    Instant( false );
}
cMenuKaraoke::~cMenuKaraoke( void )
{
  delete lists;
}
eOSState cMenuKaraoke::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 cMenuKaraoke::ScanLists( void )
{
  Clear(  );
  Status( tr( "Scanning playlists..." ) );
  bool res = lists->Load( KaraokeSources.GetSource(  ) );
  Status( 0 );
  if( res )
  {
    cPlayList *plist = lists->First(  );
    while( plist )
    {
      Add( new cMenuKaraokeItem( plist ) );
      plist = lists->Next( plist );
    }
  }
  else
    Error( tr( "Error scanning playlists!" ) );
}
eOSState cMenuKaraoke::Delete( void )
{
  if( Count(  ) > 0 )
  {
    if( Interface->Confirm( tr( "Delete playlist?" ) ) &&
        Interface->Confirm( tr( "Are you sure?" ) ) )
    {
      cPlayList *plist =
        ( ( cMenuKaraokeItem * ) Get( Current(  ) ) )->List(  );
      if( plist->Delete(  ) )
      {
        lists->Del( plist );
        cOsdMenu::Del( Current(  ) );
        Display(  );
      }
      else
        Error( tr( "Error deleting playlist!" ) );
    }
  }
  return osContinue;
}

eOSState cMenuKaraoke::New( void )
{
  cPlayList *plist = new cPlayList( KaraokeSources.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 cMenuKaraokeItem( plist ), true );
    isyslog( "karaoke: playlist %s added", plist->Name(  ) );
    return AddSubMenu( new cMenuPlayList( plist ) );
  }
  Error( tr( "Error creating playlist!" ) );
  delete plist;
  return osContinue;
}

eOSState cMenuKaraoke::Rename( bool second )
{
  if( HasSubMenu(  ) || Count(  ) == 0 )
    return osContinue;
  cPlayList *plist = ( ( cMenuKaraokeItem * ) 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 cMenuKaraoke::Edit( void )
{
  if( HasSubMenu(  ) || Count(  ) == 0 )
    return osContinue;
  cPlayList *plist = ( ( cMenuKaraokeItem * ) Get( Current(  ) ) )->List(  );
  if( !plist->Load(  ) )
    Error( tr( "Error loading playlist!" ) );
  else if( !plist->IsWinAmp(  ) )
  {
    isyslog( "karaoke: editing playlist %s", plist->Name(  ) );
    return AddSubMenu( new cMenuPlayList( plist ) );
  }
  else
    Error( tr( "Can't edit a WinAmp playlist!" ) );
  return osContinue;
}

eOSState cMenuKaraoke::Play( void )
{
  if( HasSubMenu(  ) || Count(  ) == 0 )
    return osContinue;
  Status( tr( "Loading playlist..." ) );
  cPlayList *newpl =
    new cPlayList( ( ( cMenuKaraokeItem * ) Get( Current(  ) ) )->List(  ) );
  if( newpl->Load(  ) && newpl->Count(  ) )
  {
    isyslog( "mp3-MKIV: playback started with playlist %s", newpl->Name(  ) );
    CKaraokeControl::SetPlayList( newpl );
    if( KaraokeSetup.KeepSelect )
    {
      Status( 0 );
      return osContinue;
    }
    return osEnd;
  }
  Status( 0 );
  delete newpl;
  Error( tr( "Error loading playlist!" ) );
  return osContinue;
}

eOSState cMenuKaraoke::Source( bool second )
{
  if( HasSubMenu(  ) )
    return osContinue;
  if( !second )
  {
    sourcing = true;
    return AddSubMenu( new
                       cMenuSource( &KaraokeSources,
                                    tr( "Karaoke source" ) ) );
  }
  sourcing = false;
  cFileSource *src = cMenuSource::GetSelected(  );
  if( src )
  {
    KaraokeSources.SetSource( src );
    ScanLists(  );
    Display(  );
  }
  return osContinue;
}

eOSState cMenuKaraoke::Instant( bool second )
{
  if( HasSubMenu(  ) )
    return osContinue;
  if( !second )
  {
    instanting = true;
    return AddSubMenu( new
                       cMenuInstantBrowse( KaraokeSources.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( "karaoke: playback started with instant playlist %s",
               newpl->Name(  ) );
      CKaraokeControl::SetPlayList( newpl );
      if( KaraokeSetup.KeepSelect )
      {
        Status( 0 );
        return Instant( false );
      }
      return osEnd;
    }
    Status( 0 );
    delete newpl;
    Error( tr( "Error building playlist!" ) );
  }
  return osContinue;
}

eOSState cMenuKaraoke::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;
}

// --- cPluginKaraoke
// --------------------------------------------------------------
static const char *VERSION = PLUGIN_VERSION;
static const char *DESCRIPTION = "a plugin karaoke";
static const char *MAINMENUENTRY = "Karaoke";
class cPluginKaraoke:public cPlugin
{
public:
  cPluginKaraoke( void );
  virtual ~ cPluginKaraoke(  );
  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 );
};
cPluginKaraoke::cPluginKaraoke( 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!
}

cPluginKaraoke::~cPluginKaraoke(  )
{
  InfoCache.Save(  );
  delete mgr;
}
const char *cPluginKaraoke::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 cPluginKaraoke::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,
               "karaoke: libsndfile support has not been compiled in!\n" );
      return false;
#endif
    case 'D':
#ifdef WITH_OSS
      dspdevice = optarg;
      break;
#else
      fprintf( stderr, "karaoke: OSS output has not been compiled in!\n" );
      return false;
#endif
    default:
      return false;
    }
  }
  return true;
}

bool cPluginKaraoke::Initialize( void )
{
  if( !CheckVDRVersion( 1, 1, 29, "karaoke" ) )
    return false;
  i18n_name = Name(  );
  KaraokeSources.
    Load( AddDirectory( ConfigDirectory(  ), "karaokesources.conf" ) );
  if( KaraokeSources.Count(  ) < 1 )
  {
    esyslog
      ( "ERROR: you should have defined at least one source in 'karaokesources.conf'" );
    fprintf( stderr, "No source(s) defined in 'karaokesources.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( "karaoke: using %s\n", mad_version ) )
    d( printf( "karaoke: compiled with MAD v%s\n", MAD_VERSION ) )
    return true;
}
void cPluginKaraoke::Housekeeping( void )
{
  InfoCache.Save(  );
}
const char *cPluginKaraoke::MainMenuEntry( void )
{
  return KaraokeSetup.HideMainMenu ? 0 : tr( MAINMENUENTRY );
}

cOsdObject *cPluginKaraoke::MainMenuAction( void )
{
  return new cMenuKaraoke;
}

cMenuSetupPage *cPluginKaraoke::SetupMenu( void )
{
  return new cMenuSetupKaraoke;
}

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

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