///////////////////////////////////////////////////
// activy3xx LCD plugin for VDR
//
// Original written by:
//
// Meinrad Sauter <meinrad.sauter@gmx.de>
// andreas 'randy' weinberger <vdr@smue.org>
//
// rearranged, beautified and rewritten by:
// Markus Geisler <geisler@primusnetz.de>
//
// optimized by:
// Sascha Quint <sascha.quint@gmx.net>
//
// rearranged, beautified and rewritten by:
// Helmut Auer <helmut@helmutauer.de>
//
/////////////////////////////////////////////////////////

#ifndef __ALCD_THREAD_CPP
#define __ALCD_THREAD_CPP

#include "alcd-thread.h"
#include <vdr/player.h>
#include <vdr/menu.h>
#include <math.h>

#define MOD_TV     0
#define MOD_REPLAY 1
#define MOD_OSD    2

#define REPLAY_PLAY  ">"
#define REPLAY_PAUSE "||"
#define REPLAY_FF    ">>"
#define REPLAY_FB    "<<"

#define VDR_LOCKED ( lockTimer == -1 )

extern int SleepTime;
extern int MsgTime;
extern char ProgChar;

// Konstruktor
cLCD::cLCD()
{
   // waittimer for status messages
   stop = false;
   running = true;
   displayMode = MOD_TV;
   mLastVolume = 0;

   infoTimer = 0;
   lockTimer = 0;

   lLED1 = lLED2 = lLEDPower = -1;
   szProgName[0] = '\0';
   szReplayName[0] = '\0';
   szReplayMode[0] = '\0';
   szOsdTitle[0] = '\0';
   szOsdText[0] = '\0';
   szMsgText1[0] = '\0';
   szMsgText2[0] = '\0';
   PlayControl = NULL;
   currentChannel = 0;

   // new instance of LCD
   myLCD = new activyLCD();
}

/////////////////////////////////////////////////////////
// Destruktor
cLCD::~cLCD()
{
   stop = true;
   while( running ) usleep( 10000 );
   delete myLCD;
}


void cLCD::Lock( bool bStatus )
{
   cMutexLock lock( &_mutex );
   if( bStatus )
      lockTimer = -1;
   else
      lockTimer = 0;

   lLED1 = lLED2 = lLEDPower = -1;

   myLCD->Lock( bStatus );
   _doUpdate.Broadcast();
}


void cLCD::SetText( const char *szText )
{
   cMutexLock lock( &_mutex );
#ifdef DEBUG
   isyslog( "SetText <%s>", szText );
#endif

   // only proceed, if valid data
   if( szText != NULL && strlen( szText ) > 0 ) {
      // set channel name
      char *delim = strchr( szText, '|' );
      if( delim ) {
         char buffer[ BUFSIZE ];
         int sLen = ( delim - szText ) >= BUFSIZE ? BUFSIZE : ( delim - szText );
         strncpy( buffer, szText, sLen );
         buffer[ sLen ] = '\0';
         myLCD->FormatString( szMsgText1, buffer, BUFSIZE, false, true );
         myLCD->FormatString( szMsgText2, delim + 1, BUFSIZE, false, true );
      }
      else {
         myLCD->FormatString( szMsgText1, szText, BUFSIZE, true, true );
         strncpy( szMsgText2, szMsgText1 + strlen( szMsgText1 ) + 1, sizeof( szMsgText1 ) - strlen( szMsgText1 ) - 1 );
      }
   }
   else
      szMsgText1[0] = szMsgText2[0] = '\0';

   if( lockTimer != -1 )
      lockTimer = 5;

#ifdef DEBUG
   isyslog( "EndSetText <%s><%s>", szMsgText1, szMsgText2 );
#endif
   _doUpdate.Broadcast();
}


void cLCD::SetLED( bool bLED1, bool bLED2 )
{
   cMutexLock lock( &_mutex );

   if( bLED1 == true )
      lLED1 = LED_ON;
   else
      lLED1 = LED_OFF;

   if( bLED2 == true )
      lLED2 = LED_ON;
   else
      lLED2 = LED_OFF;

   if( lockTimer != -1 )
      lockTimer = 5;

   _doUpdate.Broadcast();
}


