/*
 * autotimeredit: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id: autotimeredit.c 1.8 2005/11/07 16:47:18 hflor Exp $
 */

#include <getopt.h>
#include <stdlib.h>
#include <vdr/tools.h>
#include <vdr/plugin.h>
#include <dirent.h>
#include "autotimeredit.h"
#include "autotimers.h"
#include "ddictionary.h"
#include "menuautotimer.h"
#include "menusetup.h"
#include "vdrtools.h"
#include "i18n.h"

static const char *VERSION           = "0.1.8";
static const char *DESCRIPTION       = "OSD autotimer edit";

// Global variables that control the overall behaviour:

// var pos2
tParamFile autotimerconfigfile    = { false, false, false, "autotimeredit.conf", "autotimeredit.conf" };
tParamFile autotimerfile          = { false, false, false, "/etc/vdradmin/vdradmind.at" };
tParamInt  commandline_preference = { false, false, false, true };
tParamFile mainmenu_name          = { false, false, false, "Autotimer" };
tParamInt  mainmenu_visible       = { false, false, false, true };
#if VDRVERSNUM >= 10307
tParamInt  num_entrys             = { false, false, false, 10 };
#else
tParamInt  num_entrys             = { false, false, false, MAXOSDITEMS };
#endif
tParamInt  question               = { false, false, false, 0x0, 0x0 }; // no update
tParamInt  searchlength           = { false, false, false, 3 };
tParamInt  show_channel           = { false, false, false, true };
tParamInt  show_channelname       = { false, false, false, true };
tParamInt  show_flags             = { false, false, false, true };
tParamInt  show_startstop         = { false, false, false, 0x0, 0x0 }; // none
tParamInt  update_b_e             = { false, false, false, 0x1, 0x0 }; // begin
tParamInt  updatemethod           = { false, false, false, 0x0, 0x0 }; // none
tParamInt  updatetimerecords      = { false, false, false, -1 };
tParamInt  use_defaultdictionary  = { false, false, false, 0x1F, 0x00 }; // all source for default dictionary
tParamInt  use_except_repeat      = { false, false, false, false };
tParamInt  use_weekdays           = { false, false, false, false };
tParamFile vdradminconfig         = { false, false, false, "/etc/vdradmin/vdradmind.conf" };
tParamFile vdradminupdate         = { false, false, false, "/etc/init.d/vdradmin" };
tParamInt  verbose                = { false, false, false, false, true };

char    plugin_name[MaxFileName]  = "AutoTimerEdit";
int     q_manual_update; // data has change --> question for manual update

const char *OffOnSingle[3];
const char *StartStopTime[4];
const char *UpdateLine[4];
const char *UpdateMethod[5];
const char *UpdateQuestion[3];
const char *RecordsUpdateTime[3];
const char *SelDefaultDictionary[MaxDefaultDictionary];
      char *FileNameCharsAllowed = NULL;
int        CountDefaultDictionarys;
int        MaxSetupDefaultDictionarys = MaxDefaultDictionary;

cUpdateThread * oUpdateThread = NULL;

int GetColumn(int selpos)
{
  int column[5] = { 2, 0, 0, 0, 0 };
  int pos = 1;
  if (show_flags.u)
    column[pos++] = use_except_repeat.u ? use_weekdays.u ? 8 : 6 : 5;
  if (show_channel.u)
    column[pos++] = show_channelname.u ? 8 : numdigits(Channels.MaxNumber()) + 1;
  if (show_startstop.u & 0x1)
    column[pos++] = 6;
  if (show_startstop.u & 0x2)
    column[pos++] = 6;
  return column[selpos];
}

