/**
 *  GraphTFT plugin for the Video Disk Recorder 
 * 
 *  theme.c - A plugin for the Video Disk Recorder
 *
 *  (c) 2004 Lars Tegeler, Sascha Volkenandt
 *  (c) 2006-2008 Jörg Wendel
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * $Id: theme.c,v 1.12 2007/12/03 19:58:20 root Exp $
 *
 **/

#include <theme.h>
#include <common.h>

#include <setup.h>

//***************************************************************************
// The Theme
//***************************************************************************

cGraphTFTTheme* Thms::theTheme = 0;
cGraphTFTThemes themes;

//***************************************************************************
// Convert Channel-Name in Logo Path
//***************************************************************************

string VariableProvider::channelLogoPath(const char* channel, 
                                         const char* format, int classic)
{
   int s = 0;
   int d = 0;
   int len = strlen(channel);
   char* file = new char[len+TB]; 

   // remove '\/:*?"<>| character

   while (channel[s])
   {
      if (!strchr("/':*?\"<>\\", channel[s]))
         file[d++] = channel[s];
      
      s++;
   }

   file[d] = 0;

   // special handling fore some channels

   if (!rep(file, "D [0-9] \\- ...."))
   {
      free(file);
      file = strdup("DIREKT");
   }

   string path;

   if (classic)
       path = "columnimages/" + string(file) + "." 
          + ((format && *format) ? string(format) : "png");
   else
      path = string(file);

   delete file;

   tell(3, "converted logo name is '%s'", path.c_str());

   return path;
}

//***************************************************************************
// 
//***************************************************************************

const char* VariableProvider::splitFormatValue(const char* data, 
                                               char* value, char* format)
{
   const char* f = strchr(data, '/');
   
   if (f)
   {
      strncpy(value, data, f-data);
      value[f-data] = 0;
      strcpy(format, f+1);

      return value;
   }

   strcpy(value, data);
   *format = 0;

   return value;
}

//***************************************************************************
// Variable Of
// - returns the first variable name in expession
//  "{varTest} xx {varHallo}" -> varTest
//***************************************************************************

int VariableProvider::variableOf(string& name, const char* expression, char*& e)
{
   const char* s;

   tell(4, "variableOf '%s'", expression);

   if ((s = strchr(expression, '{')))
   {
      s++;

      if (!(e = strchr(s, '}')))
      {
         tell(0, "Parsing of [%s] failed, missing bracket '}'", expression);
         return fail;
      }

      // found expression

      name = string(expression, s-expression, e-s);
      tell(4, "variable '%s' found", name.c_str());

      return success;
   }

   return fail;
}

//***************************************************************************
// Evaluate Variable
//***************************************************************************

int VariableProvider::evaluate(string& buf, const char* var)
{
   const char* s;
   const char* e;
   const char* v;
   const char* p = var;
   char exp[1000+TB];
   int len;
   char tmp[1+TB];

   buf = "";

   tell(4, "evaluating '%s'", var);

   while ((s = strchr(p, '{')))
   {
      if (s > p)
         buf.append(p, s-p);
      
      if (!(e = strchr(p, '}')))
      {
         tell(0, "Parsing of [%s] failed, missing bracket '}'", var);
         return fail;
      }

      // found expression

      e--;
      len = min((int)(e-s), 1000);
      strncpy(exp, s+1, len);
      exp[len] = 0;

      tell(4, "variable '%s' found", exp);

      if (*exp == '\\' && strlen(exp) > 1)
      {
         tmp[0] = (char)atoi(exp+1);
         tmp[1] = 0;
         v = tmp;

         tell(2, "found character expression '%s'", v);
      }
      else 
      {
         char fmt[100+TB];
         char val[255+TB];

         // split if expression contais format string
         // in a expression a '/' always start a format string

         splitFormatValue(exp, val, fmt);
   
         tell(2, "expression '%s' with variable '%s' and format '%s'", 
              exp, val, fmt);

         // lookup the variable

         if (!(v = lookupVariable(val, fmt)))
            return fail;

         tell(2, "Variable '%s' evaluated, value is '%s'", val, v);

         // do the format stuff

         if (!Str::isEmpty(fmt) && strcasecmp(fmt, "toLogo") == 0)
            v = channelLogoPath(v, 0, no).c_str();
      }
         
      buf.append(v);

      p = e+2;
   }

   buf.append(p);

   return success;
}


//***************************************************************************
// cThemeService
//***************************************************************************

const char* cThemeService::items[] =
{
   "Include",
   "Theme",

   "Text",
   "Image",
   "ImageFile",
   "Rectangle",
   "Timebar",

   "Sym2ch",
   "SymDD",
   "SymVTX",
   "SymCrypt",
   "MailSymbol",
   "MailCount",

   "Message",
   "VolumeMuteSymbol",
   "Volumebar",

   "Menu",
   "MenuSelected",

   "MenuButtonRed",
   "MenuButtonGreen",
   "MenuButtonYellow",
   "MenuButtonBlue",

   "MenuButtonBackgroundRed",
   "MenuButtonBackgroundGreen",
   "MenuButtonBackgroundYellow",
   "MenuButtonBackgroundBlue",

   "MenuImageMap",

   "SpectrumAnalyzer",
   "PartingLine",
   "Sysinfo",
   "Background",
   "TextList",
   "Progressbar",

   "ClickArea",
   "MenuNavigationArea",
   "CalibrationCursor",

   "Column",
   "ColumnSelected",

   "EventColumn",
   "EventColumnSelected",

   "Defaults",

   0
};

