/*
 * Least Cost Routing plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */

#include <ctype.h>
#include "lcrcfg.h"
#include "lcri18n.h"
#include "lcrosd.h"
#include "lcr.h"

#define CHANNELINPUT_TIMEOUT      Setup.ZapTimeout * 1000 // in millisecs
#define OSDHEIGHT                 lcrConfig.osdheight     // in pixels
#define OSDWIDTH                  600                     // in pixels
#define OSDROWHEIGHT              m_Font->Height()        // in pixels
#define OSDLCRHEIGHT              (OSDROWHEIGHT * 10)     // in pixels (10 rows)
#define OSDSPACING                5
#define OSDCORNERING              10
#define OSDWIN_Y(offset)          offset
#define OSDWIN_X(col)             ((col == 7) ? 475 : (col == 6) ? 460 : (col == 5) ? 275 : (col == 4) ? 280 : (col == 3) ? 150 : (col == 2) ? 70 : 16)
#define INITIAL_OFFSET            OSDROWHEIGHT + 5

#define XQUOTE(S) QUOTE(S)
#define QUOTE(S) #S

#ifndef RATES_FILE_NAME
#define RATES_FILE_NAME /tmp/rates.txt
#endif

#ifndef RETRIEVAL_SCRIPT
#define RETRIEVAL_SCRIPT retrieve-data.pl
#endif

#include "symbols/logo2.xpm"
cBitmap cLcrOsd::bmLogo(logo_xpm);

cLcrOsd::cLcrOsd(void) : cOsdObject(true), cThread("lcr osd")
{
  Dprintf("%s()\n", __PRETTY_FUNCTION__);
  m_Osd = NULL;
  m_Active = false;
  m_Number = 0;
  m_OldNumber = 0;
  rates = NULL;  
  inc = 4;
  logoOffset = 3;
  m_InputTime.Set(0);
  m_Mutex = new cMutex();
  if (Setup.UseSmallFont == 0)
  {
    // Dirty hack to force the small fonts...
    Setup.UseSmallFont = 1;
    m_Font = cFont::GetFont(fontSml);
    Setup.UseSmallFont = 0;
  }
  else
  {
    m_Font = cFont::GetFont(fontSml);
  }
}

cLcrOsd::~cLcrOsd(void)
{
  Dprintf("%s()\n", __PRETTY_FUNCTION__);
  if (m_Active)
  {
    m_Active = false;
    Cancel(3);
  }
  if (m_Osd)
  {
    delete m_Osd;
  }
  SystemExec( XQUOTE(rm RATES_FILE_NAME) );
}