void DisplaySetings(void)
{
  int i;
  cDefaultDictionary *DD;
  #define WriteSource(T)   plugin_name, T.r ? 'r' : ' ', T.c ? 'c' : ' '
  #define IsDefault(T)     WriteSource(T), T.u == T.d ? '*' : ' '
  #define IsDefaultChar(T) WriteSource(T), strcmp(T.u, T.d) ? ' ' : '*'
  #define BoolValue(T)     IsDefault(T), T.u ? "yes" : "no"

  ExpandEnvironment(&autotimerfile);
  ExpandEnvironment(&vdradminconfig);
  ExpandEnvironment(&vdradminupdate);
  if (verbose.u) {
    // var pos3
    isyslog("%s: autotimerfile          = [ %c%c%c ] %s", IsDefaultChar(autotimerfile), autotimerfile.u);
    if (strcmp(autotimerfile.u, autotimerfile.e))
      isyslog("%s:                          [ --> ] %s", plugin_name, autotimerfile.e);
    isyslog("%s: commandline_preference = [ %c%c%c ] %s", BoolValue(commandline_preference));
    isyslog("%s: minimum search length  = [ %c%c%c ] %d", IsDefault(searchlength), searchlength.u);
    isyslog("%s: mainmenu_visible       = [ %c%c%c ] %s", BoolValue(mainmenu_visible));
    if (mainmenu_visible.u)
      isyslog("%s: mainmenu_name          = [ %c%c%c ] %s", IsDefaultChar(mainmenu_name), mainmenu_name.u);
    isyslog("%s: show_channel           = [ %c%c%c ] %s", BoolValue(show_channel));
    if (show_channel.u)
      isyslog("%s: show_channelname       = [ %c%c%c ] %s", BoolValue(show_channelname));
    isyslog("%s: show_flags             = [ %c%c%c ] %s", BoolValue(show_flags));
    isyslog("%s: show_startstop         = [ %c%c%c ] %s", IsDefault(show_startstop), StartStopTime[show_startstop.u]);
    isyslog("%s: use_except_repeat      = [ %c%c%c ] %s", BoolValue(use_except_repeat));
    isyslog("%s: use_weekdays           = [ %c%c%c ] %s", BoolValue(use_weekdays));
    isyslog("%s: use_defaultdictionary  = [ %c%c%c ] 0x%02X", IsDefault(use_defaultdictionary), use_defaultdictionary.u);
    if (use_defaultdictionary.u & Source_AutoTimers)
      isyslog("%s:                          [ --> ] Source_AutoTimers", plugin_name);
    if (use_defaultdictionary.u & Source_Timers)
      isyslog("%s:                          [ --> ] Source_Timers", plugin_name);
    if (use_defaultdictionary.u & Source_Records)
      isyslog("%s:                          [ --> ] Source_Records", plugin_name);
    if (use_defaultdictionary.u & Source_Commandline)
      isyslog("%s:                          [ --> ] Source_Commandline", plugin_name);
    if (use_defaultdictionary.u & Source_Setup)
      isyslog("%s:                          [ --> ] Source_Setup", plugin_name);
    isyslog("%s: defaultdictionarys (c) = [%c%c%c%c%c] %d (%d/%d/%d/%d/%d)", plugin_name, DefaultDictionarys.CountSource(Source_AutoTimers) ? 'a' : ' ', DefaultDictionarys.CountSource(Source_Timers) ? 't' : ' ', DefaultDictionarys.CountSource(Source_Records) ? 'r' : ' ', DefaultDictionarys.CountSource(Source_Commandline) ? 'c' : ' ', DefaultDictionarys.CountSource(Source_Setup) ? 's' : ' ', DefaultDictionarys.Count(), DefaultDictionarys.CountSource(Source_AutoTimers), DefaultDictionarys.CountSource(Source_Timers), DefaultDictionarys.CountSource(Source_Records), DefaultDictionarys.CountSource(Source_Commandline), DefaultDictionarys.CountSource(Source_Setup));
    i = 0;
    DD = DefaultDictionarys.First();
    while (DD) {
      isyslog("%s: %03d.Dictionary %s = [%c%c%c%c%c] %s", plugin_name, ++i, DD->Blacklist() ? DD->Includesub() ? "bll.sub" : "blackl." : "       ", DD->isSource(Source_AutoTimers) ? 'a' : ' ', DD->isSource(Source_Timers) ? 't' : ' ', DD->isSource(Source_Records) ? 'r' : ' ', DD->isSource(Source_Commandline) ? 'c' : ' ', DD->isSource(Source_Setup) ? 's' : ' ', DD->Dictionary());
      DD = DefaultDictionarys.Next(DD);
    }
    if (updatetimerecords.u < 1)
      isyslog("%s: updatetimerecords      = [ %c%c%c ] %s", IsDefault(updatetimerecords), RecordsUpdateTime[updatetimerecords.u + 1]);
    else
      isyslog("%s: updatetimerecords      = [ %c%c%c ] %dh", IsDefault(updatetimerecords), updatetimerecords.u);
    isyslog("%s: updatemethod           = [ %c%c%c ] %s", IsDefault(updatemethod), UpdateMethod[updatemethod.u]);
    if (updatemethod.u == 0x1) {
      isyslog("%s: vdradminconfig         = [ %c%c%c ] %s", IsDefaultChar(vdradminconfig), vdradminconfig.u);
      if (strcmp(vdradminconfig.u, vdradminconfig.e))
        isyslog("%s:                          [ --> ] %s", plugin_name, vdradminconfig.e);
    }
    if (updatemethod.u == 0x2) {
      isyslog("%s: vdradminupdate         = [ %c%c%c ] %s", IsDefaultChar(vdradminupdate), vdradminupdate.u);
      if (strcmp(vdradminupdate.u, vdradminupdate.e))
        isyslog("%s:                          [ --> ] %s", plugin_name, vdradminupdate.e);
    }
    if (updatemethod.u != 0x0) {
      isyslog("%s: update_b_e             = [ %c%c%c ] %s", IsDefault(update_b_e), UpdateLine[update_b_e.u]);
      if (update_b_e.u == 0x3)
        isyslog("%s: num_entrys             = [ %c%c%c ] %d", IsDefault(num_entrys), num_entrys.u);
      isyslog("%s: question               = [ %c%c%c ] %s", IsDefault(question), UpdateQuestion[question.u]);
    }
    if (autotimerfile.h)
      isyslog("%s: setup-option for autotimerfile is hidden", plugin_name);
    if (use_except_repeat.h)
      isyslog("%s: setup-option for except repeat is hidden", plugin_name);
    if (use_weekdays.h)
      isyslog("%s: setup-option for weekdays is hidden", plugin_name);
    if (updatemethod.h)
      isyslog("%s: setup-option for update method is hidden", plugin_name);
    if (vdradminupdate.h)
      isyslog("%s: setup-option for vdradmin-update/-config file is hidden", plugin_name);
    if (commandline_preference.h)
      isyslog("%s: setup-option for preferr command line parameter is hidden", plugin_name);
    if (use_defaultdictionary.h)
      isyslog("%s: setup-option for default dictionary is hidden", plugin_name);
  }
#ifdef ATE_Debug1
  dsyslog("%s: autotimerfile          = [ %c%c%c ] %s", IsDefaultChar(autotimerfile), autotimerfile.u);
  if (strcmp(autotimerfile.u, autotimerfile.e))
    dsyslog("%s:                          [ --> ] %s", plugin_name, autotimerfile.e);
  dsyslog("%s: commandline_preference = [ %c%c%c ] %s", BoolValue(commandline_preference));
  dsyslog("%s: minimum search length  = [ %c%c%c ] %d", IsDefault(searchlength), searchlength.u);
  dsyslog("%s: mainmenu_visible       = [ %c%c%c ] %s", BoolValue(mainmenu_visible));
  if (mainmenu_visible.u)
    dsyslog("%s: mainmenu_name          = [ %c%c%c ] %s", IsDefaultChar(mainmenu_name), mainmenu_name.u);
  dsyslog("%s: show_channel           = [ %c%c%c ] %s", BoolValue(show_channel));
  if (show_channel.u)
    dsyslog("%s: show_channelname       = [ %c%c%c ] %s", BoolValue(show_channelname));
  dsyslog("%s: show_flags             = [ %c%c%c ] %s", BoolValue(show_flags));
  dsyslog("%s: show_startstop         = [ %c%c%c ] %s", IsDefault(show_startstop), StartStopTime[show_startstop.u]);
  dsyslog("%s: use_except_repeat      = [ %c%c%c ] %s", BoolValue(use_except_repeat));
  dsyslog("%s: use_weekdays           = [ %c%c%c ] %s", BoolValue(use_weekdays));
  dsyslog("%s: use_defaultdictionary  = [ %c%c%c ] 0x%02X", IsDefault(use_defaultdictionary), use_defaultdictionary.u);
  if (use_defaultdictionary.u & Source_AutoTimers)
    dsyslog("%s:                          [ --> ] Source_AutoTimers", plugin_name);
  if (use_defaultdictionary.u & Source_Timers)
    dsyslog("%s:                          [ --> ] Source_Timers", plugin_name);
  if (use_defaultdictionary.u & Source_Records)
    dsyslog("%s:                          [ --> ] Source_Records", plugin_name);
  if (use_defaultdictionary.u & Source_Commandline)
    dsyslog("%s:                          [ --> ] Source_Commandline", plugin_name);
  if (use_defaultdictionary.u & Source_Setup)
    dsyslog("%s:                          [ --> ] Source_Setup", plugin_name);
  dsyslog("%s: defaultdictionarys (c) = [%c%c%c%c%c] %d (%d/%d/%d/%d/%d)", plugin_name, DefaultDictionarys.CountSource(Source_AutoTimers) ? 'a' : ' ', DefaultDictionarys.CountSource(Source_Timers) ? 't' : ' ', DefaultDictionarys.CountSource(Source_Records) ? 'r' : ' ', DefaultDictionarys.CountSource(Source_Commandline) ? 'c' : ' ', DefaultDictionarys.CountSource(Source_Setup) ? 's' : ' ', DefaultDictionarys.Count(), DefaultDictionarys.CountSource(Source_AutoTimers), DefaultDictionarys.CountSource(Source_Timers), DefaultDictionarys.CountSource(Source_Records), DefaultDictionarys.CountSource(Source_Commandline), DefaultDictionarys.CountSource(Source_Setup));
  i = 0;
  DD = DefaultDictionarys.First();
  while (DD) {
    dsyslog("%s: %03d.Dictionary %s = [%c%c%c%c%c] %s", plugin_name, ++i, DD->Blacklist() ? DD->Includesub() ? "bll.sub" : "blackl." : "       ", DD->isSource(Source_AutoTimers) ? 'a' : ' ', DD->isSource(Source_Timers) ? 't' : ' ', DD->isSource(Source_Records) ? 'r' : ' ', DD->isSource(Source_Commandline) ? 'c' : ' ', DD->isSource(Source_Setup) ? 's' : ' ', DD->Dictionary());
    DD = DefaultDictionarys.Next(DD);
  }
  if (updatetimerecords.u < 1)
    dsyslog("%s: updatetimerecords      = [ %c%c%c ] %s", IsDefault(updatetimerecords), RecordsUpdateTime[updatetimerecords.u + 1]);
  else
    dsyslog("%s: updatetimerecords      = [ %c%c%c ] %dh", IsDefault(updatetimerecords), updatetimerecords.u);
  dsyslog("%s: updatemethod           = [ %c%c%c ] %s", IsDefault(updatemethod), UpdateMethod[updatemethod.u]);
  if (updatemethod.u == 0x1) {
    dsyslog("%s: vdradminconfig         = [ %c%c%c ] %s", IsDefaultChar(vdradminconfig), vdradminconfig.u);
    if (strcmp(vdradminconfig.u, vdradminconfig.e))
      dsyslog("%s:                          [ --> ] %s", plugin_name, vdradminconfig.e);
  }
  if (updatemethod.u == 0x2) {
    dsyslog("%s: vdradminupdate         = [ %c%c%c ] %s", IsDefaultChar(vdradminupdate), vdradminupdate.u);
    if (strcmp(vdradminupdate.u, vdradminupdate.e))
      dsyslog("%s:                          [ --> ] %s", plugin_name, vdradminupdate.e);
  }
  if (updatemethod.u != 0x0) {
    dsyslog("%s: update_b_e             = [ %c%c%c ] %s", IsDefault(update_b_e), UpdateLine[update_b_e.u]);
    if (update_b_e.u == 0x3)
      dsyslog("%s: num_entrys             = [ %c%c%c ] %d", IsDefault(num_entrys), num_entrys.u);
    dsyslog("%s: question               = [ %c%c%c ] %s", IsDefault(question), UpdateQuestion[question.u]);
  }
  if (autotimerfile.h)
    dsyslog("%s: setup-option for autotimerfile is hidden", plugin_name);
  if (use_except_repeat.h)
    dsyslog("%s: setup-option for except repeat is hidden", plugin_name);
  if (use_weekdays.h)
    dsyslog("%s: setup-option for weekdays is hidden", plugin_name);
  if (updatemethod.h)
    dsyslog("%s: setup-option for update method is hidden", plugin_name);
  if (vdradminupdate.h)
    dsyslog("%s: setup-option for vdradmin-update/-config file is hidden", plugin_name);
  if (commandline_preference.h)
    dsyslog("%s: setup-option for preferr command line parameter is hidden", plugin_name);
  if (use_defaultdictionary.h)
    dsyslog("%s: setup-option for default dictionary is hidden", plugin_name);
#endif
  #undef WriteSource
  #undef IsDefault
  #undef IsDefaultChar
  #undef BoolValue
}