void cLCD::SetPowerLED( int lPwrLED )
{
   cMutexLock lock( &_mutex );

   lLEDPower = lPwrLED;

   if( lockTimer != -1 )
      lockTimer = 5;

   _doUpdate.Broadcast();
}

void cLCD::SetTextPermanent( bool bStatus )
{
   cMutexLock lock( &_mutex );

   myLCD->SetTextPermanent( bStatus );

   _doUpdate.Broadcast();
}

/////////////////////////////////////////////////////////
// Set Channel Name
void cLCD::ChannelSwitch( const cDevice *device, int chNum )
{
#ifdef DEBUG
   isyslog( "ChannelSwitch called with Device %d and ChanNr %d",device->IsPrimaryDevice(),chNum );
#endif
   const char * szChN;

   if( chNum && device->IsPrimaryDevice() && chNum == cDevice::CurrentChannel() && currentChannel != chNum ) {
      cMutexLock lock( &_mutex );
      szChN = Channels.GetByNumber( chNum )->Name();
      currentChannel = chNum;
      // only proceed, if valid data
      if( szChN != NULL && strlen( szChN ) > 0 ) {
         // set channel name
         myLCD->FormatString( szChannelName, szChN, sizeof( szChannelName ), false, false );
      }
      else {
         memset( szChannelName, ' ', sizeof( szChannelName ) );
         szChannelName[ sizeof( szChannelName ) - 1 ] = '\0';
      }
      memset( szProgName, ' ', sizeof( szProgName ) );
      szProgName[ sizeof( szProgName ) - 1 ] = '\0';
      _doUpdate.Broadcast();
   }
#ifdef DEBUG
   isyslog( "Out ChannelSwitch" );
#endif
}

/////////////////////////////////////////////////////////
// Set Program
void cLCD::OsdProgramme( time_t tPresentTime, const char *szPresentTitle, const char *szPresentSubtitle,
                        time_t tFollowingTime, const char *szFollowingTitle, const char *szFollowingSubtitle )
{
#ifdef DEBUG
   isyslog( "In OsdProg <%s>", szPresentTitle );
#endif
   cMutexLock lock( &_mutex );

   // only proceed, if valid data
   if( szPresentTitle != NULL && strlen( szPresentTitle ) > 0 )
      // set channel name
      myLCD->FormatString( szProgName, szPresentTitle, sizeof( szProgName ), false, true );
//   else {
//      memset( szProgName, ' ', sizeof( szProgName ) );
//      szProgName[ sizeof( szProgName ) - 1 ] = '\0';
//   }
   _doUpdate.Broadcast();
#ifdef DEBUG
   isyslog( "Out OsdProg" );
#endif
}

/////////////////////////////////////////////////////////
// OsdChannel

void cLCD::OsdChannel( const char *szText )
{
#ifdef DEBUG
   isyslog( "In OsdChannel <%s>", szText );
#endif
   if( szText && ! VDR_LOCKED ) {
      cMutexLock lock( &_mutex );
      displayMode = savedMode = MOD_TV;
      myLCD->FormatString( szMsgText1, szText, BUFSIZE, false, true );
      szMsgText2[0] = '\0';
      SetInfoTimer();

      _doUpdate.Broadcast();
   }
#ifdef DEBUG
   isyslog( "Out OsdChannel" );
#endif
}



