/*
 * 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 "karaoke.h"

#include "setup.h"

#include "syllable.h"
#include "song.h"

#define POSYFINPARTITION    (13.5)
#define POSYDEBPARTITION    (POSYFINPARTITION-5.5)

extern Song mySong;

int cptTimeOut = 0;
int Refreshthread = 0;
int stopThread = 0;

#define clrPartition        0xFF808080  // a Grey


#define clrNoteEmpty        0xFFbfbfbf  // Grey
#define clrNoteEmptyLight   0xFFe3e3e3  // GreyLight
#define clrNoteEmptyShadow  0xFF878787  // GreyDark

#define clrNoteFill         0xFF65c9fF  // Blue
#define clrNoteFillLight    0xFFb5eeff  // BlueLight
#define clrNoteFillShadow   0xFF458eb7  // BLueDark


#define clrTextEmpty        0xffcecece  // Grey
#define clrTextFill         0xff7373e7  // Violet
#define clrTextBack         0xff000000  // Black


#define clrProgressEmpty  0xFFbfbfbf  // another Grey
#define clrProgressFill   0xFF000000  // Black

#define ELLIPSERAD   7

static void DrawNote( cOsd * osd, int x1, int y1, int x2, int y2,
                      int fillPercent )
{
  /*
   * osd->DrawRectangle(x1,y2,x2,y1,clrNoteEmpty); // Note back x2 = x1 +
   * (fillPercent*(x2-x1))/100; osd->DrawRectangle(x1,y1,x2,y2,clrNoteFill);
   * // Note Fill 
   */

  /*
   * Background note 
   */
  if( x2 - x1 > 2 * ELLIPSERAD )
  {
    osd->DrawEllipse( x1, y2, x1 + ELLIPSERAD, y1, clrNoteEmpty, 7 ); // begin 
                                                                      // chord
    osd->DrawEllipse( x2 - ELLIPSERAD, y2, x2, y1, clrNoteEmpty, 5 ); // end
                                                                      // chord

    osd->DrawRectangle( x1 + ELLIPSERAD, y2, x2 - ELLIPSERAD, y1, clrNoteEmpty ); // central 
                                                                                  // rectangle
    osd->DrawRectangle( x1 + ELLIPSERAD, y2, x2 - ELLIPSERAD, y2 + 1, clrNoteEmptyLight );  // central 
                                                                                            // rectangle 
                                                                                            // light
    osd->DrawRectangle( x1 + ELLIPSERAD, y1 - 1, x2 - ELLIPSERAD, y1, clrNoteEmptyShadow ); // central 
                                                                                            // rectangle 
                                                                                            // shadow

  }
  else
  {
    osd->DrawRectangle( x1, y2, x2, y1, clrNoteEmpty ); // only a rectangle
  }

  /*
   * Compute fill position 
   */
  x2 = x1 + ( fillPercent * ( x2 - x1 ) ) / 100;

  /*
   * Fill the Note 
   */
  if( x2 - x1 > 2 * ELLIPSERAD )
  {
    osd->DrawEllipse( x1, y2, x1 + ELLIPSERAD, y1, clrNoteFill, 7 );  // begin 
                                                                      // chord
    osd->DrawEllipse( x2 - ELLIPSERAD, y2, x2, y1, clrNoteFill, 5 );  // end
                                                                      // chord

    osd->DrawRectangle( x1 + ELLIPSERAD, y2, x2 - ELLIPSERAD, y1, clrNoteFill );  // central 
                                                                                  // rectangle
    osd->DrawRectangle( x1 + ELLIPSERAD, y2, x2 - ELLIPSERAD, y2 + 1, clrNoteFillLight ); // central 
                                                                                          // rectangle 
                                                                                          // light
    osd->DrawRectangle( x1 + ELLIPSERAD, y1 - 1, x2 - ELLIPSERAD, y1, clrNoteFillShadow );  // central 
                                                                                            // rectangle 
                                                                                            // shadow

  }
  else
  {
    if( x1 != x2 )
    {
      osd->DrawRectangle( x1, y2, x2, y1, clrNoteFill );  // only a rectangle
    }
  }

}


