///////////////////////////////////////////////////
// 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 __ACTIVYLCD_CPP
#define __ACTIVYLCD_CPP

#include<sys/wait.h>
#include "activylcd.h"
#include <stdio.h>   /* Standard input/output definitions */
#include <string.h>  /* String function definitions */
#include <unistd.h>  /* UNIX standard function definitions */
#include <fcntl.h>   /* File control definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <time.h>
#include <stdlib.h>
#include <ctype.h>
#include <vdr/tools.h>

char *lcdBrightness[BRIGHTEST+1] = { "\xF0\xFF", "\xF0\xF0", "\xF0\xE0", "\xF0\xD0", "\xF0\xC0", "\xF0\xB0",
                                     "\xF0\xA0", "\xF0\x90", "\xF0\x80", "\xF0\x70", "\xF0\x60", "\xF0\x50",
                                     "\xF0\x40", "\xF0\x30", "\xF0\x20", "\xF0\x10", "\xF0\x00" };
									  
// sonstige Variablen
extern unsigned int ScrollSpeed;
extern bool Umlauts;              // Show Umlauts
extern int Scrolling;             // Kind of scrolling
extern int LEDRecord;             // Message LED
extern int LCDBrightness;         // Brightness 0 == bright, 4 == dark...
extern int LCDBrightnessOnExit;
extern int Alignment;
extern bool LCDavailable;
//
//// SQ: fuer Semaphor
#define KEY 4711 // Einduetigen Key

extern char *resetScript;

#undef LCDDEBUG
#undef D_EVENTFILTER

// Konstruktor
activyLCD::activyLCD()
{
   char * szLine[LCD_ROWS] = { " ", " "};

#ifdef DEBUG
   isyslog("activyLCD");
#endif

   actBrightness = -1;
   lRecStat = lInfoStat = lPowerLEDStat = -1;
   bLocked = false;
   bPermanent = false;

   if( resetScript != NULL )
      system( resetScript );

   SerialPortOpen();
   SetPowerButton(1);  // always enable PowerButton
   SetLCDBrightness(LCDBrightness);

   SetInfo(LED_OFF,LED_OFF,PWRLED_GREEN);

   SendCommand(HWREV);
   SendCommand(FWREV);
   SendCommand(LCD_CLEAR);

   picError = false;
   SendText( szLine ); // for checking if Display is available
   if( picError ) {
      isyslog("activyLCD: Set Text failed - assuming no LCD available");
      LCDavailable = false;
      picError = false;
   }

   actLine[0][0] = actLine[1][0] = '\x00';
   actPos[0] = actPos[1] = 0;
#ifdef DEBUG
   isyslog("activyLCD: Constructor done");
#endif
}

/////////////////////////////////////////////////////////
// Destruktor

activyLCD::~activyLCD()
{
#ifdef DEBUG
   isyslog("~activyLCD");
#endif
   if( !bLocked )
      SendCommand(LCD_CLEAR);
   SetPowerButton(1);
   SetLCDBrightness(LCDBrightnessOnExit);
   if( bPermanent )
      SendCommand(LCD_PERM_ON);
   else
      SendCommand(LCD_PERM_OFF);
   SetInfo(LED_OFF,LED_OFF,-1);
   SerialPortClose();
#ifdef LCDDEBUG
   fclose(logfile);
#endif
#ifdef DEBUG
   isyslog("acticyLCD: Destructor done");
#endif
}

/////////////////////////////////////////////////////////
// Open Serial Port

int activyLCD::SerialPortOpen(void)
{
   struct termios options;

   // open serial device
   fd = open(LCD_PORT, O_RDWR | O_NOCTTY | O_NDELAY);

   // check result
   if( fd == -1 ) {
      // error - port could not be opened
      isyslog("alcd-plugin: SerPort could not be opened");
      return(-1);
   }
   else {
      // ok - port opened
      fcntl(fd, F_SETFL, 0);
      // Get the current options for the port...
      tcgetattr(fd, &options);
      // Set the baud rates to 38400...
      cfsetispeed(&options, B38400);
      cfsetospeed(&options, B38400);
      // Enable the receiver and set local mode...
      options.c_cflag |= (CLOCAL);
      options.c_cflag &= ~PARENB;
      options.c_cflag &= ~CSTOPB;
      options.c_cflag &= ~CSIZE;
      options.c_cflag |= CS8;
      options.c_oflag &= ~OPOST;
      options.c_oflag &= ~(ICANON | ECHO | ECHOE | ISIG);
      options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
      options.c_cc[VMIN]  = 0;                  /* warten auf min. 0 Zeichen */
      options.c_cc[VTIME] = 10;                 /* Timeout in deciseconds */

      // Set the new options for the port...
      tcsetattr(fd, TCSANOW, &options);
      // flush ser port
      tcflush(fd, TCIOFLUSH);

#ifdef DEBUG
      isyslog("alcd-plugin: SerPort opened");
#endif
      return(fd);
   }
}