/////////////////////////////////////////////////////////
// Set Recording
void cLCD::Recording( const cDevice *device, const char *szName, const char *szFileName, bool On )
{
#ifdef DEBUG
   isyslog( "In Recording <%s>", szName );
#endif
   if( ! VDR_LOCKED ) {
      cMutexLock lock( &_mutex );

      // only proceed, if valid data
      if( On ) {
         RecordingInfo info;
         info.device = device;
         info.name = szName;
         _recordings.push_back( info );

         myLCD->FormatString( szMsgText1, tr( "Recording started:" ), BUFSIZE, false, true );
         myLCD->FormatString( szMsgText2, szName, BUFSIZE, false, true );
      }
      else {
         Recordings::iterator it = _recordings.begin();
         szMsgText2[0] = '\0';
         for( ; it != _recordings.end(); ++it ) {
            if( ( *it ).device == device && StoppedTimer( ( *it ).name.c_str() ) ) {
               myLCD->FormatString( szMsgText2, ( *it ).name.c_str(), BUFSIZE, false, true );
               _recordings.erase( it );
               break;
            }
         }
         myLCD->FormatString( szMsgText1, tr( "Recording stopped:" ), BUFSIZE, false, true );
      }
      SetInfoTimer();
      _doUpdate.Broadcast();
   }
#ifdef DEBUG
   isyslog( "Out Recording" );
#endif
}


/////////////////////////////////////////////////////////
// Replay Control
void cLCD::Replaying( const cControl *control, const char *szName, const char *szFileName, bool On )
{
#ifdef DEBUG
   isyslog( "In replay <%s><%s>", szName ? szName : "", szFileName ? szFileName : "" );
#endif
   cMutexLock lock( &_mutex );
   if( On ) {
      char szTemp[256];
      displayMode = savedMode = MOD_REPLAY;
      PlayControl = ( cControl * )control;
      memset( szTemp, 0, sizeof( szTemp ) );
      memset( szReplayMode, 0, sizeof( szReplayMode ) );

      if( szName && !isempty(szName) ) {
         bool bFound = false;
         bool bIsFileName = false;
         int slen = 0;
         if( !isempty(szFileName) )
            slen = strlen(szFileName);

         ///////////////////////////////////////////////////////////////////////
         //Looking for mp3-Plugin Replay : [LS] (444/666) title
         //
         if( slen > 6 &&
             szName[0]=='[' &&
             szName[3]==']' &&
             szName[5]=='(' ) {
            unsigned int i;
            for( i=6; szName[i] != '\0'; ++i ) { //search for [xx] (xxxx) title
               if( szName[i]==' ' && szName[i-1]==')' ) {
                  bFound = true;
                  break;
               }
            }
            if( bFound ) { //found MP3-Plugin replaymessage
               unsigned int j;
               // get loopmode
               strncpy( szReplayMode, szName + 6, i - 7 );
               //printf ("loopmode=<%s>\n", replay.loopmode.c_str ());
               for( j=0; szName[i+j] != '\0';++j ) { //trim name
                  if( szName[i+j]!=' ' )
                     break;
               }

               if( strlen(szName+i+j) > 0 ) //if name isn't empty, then copy
                  strncpy( szTemp, szName + i + j, sizeof( szTemp ) );
               else //if Name empty, set fallback title
                  strncpy( szTemp, tr("Unknown title"), sizeof( szTemp ) );
            }
         }
         ///////////////////////////////////////////////////////////////////////
         //Looking for DVD-Plugin Replay : 1/8 4/28,  de 2/5 ac3, no 0/7,  16:9, VOLUMENAME
         //cDvdPlayerControl::GerDisplayHeaderLine
         //                         titleinfo, audiolang,  spulang, aspect, title
         if( !bFound ) {
            slen = strlen(szName);
            if( slen>7 ) {
               unsigned int i,n;
               for( n=0,i=0; szName[i] != '\0';++i ) { //search volumelabel after 4*", " => xxx, xxx, xxx, xxx, title
                  if( szName[i]==' ' && szName[i-1]==',' ) {
                     if( ++n == 4 ) {
                        bFound = true;
                        break;
                     }
                  }
               }
               if( bFound ) { //found DVD replaymessage
                  unsigned int j;
                  for( j=0;szName[i+j] != '\0';++j ) { //trim name
                     if( szName[i+j] != ' ' )
                        break;
                  }

                  if( strlen(szName+i+j) > 0 ) { // if name isn't empty, then copy
                     strncpy( szTemp, szName + i + j, sizeof( szTemp ) );
                     // replace all '_' with ' '
                     for( j = 0; j < sizeof( szTemp ) && szTemp[j] != '\0'; ++j ) {
                        if( szTemp[j] == '_' )
                           szTemp[j] = ' ';
                        // KAPITALIZE -> Kaptialize
                        else if( j > 0 && szTemp[j-1] != ' ' )
                           szTemp[j] = tolower(szTemp[j]);
                     }
                  }
                  else  //if Name empty, set fallback title
                     strncpy( szTemp, tr("Unknown title"), sizeof( szTemp ) );
               }
            }
         }
         if( !bFound ) {
            slen = strlen(szName);
            int i;
            for( i=slen-1;i>0;--i ) { //Reversesearch last Subtitle
               // - filename contains '~' => subdirectory
               // or filename contains '/' => subdirectory
               if( szName[i] == '~' ) {
                  strncpy( szTemp, szName + i + 1, sizeof( szReplayName ) );
                  bFound = true;
                  break;
               }
               else {
                  if( szName[i] == '/' ) {
                     // look for file extentsion like .xxx or .xxxx
                     if( slen>5 && ( szName[slen-4] == '.' || szName[slen-5] == '.' ) )
                        bIsFileName = true;
                     else
                        break;
                  }
               }
            }
         }

         if( 0 == strncmp(szName,"[image] ",8) ) {
            if( bIsFileName != true ) //if'nt already Name stripped-down as filename
               strncpy( szTemp, szName + 8, sizeof(szTemp ));

            bFound = true;
         }
         else if( 0 == strncmp(szName,"[audiocd] ",10) ) {
            strncpy( szTemp, szName + 10, sizeof(szTemp ));
            bFound = true;
         }
         else if( 0 == strncmp(szName,"cdda",4) ) {
            int i = 8;
            if ( szFileName[7] == '(' ) {
               while ( szFileName[i] != '\0' && szFileName[i] != ')' )
                  i++;

               if( szFileName[i] == ')' )
                  strncpy( szReplayMode, szFileName + 8, i - 8 );
            }
            char *p = strstr( szFileName + i, " : " );
            if (p)
               strncpy( szTemp, p + 3, sizeof(szTemp ));
            else
               strncpy( szTemp, szFileName + i + 2, sizeof(szTemp ));

            bFound = true;
         }

         if( !bFound )
            strncpy( szTemp, szName, sizeof(szTemp ));
      }

      myLCD->FormatString( szReplayName, szTemp, sizeof( szReplayName ), false, true );
   }
   else {
      memset( szReplayMode, 0, sizeof( szReplayMode ) );
      memset( szReplayName, 0, sizeof( szReplayName ) );
      if( displayMode != MOD_OSD )
         displayMode = MOD_TV;

      savedMode = MOD_TV;
      PlayControl = NULL;
   }
   _doUpdate.Broadcast();
#ifdef DEBUG
   isyslog( "Out replay" );
#endif
}
/////////////////////////////////////////////////////////
// Menu Control

