/*
 * exec: A plugin for the Video Disk Recorder
 * Copyright (C) 2007  Winfried Koehler
 * Licence GPL
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */

#include <vdr/interface.h>
#include "exec.h"
#if VDRVERSNUM < 10507
#include "i18n.h"
#include "compat.h"
#endif //VDRVERSNUM < 10507
#include "menu.h"
#include <stdarg.h>


/****************************************************************************** 
 * some helper funcs here
 *****************************************************************************/

void log(int loglevel, const char * s, ...) {
  char t[512];
  char timeStr[16]; // (8+1+6) + 1 = 16
  va_list ap;
  time_t now;
  bool toLog = (loglevel >= EXEC_LOG);

  if (toLog)
    loglevel -= EXEC_LOG;
  if ((ExecSetup.logLevel >= loglevel) || toLog){
    time(&now);
    strftime(timeStr, sizeof(timeStr), "%d.%m. %H:%M:%S", localtime(&now));
    va_start(ap, s);
    vsnprintf(t, sizeof t, s, ap);
    if (strlen(t) > 511) {
      char shortStr[256];
      log(loglevel, "WARNING!!! Shortening log string!");
      strncpy (shortStr, t, 255);
      shortStr[255]='\0';
      log(loglevel, shortStr);
      return;
      }
    if (toLog) {
      char *msg;
      asprintf(&msg, "%s %s", timeStr, t);
      ExecLog.AddToLog(msg);
      free(msg);
      }
    switch (ExecSetup.logFile) {
      case 0:      if (ExecSetup.logLevel >= loglevel) {
                     printf("\r%s %s\033[K\r\n", timeStr, t);
                     fflush(stdout);
                     }
                   break;
      case 1:      if (ExecSetup.logLevel >= loglevel)
                     syslog(LOG_DEBUG, "%s", t);
                   break;
      case 2:      break; /*reserved*/
      }
    va_end(ap);
    }
};


/****************************************************************************** 
 * cExecSetup
 *****************************************************************************/


cExecSetup::cExecSetup() {
  logLevel=3;
  logFile=0;
  logLength=100;
  showMainMenu=1;
  enableSVDRP=1;
};


/****************************************************************************** 
 * cMenuExec
 * exec's Plugin setup page
 *****************************************************************************/


/* *** cMenuExecAbout ********************************************************
   Execs About Page                                                          */

class cMenuExecAbout : public cMenuSetupPage {
 private:
 protected:
   void Store(void) {};
 public:
   cMenuExecAbout(void);
   ~cMenuExecAbout(void);
   void AddText(const char * s, const char * t);
};

void cMenuExecAbout::AddText(const char * s, const char * t) {
  char * buf=NULL;
  asprintf(&buf, "%s %s", s, t);
  cOsdItem * osditem = new cOsdItem(buf);
  Add(osditem);
  free(buf);
}

cMenuExecAbout::cMenuExecAbout() {
  char * buf=NULL;

  asprintf(&buf, "%s-%s", "exec", ExecVersion());
  AddText(tr("Plugin:")     , buf);
  free(buf);
  AddText(tr("Author:")     , "Winfried Koehler");
  AddText(tr("Homepage:")   , "http://free.pages.at/wirbel4vdr/exec/index2.html");
  AddText(tr("License:")    , "GPLv2");
  AddText(tr("Description:"), tr("integrates shell command timers into VDR"));
  SetSection(tr("About Exec Plugin.."));
}

cMenuExecAbout::~cMenuExecAbout() {
}


/* *** cMenuExecLog **********************************************************
   Execs Log Page                                                           */

class cMenuExecLog : public cMenuSetupPage {
 private:
 protected:
   void Store(void) {};
 public:
   cMenuExecLog(void);
   ~cMenuExecLog(void);
   void AddText(const char * s);
};

void cMenuExecLog::AddText(const char * s) {
  char * buf=NULL;
  asprintf(&buf, "%s", s);
  cOsdItem * osditem = new cOsdItem(buf);
  Add(osditem);
  free(buf);
}

