/*
 * skinclassic.c: The 'classic' VDR skin
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
 * $Id: skinclassic.c 1.12 2005/05/16 10:45:07 kls Exp $
 */

#include "skinclassic.h"
#include "i18n.h"
#include <vdr/font.h>
#include <vdr/osd.h>
#include <vdr/menu.h>
#include <vdr/themes.h>
#include <vdr/plugin.h>

#include "symbols/audio.xpm"
#include "symbols/audioleft.xpm"
#include "symbols/audioright.xpm"
#include "symbols/audiostereo.xpm"
#include "symbols/dolbydigital.xpm"
#include "symbols/encrypted.xpm"
#include "symbols/radio.xpm"
#include "symbols/recording.xpm"
#include "symbols/teletext.xpm"

cBitmap bmTeletext(teletext_xpm);
cBitmap bmRadio(radio_xpm);
cBitmap bmAudio(audio_xpm);
cBitmap bmDolbyDigital(dolbydigital_xpm);
cBitmap bmEncrypted(encrypted_xpm);
cBitmap bmRecording(recording_xpm);
cBitmap bmAudioLeft(audioleft_xpm);
cBitmap bmAudioRight(audioright_xpm);
cBitmap bmAudioStereo(audiostereo_xpm);

#define ScrollWidth  5

static const char *VERSION        = "0.0.2";
static const char *DESCRIPTION    = "Classic VDR Skin-Plugin";
static const char *MAINMENUENTRY  = NULL;

static cTheme Theme;

THEME_CLR(Theme, clrBackground,             0x9F000000); // clrGray50
THEME_CLR(Theme, clrButtonRedFg,            clrWhite);
THEME_CLR(Theme, clrButtonRedBg,            clrRed);
THEME_CLR(Theme, clrButtonGreenFg,          clrBlack);
THEME_CLR(Theme, clrButtonGreenBg,          clrGreen);
THEME_CLR(Theme, clrButtonYellowFg,         clrBlack);
THEME_CLR(Theme, clrButtonYellowBg,         clrYellow);
THEME_CLR(Theme, clrButtonBlueFg,           clrWhite);
THEME_CLR(Theme, clrButtonBlueBg,           clrBlue);
THEME_CLR(Theme, clrMessageStatusFg,        clrBlack);
THEME_CLR(Theme, clrMessageStatusBg,        clrCyan);
THEME_CLR(Theme, clrMessageInfoFg,          clrBlack);
THEME_CLR(Theme, clrMessageInfoBg,          clrGreen);
THEME_CLR(Theme, clrMessageWarningFg,       clrBlack);
THEME_CLR(Theme, clrMessageWarningBg,       clrYellow);
THEME_CLR(Theme, clrMessageErrorFg,         clrWhite);
THEME_CLR(Theme, clrMessageErrorBg,         clrRed);
THEME_CLR(Theme, clrVolumePrompt,           clrGreen);
THEME_CLR(Theme, clrVolumeBarUpper,         clrWhite);
THEME_CLR(Theme, clrVolumeBarLower,         clrGreen);
THEME_CLR(Theme, clrChannelName,            clrWhite);
THEME_CLR(Theme, clrChannelDate,            clrWhite);
THEME_CLR(Theme, clrChannelEpgTimeFg,       clrWhite);
THEME_CLR(Theme, clrChannelEpgTimeBg,       clrRed);
THEME_CLR(Theme, clrChannelEpgTitle,        clrCyan);
THEME_CLR(Theme, clrChannelEpgShortText,    clrYellow);
THEME_CLR(Theme, clrChannelSymbolOn,        clrWhite);
THEME_CLR(Theme, clrChannelSymbolOff,       clrGray50);
THEME_CLR(Theme, clrChannelSymbolRecFg,     clrWhite);
THEME_CLR(Theme, clrChannelSymbolRecBg,     clrRed);
THEME_CLR(Theme, clrMenuTitleFg,            clrWhite);   // clrBlack
THEME_CLR(Theme, clrMenuTitleBg,            0x9F000000); // clrCyan
THEME_CLR(Theme, clrMenuDate,               clrWhite);   // clrBlack
THEME_CLR(Theme, clrMenuItemCurrentFg,      clrBlack);
THEME_CLR(Theme, clrMenuItemCurrentBg,      clrCyan);
THEME_CLR(Theme, clrMenuItemSelectable,     clrWhite);
THEME_CLR(Theme, clrMenuItemNonSelectable,  clrCyan);
THEME_CLR(Theme, clrMenuEventTime,          clrWhite);
THEME_CLR(Theme, clrMenuEventVpsFg,         clrBlack);
THEME_CLR(Theme, clrMenuEventVpsBg,         clrWhite);
THEME_CLR(Theme, clrMenuEventTitle,         clrCyan);
THEME_CLR(Theme, clrMenuEventShortText,     clrWhite);
THEME_CLR(Theme, clrMenuEventDescription,   clrCyan);
THEME_CLR(Theme, clrMenuScrollbarTotal,     clrWhite);
THEME_CLR(Theme, clrMenuScrollbarShown,     clrCyan);
THEME_CLR(Theme, clrMenuText,               clrWhite);
THEME_CLR(Theme, clrReplayTitle,            clrWhite);
THEME_CLR(Theme, clrReplayCurrent,          clrWhite);
THEME_CLR(Theme, clrReplayTotal,            clrWhite);
THEME_CLR(Theme, clrReplayModeJump,         clrWhite);
THEME_CLR(Theme, clrReplayProgressSeen,     clrGreen);
THEME_CLR(Theme, clrReplayProgressRest,     clrWhite);
THEME_CLR(Theme, clrReplayProgressSelected, clrRed);
THEME_CLR(Theme, clrReplayProgressMark,     clrBlack);
THEME_CLR(Theme, clrReplayProgressCurrent,  clrRed);