void cLcrOsd::DrawWindow(void)
{
  Dprintf("%s()\n", __PRETTY_FUNCTION__);
  
  cMutexLock lock(m_Mutex);
  char buf[128];  
  int offset = 0;

  cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());

  if (m_Osd)
  {
    m_Osd->DrawRectangle(0, OSDWIN_Y(offset), OSDWIDTH, OSDWIN_Y(offset+OSDROWHEIGHT-1), lcrTheme[lcrConfig.theme].clrTitleBackground);
    snprintf(buf, sizeof(buf), "%s", tr(MAINMENUENTRY));
    m_Osd->DrawText(OSDWIN_X(1), OSDWIN_Y(offset), buf, lcrTheme[lcrConfig.theme].clrTitleText, lcrTheme[lcrConfig.theme].clrTitleBackground, m_Font);
    m_Osd->DrawEllipse(0, OSDWIN_Y(0), OSDCORNERING, OSDWIN_Y(OSDCORNERING), clrTransparent, -2);
    m_Osd->DrawEllipse((OSDWIDTH-OSDCORNERING), OSDWIN_Y(0), OSDWIDTH, OSDWIN_Y(OSDCORNERING), clrTransparent, -1);

    offset += INITIAL_OFFSET; // looks nicer
     
    // Rectangle around the logo
    if ( lcrConfig.logoFrame )
    {
      int frameWidth = 4;
      m_Osd->DrawRectangle(OSDWIN_X(0)-frameWidth, OSDWIN_Y(0) + offset + logoOffset - frameWidth, OSDWIN_X(0) + bmLogo.Width()+frameWidth, OSDWIN_Y(0)+ offset + logoOffset + bmLogo.Height()+frameWidth, lcrTheme[lcrConfig.theme].clrTitleBackground);
    }
    // Draw the logo
    m_Osd->DrawBitmap( OSDWIN_X(0), OSDWIN_Y(0) + offset + logoOffset, bmLogo );     

    rates = fopen( XQUOTE(RATES_FILE_NAME), "r" );
    
    // just display a message that data is being loaded
    if (rates == NULL)
    {
      offset += OSDROWHEIGHT*3;
      snprintf(buf, sizeof(buf), "%s ...", tr("Loading tariff data"));
      m_Osd->DrawText(OSDWIN_X(4), OSDWIN_Y(offset), buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrBackground, m_Font);
      offset += OSDROWHEIGHT*2;
      snprintf(buf, sizeof(buf), "%s:", tr("Source"));
      m_Osd->DrawText(OSDWIN_X(0), OSDWIN_Y(offset), buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrBackground, m_Font);
      offset += OSDROWHEIGHT;
      snprintf(buf, sizeof(buf), "%s", SOURCE);
      m_Osd->DrawText(OSDWIN_X(0), OSDWIN_Y(offset), buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrBackground, m_Font);
      m_Osd->Flush();

      Dprintf("ERROR: Cannot open rates-file yet: %s\n", XQUOTE(RATES_FILE_NAME));
//      esyslog("ERROR: cLcrOsd::DrawWindow() cannot open rates-file yet: %s", XQUOTE(RATES_FILE_NAME));
      return;
    }
    
    cReadLine rl = cReadLine();
    char *destinations[7] = { "City", "Near", "Nat.", "D1", "D2", "E-Plus", "O2" };
    
    // get rates-data line by line
    for ( int i=0; i<7; i++ )
    {
      rates_line[i*3] = rl.Read(rates);
      snprintf(buf, sizeof(buf), "%s: %s", tr(destinations[i]), rates_line[i*3]);
      m_Osd->DrawText(OSDWIN_X(3), OSDWIN_Y(offset), buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrBackground, m_Font);
      rates_line[i*3+1] = rl.Read(rates);
      snprintf(buf, sizeof(buf), "%s: %s", tr("Price"), rates_line[i*3+1]);
      m_Osd->DrawText(OSDWIN_X(4), OSDWIN_Y(offset), buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrBackground, m_Font);
      rates_line[i*3+2] = rl.Read(rates);
      snprintf(buf, sizeof(buf), "%s: %s", tr("Pulse"), rates_line[i*3+2]);
      m_Osd->DrawText(OSDWIN_X(6), OSDWIN_Y(offset), buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrBackground, m_Font);     
      offset += OSDROWHEIGHT;
    }
	fclose(rates);
	
	// step back to be able to draw the text below the logo 
	offset -= 2*OSDROWHEIGHT;

    m_Osd->DrawRectangle(0, OSDWIN_Y(OSDLCRHEIGHT-OSDROWHEIGHT-inc), OSDWIDTH, OSDWIN_Y(OSDLCRHEIGHT-inc), lcrTheme[lcrConfig.theme].clrTitleBackground);
    snprintf(buf, sizeof(buf), "%s:", tr("Source"));
    m_Osd->DrawText(OSDWIN_X(0), OSDWIN_Y(offset), buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrBackground, m_Font);
    offset += OSDROWHEIGHT;
    snprintf(buf, sizeof(buf), "%s", SOURCE);
    m_Osd->DrawText(OSDWIN_X(0), OSDWIN_Y(offset), buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrBackground, m_Font);
    snprintf(buf, sizeof(buf), "%d%s %s", m_Number ? m_Number : channel->Number(), m_Number ? "-" : "", channel->ShortName(true));
    m_Osd->DrawText(OSDWIN_X(1), OSDWIN_Y(OSDLCRHEIGHT-OSDROWHEIGHT-inc), buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrTitleBackground, m_Font);
    m_Osd->DrawEllipse(0, OSDWIN_Y(OSDLCRHEIGHT-OSDCORNERING-inc), OSDCORNERING, OSDWIN_Y(OSDLCRHEIGHT-inc), clrTransparent, -3);
    m_Osd->DrawEllipse((OSDWIDTH-OSDCORNERING), OSDWIN_Y(OSDLCRHEIGHT-OSDCORNERING-inc), OSDWIDTH, OSDWIN_Y(OSDLCRHEIGHT-inc), clrTransparent, -4);
    m_Osd->Flush();
  }
}