const char* cThemeService::toName(eThemeItem aItem)
{
   if (!isValid(aItem))
      return "unknown";

   return items[aItem];
}

cThemeService::eThemeItem cThemeService::toItem(const char* aName)
{
   for (int i = 0; items[i]; i++)
      if (strcasecmp(items[i], aName) == 0)
         return (eThemeItem)i;

   return itemUnknown;
}

//***************************************************************************
// Translations
//***************************************************************************

cThemeService::Translation cThemeService::alignments[] =
{
   { algLeft,   "left"   },
   { algCenter, "center" },
   { algRight,  "right"  },
   { 0, 0 }
};

cThemeService::Translation cThemeService::scrollmodes[] =
{
   { smOff,      "off"  },
   { smMarquee,  "marquee"  },
   { smTicker,   "ticker"  },
   { 0, 0 }
};

int cThemeService::toDenum(Translation t[], const char* value)
{
   for (int i = 0; t[i].name; i++)
   {
      if (strcasecmp(t[i].name, value) == 0)
         return t[i].denum;
   }

   return na;
}

//***************************************************************************
// Class cThemeItem
//***************************************************************************

cThemeSection* cThemeItem::currentSection = 0;
string cThemeItem::lineBuffer = "";
string cThemeItem::condition = "";

cThemeItem::cThemeItem()
{
   _item = itemUnknown;
   _x = na;
   _y = 0;
   _bg_x = na;
   _bg_y = na;
   _image_map = 0;
   _stat_pic=0;
   _stat_text=0;
   _stat_width = 0;
   _stat_height = 0;
   _stat_x=0;
   _stat_y=0;
   _width=0;
   _height=0;
   _bg_width=0;
   _bg_height=0;
   _red=255;
   _green=255;
   _blue=255;
   _transparent=255;
   _bg_red=0;
   _bg_green=0;
   _bg_blue=0;
   _bg_transparent=255;
   _lines=0;
   _start_line="0";
   _line=0;
   _size=32;
   _switch=no;
   _align = Ts::algLeft;
   _align_v=0;
   _animated=0;
   _delay=0;
   _foreground = no;
   _count=0;
   _spacing = 0;
   _bar_height = 100;
   _bar_height_unit = iuPercent;
   _scroll = smOff;
   _scroll_count = 0;
   _dots = no;
   _permanent = no;
   _factor = 1;
   _aspect_ratio = no;
   _fit = no;
   _value = "";
   _total = "";

   _menu_x = 0;
   _menu_y = 0;
   _menu_width = 0;
   _menu_height = 0;

   _unit = "";
   _reference = "";
   _font = "Vera";
   _type = "";
   _format = "";
   _path = "";
   _path2 = "";
   _focus = "";
   _text = "";
   _number = na;
   _index = 0;
   _whipe_res = 20;
   _onClick = "";
   _onDblClick = "";
   _onUp = "";
   _onDown = "";
   _sectionInclude = "";
   _condition = "";

   section = 0;
   pathCount = 0;
}

cThemeItem::~cThemeItem()
{
}

//***************************************************************************
// Parse one line => one Item
//***************************************************************************