// --- cSkinKlassicDisplayChannel --------------------------------------------

class cSkinKlassicDisplayChannel : public cSkinDisplayChannel {
private:
  cOsd *osd;
  bool withInfo;
  int lineHeight;
  int timeWidth;
  bool message;
  const cEvent *present;
  int lastSeen;
  cBitmap *classic;
  cBitmap *timeline;
public:
  cSkinKlassicDisplayChannel(bool WithInfo);
  virtual ~cSkinKlassicDisplayChannel();
  virtual void SetChannel(const cChannel *Channel, int Number);
  virtual void SetEvents(const cEvent *Present, const cEvent *Following);
  virtual void SetMessage(eMessageType Type, const char *Text);
  virtual void Flush(void);
  };

cSkinKlassicDisplayChannel::cSkinKlassicDisplayChannel(bool WithInfo)
{
  present = NULL;
  lastSeen = -1;
  withInfo = WithInfo;
  int Lines = withInfo ? 5 : 1;
  const cFont *font = cFont::GetFont(fontOsd);
  lineHeight = font->Height();
  message = false;
  osd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + (Setup.ChannelInfoPos ? 0 : Setup.OSDHeight - (lineHeight + Lines * lineHeight)));
  timeWidth = font->Width("00:00") + 4;
  tArea Areas[] = { { 0, lineHeight, Setup.OSDWidth - 1, lineHeight + Lines * lineHeight - 1, 4 },
	    	    { Setup.OSDWidth - 128, 0, Setup.OSDWidth - 1, lineHeight / 2 - 1, 2 }, };
  osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
  classic = osd->GetBitmap(0);
  timeline = osd->GetBitmap(1);
  timeline->DrawRectangle(timeline->X0(), 0, timeline->X0() + timeline->Width() - 1, timeline->Height() - 1, clrTransparent);
  classic->DrawRectangle(0, classic->Y0(), osd->Width() - 1, classic->Y0() + classic->Height() - 1, Theme.Color(clrBackground));
}

cSkinKlassicDisplayChannel::~cSkinKlassicDisplayChannel()
{
  delete osd;
}

void cSkinKlassicDisplayChannel::SetChannel(const cChannel *Channel, int Number)
{
  int x = 2;
  classic->DrawRectangle(0, classic->Y0(), osd->Width() - 1, classic->Y0() + lineHeight - 1, Theme.Color(clrBackground));
  if (Channel && !Channel->GroupSep()) {
     int d = 3;
     bool rec = cRecordControls::Active();
     classic->DrawBitmap(x, classic->Y0() + max (0, (lineHeight - bmRecording.Height()) / 2), bmRecording, Theme.Color(rec ? clrChannelSymbolRecFg : clrChannelSymbolOff), Theme.Color(rec ? clrChannelSymbolRecBg : clrBackground));
     x += bmRecording.Width() + d;
     classic->DrawBitmap(x, classic->Y0() + max (0, (lineHeight - bmEncrypted.Height()) / 2), bmEncrypted, Theme.Color(Channel->Ca() ? clrChannelSymbolOn : clrChannelSymbolOff), Theme.Color(clrBackground));
     x += bmEncrypted.Width() + d;
     classic->DrawBitmap(x, classic->Y0() + max (0, (lineHeight - bmDolbyDigital.Height()) / 2), bmDolbyDigital, Theme.Color(Channel->Dpid(0) ? clrChannelSymbolOn : clrChannelSymbolOff), Theme.Color(clrBackground));
     x += bmDolbyDigital.Width() + d;
     classic->DrawBitmap(x, classic->Y0() + max (0, (lineHeight - bmAudio.Height()) / 2), bmAudio, Theme.Color(Channel->Apid(1) ? clrChannelSymbolOn : clrChannelSymbolOff), Theme.Color(clrBackground));
     x += bmAudio.Width() + d;
     if (Channel->Vpid()) {
        classic->DrawBitmap(x, classic->Y0() + max (0, (lineHeight - bmTeletext.Height()) / 2), bmTeletext, Theme.Color(Channel->Tpid() ? clrChannelSymbolOn : clrChannelSymbolOff), Theme.Color(clrBackground));
        x += bmTeletext.Width() + d;
        }
     else if (Channel->Apid(0)) {
        classic->DrawBitmap(x, classic->Y0() + max (0, (lineHeight - bmRadio.Height()) / 2), bmRadio, Theme.Color(clrChannelSymbolOn), Theme.Color(clrBackground));
        x += bmRadio.Width() + d;
        }
     }
  classic->DrawText(x, classic->Y0(), ChannelString(Channel, Number), Theme.Color(clrChannelName), Theme.Color(clrBackground), cFont::GetFont(fontOsd));
}