void cLcrOsd::Action(void)
{
  Dprintf("%s()\n", __PRETTY_FUNCTION__);
  cTimeMs t;
  m_Active = false;
   
  {
    m_Active = true;
    cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
    while (m_Active)
    {
      t.Set(0);
      int offset = 0;
      char buf[128];

      int j = 0;
      int i;

      for ( i=OSDROWHEIGHT; i<OSDWIN_Y(OSDLCRHEIGHT-OSDROWHEIGHT); i += inc )
      {
        if ( !lcrConfig.zoomIn ) // set to final values to display the same as
                                 // if zoom-in had finished
        {
          i = OSDWIN_Y(OSDLCRHEIGHT-OSDROWHEIGHT-inc);
          j = (OSDWIDTH-OSDWIN_Y(0))/2;       	  
        }

        m_Osd->DrawRectangle(0, OSDWIN_Y(OSDROWHEIGHT), OSDWIDTH, OSDWIN_Y(OSDLCRHEIGHT-OSDROWHEIGHT), clrTransparent);
        m_Osd->DrawRectangle( (OSDWIDTH-OSDWIN_Y(0))/2-j, OSDWIN_Y(OSDROWHEIGHT), (OSDWIDTH-OSDWIN_Y(0))/2+j, i, lcrTheme[lcrConfig.theme].clrBackground);
        
        m_Osd->DrawRectangle(0, OSDWIN_Y(offset), OSDWIDTH, OSDWIN_Y(offset+OSDROWHEIGHT-1), lcrTheme[lcrConfig.theme].clrTitleBackground);
        m_Osd->DrawText(OSDWIN_X(1), OSDWIN_Y(offset), tr(MAINMENUENTRY), lcrTheme[lcrConfig.theme].clrTitleText, lcrTheme[lcrConfig.theme].clrTitleBackground, m_Font);
        m_Osd->DrawEllipse(0, OSDWIN_Y(0), OSDCORNERING, OSDWIN_Y(OSDCORNERING), clrTransparent, -2);
        m_Osd->DrawEllipse((OSDWIDTH-OSDCORNERING), OSDWIN_Y(0), OSDWIDTH, OSDWIN_Y(OSDCORNERING), clrTransparent, -1);                   
                       
       	m_Osd->DrawRectangle(0, i, OSDWIDTH, i + OSDROWHEIGHT, lcrTheme[lcrConfig.theme].clrTitleBackground);
        snprintf(buf, sizeof(buf), "%d%s %s", m_Number ? m_Number : channel->Number(), m_Number ? "-" : "", channel->ShortName(true));
        m_Osd->DrawText(OSDWIN_X(1), i, buf, lcrTheme[lcrConfig.theme].clrInactiveText, lcrTheme[lcrConfig.theme].clrTitleBackground, m_Font);
        m_Osd->DrawEllipse(0, i+OSDROWHEIGHT-OSDCORNERING, OSDCORNERING, i + OSDROWHEIGHT, clrTransparent, -3);
        m_Osd->DrawEllipse((OSDWIDTH-OSDCORNERING), i+OSDROWHEIGHT-OSDCORNERING, OSDWIDTH, i + OSDROWHEIGHT, clrTransparent, -4);
        m_Osd->Flush();
        
        if ( !lcrConfig.zoomIn )
        {
          break;
        }
        else
        {
          // with my CPU (PIII 1,2GHz) this works fine
          cCondWait::SleepMs(50 * lcrConfig.updateinterval - t.Elapsed());
          if ( j < OSDWIDTH-OSDROWHEIGHT )
          {
            j += 7;
          }
          else
          {
            j = OSDWIDTH;
          }
        }
      }
      m_Active = false;
    }
  }
  DrawWindow();
}