bool cThemeItem::Parse(const char* s)
{
   cDisplayItem* dspItem;
   string tmp;
   unsigned int posA;
   unsigned int posB;
   int value;
   string toParse;

   while (*s && (*s == ' ' || *s == '\t'))
      s++;

   // skip empty lines

   if (Str::isEmpty(s))
      return true;

   // append

   lineBuffer.append(s);

   // skip line and inline comments

   if ((posA = lineBuffer.find("//")) != string::npos)
   {   
      lineBuffer.erase(posA);
      
      if (Str::isBlank(lineBuffer.c_str()))
         return true;
   }
   
   // parse directives

   if ((posA = lineBuffer.find("#")) == 0)
   {
      parseDirectives(lineBuffer);
      lineBuffer.clear();
      return true;
   }

   // parse variables

   if ((posA = lineBuffer.find("var ")) == 0)
   {
      string tmp = lineBuffer.substr(posA+4);
      parseVariable(tmp, currentSection);
      lineBuffer.clear();

      return true;
   }

   // parse condition

   if ((posA = lineBuffer.find("if ")) == 0)
   {
      condition = lineBuffer.substr(posA+3);
      lineBuffer.clear();

      return true;
   }
   else if (lineBuffer.find("endif") == 0)
   {
      condition = "";
      lineBuffer.clear();
      return true;
   }

   // check if it is a section start

   posA = lineBuffer.find("[");
   posB = lineBuffer.find("]", posA);
   
   if (posA == 0 && posB > 0 && posB != string::npos)
   {
      // new section, switch parsing on !

      if (!Thms::theTheme->skipContent.empty())
      {
         tell(0, "Warning: Missing '#endif'");
         Thms::theTheme->clearIfdefs();
      }

      // section ...
      
      tmp = lineBuffer.substr(posA+1, posB-1);

      // append section

      currentSection = new cThemeSection(tmp);
      Thms::theTheme->getSections()->Add(currentSection);
      Thms::theTheme->addNormalSection(currentSection->getName());
      lineBuffer.clear();

      return true;
   }

   // line completed ?

   if (lineBuffer.find(";") == string::npos)
   {
      tell(5, "line buffer now [%s]", lineBuffer.c_str());
      return true;
   }

   // ----------------------------
   // 

   tell(3, "line completed, processing [%s]", toParse.c_str());
   toParse = lineBuffer;
   lineBuffer.clear();

   if (Thms::theTheme->isSkipContent())
   {
      tell(2, "Skipping line due to directive [%.50s%s]",
           s, strlen(s) > 50 ? ".." : "");

      return true;
   }

   if (!currentSection)
      return true;

   // include section ...

   if (ParseVar(toParse, "Include", &tmp))
   {
      if ((dspItem = newDisplayItem(itemSectionInclude)))
      {
         currentSection->Add(dspItem);
         dspItem->_sectionInclude = tmp;
      }

      return true;
   }

   // item ...

   if ((posA = toParse.find(" ")) == string::npos)
   {
      tell(0, "Ignoring invalid theme line [%.50s%s]",
           s, strlen(s) > 50 ? ".." : "");

      return true;
   }

   // parse item attributes ..

   _item = toItem(toParse.substr(0, posA).c_str());

   switch (_item)
   {
      case itemTheme:
      {
         ParseVarExt(toParse, "name", &tmp);
         Thms::theTheme->setName(tmp);
         ParseVarExt(toParse, "themeVersion", &tmp);
         Thms::theTheme->setThemeVersion(tmp);
         ParseVarExt(toParse, "syntaxVersion", &tmp);
         Thms::theTheme->setSyntaxVersion(tmp);
         ParseVarExt(toParse, "dir", &tmp);
         Thms::theTheme->setDir(tmp);
         ParseVarExt(toParse, "startImage", &tmp);
         Thms::theTheme->setStartImage(tmp);
         ParseVarExt(toParse, "endImage", &tmp);
         Thms::theTheme->setEndImage(tmp);
         if (ParseVarExt(toParse, "width", &value))
            Thms::theTheme->setWidth(value);
         if (ParseVarExt(toParse, "height", &value))
            Thms::theTheme->setHeight(value);
         if (ParseVarExt(toParse, "fontPath", &tmp))
            Thms::theTheme->setFontPath(tmp);

         return true;
      }

      case itemDefaults:
      {
         currentSection->setDefaultsItem(newDisplayItem(_item));
         currentSection->getDefaultsItem()->ParseText(toParse);

         return true;
      }

      case itemUnknown:
      {
         tell(0, "Ignoring unknown theme item = [%s]", tmp.c_str());

         return true;
      }
   }

   dspItem = newDisplayItem(_item);

   if (dspItem)
   {
      if (_item == itemMenuImageMap)
         Thms::theTheme->AddMapItem(dspItem);
      else
         currentSection->Add(dspItem);

      // copy default values

      if (currentSection->getDefaultsItem())
         *dspItem = *currentSection->getDefaultsItem(); // copy constructor
      else
         tell(1, "Missing defaults of section '%s'", 
              currentSection->getName().c_str());

      // set/parse item properties

      dspItem->_item = _item;
      dspItem->setSection(currentSection);
      dspItem->ParseText(toParse);

      if (condition.size())
      {
         if (dspItem->_condition.size())
            dspItem->_condition += " && ";

         dspItem->_condition += condition;

         tell(3, "Attach condition '%s' to '%s', condition now '%s'", 
              condition.c_str(), dspItem->nameOf(), dspItem->_condition.c_str());
      }
   }

   return true;
}

//***************************************************************************
// Parse Variable
//***************************************************************************

int cThemeItem::parseVariable(string& toParse, cThemeSection* section)
{
   int menu = no;
   string name, value;
   Scan scan(toParse.c_str());

   scan.eat();
   
   if (!scan.isIdent())
   {
      tell(0, "Error: Invalid left value '%s' in '%s'",
           scan.lastIdent(), toParse.c_str());
      return fail;
   }

   if (strcmp(scan.lastIdent(), "menu") == 0)
   {
      menu = yes;
      scan.eat();

      if (!scan.isIdent())
      {
         tell(0, "Error: Invalid left value '%s' in '%s'",
              scan.lastIdent(), toParse.c_str());
         return fail;
      }
   }
   
   name = scan.lastIdent();

   scan.eat();   

   if (!scan.isOperator() || scan.lastIdent()[0] != '=')
   {
      tell(0, "Error: Invalid operator '%s' in '%s', '=' expected", 
           scan.lastIdent(), toParse.c_str());
      return fail;
   }

   scan.eat();

   if (!scan.isString() && scan.isIdent())
   {
      tell(0, "Error: Invalid right value '%s' in '%s'",
           scan.lastIdent(), toParse.c_str());
      return fail;
   }

   value = scan.lastIdent();

   if (section)
   {
      tell(3, "adding section variable '%s' with '%s' to '%s'", 
           name.c_str(), value.c_str(), section->getName().c_str());
      section->variables[name] = value;
   }
   else
   {
      tell(0, "adding theme variable '%s' with '%s' %s",
           name.c_str(), value.c_str(), menu ? "mode menu" : "");

      if (menu)
         Thms::theTheme->menuVariables[name] = value;
      else
         Thms::theTheme->variables[name] = value;
   }
   
   return success;
}