void cLCD::OsdCurrentItem( const char *szText )
{
#ifdef DEBUG
   isyslog( "In OsdCurr <%s>", szText );
#endif
   cMutexLock lock( &_mutex );
   if( szText ) {
      displayMode = MOD_OSD;

      myLCD->FormatString( szOsdText, szText, sizeof( szOsdText ), false, true );
   }
   _doUpdate.Broadcast();
#ifdef DEBUG
   isyslog( "Out OsdCurr" );
#endif
}


/////////////////////////////////////////////////////////
// Set Title

void cLCD::OsdTitle( const char *szText )
{
#ifdef DEBUG
   isyslog( "In OsdTitle <%s>", szText );
#endif
   cMutexLock lock( &_mutex );
   if( szText ) {
      displayMode = MOD_OSD;

      myLCD->FormatString( szOsdTitle, szText, sizeof( szOsdTitle ), false, true );
   }
   _doUpdate.Broadcast();
#ifdef DEBUG
   isyslog( "Out OsdTitle" );
#endif
}


/////////////////////////////////////////////////////////
// Status Information

void cLCD::OsdStatusMessage( const char *szText )
{
#ifdef DEBUG
   isyslog( "In StatMsg <%s>", szText );
#endif
   if( szText && ! VDR_LOCKED ) {
      cMutexLock lock( &_mutex );
      myLCD->FormatString( szMsgText1, szText, BUFSIZE, true, true );
      strncpy( szMsgText2, szMsgText1 + strlen( szMsgText1 ) + 1, sizeof( szMsgText1 ) - strlen( szMsgText1 ) - 1 );
      SetInfoTimer();
      _doUpdate.Broadcast();
   }
#ifdef DEBUG
   isyslog( "Out StatMsg" );
#endif
}