/////////////////////////////////////////////////////////
// Close Serial Port

void activyLCD::SerialPortClose()
{
   close(fd);

#ifdef DEBUG
   isyslog("alcd-plugin: SerPort closed");
#endif
}

/////////////////////////////////////////////////////////
// Send Command
void activyLCD::SendCommand(char *command)
{
   if( !LCDavailable &&
       ( !strncmp( command, MESSAGELED_ON, 2 )   ||
         !strncmp( command, MESSAGELED_OFF, 2 )  ||
         !strncmp( command, LCD_WRITE_LINE0, 1 ) ||
         !strncmp( command, LCD_CLEAR, 2 )       ||
         !strncmp( command, lcdBrightness[0], 1 ) ) ) {
      return;
   }

   PicWrite( command, 2 );
}

/////////////////////////////////////////////////////////
// Send Command
void activyLCD::SendText(char *text[LCD_ROWS])
{
   char buffer[ LCD_COLUMNS + 3 ];
   bool write = false;
   char *txt;

#ifdef DEBUG
   isyslog( "SendText <%s><%s>", text[0], text[1] );
#endif
   if( !LCDavailable )
      return;

   for( int i=0; i < LCD_ROWS; i++ ) {
      if( text[i] == NULL )
         txt = "";
      else
         txt = text[i];


      if( strcmp( actLine[i], txt ) ) {
         strncpy( actLine[i], txt, sizeof( actLine[i] ) );
         scroll[i] = actPos[i] = 0;
         write = true;
      }
      else {
         if( strlen( actLine[i] ) > LCD_COLUMNS && ScrollSpeed > 0 ) {
            if( ++(scroll[i]) > ScrollSpeed ) {
               scroll[i] = 0;
               write = true;
               if( actPos[i] + LCD_COLUMNS >= strlen( actLine[i] ) )
                  actPos[i] = 0;
               else {
                  if( Scrolling == SCROLL_BLOCK )
                     actPos[i] += LCD_COLUMNS;
                  else
                     actPos[i]++;
               }
            }
         }
      }
      if( write ) {
         snprintf( buffer, sizeof(buffer), "%s%-20.20s%c", i==0 ? LCD_WRITE_LINE0 : LCD_WRITE_LINE1, actLine[i] + actPos[i], '\x00');
         PicWrite( buffer, LCD_COLUMNS + 3 );
      }
   }
   SetLCDBrightness(LCDBrightness);
}


void activyLCD::SetTextPermanent(bool bStatus) 
{ 
   bPermanent = bStatus; 
   
   if( bPermanent )
      SendCommand(LCD_PERM_ON);
   else
      SendCommand(LCD_PERM_OFF);
}