//***************************************************************************
// Parse Directive
//***************************************************************************

int cThemeItem::parseDirectives(string& toParse)
{
   string exeption;
   
   if (ParseDirective(toParse, "#define", &exeption) == success 
       && exeption.length())
   {
      tell(1, "Adding define '%s'", exeption.c_str());
      Thms::theTheme->defines[Thms::theTheme->defineCount] = exeption;
      Thms::theTheme->defineCount++;
      
      return success;
   }

   else if (ParseDirective(toParse, "#endif", &exeption) == success)
   {
      if (Thms::theTheme->skipContent.empty())
         tell(0, "Warning: Ignoring unexpected '#endif'");
      else
         Thms::theTheme->skipContent.pop();

      tell(4, "Swiching parser '%s' ('#endif' found)",
           Thms::theTheme->isSkipContent() ? "off" : "on");
      
      return success;
   }

   else if (ParseDirective(toParse, "#else", &exeption) == success)
   {
      int actualSkip = Thms::theTheme->isSkipContent();

      if (Thms::theTheme->skipContent.empty())
         tell(0, "Warning: Ignoring unexpected '#else'");
      else
         Thms::theTheme->skipContent.top() = !actualSkip;

      tell(4, "Swiching parser '%s' [from '%s'] ('#else' found)", 
           Thms::theTheme->isSkipContent() ? "off" : "on",
           actualSkip ? "off" : "on");

      return success;
   }

   else if ((ParseDirective(toParse, "#ifdef", &exeption) == success
             || ParseDirective(toParse, "#ifndef", &exeption) == success) 
            && exeption.length())
   {
      int actualSkip = Thms::theTheme->isSkipContent();
      int negate = no;

      if (toParse.find("#ifndef") == 0)
         negate = yes;

      tell(4, "Found '%s %s'", negate ? "#ifndef" : "#ifdef", exeption.c_str());

      int skip = negate ? no : yes;

      for (int i = 0; i < Thms::theTheme->defineCount; i++) 
      {
         if (Thms::theTheme->defines[i] == exeption)
         {
            tell(4, "Define '%s' is set, switching parser '%s'", 
                 exeption.c_str(), negate ? "off" : "on");

            skip = negate ? yes : no;

            break;
         }
      }

      // aus bleibt aus (bei verschachtelten ifdef)

      skip = skip || actualSkip;

      Thms::theTheme->skipContent.push(skip);

      tell(4, "Parser now '%s' due to '%s' '%s'", 
           Thms::theTheme->isSkipContent() ? "off" : "on",
           negate ? "#ifndef" : "#ifdef",
           exeption.c_str());

      return success;
   }

   return ignore;
}

//***************************************************************************
// 
//***************************************************************************

cDisplayItem* cThemeItem::newDisplayItem(int item)
{
   cDisplayItem* newItem = 0;

   switch (item)
   {
      case itemMenuImageMap: 
      case itemDefaults:
      case itemMenuNavigationArea:
      case itemSectionInclude:     newItem = new cDisplayItem();                break;

      case itemText:               newItem = new cDisplayText();                break;
      case itemTextList:           newItem = new cDisplayTextList();            break;
      case itemProgressbar:        newItem = new cDisplayProgressBar();         break;
      case itemRectangle:          newItem = new cDisplayRectangle();           break;
      case itemImage:              newItem = new cDisplayImage();               break;
      case itemImageFile:          newItem = new cDisplayImageFile();           break;
      case itemCalibrationCursor:  newItem = new cDisplayCalibrationCursor();   break;
      case itemMessage:            newItem = new cDisplayMessage();             break;

      case itemMenu:               newItem = new cDisplayMenu();                break;
      case itemMenuSelected:       newItem = new cDisplayMenuSelected();        break;
      case itemColumn:             newItem = new cDisplayMenuColumn();          break;
      case itemColumnSelected:     newItem = new cDisplayMenuColumnSelected();  break;
      case itemEventColumn:         newItem = new cDisplayMenuEventColumn();          break;
      case itemEventColumnSelected: newItem = new cDisplayMenuEventColumnSelected();  break;

      case itemSpectrumAnalyzer:   newItem = new cDisplaySpectrumAnalyzer();    break;
      case itemPartingLine:        newItem = new cDisplayPartingLine();         break;
      case itemSysinfo:            newItem = new cDisplaySysinfo();             break;
      case itemBackground:         newItem = new cDisplayBackground();          break;

      case itemVolumeMuteSymbol:   newItem = new cDisplayVolumeMuteSymbol();    break;
      case itemVolumebar:          newItem = new cDisplayVolumebar();           break;

      case itemTimebar:            newItem = new cDisplayTimebar();             break;
      case itemMailCount:          newItem = new  cDisplayMailCount();          break;
      case itemSym2ch:         
      case itemSymDD:       
      case itemSymVTX:       
      case itemSymCrypt:
      case itemMailSymbol:         newItem = new  cDisplaySymbol();             break;

      case itemMenuButtonRed:
      case itemMenuButtonGreen:
      case itemMenuButtonYellow:
      case itemMenuButtonBlue:
         newItem = new cDisplayMenuButton();
         break;

      case itemMenuButtonBackgroundRed:
      case itemMenuButtonBackgroundGreen:
      case itemMenuButtonBackgroundYellow:
      case itemMenuButtonBackgroundBlue:
         newItem = new cDisplayMenuButtonBackground();
         break;

   };

   if (newItem)
      newItem->_item = item;

   return newItem;
}