void cSkinKlassicDisplayChannel::SetEvents(const cEvent *Present, const cEvent *Following)
{
  if (!withInfo)
     return;
  if (present != Present)
     lastSeen = -1;
  present = Present;
  classic->DrawRectangle(0, classic->Y0() + lineHeight, timeWidth - 1, classic->Y0() + classic->Height(), Theme.Color(clrChannelEpgTimeBg));
  classic->DrawRectangle(timeWidth, classic->Y0() + lineHeight, osd->Width() - 1, classic->Y0() + classic->Height(), Theme.Color(clrBackground));
  for (int i = 0; i < 2; i++) {
      const cEvent *e = !i ? Present : Following;
      if (e) {
         classic->DrawText(             2, classic->Y0() + (2 * i + 1) * lineHeight, e->GetTimeString(), Theme.Color(clrChannelEpgTimeFg), Theme.Color(clrChannelEpgTimeBg), cFont::GetFont(fontOsd));
         classic->DrawText(timeWidth + 10, classic->Y0() + (2 * i + 1) * lineHeight, e->Title(), Theme.Color(clrChannelEpgTitle), Theme.Color(clrBackground), cFont::GetFont(fontOsd));
         classic->DrawText(timeWidth + 10, classic->Y0() + (2 * i + 2) * lineHeight, e->ShortText(), Theme.Color(clrChannelEpgShortText), Theme.Color(clrBackground), cFont::GetFont(fontSml));
         }
      }
}

void cSkinKlassicDisplayChannel::SetMessage(eMessageType Type, const char *Text)
{
  const cFont *font = cFont::GetFont(fontOsd);
  if (Text) {
     timeline ? osd->SaveRegion(0, lineHeight, osd->Width() - 1, lineHeight + lineHeight - 1) : osd->SaveRegion(0, 0, osd->Width() - 1, lineHeight - 1);
     classic->DrawText(0, classic->Y0(), Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), font, osd->Width(), 0, taCenter);
     message = true;
     }
  else {
     osd->RestoreRegion();
     message = false;
     }
}

void cSkinKlassicDisplayChannel::Flush(void)
{
  if (!message) {
     cString date = DayDateTime();
     classic->DrawText(osd->Width() - cFont::GetFont(fontSml)->Width(date) - 2, classic->Y0(), date, Theme.Color(clrChannelDate), Theme.Color(clrBackground), cFont::GetFont(fontSml));
     }
  if (withInfo) {
     int seen = 0;
     if (present) {
        time_t t = time(NULL);
        if (t > present->StartTime()) {
    	    seen = min(timeline->Width() - 2 * 2 - 1, int((timeline->Width() - 2 * 2) * double(t - present->StartTime()) / present->Duration()));
    	    if (seen < 0)
		seen = 0;
	    }
        }
     else {
        timeline->DrawRectangle(timeline->X0(), 0, timeline->X0() + timeline->Width() - 1, timeline->Height() - 1, clrTransparent);
	lastSeen = 0;
	}
     if (seen != lastSeen) {
        osd->DrawRectangle(timeline->X0(), 0, timeline->X0() + timeline->Width() - 1, timeline->Height() - 1, tColor(0xAA777777));
        if (seen)
    	    osd->DrawRectangle(timeline->X0() + 2, 2, timeline->X0() + seen, timeline->Height() -  2 - 1, clrWhite);
        lastSeen = seen;
        }
     }
  osd->Flush();
}

// --- cSkinKlassicDisplayMenu -----------------------------------------------

class cSkinKlassicDisplayMenu : public cSkinDisplayMenu {
private:
  cOsd *osd;
  int x0, x1;
  int y0, y1, y2, y3, y4, y5;
  int lineHeight;
  void SetScrollbar(void);
public:
  cSkinKlassicDisplayMenu(void);
  virtual ~cSkinKlassicDisplayMenu();
  virtual void Scroll(bool Up, bool Page);
  virtual int MaxItems(void);
  virtual void Clear(void);
  virtual void SetTitle(const char *Title);
  virtual void SetButtons(const char *Red, const char *Green = NULL, const char *Yellow = NULL, const char *Blue = NULL);
  virtual void SetMessage(eMessageType Type, const char *Text);
  virtual void SetItem(const char *Text, int Index, bool Current, bool Selectable);
  virtual void SetEvent(const cEvent *Event);
  virtual void SetRecording(const cRecording *Recording);
  virtual void SetText(const char *Text, bool FixedFont);
  virtual void Flush(void);
  };

cSkinKlassicDisplayMenu::cSkinKlassicDisplayMenu(void)
{
  const cFont *font = cFont::GetFont(fontOsd);
  lineHeight = font->Height();
  x0 = 0;
  x1 = Setup.OSDWidth;
  y0 = 0;
  y1 = lineHeight;
  y2 = y1 + lineHeight;
  y5 = Setup.OSDHeight;
  y4 = y5 - lineHeight;
  y3 = y4 - lineHeight;
  osd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop);
  tArea Areas[] = { { x0, y0, x1 - 1, y5 - 1, 4 } };
  if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk)
     osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
  else {
     tArea Areas[] = { { x0, y0, x1 - 1, y1 - 1, 2 },
                       { x0, y1, x1 - 1, y3 - 1, 2 },
                       { x0, y3, x1 - 1, y5 - 1, 4 }
                     };
     osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
     }
  osd->DrawRectangle(x0, y0, x1 - 1, y5 - 1, Theme.Color(clrBackground));
}