/////////////////////////////////////////////////////////
// Menu Control

void cLCD::OsdClear()
{
#ifdef DEBUG
   isyslog( "In OsdClear" );
#endif
   cMutexLock lock( &_mutex );
   displayMode = savedMode;
   _doUpdate.Broadcast();
#ifdef DEBUG
   isyslog( "Out OsdClear" );
#endif
}


/////////////////////////////////////////////////////////
// Set Volume

void cLCD::SetVolume( int volume, bool Absolute )
{
#ifdef DEBUG
   isyslog( "In SetVol" );
#endif
   if ( Absolute )
      mLastVolume = volume;
   else
      mLastVolume += volume;

   if( ! VDR_LOCKED ) {
      cMutexLock lock( &_mutex );
      int vol = ( mLastVolume * 18 ) / 255;

      myLCD->FormatString( szMsgText1, ( char * )tr( "Volume" ), BUFSIZE, false, true );
      szMsgText2[0] = '[';
      if (( volume == 0 ) && ( Absolute ))
         strcpy( szMsgText2 + 1, "    -- MUTE --    " );
      else {
         memset( szMsgText2 + 1, '#', vol );
         memset( szMsgText2 + 1 + vol, ' ', 18 - vol );
      }
      strcpy( szMsgText2 + 19,"]" );

      SetInfoTimer();
      _doUpdate.Broadcast();
   }
#ifdef DEBUG
   isyslog( "Out SetVol" );
#endif
}

/////////////////////////////////////////////////////////
// SQ: get Programm
//     aktuelle Programminformation ides laufenden Kanals vom VDR lesen
void cLCD::GetProgram()
{
   // read current Programme from VDR
   const cEvent * present = NULL;
   cSchedulesLock schedulesLock;
   const cSchedules * schedules = cSchedules::Schedules( schedulesLock );
   bool bFound = false;

   if( schedules && currentChannel ) {
      cChannel *ChID = Channels.GetByNumber( currentChannel );
      if( ChID ) {
         const cSchedule * schedule = schedules->GetSchedule( ChID->GetChannelID() );
         if( schedule ) {
            if( ( present = schedule->GetPresentEvent() ) != NULL ) {
               // set channel name
               myLCD->FormatString( szProgName, present->Title(), sizeof( szProgName ), false, true );
               bFound = true;
            }
         }
      }
   }
   if( bFound == false ) {
      memset( szProgName, ' ', sizeof( szProgName ) );
      szProgName[ sizeof( szProgName ) - 1 ] = '\0';
   }
}


/////////////////////////////////////////////////////////
// Thread Action