//***************************************************************************
// Parse Properties
//***************************************************************************

bool cThemeItem::ParseText(string toParse)
{
   ParseVar(toParse, "x", &_x);
   ParseVar(toParse, "y", &_y);
   ParseVar(toParse, "width", &_width);
   ParseVar(toParse, "height", &_height);
   ParseVar(toParse, "red", &_red);
   ParseVar(toParse, "green", &_green);
   ParseVar(toParse, "blue", &_blue);
   ParseVar(toParse, "transparent", &_transparent);
   ParseVar(toParse, "lines", &_lines);
   ParseVar(toParse, "start_line", &_start_line);
   ParseVar(toParse, "line", &_line);
   ParseVar(toParse, "font", &_font);
   ParseVar(toParse, "size", &_size);
   ParseVar(toParse, "text", &_text);
   ParseVar(toParse, "switch", &_switch);         // bool
   ParseVar(toParse, "align_v", &_align_v);
   ParseVar(toParse, "align", &_align, alignments);
   ParseVar(toParse, "focus", &_focus);
   ParseVar(toParse, "name", &_path);
   ParseVar(toParse, "file", &_path2);
   ParseVar(toParse, "type", &_type);
   ParseVar(toParse, "format", &_format);
   ParseVar(toParse, "animated", &_animated);     // bool
   ParseVarTime(toParse, "delay", &_delay);
   ParseVar(toParse, "foreground", &_foreground);
   ParseVar(toParse, "count", &_count);
   ParseVar(toParse, "image_map", &_image_map);   // bool
   ParseVar(toParse, "stat_pic", &_stat_pic);     // bool
   ParseVar(toParse, "stat_text", &_stat_text);   // bool
   ParseVar(toParse, "stat_width", &_stat_width);
   ParseVar(toParse, "stat_height", &_stat_height);
   ParseVar(toParse, "stat_x", &_stat_x);
   ParseVar(toParse, "stat_y", &_stat_y);
   ParseVar(toParse, "bg_x", &_bg_x);
   ParseVar(toParse, "bg_y", &_bg_y);
   ParseVar(toParse, "bg_width", &_bg_width);
   ParseVar(toParse, "bg_height", &_bg_height);
   ParseVar(toParse, "bg_red", &_bg_red);
   ParseVar(toParse, "bg_green", &_bg_green);
   ParseVar(toParse, "bg_blue", &_bg_blue);
   ParseVar(toParse, "bg_transparent", &_bg_transparent);
   ParseVar(toParse, "number", &_number);
   ParseVar(toParse, "spacing", &_spacing);
   ParseVar(toParse, "scroll", &_scroll, scrollmodes);
   ParseVar(toParse, "factor", &_factor);
   ParseVar(toParse, "unit", &_unit);
   ParseVar(toParse, "reference", &_reference);
   ParseVar(toParse, "scroll_count", &_scroll_count);
   ParseVar(toParse, "dots", &_dots);
   ParseVar(toParse, "permanent", &_permanent);
   ParseVar(toParse, "value", &_value);
   ParseVar(toParse, "total", &_total);

   ParseVar(toParse, "menu_x", &_menu_x);
   ParseVar(toParse, "menu_y", &_menu_y);
   ParseVar(toParse, "menu_width", &_menu_width);
   ParseVar(toParse, "menu_height", &_menu_height);

   ParseVar(toParse, "fit", &_fit);
   ParseVar(toParse, "aspect_ratio", &_aspect_ratio);

   ParseVar(toParse, "whipe_res", &_whipe_res);
   ParseVar(toParse, "on_click", &_onClick);
   ParseVar(toParse, "on_dblclick", &_onDblClick);

   ParseVar(toParse, "on_up", &_onUp);
   ParseVar(toParse, "on_down", &_onDown);

   ParseVar(toParse, "path2", &_path2);
   ParseVar(toParse, "pathON", &_path);
   ParseVar(toParse, "pathOFF", &_path2);
   ParseVar(toParse, "condition", &_condition);

   // now some properties with special handling

   string tmp;

   if (ParseVar(toParse,"bar_height", &tmp))
   {
      _bar_height = atoi(tmp.c_str());

      if (strchr(tmp.c_str(), '%'))
         _bar_height_unit = iuPercent;
      else
         _bar_height_unit = iuAbsolute;
   }

   if (ParseVar(toParse, "path", &_path))
   {
      unsigned int e, s;
      e = s = 0;

      do
      {
         e = _path.find(':', s);

         pathList[pathCount] = _path.substr(s, e == string::npos ? _path.length()-s : e-s);
         tell(4, "path[%d] = '%s'", pathCount, pathList[pathCount].c_str());
         pathCount++;

         s = e+1;
      } while (s > 0 && pathCount < maxPathCount);
   }

   return true;
}

//***************************************************************************
// Search via translation list
//***************************************************************************

bool cThemeItem::ParseVar(string toParse, string name, int* value, Translation* t)
{
   string val;
   int status;
   int denum;

   if ((status = ParseVar(toParse, name, &val)))
   {
      denum = cThemeService::toDenum(t, val.c_str());

      if (denum == na)
      {
         tell(0, "Error: Unexpected value '%s' for '%s'",
              val.c_str(), name.c_str());

         denum = 0;  // 0 -> always the default
      }

      *value = denum;
   }  

   return status;
}