cSkinKlassicDisplayMenu::~cSkinKlassicDisplayMenu()
{
  delete osd;
}

void cSkinKlassicDisplayMenu::SetScrollbar(void)
{
  if (textScroller.CanScroll()) {
     int yt = textScroller.Top();
     int yb = yt + textScroller.Height();
     int st = yt;
     int sb = yb;
     int tt = st + (sb - st) * textScroller.Offset() / textScroller.Total();
     int tb = tt + (sb - st) * textScroller.Shown() / textScroller.Total();
     int xl = x1 - ScrollWidth;
     osd->DrawRectangle(xl, st, x1 - 1, sb, Theme.Color(clrMenuScrollbarTotal));
     osd->DrawRectangle(xl, tt, x1 - 1, tb, Theme.Color(clrMenuScrollbarShown));
     }
}

void cSkinKlassicDisplayMenu::Scroll(bool Up, bool Page)
{
  cSkinDisplayMenu::Scroll(Up, Page);
  SetScrollbar();
}

int cSkinKlassicDisplayMenu::MaxItems(void)
{
  return (y3 - y2) / lineHeight;
}

void cSkinKlassicDisplayMenu::Clear(void)
{
  textScroller.Reset();
  osd->DrawRectangle(x0, y1, x1 - 1, y4 - 1, Theme.Color(clrBackground));
}

void cSkinKlassicDisplayMenu::SetTitle(const char *Title)
{
  const cFont *font = cFont::GetFont(fontOsd);
  osd->DrawText(x0, y0, Title, Theme.Color(clrMenuTitleFg), Theme.Color(clrMenuTitleBg), font, x1 - x0);
}

void cSkinKlassicDisplayMenu::SetButtons(const char *Red, const char *Green, const char *Yellow, const char *Blue)
{
  const cFont *font = cFont::GetFont(fontOsd);
  int w = x1 - x0;
  int t0 = x0;
  int t1 = x0 + w / 4;
  int t2 = x0 + w / 2;
  int t3 = x1 - w / 4;
  int t4 = x1;
  osd->DrawText(t0, y4, Red,    Theme.Color(clrButtonRedFg),    Red    ? Theme.Color(clrButtonRedBg)    : Theme.Color(clrBackground), font, t1 - t0, 0, taCenter);
  osd->DrawText(t1, y4, Green,  Theme.Color(clrButtonGreenFg),  Green  ? Theme.Color(clrButtonGreenBg)  : Theme.Color(clrBackground), font, t2 - t1, 0, taCenter);
  osd->DrawText(t2, y4, Yellow, Theme.Color(clrButtonYellowFg), Yellow ? Theme.Color(clrButtonYellowBg) : Theme.Color(clrBackground), font, t3 - t2, 0, taCenter);
  osd->DrawText(t3, y4, Blue,   Theme.Color(clrButtonBlueFg),   Blue   ? Theme.Color(clrButtonBlueBg)   : Theme.Color(clrBackground), font, t4 - t3, 0, taCenter);
}

void cSkinKlassicDisplayMenu::SetMessage(eMessageType Type, const char *Text)
{
  const cFont *font = cFont::GetFont(fontOsd);
  if (Text)
     osd->DrawText(x0, y3, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), font, x1 - x0, 0, taCenter);
  else
     osd->DrawRectangle(x0, y3, x1 - 1, y4 - 1, Theme.Color(clrBackground));
}

void cSkinKlassicDisplayMenu::SetItem(const char *Text, int Index, bool Current, bool Selectable)
{
  int y = y2 + Index * lineHeight;
  tColor ColorFg, ColorBg;
  if (Current) {
     ColorFg = Theme.Color(clrMenuItemCurrentFg);
     ColorBg = Theme.Color(clrMenuItemCurrentBg);
     }
  else {
     ColorFg = Theme.Color(Selectable ? clrMenuItemSelectable : clrMenuItemNonSelectable);
     ColorBg = Theme.Color(clrBackground);
     }
  const cFont *font = cFont::GetFont(fontOsd);
  for (int i = 0; i < MaxTabs; i++) {
      const char *s = GetTabbedText(Text, i);
      if (s) {
         int xt = x0 + Tab(i);
         osd->DrawText(xt, y, s, ColorFg, ColorBg, font, x1 - xt);
         }
      if (!Tab(i + 1))
         break;
      }
  SetEditableWidth(x1 - x0 - Tab(1));
}

