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

#include "autotimeredit.h"
#include "autotimers.h"
#include "vdrtools.h"
#include "i18n.h"
#include <ctype.h>
#include <stropts.h>
#include <signal.h>
#include <vdr/device.h>

// -- cAutoTimer -----------------------------------------------------------------

char *cAutoTimer::buffer = NULL;

cAutoTimer::cAutoTimer(void)
{
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimer::cAutoTimer", plugin_name);
#endif
  active = true;
  *search = 0;
  options = 1;
  useStart = false;
  startTime = 0000;
  useStop = false;
  stopTime = 2359;
  serie = false;
  priority = Setup.DefaultPriority;
  lifetime = Setup.DefaultLifetime;
  useChannel = false;
  channel = Channels.GetByNumber(cDevice::CurrentChannel());
  store_cid = AutoTimers.Use_CID();

  *folder = 0;
  except_repeat = false;
  weekdays = 1111111;
  buffer = NULL;
}

cAutoTimer::~cAutoTimer(void)
{
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimer::~cAutoTimer [%s]", plugin_name, search);
#endif
  if (buffer) {
    free(buffer);
    buffer = NULL;
  }
}

cAutoTimer& cAutoTimer::operator= (const cAutoTimer &AutoTimer)
{
  memcpy(this, &AutoTimer, sizeof(*this));
  return *this;
}

bool cAutoTimer::operator== (const cAutoTimer &AutoTimer)
{
  return memcmp(&AutoTimer, this, sizeof(*this)) == 0; // no char* used
}            

bool cAutoTimer::operator!= (const cAutoTimer &AutoTimer)
{
  return memcmp(&AutoTimer, this, sizeof(*this)) != 0; // no char* used
}            

#if VDRVERSNUM >= 10315
int cAutoTimer::Compare(const cListObject &ListObject) const
{
  cAutoTimer *AT = (cAutoTimer *)&ListObject;
  return strcasecmp(search, AT->search);
}
#else
bool cAutoTimer::operator< (const cListObject &ListObject)
{
  cAutoTimer *AT = (cAutoTimer *)&ListObject;
  return strcasecmp(search, AT->search) < 0;
}
#endif

const char *cAutoTimer::ToText(void)
{
  char tmp_Start[5] = "";
  char tmp_Stop[5] = "";
  char tmp_Repeat[20] = "";
  char tmp_Weekdays[20] = "";
  char *tmp_Channel;

  free(buffer);
  strreplace(search, ':', '|');
  strreplace(folder, ':', '|');

  if (useStart)
    sprintf(tmp_Start, "%04d", startTime);
  if (useStop)
    sprintf(tmp_Stop, "%04d", stopTime);
  if (use_except_repeat.u) {
    sprintf(tmp_Repeat, ":%d", except_repeat);
    if (use_weekdays.u)
      sprintf(tmp_Weekdays, ":%07d", weekdays);
  }

  if (!useChannel || !AutoTimers.Use_CID())
    asprintf(&tmp_Channel, "%d", useChannel ? channel->Number() : 0);
  else
    tmp_Channel = strdup(channel->GetChannelID().ToString());

  asprintf(&buffer, "%d:%s:%d:%s:%s:%d:%d:%d:%s:%s%s%s\n",
                    use_weekdays.u ? active : active ? 1 : 0,
                    search,
                    options,
                    tmp_Start,
                    tmp_Stop,
                    serie,
                    priority,
                    lifetime,
                    tmp_Channel,
                    folder,
                    tmp_Repeat,
                    tmp_Weekdays);

  free(tmp_Channel);
  strreplace(search, '|', ':');
  strreplace(folder, '|', ':');
  
  if (verbose.u)
    isyslog("%s: write line [%s]", plugin_name, buffer);
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimer::ToText buffer=%s", plugin_name, buffer);
#endif
  return buffer;
}