void cLCD::Action( void )
{
   static int updCounter = 0;
   struct timeval now;
   running=true;
   char szBufLine[BUFSIZE];
   char * szLine[LCD_ROWS] = { NULL, NULL};


   usleep( 1000000 );
   _mutex.Lock(); // mutex gets ONLY unlocked when sleeping
   while( !stop ) {
#ifdef DEBUG
      isyslog( "Action Thread" );
#endif
      gettimeofday( &now,NULL );

      if( lockTimer == 0 ) {
         switch( displayMode ) {
            //
            // case 0 : Channeldisplay
            //
            case MOD_TV:
               {
                  struct tm uhr;
                  time_t val;
                  val = time( NULL );
                  localtime_r( &val,&uhr );

                  snprintf( szBufLine,sizeof( szBufLine ),"%-14.14s %02d:%02d", szChannelName, uhr.tm_hour, uhr.tm_min );
                  szLine[0] = szBufLine;

                  // SQ: ab und zu mal die Programminformation nachlesen
                  //     die Sendung koennte sich ja geaendert haben
                  if( ++updCounter > 20 ) {
                     GetProgram();
                     updCounter = 0;
                  }
                  szLine[1] = szProgName;
                  break;
               }
            case MOD_REPLAY:
               {
                  char * szPlaySymbol = REPLAY_PLAY;
                  char szProgress[5];
                  int Current = 0, Total = 0, Speed = 0;
                  bool Play, Forward;

                  szProgress[0] = '\0';

                  if( PlayControl && PlayControl->GetReplayMode( Play,Forward,Speed ) ) {
                     if( Speed == -1 ) {
                        if( Play )
                           szPlaySymbol = REPLAY_PLAY;
                        else
                           szPlaySymbol = REPLAY_PAUSE;
                     }
                     else {
                        if( Forward )
                           szPlaySymbol = REPLAY_FF;
                        else
                           szPlaySymbol = REPLAY_FB;
                     }
                  }

                  if( PlayControl && PlayControl->GetIndex( Current,Total ) ) {
                     unsigned int pp;

                     if( Total >= 1 && Current <= Total && Current >= 0 ) {
                        pp = 1 + (( 4*Current ) / Total);
                        if( pp >= sizeof( szProgress ) )
                           pp = sizeof( szProgress ) - 1;
                     }
                     else
                        pp = 0;


                     memset( szProgress, ProgChar, pp );
                     szProgress[ pp ] = '\0';

                     if( strlen( szReplayMode) > 0 )
                        snprintf( szBufLine,sizeof( szBufLine ),
                                    "%s%-5.5s %-5.5s (%-5.5s)",
                                       szPlaySymbol,
                                       szReplayMode,
                                       (( const char * )IndexToHMSF( Current,false )) + 2,
                                       (( const char * )IndexToHMSF( Total,false )) + 2 );
                     else
                        snprintf( szBufLine,sizeof( szBufLine ),
                                    "%-2.2s %-7.7s (%-7.7s)",
                                       szPlaySymbol,
                                       ( const char * )IndexToHMSF( Current,false ),
                                       ( const char * )IndexToHMSF( Total,false ) );
                     szLine[0] = szBufLine;
                  }
                  szLine[1] = szReplayName;

                  break;
               }
            case MOD_OSD:
               {
                  szLine[0] = szOsdTitle;
                  szLine[1] = szOsdText;
                  break;
               }
            default:
               szLine[0] = szLine[1] = NULL;
               break;
         }
         myLCD->SetInfo( cRecordControls::Active() ? LED_ON : LED_OFF,
                         infoTimer > 0 ? LED_ON : LED_OFF, -1 );
      }
      else {
         if( lockTimer > 0 ) {
            lockTimer--;
         }
         infoTimer = 1;
         myLCD->SetInfo( lLED1, lLED2, lLEDPower );

         if( lockTimer == 0 ) {
            lLED1 = lLED2 = lLEDPower = -1;
            szMsgText1[0] = szMsgText2[0] = '\0';
         }
      }

      if( infoTimer > 0 ) {
         szLine[0] = szMsgText1;
         szLine[1] = szMsgText2;
         infoTimer--;
      }
      myLCD->SendText( szLine );

//      myLCD->CheckErrors();

      // wait for event or timeout
      _doUpdate.TimedWait( _mutex, SleepTime * 100 );
   }
   running=false;
}


bool cLCD::StoppedTimer( const char *szName )
{
   cTimer *timer = Timers.First();
   while( timer ) {
      if( strcmp( szName, timer->File() ) == 0 )
         break;
      timer = Timers.Next( timer );
   }
   return timer == NULL || !timer->Recording();
}


void cLCD::SetInfoTimer( void )
{
   infoTimer = ( MsgTime * 10 ) / SleepTime;
}
#endif