void cSkinKlassicDisplayMenu::SetEvent(const cEvent *Event)
{
  if (!Event)
     return;
  const cFont *font = cFont::GetFont(fontOsd);
  int xl = x0 + 10;
  int y = y2;
  cTextScroller ts;
  char t[32];
  snprintf(t, sizeof(t), "%s  %s - %s", *Event->GetDateString(), *Event->GetTimeString(), *Event->GetEndTimeString());
  ts.Set(osd, xl, y, x1 - xl, y3 - y, t, font, Theme.Color(clrMenuEventTime), Theme.Color(clrBackground));
  if (Event->Vps() && Event->Vps() != Event->StartTime()) {
     char *buffer;
     asprintf(&buffer, " VPS: %s", *Event->GetVpsString());
     const cFont *font = cFont::GetFont(fontSml);
     osd->DrawText(x1 - font->Width(buffer), y, buffer, Theme.Color(clrMenuEventVpsFg), Theme.Color(clrMenuEventVpsBg), font);
     free(buffer);
     }
  y += ts.Height();
  y += font->Height();
  ts.Set(osd, xl, y, x1 - xl, y3 - y, Event->Title(), font, Theme.Color(clrMenuEventTitle), Theme.Color(clrBackground));
  y += ts.Height();
  if (!isempty(Event->ShortText())) {
     const cFont *font = cFont::GetFont(fontSml);
     ts.Set(osd, xl, y, x1 - xl, y3 - y, Event->ShortText(), font, Theme.Color(clrMenuEventShortText), Theme.Color(clrBackground));
     y += ts.Height();
     }
  y += font->Height();
  if (!isempty(Event->Description())) {
     textScroller.Set(osd, xl, y, x1 - xl - 2 * ScrollWidth, y3 - y, Event->Description(), font, Theme.Color(clrMenuEventDescription), Theme.Color(clrBackground));
     SetScrollbar();
     }
}

void cSkinKlassicDisplayMenu::SetRecording(const cRecording *Recording)
{
#if VDRVERSNUM >= 10325
  if (!Recording)
     return;
  const cRecordingInfo *Info = Recording->Info();
  const cFont *font = cFont::GetFont(fontOsd);
  int xl = x0 + 10;
  int y = y2;
  cTextScroller ts;
  char t[32];
  snprintf(t, sizeof(t), "%s  %s", *DateString(Recording->start), *TimeString(Recording->start));
  ts.Set(osd, xl, y, x1 - xl, y3 - y, t, font, Theme.Color(clrMenuEventTime), Theme.Color(clrBackground));
  y += ts.Height();
  y += font->Height();
  const char *Title = Info->Title();
  if (isempty(Title))
     Title = Recording->Name();
  ts.Set(osd, xl, y, x1 - xl, y3 - y, Title, font, Theme.Color(clrMenuEventTitle), Theme.Color(clrBackground));
  y += ts.Height();
  if (!isempty(Info->ShortText())) {
     const cFont *font = cFont::GetFont(fontSml);
     ts.Set(osd, xl, y, x1 - xl, y3 - y, Info->ShortText(), font, Theme.Color(clrMenuEventShortText), Theme.Color(clrBackground));
     y += ts.Height();
     }
  y += font->Height();
  if (!isempty(Info->Description())) {
     textScroller.Set(osd, xl, y, x1 - xl - 2 * ScrollWidth, y3 - y, Info->Description(), font, Theme.Color(clrMenuEventDescription), Theme.Color(clrBackground));
     SetScrollbar();
     }
#else
  SetText(Recording->Summary(), false); //TODO
#endif
}

void cSkinKlassicDisplayMenu::SetText(const char *Text, bool FixedFont)
{
  const cFont *font = cFont::GetFont(FixedFont ? fontFix : fontOsd);
  textScroller.Set(osd, x0, y2, x1 - x0 - 2 * ScrollWidth, y3 - y2, Text, font, Theme.Color(clrMenuText), Theme.Color(clrBackground));
  SetScrollbar();
}

void cSkinKlassicDisplayMenu::Flush(void)
{
  cString date = DayDateTime();
  const cFont *font = cFont::GetFont(fontOsd);
  osd->DrawText(x1 - font->Width(date) - 2, y0, date, Theme.Color(clrMenuDate), Theme.Color(clrMenuTitleBg), font);
  osd->Flush();
}

// --- cSkinKlassicDisplayReplay ---------------------------------------------

class cSkinKlassicDisplayReplay : public cSkinDisplayReplay {
private:
  cOsd *osd;
  int x0, x1;
  int y0, y1, y2, y3;
  int lastCurrentWidth;
  bool message;
public:
  cSkinKlassicDisplayReplay(bool ModeOnly);
  virtual ~cSkinKlassicDisplayReplay();
  virtual void SetTitle(const char *Title);
  virtual void SetMode(bool Play, bool Forward, int Speed);
  virtual void SetProgress(int Current, int Total);
  virtual void SetCurrent(const char *Current);
  virtual void SetTotal(const char *Total);
  virtual void SetJump(const char *Jump);
  virtual void SetMessage(eMessageType Type, const char *Text);
  virtual void Flush(void);
  };