//***************************************************************************
// Search the int parameter
//***************************************************************************

bool cThemeItem::ParseVar(string toParse, string name, int* value)
{
   string val;
   int status;

   if ((status = ParseVar(toParse, name, &val)))
   {
      if (val == "yes" || val == "true")
         *value = 1;
      else if (val == "no" || val == "false")
         *value = 0;
      else
         *value = atoi(val.c_str());
   }

   return status;
}

//***************************************************************************
// Search the time parameter
//***************************************************************************

bool cThemeItem::ParseVarTime(string toParse, string name, uint64_t* value)
{
   string val;
   int status;

   if ((status = ParseVar(toParse, name, &val)))
   {
      if (val.find("ms") != string::npos)
         *value = atoi(val.c_str());
      else
         *value = atoi(val.c_str()) * 1000;
   }

   return status;
}

//***************************************************************************
// Lookup Variable
//***************************************************************************

const char* cThemeItem::lookupVariable(const char* name, const char* fmt)
{
   const char* v = 0;

   tell(4, "lookup variable '%s' in '%s'", name, 
        section ? section->getName().c_str() : "theme");

   if (section)
      v = section->lookupVar(name);

   if (!v)
      v = Thms::theTheme->lookupVar(name);

   if (v)
      tell(4, "Found variable '%s' with value '%s' in '%s'", 
           name, v, section ? section->getName().c_str() : "theme");

   return v;
}

//***************************************************************************
// Set Variable
//***************************************************************************

int cThemeItem::setVariable(const char* name, int value)
{
   char v[50];

   sprintf(v, "%d", value);

   if (section && section->lookupVar(name))
   {
      section->variables[name] = v;
      return success;
   }
   else if (Thms::theTheme->lookupVar(name))
   {
      Thms::theTheme->variables[name] = v;
      return success;
   }
   
   return fail;
}

//***************************************************************************
// Pase Variables
//***************************************************************************

bool cThemeItem::ParseVarExt(string toParse, string name, int* value)
{
   string v, p;

   if (!ParseVar(toParse, name, &v))
      return false;
   
   if (evaluate(p, v.c_str()) != success)
      return false;

   *value = atoi(p.c_str());
      
   return true;
}

bool cThemeItem::ParseVarExt(string toParse, string name, string* value)
{
   string v;

   if (!ParseVar(toParse, name, &v))
      return false;

   if (evaluate(*value, v.c_str()) != success)
      return false;

   tell(4, "found var '%s' with value '%s'", 
        name.c_str(), value->c_str());

   return true;
}

//***************************************************************************
// Search the string parameter
//***************************************************************************

bool cThemeItem::ParseVar(string toParse, string name, string* value)
{
   unsigned int posA, posB, end;

   name += "=";

   if ((posA = toParse.find("," + name)) == string::npos)
      if ((posA = toParse.find(" " + name)) == string::npos)
         if ((posA = toParse.find(name)) != 0)
            return false;

   if (posA)
      posA++;

   posB = posA;

   while (posB < toParse.length())
   {
      if ((end = toParse.find(",", posB)) == string::npos)
         if ((end = toParse.find(";", posB)) == string::npos)
            return false;

      // if at first pos or without mask sign, 
      // then we have found the end of the item
      // -> break the loop

      if (end == 0)                           // wenn ","   -> fertig
         break;

      if (toParse[end-1] != '\\')             // wenn "x,"  -> fertig
         break;

      if (end > 1 && toParse[end-2] == '\\')  // wenn "\\," -> fertig
         break;

      // => "\," -> nicht fertig
      // => search again behind the ',' or ';' sign

      posB = end+1;
   }

   *value = toParse.substr(posA + name.size(), end-posA-name.size());

   // de mask '\' sign

   if ((value->find("\\", 0)) != string::npos)
   {
      char* buf; char* s; char* d;

      asprintf(&buf, "%s", value->c_str());

      s = d = buf;

      while (*s)
      {
         if (*s != '\\' || *(s+1) == '\\')
            *d++ = *s;

         s++;
      }
      
      *d = 0;

      tell(5, "got '%s' build '%s'", value->c_str(), buf);

      *value = buf;
      free(buf);
   }

   return true;
}

//***************************************************************************
// Parse Directive
//***************************************************************************

bool cThemeItem::ParseDirective(string toParse, string name, string* value)
{
   unsigned int posA;

   *value = "";

   if ((posA = toParse.find(name)) != 0)
      return fail;

   if (toParse.length() > posA + name.size() + 1)
      *value = toParse.substr(posA + name.size() + 1);

   return success;
}

//***************************************************************************
// Class cThemeSections
//***************************************************************************

//***************************************************************************
// Get Section By Name
//***************************************************************************

cThemeSection* cThemeSections::getSection(string name)
{
   for (cThemeSection* p = First(); p; p = Next(p)) 
      if (p->getName() == name)
         return p;

   return 0;
}


//***************************************************************************
// Class cThemeSection
//***************************************************************************