bool cAutoTimer::Parse(const char *s)
{
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimer::Parse line=%s", plugin_name, s);
#endif
  char *line;
  char *pos;
  char *pos_next;
  int parameter = 1;
  int valuelen;
  char value[MaxFileName];
  tChannelID cid;

  if (verbose.u)
    isyslog("%s: readline [%s]", plugin_name, s);

  pos = line = strdup(s);
  pos_next = pos + strlen(pos);
  if (*pos_next == '\n') *pos_next = 0;
  while (*pos) {
    while (*pos == ' ') pos++;
    if (*pos) {
      if (*pos != ':') {
        pos_next = strchr(pos, ':');
        if (!pos_next)
          pos_next = pos + strlen(pos);
        valuelen = pos_next - pos + 1;
        if (valuelen > MaxFileName) valuelen = MaxFileName;
        strn0cpy(value, pos, valuelen);
        pos = pos_next;
        switch (parameter) {
          case 1:  active = atoi(value);
                   break;
          case 2:  strcpy(search, value);
                   break;
          case 3:  options = atoi(value);
                   break;
          case 4:  useStart = true;
                   startTime = atoi(value);
                   break;
          case 5:  useStop = true;
                   stopTime = atoi(value);
                   break;
          case 6:  serie = atoi(value) != 0 ? true : false;
                   break;
          case 7:  priority = atoi(value);
                   if (priority < 0) priority = 0;
                   if (priority > MAXPRIORITY) priority = MAXPRIORITY;
                   break;
          case 8:  lifetime = atoi(value);
                   if (lifetime < 0) lifetime = 0;
                   if (lifetime > MAXLIFETIME) lifetime = MAXLIFETIME;
                   break;
          case 9:  if (!isnumber(value) || atoi(value) != 0) {
                     useChannel = true;
                     cid = tChannelID::FromString(value);
                     store_cid = cid.Valid();
                     channel = store_cid ? Channels.GetByChannelID(cid, true) : Channels.GetByNumber(atoi(value));
                     if (!channel) {
                       esyslog("ERROR: channel %s not defined", value);
                       free(line);
                       return false;
                     }
                   }
                   break;
          case 10: strcpy(folder, value);
                   break;
          case 11: except_repeat = atoi(value) != 0 ? true : false;
                   if (!use_except_repeat.u && use_except_repeat.i) {
                     if (verbose.u)
                       isyslog("%s: auto enable except repeat feature is disabled --> removed", plugin_name);
                   } else {
                     if (verbose.u && !use_except_repeat.u)
                       isyslog("%s: auto enable except repeat feature", plugin_name);
                     use_except_repeat.u = true;
                   }
                   break;
          case 12: weekdays = atoi(value);
                   if (!use_weekdays.u && use_weekdays.i) {
                     if (verbose.u)
                       isyslog("%s: auto enable weekdays feature is disabled --> removed", plugin_name);
                   } else {
                     if (verbose.u && !use_weekdays.u)
                       isyslog("%s: auto enable weekdays feature", plugin_name);
                     use_weekdays.u = true;
                   }
                   break;
        } //switch
      }
      parameter++;
    }
    if (*pos) pos++;
  } //while

  strreplace(search, '|', ':');
  strreplace(folder, '|', ':');

  free(line);
  return (parameter >= 10) ? true : false;
}

bool cAutoTimer::Save(FILE *f)
{
  return fputs(ToText(), f) > 0;
}

void cAutoTimer::OnOff(void)
{
  active = !active;
}

// -- cAutoTimers ----------------------------------------------------------------

cAutoTimers AutoTimers;

cAutoTimers::cAutoTimers(void)
{
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimers::cAutoTimers", plugin_name);
#endif
  user[0] = 0;
  pass[0] = 0;
  addr[0] = 0;
  port = 0;
}

pid_t cAutoTimers::GetUpdatePID(void)
{
  pid_t pid = 0;
  if (updatemethod.u == 0x3 || updatemethod.u == 0x4) {
    #define PIDCMD_vdradmin "pidof -x vdradmind.pl"
    #define PIDCMD_xxv "pidof -x xxvd"
    FILE *p;
    if (updatemethod.u == 0x3)
      p = popen(PIDCMD_vdradmin, "r");
    else
      p = popen(PIDCMD_xxv, "r");
    if (p) {
      cReadLine ReadLine;
      char *s = ReadLine.Read(p);
      if (s && isnumber(s))
        pid = atoi(s);
      else
        pid = -1;
      pclose(p);
    } else {
      esyslog("%s: Cannot run cmd (%s) to acquire the pid for vdradmind/xxv", plugin_name, updatemethod.u == 0x3 ? PIDCMD_vdradmin : PIDCMD_xxv);
      pid = -2;
    }
  }
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimers::GetUpdatePID pid=%d", plugin_name, pid);
#endif
  return pid;
  #undef PIDCMD
}

bool cAutoTimers::ParseConfig(void)
{
  ExpandEnvironment(&vdradminconfig);
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimers::ParseConfig file=%s", plugin_name, vdradminconfig.e);
#endif
  bool result = false;
  user[0] = 0;
  pass[0] = 0;
  addr[0] = 0;
  port = 0;

  char *s = NULL;
  int selectValue = 0;
  if (!access(vdradminconfig.e, F_OK)) {
    FILE *f = fopen(vdradminconfig.e, "r");
    if (f) {
      cReadLine ReadLine;
      while ((s = compactspace(ReadLine.Read(f))) != NULL) {
        if (!isempty(s)) {
          selectValue = 0;
          if (strncmp(s, "USERNAME_GUEST", 14) && !strncmp(s, "USERNAME", 8)) {
            s += 9;
            selectValue = 1;
          }
          if (strncmp(s, "PASSWORD_GUEST", 14) && !strncmp(s, "PASSWORD", 8)) {
            s += 9;
            selectValue = 2;
          }
          if (!strncmp(s, "SERVERHOST", 10)) {
            s += 11;
            selectValue = 3;
          }
          if (!strncmp(s, "SERVERPORT", 10)) {
            s += 11;
            selectValue = 4;
          }
          if (selectValue) {
            while (*s && (isspace(*s) || *s == '='))
              s++;
            switch (selectValue) {
              case 1: strn0cpy(user, s, sizeof(user));
                      break;
              case 2: strn0cpy(pass, s, sizeof(pass));
                      break;
              case 3: strn0cpy(addr, s, sizeof(addr));
                      if (!strcmp(addr, "0.0.0.0"))
                        strcpy(addr, "localhost");
                      break;
              case 4: if (isnumber(s))
	                port = atoi(s);
                      break;
            }
          }
        }
      }
      fclose(f);
      if (!strlen(addr))
        strcpy(addr, "localhost");
      result = strlen(user) && strlen(pass) && port != 0;
    } else
      LOG_ERROR_STR(vdradminconfig.e);
  }
  return result;
}