cSkinKlassicDisplayReplay::cSkinKlassicDisplayReplay(bool ModeOnly)
{
  const cFont *font = cFont::GetFont(fontOsd);
  int lineHeight = font->Height();
  lastCurrentWidth = 0;
  message = false;
  x0 = 0;
  x1 = Setup.OSDWidth;
  y0 = 0;
  y1 = lineHeight;
  y2 = 2 * lineHeight;
  y3 = 3 * lineHeight;
  osd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + Setup.OSDHeight - y3);
  tArea Areas[] = { { x0, y0, x1 - 1, y3 - 1, 4 } };
  osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
  osd->DrawRectangle(x0, y0, x1 - 1, y3 - 1, ModeOnly ? clrTransparent : Theme.Color(clrBackground));
}

cSkinKlassicDisplayReplay::~cSkinKlassicDisplayReplay()
{
  delete osd;
}

void cSkinKlassicDisplayReplay::SetTitle(const char *Title)
{
  osd->DrawText(x0, y0, Title, Theme.Color(clrReplayTitle), Theme.Color(clrBackground), cFont::GetFont(fontOsd), x1 - x0);
}

void cSkinKlassicDisplayReplay::SetMode(bool Play, bool Forward, int Speed)
{
  if (Setup.ShowReplayMode) {
     const char *Mode;
     if (Speed == -1) Mode = Play    ? "  >  " : " ||  ";
     else if (Play)   Mode = Forward ? " X>> " : " <<X ";
     else             Mode = Forward ? " X|> " : " <|X ";
     char buf[16];
     strn0cpy(buf, Mode, sizeof(buf));
     char *p = strchr(buf, 'X');
     if (p)
        *p = Speed > 0 ? '1' + Speed - 1 : ' ';
     SetJump(buf);
     }
}

void cSkinKlassicDisplayReplay::SetProgress(int Current, int Total)
{
  cProgressBar pb(x1 - x0, y2 - y1, Current, Total, marks, Theme.Color(clrReplayProgressSeen), Theme.Color(clrReplayProgressRest), Theme.Color(clrReplayProgressSelected), Theme.Color(clrReplayProgressMark), Theme.Color(clrReplayProgressCurrent));
  osd->DrawBitmap(x0, y1, pb);
}

void cSkinKlassicDisplayReplay::SetCurrent(const char *Current)
{
  const cFont *font = cFont::GetFont(fontOsd);
  int w = font->Width(Current);
  osd->DrawText(x0, y2, Current, Theme.Color(clrReplayCurrent), Theme.Color(clrBackground), font, lastCurrentWidth > w ? lastCurrentWidth : 0);
  lastCurrentWidth = w;
}

void cSkinKlassicDisplayReplay::SetTotal(const char *Total)
{
  const cFont *font = cFont::GetFont(fontOsd);
  osd->DrawText(x1 - font->Width(Total), y2, Total, Theme.Color(clrReplayTotal), Theme.Color(clrBackground), font);
}

void cSkinKlassicDisplayReplay::SetJump(const char *Jump)
{
  osd->DrawText(x0 + (x1 - x0) / 4, y2, Jump, Theme.Color(clrReplayModeJump), Theme.Color(clrBackground), cFont::GetFont(fontOsd), (x1 - x0) / 2, 0, taCenter);
}

void cSkinKlassicDisplayReplay::SetMessage(eMessageType Type, const char *Text)
{
  const cFont *font = cFont::GetFont(fontOsd);
  if (Text) {
     osd->SaveRegion(x0, y2, x1 - 1, y3 - 1);
     osd->DrawText(x0, y2, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), font, x1 - x0, y3 - y2, taCenter);
     message = true;
     }
  else {
     osd->RestoreRegion();
     message = false;
     }
}

void cSkinKlassicDisplayReplay::Flush(void)
{
  osd->Flush();
}

// --- cSkinKlassicDisplayVolume ---------------------------------------------

class cSkinKlassicDisplayVolume : public cSkinDisplayVolume {
private:
  cOsd *osd;
public:
  cSkinKlassicDisplayVolume(void);
  virtual ~cSkinKlassicDisplayVolume();
  virtual void SetVolume(int Current, int Total, bool Mute);
  virtual void Flush(void);
  };

cSkinKlassicDisplayVolume::cSkinKlassicDisplayVolume(void)
{
  const cFont *font = cFont::GetFont(fontOsd);
  int lineHeight = font->Height();
  osd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + Setup.OSDHeight - lineHeight);
  tArea Areas[] = { { 0, 0, Setup.OSDWidth - 1, lineHeight - 1, 4 } };
  osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
}

cSkinKlassicDisplayVolume::~cSkinKlassicDisplayVolume()
{
  delete osd;
}

void cSkinKlassicDisplayVolume::SetVolume(int Current, int Total, bool Mute)
{
  const cFont *font = cFont::GetFont(fontOsd);
  if (Mute) {
     osd->DrawRectangle(0, 0, osd->Width() - 1, osd->Height() - 1, clrTransparent);
     osd->DrawText(0, 0, tr("Mute"), Theme.Color(clrVolumePrompt), Theme.Color(clrBackground), font);
     }
  else {
     const char *Prompt = tr("Volume ");
     int l = font->Width(Prompt);
     int p = (osd->Width() - l) * Current / Total;
     osd->DrawText(0, 0, Prompt, Theme.Color(clrVolumePrompt), Theme.Color(clrBackground), font);
     osd->DrawRectangle(l, 0, l + p - 1, osd->Height() - 1, Theme.Color(clrVolumeBarLower));
     osd->DrawRectangle(l + p, 0, osd->Width() - 1, osd->Height() - 1, Theme.Color(clrVolumeBarUpper));
     }
}