void ExpandEnvironment(tParamFile *FileStruc)
{
#ifdef ATE_Debug2
  dsyslog("%s: ExpandEnvironment text=%s", plugin_name, FileStruc->u);
#endif
  char *s;
  char *p;
  strn0cpy(FileStruc->e, FileStruc->u, sizeof(FileStruc->e));
  while ((s = strstr(FileStruc->e, "$"))) {
    if (*(s + 1) == '{') {
      p = strchr(s, '}'); // search closing }
      if (p) {
        *p++ = 0;
        *s = 0;
        s += 2;
        char *e = getenv(s);
        if (e) {
          char *buffer;
           asprintf(&buffer, "%s%s%s", FileStruc->e, e, p);
          strn0cpy(FileStruc->e, buffer, sizeof(FileStruc->e));
          free(buffer);
        } else {
          esyslog("%s: environmentvariable '%s' not found path=%s", plugin_name, s, FileStruc->u);
          FileStruc->e[0] = 0;
        }
      } else {
        esyslog("%s: missing '}' after '${' path=%s", plugin_name, FileStruc->u);
        FileStruc->e[0] = 0;
        s = NULL;
      }
    } else {
      p = s + 1;
      while ((*p) && (isalpha(*p) || (*p) == '_'))
        p++;
      if (*(p - 1) != '$') {
        char c = *p;
        *p = 0;
        *s++ = 0;
        char *e = getenv(s);
        if (e) {
          *p = c;
          char *buffer;
          asprintf(&buffer, "%s%s%s", FileStruc->e, e, p);
          strn0cpy(FileStruc->e, buffer, sizeof(FileStruc->e));
          free(buffer);
        } else {
          esyslog("%s: environmentvariable '%s' not found path=%s", plugin_name, s, FileStruc->u);
          FileStruc->e[0] = 0;
        }
      } else {
        esyslog("%s: missing variable name after '$' path=%s", plugin_name, FileStruc->u);
        FileStruc->e[0] = 0;
        s = NULL;
      }
    }
  }
  while ((p = strstr(FileStruc->e, "//")))
    strcpy(p, p + 1);
#ifdef ATE_Debug2
  dsyslog("%s: ExpandEnvironment return=%s", plugin_name, FileStruc->e);
#endif  
}