void cLcrOsd::Show(void)
{
  Dprintf("%s()\n", __PRETTY_FUNCTION__);

  m_Osd = cOsdProvider::NewOsd(((Setup.OSDWidth - OSDWIDTH) / 2) + Setup.OSDLeft + lcrConfig.osdoffsetX, ((Setup.OSDHeight - OSDHEIGHT) / 2) + Setup.OSDTop + lcrConfig.osdoffsetY);  
  /**
   * Conditions for areas (depending color depth):
   * 8 bit: no restrictions
   * 4 bit: width % 2 == 0
   * 2 bit: width % 4 == 0
   * 1 bit: width % 8 == 0
   */
  if (m_Osd)
  {
  	// for 4MB-cards
    tArea Areas1[] = { { 0, 0, OSDWIDTH, OSDHEIGHT, 4 } }; //4
    if (m_Osd->CanHandleAreas(Areas1, sizeof(Areas1) / sizeof(tArea)) == oeOk) {
      m_Osd->SetAreas(Areas1, sizeof(Areas1) / sizeof(tArea));
    }
    else
    { // for 2MB-cards
      tArea Areas2[] = { { 0, OSDWIN_Y(0), (OSDWIDTH-1), OSDWIN_Y(OSDLCRHEIGHT-1), 4 } };
      if (m_Osd->CanHandleAreas(Areas2, sizeof(Areas2) / sizeof(tArea)) == oeOk) {
        m_Osd->SetAreas(Areas2, sizeof(Areas2) / sizeof(tArea));
      }
      // TODO: Test this with DXR3
      else // for DXR3s ??? (the minimum)
      {      
        tArea Areas3[] = { { 0          , OSDWIN_Y(0)                              , OSDWIDTH-1                      , OSDWIN_Y(0) + INITIAL_OFFSET + logoOffset-1               , 2 }, //4
                         { OSDWIN_X(0), OSDWIN_Y(0) + INITIAL_OFFSET + logoOffset, OSDWIN_X(0) + bmLogo.Width()-1, OSDWIN_Y(0)+ INITIAL_OFFSET + logoOffset + bmLogo.Height()-1, 4 }, //4
                         { OSDWIN_X(0) + bmLogo.Width(), OSDWIN_Y(0) + INITIAL_OFFSET + logoOffset, OSDWIDTH-1, OSDWIN_Y(OSDLCRHEIGHT),     2 },
                         { 0          , OSDWIN_Y(0) + INITIAL_OFFSET + logoOffset, OSDWIN_X(0)-1                , OSDWIN_Y(OSDLCRHEIGHT),   2 },
                         { OSDWIN_X(0), OSDWIN_Y(0)+ INITIAL_OFFSET + logoOffset + bmLogo.Height(), OSDWIN_X(0) + bmLogo.Width()-1, OSDWIN_Y(OSDLCRHEIGHT), 2 }
                         };
        m_Osd->SetAreas(Areas3, sizeof(Areas3) / sizeof(tArea));
//esyslog("INFO: cLcrOsd::Show() 0: X1(%d) X2(%d), Y1(%d) Y2(%d) width(%d) height(%d)", Areas2[0].x1, Areas2[0].y1, Areas2[0].x2, Areas2[0].y2, Areas2[0].Width(), Areas2[0].Height() );
      }
    }
    m_Osd->DrawRectangle(0, OSDWIN_Y(0), OSDWIDTH, OSDWIN_Y(OSDLCRHEIGHT), clrTransparent);
    m_Osd->Flush();

    /** First start the thread (pseudo-thread just to manage the zoom-in effect) */
    Start();
    /** Then (in background, because the thread is running simultaneously) grab
     *  the data from the web. */
    SystemExec( XQUOTE(RETRIEVAL_SCRIPT > RATES_FILE_NAME.tmp) );
    /**
     * Because the file (seemingly) is created immediately, we have to save it
     * with another name and rename it later on to its final name.
     * @see cLcrOsd::DrawWindow()
     */
    SystemExec( XQUOTE(mv RATES_FILE_NAME.tmp RATES_FILE_NAME) );
  }
}