/////////////////////////////////////////////////////////
// Send Command to LCD-Device
void activyLCD::PicWrite(char *command,int length)
{
   char picRes[6];
   int result;

   if( !command || !length )
      return;

#ifdef D_EVENTFILTER
   write(fd,EVENTFILTER,2);
   usleep(10000);
   read(fd,picRes, 2);

   // eventfilter set?
   CheckPicRes(picRes);
#endif

#ifdef DEBUG
   isyslog("alcd-plugin: Send to lcd: <%02x%02x>",(unsigned char)command[0], (unsigned char)command[1]);
   if( (unsigned)command[0] == (unsigned)LCD_WRITE_LINE0[0] )
      isyslog("alcd-plugin: Text <%s> - %d",(char *)(command + 2), length);
#endif

   // send real command to lcd
   write(fd,command,length);
   usleep(10000);
   result = read(fd,picRes,2);

   while( (result == 2) && (picRes[0] != command[0]) ) {
      isyslog("alcd-plugin: received: %d <%02x%02x>",result,(unsigned char)picRes[0],(unsigned char)picRes[1]);
      if( picRes[1] == command[0] ) {
         picRes[0] = command[0];
         result = read(fd,picRes+1,1) + 1;
      }
      else
         result = read(fd,picRes,2);
   }
#ifdef DEBUG
   isyslog("alcd-plugin: result: %02x%02x (%d)",(unsigned char)picRes[0],(unsigned char)picRes[1], result);
#endif
   if( (result == 2) && (picRes[0] == command[0]) && (picRes[1] & PIC_RES_ACK) ) {
      // ergebniswerte auswerten
      switch( command[0] ) {
         // hw revision
         case 0x89 :
            // hw revision - 1 byte!
            read(fd,picRes+2,1);
            isyslog("alcd-plugin: Activy300-LCD-HW Revision: %i",picRes[2]);

            break;
            // fw revision
         case 0x8a :
            // fw revision - 4 bytes!
            read(fd,picRes+2,4);
            isyslog("alcd-plugin: Activy300-LCD-SW Revision: Release: %i Version: %i PreRelease: %c VirtualNumber: %i",
                    picRes[2],picRes[3],picRes[4],picRes[5] );

            break;
            // blast ir buffer
         case 0x85 :
            // set LED
         case 0x90 :
            // set powerbutton
         case 0x94 :
            // clear lcd
         case 0x9b :
            // writeline
         case 0x9a :
            // set brightness
         case 0xf0 :
            // set brightness
            break;
            // buttons
         case 0x92 :
            // read buttons

            // bit 0 reserved
            // bit 1 power button
            // bit 2 up button
            // bit 3 down button
            // bit 4 left button
            // bit 5 right button
            // bit 6 select button
            // bit 7 reserved


            read(fd,picRes+2,1);
            picRes[2] &= ~0x81;
            if( picRes[2] != 0x00 )
               isyslog("alcd-plugin: %02x button pressed", picRes[2]);

            break;
         default :
            // command unknown!
            isyslog("alcd-plugin: ERROR: unknown command! <%02x%02x>",(unsigned char)command[0], (unsigned char)command[1]);
            break;
      }
   }
   else {
      picError = true;
      isyslog("alcd-plugin: Failing Command: <%02x%02x>",(unsigned char)command[0], (unsigned char)command[1]);
      isyslog("alcd-plugin: ResCode: %d <%02x%02x>",result, (unsigned char)picRes[0],(unsigned char)picRes[1]);
      if( command[0] == LCD_WRITE_LINE0[0] )
         isyslog("Failing Text <%s> - %d",(char *)command + 2, length);
   }

#ifdef D_EVENTFILTER
   write(fd,EVENTFILTEROFF,2);
   usleep(10000);
   read(fd,picRes, 2);

   // eventfilter set?
   CheckPicRes(picRes);
#endif

#ifdef DEBUG
   isyslog("alcd-plugin: SendCommand done");
#endif
}

/////////////////////////////////////////////////////////
// Check Errors
bool activyLCD::CheckErrors()
{
   bool bRc = false;

   if( picError == true ) {
      Reset();
      bRc = true;
   }
   return bRc;
}


void activyLCD::Reset()
{
   unsigned char picRes[6];
   int result;

   isyslog("alcd-plugin: ERROR: Performing PICRESET!");
   usleep(100000);
   // Clear read buffer
   result = read(fd,picRes,6);
   isyslog("alcd-plugin: Result Clear: %02x%02x%02x%02x%02x%02x (%d)",picRes[0],picRes[1],picRes[2],picRes[3],picRes[4],picRes[5], result);

   write(fd,PICRESET,2);
   usleep(10000);
   result = read(fd,picRes,2);
   isyslog("alcd-plugin: Result PICRESET: %02x%02x (%d)",picRes[0],picRes[1], result);

   if( resetScript != NULL ) {
      int rc;
      SerialPortClose();
      rc = system( resetScript );
      isyslog("alcd-plugin: Executing <%s> result: %x", resetScript, rc );
      SerialPortOpen();
   }

   LCDavailable = true;
   picError = false;
   actBrightness = -1;
   lRecStat = lInfoStat = lPowerLEDStat = -1;

   SetPowerButton(0);  // always enable PowerButton
   SetLCDBrightness(LCDBrightness);
   SetInfo(LED_OFF,LED_OFF,PWRLED_GREEN);

   SendCommand(LCD_CLEAR);
   actLine[0][0] = actLine[1][0] = '\x00';
   actPos[0] = actPos[1] = 0;
}

/////////////////////////////////////////////////////////
// Set Power Button