cMenuExecLog::cMenuExecLog() {
  char * buf=NULL;
  for (cExecLogLine *line = ExecLog.First(); line; line = ExecLog.Next(line)) {
    asprintf(&buf, line->Text());
    AddText(buf);
    free(buf);
    }
  SetSection(tr("Exec Log File"));
}

cMenuExecLog::~cMenuExecLog() {
}

/* *** cMenuExec **************************************************************
   Execs Setup Page                                                          */

cMenuExec::cMenuExec() {
  static const char *logfiles[3];

  data = ExecSetup;
  logfiles[0]="stdout";
  logfiles[1]="syslog";
  logfiles[2]="/var/log only";

  /*      red   green yellow     blue */
  SetHelp(NULL, NULL, tr("Log"), tr("About"));
  SetSection(tr("Exec Settings"));
  Add(new cMenuEditStraItem (tr("Log File") ,      &data.logFile,  3, logfiles));
  Add(new cMenuEditIntItem  (tr("Log Level"),      &data.logLevel, 0, 5));
  Add(new cMenuEditIntItem  (tr("Log Length"),     &data.logLength, 10, 1000));
  Add(new cMenuEditBoolItem (tr("show Main Menu"), &data.showMainMenu));
  Add(new cMenuEditBoolItem (tr("SVDRP support"),  &data.enableSVDRP));
}

cMenuExec::~cMenuExec() {
}

void cMenuExec::Store() {
  /* if a val is added, it needs to be added also to
   * cPluginExec::SetupParse(..) (file exec.c) 
   */
  ExecSetup = data;
  SetupStore("loglevel",        ExecSetup.logLevel );
  SetupStore("logFile",         ExecSetup.logFile  );
  SetupStore("logLength",       ExecSetup.logLength);
  SetupStore("showMainMenu",    ExecSetup.showMainMenu);
  SetupStore("enableSVDRP",     ExecSetup.enableSVDRP);
}

eOSState cMenuExec::ProcessKey(eKeys Key) {
  eOSState state = cOsdMenu::ProcessKey(Key);
  if (state == osUnknown) {
     switch (Key) {
       case kYellow: return AddSubMenu(new cMenuExecLog  ()); break; 
       case kBlue:   return AddSubMenu(new cMenuExecAbout()); break;
       case kOk:     Store(); return osBack;
       default: break; 
       }
     }
  return state;
 }


/****************************************************************************** 
 * cMenuExecTimers
 * exec's Timer page
 *****************************************************************************/


/* *** cMenuExecTimerItem ****************************************************
   a single item in main menu -> Exec timers                                */

class cMenuExecTimerItem : public cOsdItem {
private:
  cExecTimer *et;
public:
  cMenuExecTimerItem(cExecTimer *ExecTimer);
  virtual int Compare(const cListObject &ListObject) const;
  virtual void Set(void);
  cExecTimer *ExecTimer(void) { return et; }
  };

cMenuExecTimerItem::cMenuExecTimerItem(cExecTimer *ExecTimer) {
  et = ExecTimer;
  Set();
  }

int cMenuExecTimerItem::Compare(const cListObject &ListObject) const {
  return et->Compare(*((cMenuExecTimerItem *)&ListObject)->et);
  }

void cMenuExecTimerItem::Set(void) {
  char *buffer = NULL;
  asprintf(&buffer, "%c\t%s\t%s\t%s\t%s",
    ! et->isActive()?' ':et->isRunning()?'#':'>',
    *et->PrintWeekdays(),
    *et->PrintTime(),
    (et->Flags() & ET_SHELLCOMMAND)?"shell":
    (et->Flags() & ET_SHUTDOWN)?"shutdown":
    (et->Flags() & ET_MESSAGE)?"message":
    (et->Flags() & ET_WARNING)?"warning":"unknown",
    *et->Command());
  SetText(buffer, false);
  }