class cPluginAutoTimer : public cPlugin
{
private:
  // Add any member variables or functions you may need here.
  char* OSDLanguage;
  void TestAndSetOSDLanguage(void);
  bool ProcessArg(int argc, char *argv[]);
public:
  cPluginAutoTimer(void);
  virtual ~cPluginAutoTimer();
  virtual const char *Version(void)                             { return VERSION; }
  virtual const char *Description(void)                         { return tr(DESCRIPTION); }
  virtual const char *CommandLineHelp(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Start(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void)                       { return (mainmenu_visible.u) ? mainmenu_name.u : NULL; }
  virtual cOsdObject *MainMenuAction(void)                      { TestAndSetOSDLanguage(); return new cMenuAutoTimers; }
  virtual cMenuSetupPage *SetupMenu(void)                       { TestAndSetOSDLanguage(); return new cMenuSetupAutoTimer; }
  virtual bool SetupParse(const char *Name, const char *Value);
  };

// --- cPluginAutoTimer ----------------------------------------------------------

cPluginAutoTimer::cPluginAutoTimer(void)
{
#ifdef ATE_Debug2
  dsyslog("%s: cPluginAutoTimer::cPluginAutoTimer", plugin_name);
#endif
  // Initialize any member variables here.
  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
  OSDLanguage = (char *)malloc(64);
  OSDLanguage[0] = '\0';
  int i;
  for (i = 1; i < MaxDefaultDictionary; i++)
    SetupDefaultDictionarys.Add(new cDefaultDictionary("", 0));
}

cPluginAutoTimer::~cPluginAutoTimer()
{
#ifdef ATE_Debug2
  dsyslog("%s: cPluginAutoTimer::~cPluginAutoTimer", plugin_name);
#endif
  // Clean up after yourself!
  if (FileNameCharsAllowed)
    free(FileNameCharsAllowed);
  if (oUpdateThread)
    DELETENULL(oUpdateThread);
}

void cPluginAutoTimer::TestAndSetOSDLanguage(void)
{
#ifdef ATE_Debug2
  dsyslog("%s: cPluginAutoTimer::TestAndSetOSDLanguage OSDLanguage=%s", plugin_name, Setup.OSDLanguage);
#endif
  if (strcmp(OSDLanguage, Setup.OSDLanguage)) {
    strcpy(OSDLanguage, Setup.OSDLanguage);
    OffOnSingle[0] =        tr("no");
    OffOnSingle[1] =        tr("yes");
    OffOnSingle[2] =        tr("Choise$single");
    StartStopTime[0] =      tr("Choise$none");
    StartStopTime[1] =      tr("Choise$start Time");
    StartStopTime[2] =      tr("Choise$stop Time");
    StartStopTime[3] =      tr("Choise$both Times");
    UpdateLine[0] =         tr("Choise$none");
    UpdateLine[1] =         tr("Choise$top");
    UpdateLine[2] =         tr("Choise$bottom");
    UpdateLine[3] =         tr("Choise$top and bottom");
    UpdateMethod[0] =       tr("Choise$none");
    UpdateMethod[1] =       tr("Choise$webpage");
    UpdateMethod[2] =       tr("Choise$restart script");
    UpdateMethod[3] =       tr("Choise$vdradmind sighup");
    UpdateMethod[4] =       tr("Choise$xxv sighup");
    UpdateQuestion[0] =     tr("Choise$never");
    UpdateQuestion[1] =     tr("Choise$question");
    UpdateQuestion[2] =     tr("Choise$always");
    RecordsUpdateTime[0] =  tr("Choise$only start");
    RecordsUpdateTime[1] =  tr("Choise$always");
    RecordsUpdateTime[2] =  "1";
    if (FileNameCharsAllowed)
      free(FileNameCharsAllowed);
    asprintf(&FileNameCharsAllowed, "%s/$(){}!%%@", tr(FileNameChars));
  }
}

bool cPluginAutoTimer::ProcessArg(int argc, char *argv[])
{
  int c;
  static struct option long_options[] = {
    { "show_start",                no_argument,       NULL, 'a' },
    { "hide_start",                no_argument,       NULL, 'A' },
    { "update_begin",              no_argument,       NULL, 'b' },
    { "noupdate_begin",            no_argument,       NULL, 'B' },
    { "show_channel",              no_argument,       NULL, 'c' },
    { "hide_channel",              no_argument,       NULL, 'C' },
    { "defaultdictionary",         required_argument, NULL, 'd' },
    { "use_defaultdictionary",     required_argument, NULL, 'D' },
    { "update_end",                no_argument,       NULL, 'e' },
    { "noupdate_end",              no_argument,       NULL, 'E' },
    { "autotimerfile",             required_argument, NULL, 'f' },
    { "min_entrys",                required_argument, NULL, 'h' },
    { "show_flags",                no_argument,       NULL, 'l' },
    { "hide_flags",                no_argument,       NULL, 'L' },
    { "visible_in_mainmenu",       no_argument,       NULL, 'm' },
    { "hide_in_mainmenu",          no_argument,       NULL, 'M' },
    { "mainmenu_name",             required_argument, NULL, 'n' },
    { "use_sighup_vdradmin",       no_argument,       NULL, 'p' },
    { "use_sighup_vdradmind",      no_argument,       NULL, 'p' },
    { "question_update",           optional_argument, NULL, 'q' },
    { "noquestion_update",         no_argument,       NULL, 'Q' },
    { "use_repeat_feature",        no_argument,       NULL, 'r' },
    { "not_use_repeat_feature",    no_argument,       NULL, 'R' },
    { "show_channel_name",         no_argument,       NULL, 's' },
    { "show_channel_number",       no_argument,       NULL, 'S' },
    { "show_stop",                 no_argument,       NULL, 't' },
    { "hide_stop",                 no_argument,       NULL, 'T' },
    { "vdradminupdate",            required_argument, NULL, 'u' },
    { "verbose",                   no_argument,       NULL, 'v' },
    { "noverbose",                 no_argument,       NULL, 'V' },
    { "searchtext_lenght",         required_argument, NULL, 'w' },
    { "use_sighup_xxv",            no_argument,       NULL, 'x' },
    { "updatetimerecords",         required_argument, NULL, 'y' },
    { "nosetup_autotimerfile",     no_argument,       NULL, 1   },
    { "nosetup_repeat_feature",    no_argument,       NULL, 2   },
    { "nosetup_update_method",     no_argument,       NULL, 3   },
    { "nosetup_vdradminupdate",    no_argument,       NULL, 4   },
    { "nosetup_commandline",       no_argument,       NULL, 5   },
    { "nosetup_defaultdictionary", no_argument,       NULL, 6   },
    { "nosetup_updatetimerecords", no_argument,       NULL, 7   },
    { "nosetup_weekday_feature",   no_argument,       NULL, 8   },
    { "ns_autotimerfile",          no_argument,       NULL, 1   },
    { "ns_repeat_feature",         no_argument,       NULL, 2   },
    { "ns_update_method",          no_argument,       NULL, 3   },
    { "ns_vdradminupdate",         no_argument,       NULL, 4   },
    { "ns_commandline",            no_argument,       NULL, 5   },
    { "ns_defaultdictionary",      no_argument,       NULL, 6   },
    { "ns_updatetimerecords",      no_argument,       NULL, 7   },
    { "ns_weekday_feature",        no_argument,       NULL, 8   },
    { NULL }
  };  // free: gijkoyz

  if (argc >= 1)
    strn0cpy(plugin_name, argv[0], sizeof(plugin_name));

  for (c = 1; c < argc; c++)
    dsyslog("%s: Parameter%d=%s", plugin_name, c, argv[c]);

  #define Setvalue(T) T.c = true; T.u
  #define SetvalueChar(T) T.c = true; strn0cpy(T.u, optarg, sizeof(T.u))

  while ((c = getopt_long(argc, argv, "aAbBcCd:D:eEf:h:kKlLmMn:pq::QrRsStTu:vVw:xy:", long_options, NULL)) != -1) {
    // var pos4
    switch (c) {
      case 1:   autotimerfile.h = true;
                break;
      case 2:   use_except_repeat.h = true;
                break;
      case 3:   updatemethod.h = true; // no break
      case 4:   vdradminupdate.h = true;
                vdradminconfig.h = true;
                break;
      case 5:   commandline_preference.h = true;
                break;
      case 6:   use_defaultdictionary.h = true;
                break;
      case 7:   updatetimerecords.h = true;
                break;
      case 8:   use_weekdays.h = true;
                break;
      case 'a': Setvalue(show_startstop) |= 0x1;
                break;
      case 'A': Setvalue(show_startstop) &= ~0x1;
                break;
      case 'b': Setvalue(update_b_e) |= 0x1;
                break;
      case 'B': Setvalue(update_b_e) &= ~0x1;
                break;
      case 'c': Setvalue(show_channel) = true;
                break;
      case 'C': Setvalue(show_channel) = false;
                break;
      case 'd': {
                  const char *p = optarg;
                  const char *q;
                  int len;
                  bool b;
                  bool i;
                  char buffer[MaxFileName];
                  while (*p) {
                    q = strchr(p, ',');
                    if (q)
                      len = q - p;
                    else
                      len = strlen(p);
                    if (*p == '~') {
                      b = true;
                      p++;
                      len--;
                      if (*p == '~') {
                        i = true;
                        p++;
                        len--;
                      } else
                        i = false;
                    } else {
                      b = false;
                      i = false;
                    }
                    if (len > MaxFileName - 1)
                      len = MaxFileName - 1;
                    strn0cpy(buffer, p, len + 1);
                    if (strlen(buffer)) {
                      q = buffer;
                      char *s = buffer;
                      while (*q) {
                        switch (*q) {
                          // mapped characters:
                          case '_': *s = ' ';
                                    break;
                          case '/': *s = '~';
                                    break;
                          // encodes characters:
                          case '#': if (strlen(q) > 2) {
                                      char buf[3];
                                      buf[0] = *(q + 1);
                                      buf[1] = *(q + 2);
                                      buf[2] = 0;
                                      unsigned char c = strtol(buf, NULL, 16);
                                      *s = c;
                                      q += 2;
                                    }
                                    break;
                        }
                        s++;
                        q++;
                      }
                      *s = 0;
                      CommandlineDefaultDictionarys.Add(new cDefaultDictionary(buffer, Source_Commandline, b, i));
                    }
                    p += len;
                    if (*p == ',')
                      p++;
                  }
                }
                break;
      case 'D': {
                  char *end;
                  int value = strtol(optarg, &end, 0);
                  if (!end || !*end) {
                    Setvalue(use_defaultdictionary) = value;
                    if ((use_defaultdictionary.u & ~Source_All) == Source_none)
                      break;
                  }
                  esyslog("%s: invalid parameter for -%c option: %s", plugin_name, c, optarg);
                  return false;
                }
      case 'e': Setvalue(update_b_e) |= 0x2;
                break;
      case 'E': Setvalue(update_b_e) &= ~0x2;
                break;
      case 'f': SetvalueChar(autotimerfile);
                break;
      case 'h': if (isnumber(optarg)) {
                  Setvalue(num_entrys) = atoi(optarg);
                  Setvalue(update_b_e) = 0x3;
                  if (num_entrys.u > 0) break;
                }
                esyslog("%s: invalid parameter for -%c option: %s", plugin_name, c, optarg);
                return false;
      case 'k': Setvalue(use_weekdays) = true; // no break
      case 'r': Setvalue(use_except_repeat) = true;
                break;
      case 'K': Setvalue(use_weekdays) = false;
                break;
      case 'R': Setvalue(use_except_repeat) = false;
                break;
      case 'l': Setvalue(show_flags) = true;
                break;
      case 'L': Setvalue(show_flags) = false;
                break;
      case 'm': Setvalue(mainmenu_visible) = true;
                break;
      case 'M': Setvalue(mainmenu_visible) = false;
                break;
      case 'n': SetvalueChar(mainmenu_name);
                Setvalue(mainmenu_visible) = true;
                break;
      case 'p': Setvalue(updatemethod) = 0x3;
                break;
      case 'q': Setvalue(question) = 0x1;
                if (optarg) {
                  if (*optarg == 'a' || *optarg == 'A')
                    question.u = 0x2;
                  else {
                    esyslog("%s: invalid parameter for -%c option (only a allow): %s", plugin_name, c, optarg);
                    return false;
                  }
                }
                break;
      case 'Q': Setvalue(question) = 0x0;
                break;
      case 's': Setvalue(show_channelname) = true;
                break;
      case 'S': Setvalue(show_channelname) = false;
                break;
      case 't': Setvalue(show_startstop) |= 0x2;
                break;
      case 'T': Setvalue(show_startstop) &= ~0x2;
                break;
      case 'u': if (!access(optarg, F_OK)) {
                  if (access(optarg, X_OK)) {
                    SetvalueChar(vdradminconfig);
                    Setvalue(updatemethod) = 0x1;
                  } else {
                    SetvalueChar(vdradminupdate);
                    Setvalue(updatemethod) = 0x2;
                  }
                } else
                  esyslog("%s: file %s ist not accessible", plugin_name, optarg);
                break;
      case 'v': Setvalue(verbose) = true;
                break;
      case 'V': Setvalue(verbose) = false;
                break;
      case 'w': if (isnumber(optarg)) {
                  Setvalue(searchlength) = atoi(optarg);
                  if (searchlength.u > 0) break;
                }
                esyslog("%s: invalid parameter for -%c option: %s", plugin_name, c, optarg);
                return false;
      case 'x': Setvalue(updatemethod) = 0x4;
                break;
      case 'y': if (isnumber(optarg)) {
                  Setvalue(updatetimerecords) = atoi(optarg);
                  if (updatetimerecords.u > -2) break;
                }
                esyslog("%s: invalid parameter for -%c option: %s", plugin_name, c, optarg);
                return false;
      default:  return false;
    }
  }

  #undef Setvalue
  #undef SetvalueChar

  if (optind < argc && argv[optind][0] == '@') {
    strn0cpy(autotimerconfigfile.u, &argv[optind][1], sizeof(autotimerconfigfile.u));
    optind++;
  }
  return optind >= argc;
}

const char *cPluginAutoTimer::CommandLineHelp(void)
{
  // Return a string that describes all known command line options.
  return "  -f path      --autotimerfile=path           Select Autotimer-file (same for vdradmind!)\n"
         "               --nosetup_autotimerfile        Hide the file-setting form setup-menu\n"
         "               --ns_autotimerfile             same as --nosetup_autotimerfile\n"
         "  -p           --use_sighup_vdradmin          Use SIGHUP (vdradmind >=0.96 or patch) for update Autotimer\n"
         "  -x           --use_sighup_xxv               Use SIGHUP (xxv) for update Autotimer\n"
         "               --nosetup_update_method        Hide the update method from setup-menu (include nosetup_vdradminupdate)\n"
         "               --ns_update_method             same as --nosetup_update_method\n"
         "  -u path      --vdradminupdate=path          Script/Config-file for force update autotimer (see readme)\n"
         "               --nosetup_vdradminupdate       Hide the vdradminupdate-file form setup-menu\n"
         "               --ns_vdradminupdate            same as --nosetup_vdradminupdate\n"
         "  -m           --visible_in_mainmenu          Show the plugin in the mainmenu\n"
         "  -M           --hide_in_mainmenu             Hide the plugin in the mainmenu\n"
         "  -n Name      --mainmenu_name=Name           Select Name for entry in the mainmenu (set also -m)\n"
         "  -v           --verbose                      Enable more logging\n"
         "  -V           --noverbose                    Disable more loggig\n"
         "  -r           --use_repeat_feature           use the repeat feature from vdradmin bigpatch 0.9\n"
         "  -R           --not_use_repeat_feature       not use, it set automatic to use, if the autotimerfile with 11 values\n"
         "               --nosetup_repeat_feature       Hide the option from setup-menu\n"
         "               --ns_repeat_feature            same as --nosetup_repeat_feature\n"
         "  -k           --use_weekday_feature          use the repeat feature from vdradmin am 3.4.1, sets also -r\n"
         "  -K           --not_use_weekday_feature      not use, it set automatic to use, if the autotimerfile with 12 values\n"
         "               --nosetup_weekday_feature      Hide the option from setup-menu\n"
         "               --ns_weekday_feature           same as --nosetup_weekday_feature\n"
         "  -b           --update_begin                 Show the manual update line at begin of autotimer-list\n"
         "  -B           --noupdate_begin               Hide the manual update line at begin of autotimer-list\n"
         "  -e           --update_end                   Show the manual update line at end of autotimer-list\n"
         "  -E           --noupdate_end                 Hide the manual update line at end of autotimer-list\n"
         "  -h xx        --min_entrys=xx                minimum entrys for display update line at begin and end (include -b and -e)\n"
         "  -q [a]       --question_update=[a]          When one or more Autotimer are modifed, by quit the plugin appears a question for force search update\n"
         "  -Q           --noquestion_update            see above but no question\n"
         "  -l           --show_flags                   For the Autotimerlist show search in title, subtitle and description as a single char\n"
         "  -L           --hide_flags                   column is hide\n"
         "  -a           --show_start                   For the Autotimerlist show the startsearchtime\n"
         "  -A           --hide_start                   column is hide\n"
         "  -t           --show_stop                    For the Autotimerlist show the stopsearchtime\n"
         "  -T           --hide_stop                    column is hide\n"
         "  -c           --show_channel                 For the Autotimerlist show the searchchannel\n"
         "  -C           --hide_channel                 column is hide\n"
         "  -s           --show_channel_name            if show_channel the name of channel is use\n"
         "  -S           --show_channel_number          if show_channel the number of channel is use\n"
         "  -w x         --searchtext_lenght            minimum search text length\n"
         "               --nosetup_commandline          Hide the Preferr Command Line Parameter form setup-menu\n"
         "               --ns_commandline               same as --nosetup_commandline\n"
         "  -d dir[,dir] --defaultdictionary=dir[,dir]  set the default choise for the dictionary (space '_', any special char with #xx)\n"
         "  -D bitset    --use_defaultdictionary=bitset Select the source for the defaultdictionary 0x01 - autotimer 0x02 - timer 0x04 - records 0x08 - commandline 0x10 - setup\n"
         "               --nosetup_defaultdictionary    Hide many options with default dictionary from setup-menu\n"
         "               --ns_defaultdictionary         same as nosetup_defaultdictionary\n"
         "  -y time      --updatetimerecords=time       time (hours) between two updates for defaultdictionary from records (-1 only by start, 0 by each call autotimeredit)\n"
         "               --nosetup_updatetimerecords    Hide the updatetimerecords option from setup-menu\n"
         "               --ns_updatetimerecords         same as nosetup_updatetimerecords\n";
// free: g i j o
}

bool cPluginAutoTimer::ProcessArgs(int argc, char *argv[])
{
  // Implement command line argument processing here if applicable.
  return ProcessArg(argc, argv);
}

bool cPluginAutoTimer::Start(void)
{
  // Start any background activities the plugin shall perform.
  RegisterI18n(Phrases);
  TestAndSetOSDLanguage();

#ifdef ATE_Debug2
  dsyslog("%s: cPluginAutoTimer::Start autotimerconfigfile=%s", plugin_name, autotimerconfigfile.u);
#endif
  char *p;
  char *q = strdup(ConfigDirectory(""));
  asprintf(&p, "%s%s%s", q, *(q + strlen(q) - 1) == '/' ? "" : "/", autotimerconfigfile.u);
  if (!access(autotimerconfigfile.u, F_OK) && !access(autotimerconfigfile.u, R_OK) || !access(p, F_OK) && !access(p, R_OK)) {
    #define MAXARGS 100
    int fargc = 1;
    char *fargv[MAXARGS];
    cReadLine ReadLine;
    bool done;
    FILE *f;

    if (!access(autotimerconfigfile.u, F_OK)) {
      f = fopen(autotimerconfigfile.u, "r");
#ifdef ATE_Debug1
      dsyslog("%s: cPluginAutoTimer::Start open autotimerconfigfile=%s", plugin_name, autotimerconfigfile.u);
#endif
    } else {
      f = fopen(p, "r");
#ifdef ATE_Debug1
      dsyslog("%s: cPluginAutoTimer::Start open autotimerconfigfile=%s", plugin_name, p);
#endif
    }
    free(p);
    free(q);
    if (!f) {
      esyslog("%s: ERROR: cannot open config file: [%s]%s", plugin_name, ConfigDirectory(""), autotimerconfigfile.u);
      return false;
    }
    while ((p = compactspace(ReadLine.Read(f))) != NULL) {
      q = NULL;
      done = false;
      while (!done) {
        if (!q)
          q = p;
        switch (*p) {
          case '\\': strcpy(p, p + 1);
                     if (*p)
                       p++;
                     else {
                       esyslog("%s: ERROR: missing character after \\", plugin_name);
                       return false;
                     }
                     break;
          case '"':
          case '\'': if ((p = SkipQuote(p)) == NULL)
                       return false;
                     break;
          default:   if (!*p || isspace(*p)) {
                       done = !*p;
                       *p = 0;
                       if (q) {
                         if (fargc < MAXARGS - 1) {
                           if (*q != '#')
                             fargv[fargc++] = strdup(q);
                         } else {
                           esyslog("%s: ERROR: plugin argument list too long", plugin_name);
                           return false;
                         }
                         q = NULL;
                       }
                     }
                     if (!done)
                       p = *p ? p + 1 : skipspace(p + 1);
        }
      }
    }
    fclose(f);
    fargv[0] = strdup(plugin_name);
    fargv[fargc] = NULL;
    optind = 0; // to reset the getopt() data
    if (fargc > 1)
      if (!ProcessArg(fargc, fargv)) {
        esyslog("%s: ERROR: cannot parse config file: [%s]%s", plugin_name, ConfigDirectory(""), autotimerconfigfile.u);
        return false;
      }
      while(fargc) free(fargv[--fargc]);
  } else {
    free(p);
    free(q);
    if (strcmp(autotimerconfigfile.u, autotimerconfigfile.d)) {
      esyslog("%s: ERROR: config file not found: [%s]%s", plugin_name, ConfigDirectory(""), autotimerconfigfile.u);
      return false;
    } else if (verbose.u)
      isyslog("%s: INFO: config file not found: [%s]%s", plugin_name, ConfigDirectory(""), autotimerconfigfile.u);
  }

  // default parameter                --> d_
  // read parameter from commandline  --> c_  (data in u_)
  // value parameter from config-file --> s_
  // read parameter from config-file  --> r_
  // paramater used                   --> u_

  #define SetParam(T) if (!T.c || (!commandline_preference.u && T.r)) T.u = (!T.c && !T.r) ? T.d : T.s
  #define SetParamChar(T) if (!T.c || (!commandline_preference.u && T.r)) strcpy(T.u, (!T.c && !T.r) ? T.d : T.s)

  commandline_preference.u = commandline_preference.r ? commandline_preference.s : commandline_preference.d;

  // var pos5
  SetParamChar (autotimerconfigfile);
  SetParamChar (autotimerfile);
  SetParamChar (mainmenu_name);
  SetParam     (mainmenu_visible);
  SetParam     (num_entrys);
  SetParam     (question);
  SetParam     (searchlength);
  SetParam     (show_channel);
  SetParam     (show_channelname);
  SetParam     (show_flags);
  SetParam     (show_startstop);
  SetParam     (update_b_e);
  SetParam     (updatemethod);
  SetParam     (updatetimerecords);
  SetParam     (use_defaultdictionary);
  SetParam     (use_except_repeat);
  SetParam     (use_weekdays);
  SetParamChar (vdradminconfig);
  SetParamChar (vdradminupdate);
  SetParam     (verbose);

  #undef SetParam
  #undef SetParamChar

  if (!AutoTimers.TestUpdate(false) && updatemethod.u != 0x3 && updatemethod.u != 0x4)
    return false;

  if (verbose.u)
    isyslog("%s: Start", plugin_name);
  dsyslog("%s: Start", plugin_name);
  ExpandEnvironment(&autotimerfile);
  struct stat buf;
  if (!stat(autotimerfile.e, &buf)) {
    if (S_ISDIR(buf.st_mode)) {
      size_t len = strlen(autotimerfile.u);
      char *last = &autotimerfile.u[len - 1];
      if (*last != '/' && len < sizeof(autotimerfile.u)) {
        *last++ = '/';
        len++;
      }
      if ((len + 12) < sizeof(autotimerfile.u))
        strcpy(last, "vdradmind.at");
      ExpandEnvironment(&autotimerfile);
    }
  }
  if (!stat(autotimerfile.e, &buf)) {
    if (S_ISREG(buf.st_mode)) {
      AutoTimers.Load(autotimerfile.e); //if the except repeat feature used, the column for flags ist +1
    } else
      esyslog("%s: Error while access (1) autotimerfile '%s'.", plugin_name, autotimerfile.e);
  } else
    esyslog("%s: Error while access (2) autotimerfile '%s'.", plugin_name, autotimerfile.e);
  if (SetupDefaultDictionarys.Count() > MaxSetupDefaultDictionarys) {
    cDefaultDictionary *DD = SetupDefaultDictionarys.Get(MaxSetupDefaultDictionarys); //todo +-1 ?
    while (DD) {
      cDefaultDictionary *DDd = DD;
      DD = SetupDefaultDictionarys.Next(DD);
      SetupDefaultDictionarys.Del(DDd);
    }
  }
  SetupDefaultDictionarys.MergeDictionary();

  DefaultDictionarys.AddMergeFill();
  DisplaySetings();
  if (verbose.u) {
    if (AutoTimers.GetUpdatePID())
      isyslog("%s: %s runs with pid=%d", plugin_name, updatemethod.u == 0x3 ? "vdradmind" : "xxv", AutoTimers.GetUpdatePID());
    else
      isyslog("%s: vdradmind or xxv not running", plugin_name);
  }
  return true;
}

void cPluginAutoTimer::Housekeeping(void)
{
#ifdef ATE_Debug2
  dsyslog("%s: cPluginAutoTimer::Housekeeping UpdateThread-Active=%s UpdateThread-PID=%d", plugin_name, oUpdateThread ? oUpdateThread->Active() ? "true" : "false" : "(null)", oUpdateThread ? oUpdateThread->PID() : -1);
#endif
  if (oUpdateThread)
    if (oUpdateThread->Active()) {
      if (time(NULL) > oUpdateThread->StartTime() + 240 ) {
        esyslog("%s: update thread is still running after 240 seconds --> canceling pid=%d", plugin_name, oUpdateThread->PID());
        dsyslog("%s: update thread is still running after 240 seconds --> canceling pid=%d", plugin_name, oUpdateThread->PID());
        oUpdateThread->Stop();
      }
    } else
      DELETENULL(oUpdateThread);
}

bool cPluginAutoTimer::SetupParse(const char *Name, const char *Value)
{
  #define SetParam(T) { T.r = true; T.s = strtol(Value, NULL, 0); }
  #define SetParamChar(T) { T.r = true; strn0cpy(T.s, Value, sizeof(T.s)); }

  // Parse your own setup parameters and store their values.
  dsyslog("%s: Setupparameter %s=%s", plugin_name, Name, Value);
  // var pos6
  if      (!strcasecmp(Name, "autotimerfile"))     SetParamChar (autotimerfile)
  else if (!strcasecmp(Name, "commandline"))       SetParam     (commandline_preference)
  else if (!strcasecmp(Name, "name"))              SetParamChar (mainmenu_name)
  else if (!strcasecmp(Name, "visible"))           SetParam     (mainmenu_visible)
  else if (!strcasecmp(Name, "entrys"))            SetParam     (num_entrys)
  else if (!strcasecmp(Name, "question"))          SetParam     (question)
  else if (!strcasecmp(Name, "searchlength"))      SetParam     (searchlength)
  else if (!strcasecmp(Name, "channel"))           SetParam     (show_channel)
  else if (!strcasecmp(Name, "channelname"))       SetParam     (show_channelname)
  else if (!strcasecmp(Name, "flags"))             SetParam     (show_flags)
  else if (!strcasecmp(Name, "startstop"))         SetParam     (show_startstop)
  else if (!strcasecmp(Name, "updatelines"))       SetParam     (update_b_e)
  else if (!strcasecmp(Name, "updatemethod"))      SetParam     (updatemethod)
  else if (!strcasecmp(Name, "updatetime"))        SetParam     (updatetimerecords)
  else if (!strcasecmp(Name, "defaultdictionary")) SetParam     (use_defaultdictionary)
  else if (!strcasecmp(Name, "except_repeat"))     SetParam     (use_except_repeat)
  else if (!strcasecmp(Name, "weekdays"))          SetParam     (use_weekdays)
  else if (!strcasecmp(Name, "vdradminconfig"))    SetParamChar (vdradminconfig)
  else if (!strcasecmp(Name, "vdradminupdate"))    SetParamChar (vdradminupdate)
  else if (!strcasecmp(Name, "verbose"))           SetParam     (verbose)
  else if (!strcasecmp(Name, "countdictionary"))   MaxSetupDefaultDictionarys = atoi(Value);
  else if (!strncasecmp(Name, "dictionary", 10)) {
    if (isnumber(Name + 10)) {
      int nr = atoi(Name + 10);
      if (nr <= MaxDefaultDictionary) {
        const char *pos = Value;
        bool b;
        bool i;
        if (*pos == '~') {
          b = true;
          pos++;
          if (*pos == '~') {
            i = true;
            pos++;
          } else
            i = false;
        } else {
          b = false;
          i = false;
        }
        cDefaultDictionary *DD = SetupDefaultDictionarys.Get(nr - 1);
        if (DD)
          DD->SetValues(pos, Source_Setup, b, i);
      }
    }
  } else
    return false;
  return true;

  #undef SetParam
  #undef SetParamChar
}

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