void activyLCD::SetPowerButton(bool status) // switch powerbutton on
{
   switch( status ) {
      case 0:
         SendCommand(POWERBUTTON_SCANCODE_OFF);
         SendCommand(POWERBUTTON_OFF);
         break;

      case 1:
         SendCommand(POWERBUTTON_SCANCODE_ON);
         SendCommand(POWERBUTTON_ON);
         break;
   }
}

/////////////////////////////////////////////////////////
// Set LCD Brightness

void activyLCD::SetLCDBrightness(const int brightness)
{
   if( brightness != actBrightness && brightness >= OFF && brightness <= BRIGHTEST ) {
      actBrightness = brightness;
      SendCommand(lcdBrightness[brightness]);
   }
}

/////////////////////////////////////////////////////////
// Set RecordLED

void activyLCD::SetInfo(int lRec, int lInfo, int lPwrLED)
{
   char *cmd = NULL;

   if( lRec != -1 && lRecStat != lRec ) {
      lRecStat = lRec;
      if( lRecStat == LED_ON ) {
         if( LEDRecord == LEDREC_RED )
            cmd = MESSAGELED_ON;
         else
            cmd = BASICLED_ON;
      }
      else {
         if( LEDRecord == LEDREC_RED )
            cmd = MESSAGELED_OFF;
         else
            cmd = BASICLED_OFF;
      }

      SendCommand(cmd);
   }
   if( lInfo != -1 && lInfoStat != lInfo ) {
      lInfoStat = lInfo;
      if( lInfoStat == LED_ON ) {
         if( LEDRecord == LEDREC_RED )
            cmd = BASICLED_ON;
         else
            cmd = MESSAGELED_ON;
      }
      else {
         if( LEDRecord == LEDREC_RED )
            cmd = BASICLED_OFF;
         else
            cmd = MESSAGELED_OFF;
      }

      SendCommand(cmd);
   }

   if( lPwrLED != -1 && lPwrLED != lPowerLEDStat ) {
      if( lPowerLEDStat == PWRLED_BLINK )
         SendCommand(POWERLED_BLINK_OFF);

      lPowerLEDStat = lPwrLED;
      if( lPwrLED == PWRLED_RED )
         cmd = POWERLED_RED;
      else if( lPwrLED == PWRLED_BLINK )
         cmd = POWERLED_BLINK_ON;
      else
         cmd = POWERLED_GREEN;

      SendCommand(cmd);
   }
}

/////////////////////////////////////////////////////////
// Format String according to settings