void cSkinKlassicDisplayVolume::Flush(void)
{
  osd->Flush();
}

// --- cSkinKlassicDisplayTracks ---------------------------------------------

class cSkinKlassicDisplayTracks : public cSkinDisplayTracks {
private:
  cOsd *osd;
  int x0, x1;
  int y0, y1, y2;
  int lineHeight;
  int currentIndex;
  int offset;
  int numTracks;
  void SetItem(const char *Text, int Index, bool Current);
public:
  cSkinKlassicDisplayTracks(const char *Title, int NumTracks, const char * const *Tracks);
  virtual ~cSkinKlassicDisplayTracks();
  virtual void SetTrack(int Index, const char * const *Tracks);
  virtual void SetAudioChannel(int AudioChannel);
  virtual void Flush(void);
  virtual int MaxItems(void);
  virtual void Clear(void);
  };

cSkinKlassicDisplayTracks::cSkinKlassicDisplayTracks(const char *Title, int NumTracks, const char * const *Tracks)
{
  const cFont *font = cFont::GetFont(fontOsd);
  lineHeight = font->Height();
  currentIndex = -1;
  offset = 0;
  numTracks = NumTracks;
  int ItemsLeft = max (bmAudioStereo.Width(), max (bmAudioLeft.Width(), bmAudioRight.Width()));
  int ItemsWidth = font->Width(Title) + ItemsLeft + 2;
  for (int i = 0; i < NumTracks; i++)
      ItemsWidth = max(ItemsWidth, font->Width(Tracks[i]));
  ItemsWidth += 10;
  x0 = 2;
  x1 = Setup.OSDWidth;
  int d = x1 - x0;
  if (d > ItemsWidth) {
     d = (d - ItemsWidth) & ~0x07; // must be multiple of 8
     x1 -= d;
     }
  y0 = 0;
  y1 = lineHeight;
  y2 = y1 + NumTracks * lineHeight;

  // -> ugly hack
  if (y2 > Setup.OSDHeight)
    y2 = Setup.OSDHeight;

  osd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + Setup.OSDHeight - y2);
  tArea Areas[] = { { x0 - 2, y0, x1 + 1, y2 - 1, 4 } };
  osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
  osd->DrawText(ItemsLeft + 2, y0, Title, Theme.Color(clrMenuTitleFg), Theme.Color(clrMenuTitleBg), font, x1 - x0);

//  for (int i = 0; i < NumTracks; i++)
  int MaxTracks = MaxItems();
  Clear();
  if (MaxTracks >= (numTracks - offset)) MaxTracks = numTracks - offset;
  for (int i = offset; i < offset + MaxTracks; i++)

      SetItem(Tracks[i], i, false);
}

cSkinKlassicDisplayTracks::~cSkinKlassicDisplayTracks()
{
  delete osd;
}

int cSkinKlassicDisplayTracks::MaxItems(void)
{
  return (y2 - y1) / lineHeight;
}

void cSkinKlassicDisplayTracks::Clear(void)
{
  osd->DrawRectangle(x0, y1, x1 - 1, y2 - 1, Theme.Color(clrBackground));
}

void cSkinKlassicDisplayTracks::SetItem(const char *Text, int Index, bool Current)
{
  int y = y1 + Index * lineHeight - offset * lineHeight;
  tColor ColorFg, ColorBg;
  if (Current) {
     ColorFg = Theme.Color(clrMenuItemCurrentFg);
     ColorBg = Theme.Color(clrMenuItemCurrentBg);
     currentIndex = Index;
     }
  else {
     ColorFg = Theme.Color(clrMenuItemSelectable);
     ColorBg = Theme.Color(clrBackground);
     }
  const cFont *font = cFont::GetFont(fontOsd);
  osd->DrawText(x0, y, Text, ColorFg, ColorBg, font, x1 - x0);
}

void cSkinKlassicDisplayTracks::SetTrack(int Index, const char * const *Tracks)
{
  if (currentIndex >= 0) {
     int MaxTracks = MaxItems();
     if ((Index - offset) >= MaxTracks) {
	offset += MaxTracks;
  	Clear();
	if (MaxTracks >= (numTracks - offset)) MaxTracks = numTracks - offset;
	for (int i = offset; i < offset + MaxTracks; i++)
    	    SetItem(Tracks[i], i, false);
     }
     else if (Index < offset) {
	offset -= MaxTracks;
  	Clear();
	if (MaxTracks >= (numTracks - offset)) MaxTracks = numTracks - offset;
	for (int i = offset; i < offset + MaxTracks; i++)
    	    SetItem(Tracks[i], i, false);
     }
     else SetItem(Tracks[currentIndex], currentIndex, false);
  }
  else {
     int MaxTracks = MaxItems();
     if (Index > MaxTracks) {
	int temp = Index;
        while (temp > MaxTracks) {
	    offset += MaxTracks;
	    temp   -= MaxTracks;
	}
  	Clear();
	if (MaxTracks >= (numTracks - offset)) MaxTracks = numTracks - offset;
	for (int i = offset; i < offset + MaxTracks; i++)
    	    SetItem(Tracks[i], i, false);
     }
  }
  SetItem(Tracks[Index], Index, true);
}