uint64_t cThemeSection::getNextUpdateTime()
{
   uint64_t next = cTimeMs::Now() + SECONDS(300); // 5 minutes
   cDisplayItem* pNext = 0;

   if (logLevel >= 1)
   {
      uint64_t now = cTimeMs::Now();
      string date = *TimeToString(now/1000);
      tell(2, "now it is '%s,%03d'", date.substr(11,8).c_str(), now%1000);
   }

   for (cDisplayItem* p = First(); p; p = Next(p)) 
   {
      if (p->getNextDraw() && p->getNextDraw() < next)
      {
         const char* detail = "<no>";

         switch (p->Item())
         {
            case itemText:      detail = p->Text().c_str(); break;
            case itemImage:     detail = p->Path().c_str(); break;
            case itemImageFile: detail = p->Path().c_str(); break;
         }

         int drawIn = p->getNextDraw() - cTimeMs::Now();

         tell(1, "setting next for '%-20.20s' in (%d ms) [%s]",
              p->nameOf(), drawIn, Str::notNull(detail));

         pNext = p;
         next = p->getNextDraw();
      }
   }

   if (pNext)
   {
      int updateIn = next - cTimeMs::Now();

      if (updateIn < 0)
         updateIn = 0;

      unsigned int s = updateIn/1000; 
      unsigned int us = updateIn%1000;

      tell(1, "schedule next for '%s' (%d) in %d,%03d seconds",
        pNext->nameOf(), pNext->Item(), s, us);
   }

   return next < cTimeMs::Now() ? cTimeMs::Now() : next;
}

int cThemeSection::updateGroup(int group)
{
   tell(2, "update item group (%d)", group);

   for (cDisplayItem* p = First(); p; p = Next(p))
   {
      if (p->groupOf() & group)
      {
         p->reset();
         p->setNextDraw();
      }
   }

   return done;
}

int cThemeSection::reset()
{
   tell(1, "reset items");

   for (cDisplayItem* p = First(); p; p = Next(p)) 
      p->reset();

   return done;
}

//***************************************************************************
// Class cDisplayItems
//***************************************************************************
//***************************************************************************
// Get Item By ID
//***************************************************************************

cDisplayItem* cDisplayItems::getItem(int id)
{
   for (cDisplayItem* p = First(); p; p = Next(p)) 
   {
      if (p->Item() == id)
         return p;
   }

   return 0;
}

//***************************************************************************
// Class GraphTFTTheme
//***************************************************************************

cGraphTFTTheme::cGraphTFTTheme()
{ 
   initialized = no; 

   width = 720;
   height = 576;
   memset(normalModes, 0, sizeof(normalModes));
   normalModesCount = 0; 
   fontPath = "";
   variables.clear();
   menuVariables.clear();

   cThemeItem::currentSection = 0;
   cThemeItem::lineBuffer = "";
   cThemeItem::condition = "";

   resetDefines();
}

//***************************************************************************
// Init
//***************************************************************************

int cGraphTFTTheme::init()
{
   cThemeSection* s;
   cThemeSection* sec;
   cDisplayItem* newItem;
   cDisplayItem* item;
   cDisplayItem* p;
   cDisplayItem* background;

   if (initialized)
      exit();

   // loop over sections 
   
   for (s = FirstSection(); s; s = NextSection(s))
   {
      // evaluate includes ...

      // loop over sections items

      tell(4, "Section: '%s'", s->getName().c_str());

      for (p = s->First(); p; p = s->Next(p)) 
      {
         // add items of 'included' sections

         tell(4, "Item: '%s'", Ts::toName((cThemeService::eThemeItem)p->Item()));

         if (p->Item() == itemSectionInclude && (sec = getSection(p->SectionInclude())))
         {
            tell(4, "Icluding section: '%s'", sec->getName().c_str());

            // include items

            for (item = sec->First(); item; item = sec->Next(item))
            {
               tell(4, "Including Item: '%s'", Ts::toName((cThemeService::eThemeItem)item->Item()));

               if (item->Item() >= itemBegin
                   && (newItem = cThemeItem::newDisplayItem(item->Item())))
               {
                  *newItem = *item;           // copy constructor
                  s->Ins(newItem, p);         // s->First());
               }
            }

            // include variables

            map<string,string>::iterator iter;

            for (iter = sec->variables.begin(); iter != sec->variables.end(); ++iter)
               s->variables[iter->first] = iter->second;
         }
      }

      // assign background item ...

      background = 0;

      // loop over sections items

      for (p = s->First(); p; p = s->Next(p)) 
      {
         // detect/assign background
         //    only working properly if background is the first 
         //    item in the theme section!
      
         if (p->Item() == itemBackground)
            background = p;
         else
            p->setBackgroundItem(background);
      }
   }
   int x = 0, xs = 0, ex = 0, exs = 0;

   // init image map
   // init column positions
   // init ...

   // loop over all sections

   for (s = FirstSection(); s; s = NextSection(s)) 
   {
      // loop over items of this section

      x = xs = ex = exs = 0;

      for (p = s->First(); p; p = s->Next(p)) 
      {
         cDisplayItem* dspItem = p;

         if (!dspItem)
            continue;

         // calculate x position of the column items

         if (dspItem->Item() == itemEventColumn)
         {
            if (dspItem->X() == na)
               dspItem->X(ex);                                     // not configured, take calculated
            else if (dspItem->X() < na && ex + dspItem->X() > 0)
               dspItem->X(ex + dspItem->X());                      // go back
            else
               ex = dspItem->X();                                  // take configured value

            if (dspItem->Width())
               ex += dspItem->Width() + dspItem->Spacing();        // calc next x
            else 
               dspItem->Width(Thms::theTheme->getWidth() - dspItem->X());
         }

         if (dspItem->Item() == itemEventColumnSelected)
         {
            if (dspItem->X() == na)
               dspItem->X(exs);                                    // not configured, take calculated
            else if (dspItem->X() < na && exs + dspItem->X() > 0)
               dspItem->X(exs + dspItem->X());                     // go back
            else
               exs = dspItem->X();                                 // take configured value
         
            if (dspItem->Width())
               exs += dspItem->Width() + dspItem->Spacing();       // calc next x
            else 
               dspItem->Width(Thms::theTheme->getWidth() - dspItem->X());
         }

      }
   }

   // at leat reset all items

   for (s = FirstSection(); s; s = NextSection(s)) 
      s->reset();

   initialized = yes;

   return success;
}