void *refreshThread( void *ptr )
{
  CKaraokeControl *theControl = ( CKaraokeControl * ) ptr;
  int index, total;
  int x0, x1, y0, y5;
  int xnd, xnf, i, xtextCenter;
  int xnb, yn, hn;
  char oneLineBack[200];
  char oneLineFront[200];
  char lineTwo[200];
  int status, xd, xf;
  int nsent;
  float timeSong, timeMpg;
  const cFont *font;
  int fw, fh;
  int finThread = 0;

  int noteMin = 1000, noteMax = 0;
  int octave, octaveBase;

  Syllable oneSyll;

  int tabNote[30];
  int tabDur[30];
  int tabStart[30];
  int tabStatus[30];
  int nbNote;
  int offsetNote, tnoteMax, tnoteMin;

  printf( "[karaoke] Begin Thread\n" );
  font = cFont::GetFont( fontOsd );
  fw = font->Width( ' ' );
  fh = font->Height(  );
  x0 = 0;
  x1 = Setup.OSDWidth;
  y0 = 0;
  y5 = Setup.OSDHeight;
  // 736x576
  int LastIndex = index, delta = 0, cpt = 0;
  float pasT = 10;
  float tim = 0;
  theControl->osd =
    cOsdProvider::NewOsd( Setup.OSDLeft + KaraokeSetup.OSDoffsetx,
                          Setup.OSDTop + KaraokeSetup.OSDoffsety );
  if( !theControl->osd )
  {
    printf( "[karaoke] Fin Thread(%d,%d) error NewOsd \n", finThread,
            cptTimeOut );
    Refreshthread = 0;
    theControl->visible = 0;
    return ( 0 );
  }
  // playlist , symbols , track-titel , progress

  tArea Area[] = {
    {0, 1 * fh, x1 - 1, 2 * fh, 2}
    ,                           // Notes
    {0, POSYDEBPARTITION * fh, x1 - 1, POSYFINPARTITION * fh, 4}
    ,                           // Partition
    {0, 14 * fh, x1 - 1, 16 * fh, 2}
    ,                           // Lyrics
  };
  eOsdError result =
    theControl->osd->CanHandleAreas( Area, sizeof( Area ) / sizeof( tArea ) );
  if( result == oeOk )
  {
    theControl->osd->SetAreas( Area, sizeof( Area ) / sizeof( tArea ) );
    theControl->osd->DrawRectangle( 0, 0 * fh, x1 - 1, 16 * fh, clrTransparent ); // Info
  }
  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( "[karaoke] ERROR! OSD open failed! Can't handle areas (%d)-%s\n",
             result, errormsg );
    printf( "[karaoke] Fin Thread(%d,%d) Error SetAreas \n", finThread,
            cptTimeOut );
    Refreshthread = 0;
    theControl->visible = 0;
    return ( 0 );
  }
  printf( "[karaoke] Thread loop\n" );
  while( finThread == 0 && stopThread == 0 )
  {
    if( theControl->player != NULL )
    {
      theControl->player->GetIndex( index, total );
      if( LastIndex != index )
      {
        // printf("- %f\n" , (index-LastIndex)/
        // (float)theControl->framesPerSecond);
        pasT =
          1000 * ( float ) ( ( index - LastIndex ) /
                             ( float ) theControl->framesPerSecond );
        if( cpt != 0 )
        {
          pasT /= cpt;
        }
        else
          pasT = 10;
        LastIndex = index;
        delta = 0;
        cpt = 0;
      }
      else
      {
        cpt++;
        delta += pasT;
      }
      timeMpg = index;
      timeMpg /= ( float ) theControl->framesPerSecond;
      timeSong = timeMpg + ( delta / 1000.f );
      if( timeSong > tim )
        tim = timeSong;

      mySong.SetCurrentTime( tim ); // Set song Time

      /*
       * -- prepare data to display -- 
       */

      oneLineBack[0] = 0;
      oneLineFront[0] = 0;
      nbNote = 0;
      nsent = mySong.getNextSyllable( oneSyll, &status );
      tabNote[nbNote] = oneSyll.m_note;
      tabDur[nbNote] = oneSyll.m_duration;
      tabStart[nbNote] = oneSyll.m_start;
      tabStatus[nbNote] = status;
      nbNote++;

      if( oneSyll.m_note < noteMin )
        noteMin = oneSyll.m_note;
      if( oneSyll.m_note > noteMax )
        noteMax = oneSyll.m_note;

      /*
       * Get line one 
       */
      strcpy( oneLineBack, oneSyll.m_theSyllable );
      if( status == 100 )
        strcpy( oneLineFront, oneSyll.m_theSyllable );
      while( nsent == mySong.getNextSyllable( oneSyll, &status ) )
      {
        tabNote[nbNote] = oneSyll.m_note;
        tabDur[nbNote] = oneSyll.m_duration;
        tabStart[nbNote] = oneSyll.m_start;
        tabStatus[nbNote] = status;
        nbNote++;

        if( oneSyll.m_note < noteMin )
          noteMin = oneSyll.m_note;
        if( oneSyll.m_note > noteMax )
          noteMax = oneSyll.m_note;


        strcat( oneLineBack, oneSyll.m_theSyllable );
        if( status == 100 )
          strcat( oneLineFront, oneSyll.m_theSyllable );
      }

      /*
       * Get line two 
       */
      lineTwo[0] = 0;
      nsent = mySong.getNextSyllable( oneSyll, &status );
      strcpy( lineTwo, oneSyll.m_theSyllable );
      while( nsent == mySong.getNextSyllable( oneSyll, &status ) )
      {
        strcat( lineTwo, oneSyll.m_theSyllable );
      }


      octaveBase = noteMin / 12;  // octave de base
      /*
       * Adjust les notes 
       */
      for( i = 0; i < nbNote; i++ )
      {
        tabNote[i] = tabNote[i] - octaveBase * 12;

        octave = tabNote[i] / 12;
        switch ( tabNote[i] % 12 )
        {
        case 0:
          tabNote[i] = 0;
          break;                // do -
        case 1:
          tabNote[i] = 1;
          break;                // do#
        case 2:
          tabNote[i] = 2;
          break;                // re -
        case 3:
          tabNote[i] = 3;
          break;                // re#
        case 4:
          tabNote[i] = 4;
          break;                // mi -
        case 5:
          tabNote[i] = 6;
          break;                // fa -
        case 6:
          tabNote[i] = 7;
          break;                // fa#
        case 7:
          tabNote[i] = 8;
          break;                // sol -
        case 8:
          tabNote[i] = 9;
          break;                // sol#
        case 9:
          tabNote[i] = 10;
          break;                // la -
        case 10:
          tabNote[i] = 11;
          break;                // la#
        case 11:
          tabNote[i] = 12;
          break;                // si -
        }
        tabNote[i] += octave * 14;


      }
      // 19 ligne max
      tnoteMin = 1000;
      tnoteMax = 0;
      offsetNote = 0;
      for( i = 0; i < nbNote; i++ )
      {
        if( tabNote[i] < tnoteMin )
          tnoteMin = tabNote[i];
        if( tabNote[i] > tnoteMax )
          tnoteMax = tabNote[i];
      }
      if( tnoteMax > 19 )
      {
        offsetNote = tnoteMax - 19;
      }
      if( tnoteMin - offsetNote < 0 )
      {
        offsetNote /= 2;
      }

      /*
       * -- Draw Lyrics -- 
       */
      theControl->osd->DrawRectangle( 0, 14 * fh, x1 - 1, 16 * fh, clrGray50 ); // Erase 
                                                                                // lyris 
                                                                                // area
      font = cFont::GetFont( fontOsd );

      /*
       * lyric line one 
       */
      xtextCenter = ( ( x1 - 1 ) - 0 ) / 2 - font->Width( oneLineBack ) / 2;
      theControl->osd->DrawText( 1 + xtextCenter, 14 * fh, oneLineBack,
                                 clrTextBack, clrTransparent, font );
      theControl->osd->DrawText( xtextCenter, 14 * fh, oneLineBack,
                                 clrTextEmpty, clrTransparent, font );
      theControl->osd->DrawText( xtextCenter, 14 * fh, oneLineFront,
                                 clrTextFill, clrTransparent, font );

      /*
       * lyric line two 
       */
      xtextCenter = ( ( x1 - 1 ) - 0 ) / 2 - font->Width( lineTwo ) / 2;
      theControl->osd->DrawText( 1 + xtextCenter, 15 * fh, lineTwo,
                                 clrTextBack, clrTransparent, font );
      theControl->osd->DrawText( xtextCenter, 15 * fh, lineTwo, clrTextEmpty,
                                 clrTransparent, font );


      // sprintf(oneLineFront,"dbg:%4.3f %3d %3d %3d
      // %3d",tim,cptTimeOut,index,total,nbNote);
      // sprintf(oneLineFront,"dbg:%3d",nbNote);
      // theControl->osd->DrawText( 1*fw , 13*fh , oneLineFront, clrTextEmpty 
      // ,clrTransparent, font);


      /*
       * -- Draw partition -- 
       */
      theControl->osd->DrawRectangle( 0, POSYDEBPARTITION * fh, x1 - 1, POSYFINPARTITION * fh, clrGray50 ); // Erase 
                                                                                                            // notes 
                                                                                                            // area
      xnd = ( int ) ( ( x1 - 1 - 0 ) * 0.03f );
      xnf = ( int ) ( ( x1 - 1 - 0 ) * 0.97f );

      xnb = ( tabStart[nbNote - 1] + tabDur[nbNote - 1] ) - tabStart[0];

      /*
       * Grille de Partition 
       */
      hn = ( POSYFINPARTITION * fh - POSYDEBPARTITION * fh ) / 20;
      for( i = 0; i < 20; i += 2 )
      {
        yn = POSYFINPARTITION * fh - ( i * hn );
        yn -= hn;
        theControl->osd->DrawRectangle( xnd, yn, xnf, yn + 1, clrPartition ); // 
      }

      /*
       * les notes 
       */
      for( i = 0; i < nbNote; i++ )
      {
        xd = xnd + ( ( xnf - xnd ) * ( tabStart[i] - tabStart[0] ) / xnb );
        xf =
          xnd +
          ( ( xnf - xnd ) * ( ( tabStart[i] + tabDur[i] ) -
                              tabStart[0] ) / xnb );

        yn = POSYFINPARTITION * fh - ( ( tabNote[i] - offsetNote ) * hn );

        DrawNote( theControl->osd, xd, yn, xf, yn - 2 * hn, tabStatus[i] ); // Draw 
                                                                            // a 
                                                                            // note
      }

      /*
       * -- Header -- 
       */
      /*
       * Time 
       */
      // theControl->osd->DrawRectangle(1*fw , 1*fh, 6*fw , 2*fh,
      // clrTransparent); // Erase Time area
      sprintf( oneLineFront, "%02d:%02d", ( int ) ( timeMpg / 60 ),
               ( int ) ( ( ( int ) timeMpg ) % 60 ) );
      font = cFont::GetFont( fontSml );
      theControl->osd->DrawText( 1 * fw, 1 * fh, oneLineFront,
                                 clrProgressEmpty, clrGray50, font );
      /*
       * Progress bar 
       */
      theControl->osd->DrawRectangle( 13 * fw, 1 * fh + ( fh / 4 ), 43 * fw,
                                      1 * fh + ( 3 * fh / 4 ),
                                      clrProgressEmpty );
      if( total != 0 )
      {
        theControl->osd->DrawRectangle( 2 + 13 * fw, 1 * fh + ( fh / 4 ) + 2,
                                        13 * fw +
                                        fw * ( ( 30 * index ) / total ),
                                        1 * fh + ( 3 * fh / 4 ) - 2,
                                        clrProgressFill );
      }

      /*
       * flush osd 
       */
      theControl->osd->Flush(  );

    }

    usleep( 10000 );

    cptTimeOut++;
    if( cptTimeOut > 150 )
      finThread = 1;

    if( cOsd::IsOpen(  ) == 0 )
      finThread = 1;

    if( theControl->osd == NULL )
      finThread = 1;
  }
  printf( "[karaoke] Fin Thread(%d,%d)\n", finThread, cptTimeOut );
  Refreshthread = 0;
  theControl->visible = 0;

  return ( NULL );
}