bool cAutoTimers::TestUpdate(bool ErrorToScreen)
{
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimers::TestUpdate updatemethod=%d", plugin_name, updatemethod.u);
#endif
  switch (updatemethod.u) {
    case 0x0: break;
    case 0x1: ExpandEnvironment(&vdradminconfig);
              if (access(vdradminconfig.e, F_OK) || access(vdradminconfig.e, R_OK)) {
                if (ErrorToScreen)
                  ERROR(tr("Error$no access to config file"));
                else
                  esyslog("%s: no access to config file", plugin_name);
                return false;
              }
              if (!ParseConfig()) {
                if (ErrorToScreen)
                  ERROR(tr("Error$Not all requied parameters in config file found"));
                else
                  esyslog("%s: No or not all requied parameters in config file found", plugin_name);
                return false;
              }
              break;
    case 0x2: ExpandEnvironment(&vdradminupdate);
              if (access(vdradminupdate.e, F_OK)) {
                if (ErrorToScreen)
                  ERROR(tr("Error$no access to update script"));
                else
                  esyslog("%s: no access to update script", plugin_name);
                return false;
              }
              if (access(vdradminupdate.e, X_OK)) {
                if (ErrorToScreen)
                  ERROR(tr("Error$update script is not executable"));
                else
                  esyslog("%s: update script is not executable", plugin_name);
                return false;
              }
              break;
    case 0x3: if (GetUpdatePID() <= 0) {
                if (ErrorToScreen)
                  ERROR(tr("Error$vdradmind is not running on this machine"));
                else
                  esyslog("%s: vdradmind is not running on this machine", plugin_name);
                return false;
              }
              break;
    case 0x4: if (GetUpdatePID() <= 0) {
                if (ErrorToScreen)
                  ERROR(tr("Error$xxv is not running on this machine"));
                else
                  esyslog("%s: xxv is not running on this machine", plugin_name);
                return false;
              }
              break;
  }
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimers::TestUpdate return=true", plugin_name);
#endif
  return true;
}

bool cAutoTimers::RunUpdate(void)
{
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimers::RunUpdate updatemethod=%d", plugin_name, updatemethod.u);
#endif
  char * cmd = NULL;
  if (oUpdateThread && oUpdateThread->Active()) {
    ERROR(tr("Error$search update is runing"));
    return false;
  } else {
    if (!TestUpdate(false))
      return false;
    if (updatemethod.u != 0x0) {
      int OSDMessageTime = Setup.OSDMessageTime;
      Setup.OSDMessageTime = 3;
      INFO(tr("Edit$Start search update now ..."));
      Setup.OSDMessageTime = OSDMessageTime;
    }
    switch (updatemethod.u) {
      case 0x0: break;
      case 0x1: // referer is requiered! The aktion 'force_update' returned a 302 (redirection) to referer
                asprintf(&cmd, "wget --%sverbose --timeout=30 --output-document=/dev/nul --http-user=%s --http-passwd=%s --referer=http://%s:%d/ http://%s:%d/vdradmin.pl?aktion=force_update", verbose.u ? "" : "non-", user, pass, addr, port, addr, port);
                break;
      case 0x2: ExpandEnvironment(&vdradminupdate);
                asprintf(&cmd, "%s restart", vdradminupdate.e);
                break;
      case 0x3:
      case 0x4: kill(GetUpdatePID(), SIGHUP);
                break;
    }
    if (cmd) {
      if (oUpdateThread)
        DELETENULL(oUpdateThread);
      oUpdateThread = new cUpdateThread(cmd);
      oUpdateThread->Start();
    }
  }
  return true;
}

bool cAutoTimers::Use_CID(void)
{
#ifdef ATE_Debug2
  dsyslog("%s: cAutoTimers::Use_CID", plugin_name);
#endif

  bool use_channel = false;
  bool use_cid = false;
  int i = Count();
  while (i--)
    if (Get(i)->UseChannel())
      use_cid |= Get(i)->Store_CID();
  return use_channel && use_cid || !use_channel;
}