eOSState cLcrOsd::ProcessKey(eKeys Key)
{
	//TODO: check why VDR with the running plugin
    //      takes about 5-10% more cpu-time (because of the DrawWindow()
    //      call?! but without the call it even takes 15%+ cpu-time)
    //      Other plugins (e.g. mplayer/mp3,tv-onscreen) do not occupy the
    //      cpu that much. Is ProcessKey() called too often/fast?!
    //      Hints are warmly welcome!
  eOSState state = cOsdObject::ProcessKey(Key);
  if (state == osUnknown)
  {
    switch (Key) {
      case k0:
           if ((m_Number == 0) && (m_OldNumber != 0)) {
             m_Number = m_OldNumber;
             m_OldNumber = cDevice::CurrentChannel();
             Channels.SwitchTo(m_Number);
             m_Number = 0;
             DrawWindow();
             return osContinue;
           }
      case k1 ... k9:
          if (m_Number >= 0)
          {
            m_Number = m_Number * 10 + Key - k0;
            if (m_Number > 0)
            {
              DrawWindow();
              cChannel *ch = Channels.GetByNumber(m_Number);
              m_InputTime.Set(0);
//            Lets see if there can be any useful further input:
              int n = ch ? m_Number * 10 : 0;
              while (ch && (ch = Channels.Next(ch)) != NULL)
              {
                if (!ch->GroupSep())
                {
                  if (n <= ch->Number() && ch->Number() <= n + 9)
                  {
                    n = 0;
                    break;
                  }
                  if (ch->Number() > n)
                  {
                    n *= 10;
                  }
                }
              }
              if (n > 0)
              {
//              This channel is the only one that fits the input, so let's take it right away:
                m_OldNumber = cDevice::CurrentChannel();
                Channels.SwitchTo(m_Number);
                m_Number = 0;
              }
            }
          }
          DrawWindow();
          break;
      case kBack:
           return osEnd;
      case kUp|k_Repeat:
      case kUp:
      case kDown|k_Repeat:
      case kDown:
           m_OldNumber = cDevice::CurrentChannel();
           cDevice::SwitchChannel(NORMALKEY(Key) == kUp ? 1 : -1);
           m_Number = 0;
           DrawWindow();
           break;
           
      case kNone:
           if (m_Number && ((int)m_InputTime.Elapsed() > CHANNELINPUT_TIMEOUT))
           {
             if (Channels.GetByNumber(m_Number))
             {
               m_OldNumber = cDevice::CurrentChannel();
               Channels.SwitchTo(m_Number);
               m_Number = 0;
             }
             else
             {
               m_InputTime.Set(0);
               m_Number = 0;
             }               
           }
           if ( !m_Active && m_Number == 0 && m_OldNumber != cDevice::CurrentChannel() )
           {
             DrawWindow();
           }
           break;
      case kOk:
           return osEnd;
      default:
           break;
      }
    state = osContinue;
  }
  return state;
}