/* *** cMenuExecEditTimer *************************************************
   the menu opened for editing exec timers                               */

const char *ShellChars = trNOOP(" abcdefghijklmnopqrstuvwxyz0123456789-.#~,;/\\_@<>|%$!&{}'\"");

class cMenuExecEditTimer : public cOsdMenu {
private:
  cExecTimer *et;
  bool newTimer;
  int weekdays;
  time_t starttime;
  int Time;
  int action;
  int active;
  int wakeup;
  int detach;
  char cmd[256];
public:
  cMenuExecEditTimer(cExecTimer *ET, bool New = false);
  virtual ~cMenuExecEditTimer();
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuExecEditTimer::cMenuExecEditTimer(cExecTimer *ET, bool New)
:cOsdMenu(tr("Edit exec timer"), 12) {

  static const char * actions[]= {
    trNOOP("default"),
    trNOOP("shell command"),
    trNOOP("shutdown"),
    trNOOP("message"),
    trNOOP("warning")
    };
  sprintf(cmd,"(NULL)");
  et = ET;
  newTimer = New;

  if (et) {
    if (New) {
      weekdays=0; /* any day */
      starttime = Now();
      Time = 2000;
      action=1;
      wakeup=0;
      detach=0;
      sprintf(cmd,NULL);
      }
    else {     /* editing existing timer */
      if (et->WeekDays() & WD_MASK) {
        starttime = 0;
        weekdays  = (int) et->WeekDays();
        }
      else {
        starttime  = et->StartTime();
        weekdays=0;
        }
      Time  = et->GetTime();
      active=et->isActive();
      action=(et->Flags() & ET_SHELLCOMMAND)?1:
             (et->Flags() & ET_SHUTDOWN)?2:
             (et->Flags() & ET_MESSAGE)?3:
             (et->Flags() & ET_WARNING)?4:0;
      wakeup=(et->Flags() & ET_WAKEUP);
      detach=(et->Flags() & ET_BACKGROUND);
      sprintf(cmd,et->Command()); /* et->Command() is max. 255 chars */
      }
    Add(new cMenuEditBoolItem(trVDR("Active"),       &active));
    Add(new cMenuEditDateItem(trVDR("Day"),          &starttime, &weekdays));
    Add(new cMenuEditTimeItem(tr("Time"),            &Time));
    Add(new cMenuEditStraItem(tr("Action"),          &action, 5, actions));
    Add(new cMenuEditBoolItem(tr("Wake up"),         &wakeup));
    Add(new cMenuEditBoolItem(tr("Detached"),        &detach));
    Add(new cMenuEditStrItem( tr("Exec string"),     cmd, sizeof(cmd), ShellChars));
    }
  ExecTimers.IncBeingEdited();
  }

cMenuExecEditTimer::~cMenuExecEditTimer() {
  if (et && newTimer)
    delete et; // new exec timer wasn't confirmed
  ExecTimers.DecBeingEdited();
  }

eOSState cMenuExecEditTimer::ProcessKey(eKeys Key) {
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown) {
    switch (Key) {
      case kOk: {
        tm     local;
        long long fl;
        local=*(localtime(&starttime)); // dereference and assign
        if (action == 2) //shutdown
          sprintf(cmd, "SHUTDOWN");
        if (strlen(cmd) == 0) {
          log(0, "invalid parameter COMMAND");
          Skins.Message(mtError, tr("*** Invalid Command ***"));
          break;
          }
        local.tm_hour = Time / 100;
        local.tm_min  = Time % 100;
        local.tm_sec  = 0;
        starttime=mktime(&local);
        et->SetTime(starttime,0);
        fl = action == 1? ET_SHELLCOMMAND :
             action == 2? ET_SHUTDOWN :
             action == 3? ET_MESSAGE  :
             action == 4? ET_WARNING  : ET_SHELLCOMMAND;
        if (wakeup > 0) fl += ET_WAKEUP;
        if (detach > 0) fl += ET_BACKGROUND;
        et->SetFlags(fl);
        et->SetActive(active);
	et->SetWeekDays(weekdays>0 ? (long long) weekdays : WD_IGNORED);
        et->SetCommand(cmd);
        if (newTimer)
          ExecTimers.Add(et);
        ExecTimers.SetModified();
        log(EXEC_LOG + 3,"%s: %s ", newTimer ? "new" : "modified", *et->ToSVDRDescr());
        newTimer = false; /* do not delete this timer on ~cMenuExecEditTimer */        
        return osBack;
        }
      case kRed:
      case kGreen:
      case kYellow:
      case kBlue:   return osContinue;
      default:      break;
        }
     }
  return state;
  }