void activyLCD::FormatString( char *output, const char *input, int outLen, bool bSplit, bool bAlign )
{
   int bufLen = 0;
   char *outputPtr = output;

#ifdef DEBUG
   isyslog( "FormatString <%s>", input );
#endif

   // only proceed, if valid data
   if( input ) {
      char buffer[256];
      char *in = (char *)input;
      if( bAlign )
         while( *in == ' ' )
            in++;

      memset( output, (int)' ', outLen );
      memset( buffer, 0, sizeof(buffer) );
      char *bufStart = buffer;
      char *out = buffer;
      char *lsp = out;
      char *lbp = out;
      char *endPtr = buffer + sizeof(buffer) - 2*LCD_COLUMNS;
      char *nextSplitPos = out + LCD_COLUMNS;

      while( out < endPtr && *in != '\0' ) {
         if( ( Scrolling == SCROLL_BLOCK || bSplit == true ) && out >= nextSplitPos ) {
            char *actPos;

            if( nextSplitPos - lbp < ( LCD_COLUMNS / 2 ) )
               actPos = lbp + 1;
            else if( nextSplitPos - lsp < ( LCD_COLUMNS / 2 ) )
               actPos = lsp + 1;
            else if( nextSplitPos - lbp < ( LCD_COLUMNS * 3 / 4 ) )
               actPos = lbp + 1;
            else if( nextSplitPos - lsp < ( LCD_COLUMNS * 3 / 4 ) )
               actPos = lsp + 1;
            else
               actPos = nextSplitPos;

            char *strEnd = actPos - 1;
            // align the string
            int offset = 0;

            if( bAlign ) {
               // delete leading and trailing spaces
               while( *bufStart == ' ' && strEnd > bufStart )
                  bufStart++;

               while( *strEnd == ' ' && strEnd > bufStart )
                  strEnd--;

               bufLen = strEnd - bufStart + 1;

               if( Alignment == ALIGN_CENTER )
                  offset = ( LCD_COLUMNS - bufLen ) / 2;
               else if( Alignment == ALIGN_RIGHT ) 
                  offset = LCD_COLUMNS - bufLen;
            }
            else
               bufLen = LCD_COLUMNS;

            strncpy( outputPtr + offset, bufStart, bufLen );

            outputPtr += LCD_COLUMNS;

            if( bSplit == true ) {
               *outputPtr = '\0';
               outputPtr ++;
               bSplit = false;
            }

            lsp = lbp = actPos;
            bufStart = actPos;
            nextSplitPos = bufStart + LCD_COLUMNS;
//            isyslog("alcd-plugin: FS <%-10.10s><%-10.10s><%-10.10s>", actPos, endPos, out);
//          if( actPos < endPos ) {
//             char szTmp[LCD_COLUMNS];
//             int sLen = endPos - actPos;
//
//             strncpy( szTmp, actPos, sLen );
//             szTmp[sLen] = '\0';
//             memset( actPos, ' ', sLen );
//             strcpy( nextSplitPos, szTmp );
//             out = nextSplitPos + sLen;
//          }
//              isyslog("alcd-plugin: FS <%-10.10s><%-10.10s>", lbp,actPos);
            if( bAlign )
               while( *in == ' ' )
                  in++;

            if( *in == '\0' )
               break;
         }

         if( Umlauts == false ) {
            switch( *in ) {
               case '\x09':  // Tab
                  *out = ' ';
                  break;

               case 'ä':    // ae
                  *out = 'a';
                  out++;
                  *out = 'e';
                  break;

               case 'ö':    // oe
                  *out = 'o';
                  out++;
                  *out = 'e';
                  break;

               case 'ü':    // ue
                  *out = 'u';
                  out++;
                  *out = 'e';
                  break;

               case 'ß':    // ss
                  *out = 's';
                  out++;
                  *out = 's';
                  break;

               case 'Ä':    // AE
                  *out = 'A';
                  out++;
                  if( islower( *(in+1) ) )
                     *out = 'e';
                  else
                     *out = 'E';
                  break;

               case 'Ö':    // OE
                  *out = 'O';
                  out++;
                  if( islower( *(in+1) ) )
                     *out = 'e';
                  else
                     *out = 'E';
                  break;

               case 'Ü':    // UE
                  *out = 'U';
                  out++;
                  if( islower( *(in+1) ) )
                     *out = 'e';
                  else
                     *out = 'E';
                  break;

               default:
                  *out = *in;
                  break;
            }
         }
         else {
            switch( *in ) {
               case '\x09':  // Tab
                  *out = ' ';
                  break;

               case 'ä':    // ae
               case 'Ä':    // AE
                  *out = '\xE1';
                  break;

               case 'ö':    // oe
               case 'Ö':    // OE
                  *out = '\xEF';
                  break;

               case 'ü':    // ue
               case 'Ü':    // UE
                  *out = '\xF5';
                  break;

               case 'ß':    // ss
                  *out = '\xE2';
                  break;

               default:
                  *out = *in;
                  break;
            }
         }
         if( *out == ' ' )
            lbp = out;
         else
            if( !isalnum( *out ) )
               lsp = out;

         out++;
         in++;
      }

      // center the string
      if( bAlign && (out - bufStart) < LCD_COLUMNS ) {
         out--;
         // delete leading and trailing spaces
         while( *bufStart == ' ' && out > bufStart )
            bufStart++;

         while( *out == ' ' && out > bufStart )
            out--;

         bufLen = out - bufStart + 1;

         if( Alignment == ALIGN_CENTER )
            outputPtr += ( LCD_COLUMNS - bufLen ) / 2;
         else if( Alignment == ALIGN_RIGHT ) 
            outputPtr += LCD_COLUMNS - bufLen;
      }
      else
         bufLen = out - bufStart;

      strncpy( outputPtr, bufStart, bufLen );

//    if( (out > output + LCD_COLUMNS) && (Scrolling != SCROLL_BLOCK) )
//       sprintf( out, "    %20.20s%c", output, '\x00');
//    else
//       *out = '\0';
   }
   outputPtr[ bufLen ] = '\0';
   if( bSplit )
      outputPtr[ bufLen + 1 ] = '\0';
#ifdef DEBUG
   isyslog( "FormatString End<%s>", output );
#endif
}

#endif