int cGraphTFTTheme::checkViewMode()
{
   // check normal view

   int i = 0;

   while (normalModes[i])
   {
      if (normalModes[i] == GraphTFTSetup.normalMode)
         break;
      
      i++;
   }

   if (!normalModes[i])
      GraphTFTSetup.normalMode = "Standard";

   tell(0, "normal mode now '%s'", GraphTFTSetup.normalMode.c_str());
   
   return done;
}

//***************************************************************************
// Exit
//***************************************************************************

int cGraphTFTTheme::exit()
{
   int i = 0;
   cThemeSection* s;

   if (!initialized)
      return done;

   cThemeItem::currentSection = 0;
   cThemeItem::lineBuffer = "";
   cThemeItem::condition = "";

   tell(3, "destroy theme '%s'", getName().c_str());

   for (s = FirstSection(); s; s = NextSection(s))
      s->getItems()->Clear();
   
   sections.Clear();

   while (normalModes[i] && i < 100)
      free(normalModes[i++]);

   // delete of this object's is done by 
   // cConfig before new Load() or in destructor!

   Thms::theTheme->resetDefines();

   initialized = no;
   
   return done;
}

//***************************************************************************
// Check
//***************************************************************************

int cGraphTFTTheme::check(const char* theVersion)
{
   // check if the theme fit to current version
      
   if (strcmp(syntaxVersion.c_str(), theVersion) != 0)
   {
      tell(0, "Warning: graphtft - version of themefile syntax "
           "does not match, found '%s' instead of '%s'", 
           syntaxVersion.c_str(), theVersion);
      return fail;
   }

   return success;
}

//***************************************************************************
// Load
//***************************************************************************

int cGraphTFTTheme::load(const char* path)
{
   exit();
   Load(path);
   
   return init();
}

//***************************************************************************
// Add Normal Section
//***************************************************************************

void cGraphTFTTheme::addNormalSection(string sectionName)
{
   if (normalModesCount >= 100)
      return ;

   if (sectionName.find("Normal") != 0 || sectionName.length() <= strlen("Normal"))
      return ;

   if (sectionName == "NormalRadio")
      return ;

   if (sectionName == "NormalTV")
      asprintf(&normalModes[normalModesCount], "%s", 
               "Standard");
   else
      asprintf(&normalModes[normalModesCount], "%s", 
               sectionName.substr(strlen("Normal")).c_str());

   normalModesCount++;
}

//***************************************************************************
// Is Normal Mode Section
//***************************************************************************

int cGraphTFTTheme::isNormalMode(const char* modeName)
{
   for (int i = 0; i < normalModesCount; i++)
   {
      if (strcmp(normalModes[i], modeName) == 0)
         return yes;
   }

   return no;
}

const char* cGraphTFTTheme::nextNormalMode(const char* modeName)
{
   int i;

   for (i = 0; i < normalModesCount; i++)
   {
      if (strcmp(normalModes[i], modeName) == 0)
         break;
   }

   if (++i < normalModesCount)
      return normalModes[i];
   
   return normalModes[0];
}

string cGraphTFTTheme::getPathFromImageMap(const char* name)
{
   char buf[1000];
   char* c = buf;

   strcpy(buf, name);

   // skip leading blanks and numbers

   while (*c && (isdigit(*c) || *c == ' ')) c++;

   string temp = c;
   string temp2 = "";

   tell(2, "checking imagemap for menu entry '%s'", c);

   for (cDisplayItem* p = mapSection.First(); p; p = mapSection.Next(p)) 
   {
      if (p->Item() == itemMenuImageMap)
      {
         // init translated name at first usage
         
         if (p->Name() == "")
            p->Name(trVDR(p->Path().c_str()));

         // check

         if (p->Name() == temp)
            temp2 = p->Path2();
      }
   }

   if (temp2 != "")
   {
      tell(2, "MenuImageMap: Picture for '%s' is '%s'",
        name, temp2.c_str());
   }

   return temp2;
}

//***************************************************************************
// Calss cGraphTFTThemes
//***************************************************************************
//***************************************************************************
// Get Theme
//***************************************************************************

cGraphTFTTheme* cGraphTFTThemes::getTheme(string aTheme)
{
   cGraphTFTTheme* t;

   tell(3, "looking for theme '%s'", aTheme.c_str());

   for (t = First(); t; t = Next(t))
      if (t->getName() == aTheme)
         return t;

   return 0;
}