/* *** cMenuExecTimers ****************************************************
 * -> Menu for Viewing/Editing exec timers 
 */

cMenuExecTimers::cMenuExecTimers(void)
:cOsdMenu(tr("Exec Timers"), 2, 10, 5, 9, 30) { // column widths 1..4
  helpKeys = -1;
  for (cExecTimer *et = ExecTimers.First(); et; et = ExecTimers.Next(et)) {
    Add(new cMenuExecTimerItem(et));
    }
  Sort();
  SetCurrent(First());
  SetHelpKeys();
  ExecTimers.IncBeingEdited();
  }

cMenuExecTimers::~cMenuExecTimers() {
  ExecTimers.DecBeingEdited();
  }

cExecTimer *cMenuExecTimers::CurrentExecTimer(void) {
  cMenuExecTimerItem *item = (cMenuExecTimerItem *)Get(Current());
  return item ? item->ExecTimer() : NULL;
  }

void cMenuExecTimers::SetHelpKeys(void) {
// may be there is a need to be more sophisticated later here..
  if (helpKeys < 0) {
    SetHelp(NULL, trVDR("Button$New"), trVDR("Button$Delete"), tr("Button$Run"));
    helpKeys = 1;
    }
  }

eOSState cMenuExecTimers::Edit(void) {
  if (HasSubMenu() || Count() == 0)
     return osContinue;
  log(EXEC_LOG + 3, "edit %s", *CurrentExecTimer()->ToSVDRDescr());
  return AddSubMenu(new cMenuExecEditTimer(CurrentExecTimer()));
  }

eOSState cMenuExecTimers::New(void) {
  if (HasSubMenu())
     return osContinue;
  return AddSubMenu(new cMenuExecEditTimer(new cExecTimer, true));
  }

eOSState cMenuExecTimers::Delete(void) {
  cExecTimer *et = CurrentExecTimer();
  if (et) {
    if (Interface->Confirm(tr("Delete exec timer?"))) {
      log(EXEC_LOG + 3, "deleted %s", *et->ToSVDRDescr());
      ExecTimers.Del(et);
      cOsdMenu::Del(Current());
      ExecTimers.SetModified();
      Display();
      }
    }
  return osContinue;
  }

eOSState cMenuExecTimers::Run(void) {
  cExecTimer *et = CurrentExecTimer();
  if (et) {
    if (Interface->Confirm(tr("Run exec timer?"))) {
      log(EXEC_LOG + 3, "run: %s",  *et->Command());
      et->Execute();
      }
    }
  return osContinue;
  }

eOSState cMenuExecTimers::ProcessKey(eKeys Key) {
  int Index = HasSubMenu() ? Count() : -1;
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown) {
     switch (Key) {
       case kOk:     return Edit();
       case kRed:    break; // reserved for later.
       case kGreen:  return New();
       case kYellow: state = Delete(); break;
       case kBlue:   state = Run();    break;
       default: break;
       }
     }
  if (Index >= 0 && !HasSubMenu() && ExecTimers.Get(Index)) {
     // a newly created timer was confirmed with Ok
     Add(new cMenuExecTimerItem(ExecTimers.Get(Index)), true);
     Display();
     }
  if (Key != kNone)
     SetHelpKeys();
  return state;
 }