void cSkinKlassicDisplayTracks::SetAudioChannel(int AudioChannel)
{
  cBitmap *bm = NULL;
  switch (AudioChannel) {
    case 0: bm = &bmAudioStereo; break;
    case 1: bm = &bmAudioLeft;   break;
    case 2: bm = &bmAudioRight;  break;
    }
  int y = max (0, (lineHeight - bm->Height()) / 2);
  if (bm)
     osd->DrawBitmap(2, y, *bm, Theme.Color(clrMenuTitleFg), Theme.Color(clrMenuTitleBg));
  else
     osd->DrawRectangle(2, y, bm->Width() + 1, y + bm->Height() - 1, Theme.Color(clrMenuTitleBg));
}

void cSkinKlassicDisplayTracks::Flush(void)
{
  osd->Flush();
}

// --- cSkinKlassicDisplayMessage --------------------------------------------

class cSkinKlassicDisplayMessage : public cSkinDisplayMessage {
private:
  cOsd *osd;
public:
  cSkinKlassicDisplayMessage(void);
  virtual ~cSkinKlassicDisplayMessage();
  virtual void SetMessage(eMessageType Type, const char *Text);
  virtual void Flush(void);
  };

cSkinKlassicDisplayMessage::cSkinKlassicDisplayMessage(void)
{
  const cFont *font = cFont::GetFont(fontOsd);
  int lineHeight = font->Height();
  osd = cOsdProvider::NewOsd(Setup.OSDLeft, Setup.OSDTop + Setup.OSDHeight - lineHeight);
  tArea Areas[] = { { 0, 0, Setup.OSDWidth - 1, lineHeight - 1, 2 } };
  osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea));
}

cSkinKlassicDisplayMessage::~cSkinKlassicDisplayMessage()
{
  delete osd;
}

void cSkinKlassicDisplayMessage::SetMessage(eMessageType Type, const char *Text)
{
  const cFont *font = cFont::GetFont(fontOsd);
  osd->DrawText(0, 0, Text, Theme.Color(clrMessageStatusFg + 2 * Type), Theme.Color(clrMessageStatusBg + 2 * Type), font, Setup.OSDWidth, 0, taCenter);
}

void cSkinKlassicDisplayMessage::Flush(void)
{
  osd->Flush();
}

// --- cSkinKlassic ----------------------------------------------------------

cSkinKlassic::cSkinKlassic(void)
:cSkin("Classic_Plugin", &::Theme)//XXX naming problem???
{
}

const char *cSkinKlassic::Description(void)
{
  return tr("Classic VDR (Plugin)");
}

cSkinDisplayChannel *cSkinKlassic::DisplayChannel(bool WithInfo)
{
  return new cSkinKlassicDisplayChannel(WithInfo);
}

cSkinDisplayMenu *cSkinKlassic::DisplayMenu(void)
{
  return new cSkinKlassicDisplayMenu;
}

cSkinDisplayReplay *cSkinKlassic::DisplayReplay(bool ModeOnly)
{
  return new cSkinKlassicDisplayReplay(ModeOnly);
}

cSkinDisplayVolume *cSkinKlassic::DisplayVolume(void)
{
  return new cSkinKlassicDisplayVolume;
}


cSkinDisplayTracks *cSkinKlassic::DisplayTracks(const char *Title, int NumTracks, const char * const *Tracks)
{
  return new cSkinKlassicDisplayTracks(Title, NumTracks, Tracks);
}

cSkinDisplayMessage *cSkinKlassic::DisplayMessage(void)
{
  return new cSkinKlassicDisplayMessage;
}

// --- cPluginSkinKlassic -----------------------------------------------------

class cPluginSkinKlassic : public cPlugin {
private:
public:
  cPluginSkinKlassic(void);
  virtual ~cPluginSkinKlassic();
  virtual const char *Version(void) { return VERSION; }
  virtual const char *Description(void) { return DESCRIPTION; }
  virtual const char *CommandLineHelp(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Initialize(void);
  virtual bool Start(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
  virtual cOsdObject *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
  };

cPluginSkinKlassic::cPluginSkinKlassic(void)
{
}

cPluginSkinKlassic::~cPluginSkinKlassic()
{
}

const char *cPluginSkinKlassic::CommandLineHelp(void)
{
  return NULL;
}

bool cPluginSkinKlassic::ProcessArgs(int argc, char *argv[])
{
  return true;
}

bool cPluginSkinKlassic::Initialize(void)
{
  return true;
}

bool cPluginSkinKlassic::Start(void)
{
  new cSkinKlassic;
  return true;
}

void cPluginSkinKlassic::Housekeeping(void)
{
}

cOsdObject *cPluginSkinKlassic::MainMenuAction(void)
{
  return NULL;
}

cMenuSetupPage *cPluginSkinKlassic::SetupMenu(void)
{
  return NULL;
}

bool cPluginSkinKlassic::SetupParse(const char *Name, const char *Value)
{
  return false;
}

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