/*
 * viewer.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */

#include <vdr/plugin.h>
#include <vdr/remote.h>
#include "viewer.h"
#include "i18n.h"
#include "menu_search.h"
#include "menu_open.h"
#include "menu_syslog.h"
#include "tools.h"
#include "menu_vwfilebrowser.h"

static const char *VERSION        = "0.0.2-pre2";
static const char *DESCRIPTION    = tr("a simple file viewer (for syslog)");
static const char *MAINMENUENTRY  = tr("Viewer");

// Global variables that control the overall behaviour:

char *gFileName;
char *FileNames[MAX_VIEWER_FILES];
char gSyslogConf[MaxFileName+1];
int LinesToRead = 30;
bool vdrFilter = true;
bool bOrder = true;
bool bDirectCall = true;

// --- cMessageContent -------------------------------------------------------
// Apr  9 16:04:09 stonevdr dhcpd: DHCPACK on 192.168.77.117 to 00:02:2d:28:01:31 (atpc494c) via eth2
// Apr  9 17:18:57 stonevdr vdr[9933]: tail
class cMessageContent {
public:
   char msgDate[32];
   char msgComputer[32];
   char msgProcess[32];
   char msgText[200];
public:
   cMessageContent(void) {};
   bool ParseMessageLine(const char *pLine);
};

bool cMessageContent::ParseMessageLine(const char *pLine)
{int day, hh, mm, ss;
 char *month;
 char temp[200];
 int ret=0;

   if(pLine==NULL)
      return false;

   month=(char *)malloc(strlen(pLine)+1);

   ret=sscanf(pLine, "%s %d %d:%d:%d %s %s %s", month, &day, &hh, &mm, &ss, msgComputer, msgProcess, msgText);
   if(ret<8) {
      esyslog("error parsing message line, found only %d items in line!", ret);
      free(month);
      return false;
   }
   sprintf(msgDate, "%s %d %02d:%02d:%02d", month, day, hh, mm, ss);
   sprintf(temp, "%s %s %s", msgDate, msgComputer, msgProcess);
   strcpy(msgText, pLine + strlen(temp)+1);

   int length=strlen(msgProcess)-1;
   if((length>0)&&(msgProcess[length]==':')) 
      msgProcess[length]='\0';

   free(month);
   return true;
}

class cPluginViewer : public cPlugin {
private:
  // Add any member variables or functions you may need here.
public:
  cPluginViewer(void);
  virtual ~cPluginViewer();
  virtual const char *Version(void) { return VERSION; }
  virtual const char *Description(void) { return DESCRIPTION; }
  virtual const char *CommandLineHelp(void);
  virtual bool ProcessArgs(int argc, char *argv[]);
  virtual bool Initialize(void);
  virtual bool Start(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void) { return MAINMENUENTRY; }
  virtual cOsdObject *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
  virtual const char **SVDRPHelpPages(void);
  virtual cString SVDRPCommand(const char *Cmd, const char *Option, int &ReplyCode);
  };

// --- cMenuSetupViewer -------------------------------------------------------
class cMenuSetupViewer : public cMenuSetupPage {
private:
  char newFileNames[MAX_VIEWER_FILES][MaxFileName+1];
  char *SetupFilename;
  int  newLinesToRead;
  int  newvdrFilter;
  int  newiOrder;
  int  lastCurrent;
  bool bSubMenuOk;
protected:
  virtual void Store(void);
  void Update(void);
  eOSState HandleFilebrowser(void);
public:
  cMenuSetupViewer(void);
  virtual ~cMenuSetupViewer(void);
  virtual eOSState ProcessKey(eKeys Key);
  };

cMenuSetupViewer::cMenuSetupViewer(void)
{char txt[256];

  for(int i=0;i<MAX_VIEWER_FILES;i++)
     strn0cpy(newFileNames[i], FileNames[i], MaxFileName);
  newLinesToRead = LinesToRead;
  newvdrFilter = vdrFilter;
  newiOrder = bOrder;

  Add(new cMenuEditStrItem( tr("Name of syslog"),     newFileNames[0], MaxFileName, tr(FileNameChars)));
  Add(new cMenuEditIntItem( tr("Lines to read"),      &newLinesToRead));
  Add(new cMenuEditBoolItem(tr("Filter by VDR"),      &newvdrFilter));
  Add(new cMenuEditBoolItem(tr("Viewing order"),      &newiOrder, tr("older first"), tr("newer first")));

  for(int i=1;i<MAX_VIEWER_FILES;i++){
    sprintf(txt, tr("FileName%d"), i);
    Add(new cMenuEditStrItem(txt,                      newFileNames[i], MaxFileName, tr(FileNameChars)));
  }

  SetupFilename=NULL;
  lastCurrent=0;
  SetCurrent(0);
  bSubMenuOk=false;
  Update();
}

cMenuSetupViewer::~cMenuSetupViewer()
{
  if(SetupFilename)
     free(SetupFilename);
}

void cMenuSetupViewer::Update(void)
{
  int index=Current();

  if(index==0)
     lastCurrent=0;
  else
     if(index>=4 && index<=12)
        lastCurrent=Current()-3;
     else
        lastCurrent=-1;

  if(lastCurrent>=0)
     SetHelp(tr("Open"), tr("Syslog-config"), tr("Delete"), NULL);
  else
     SetHelp(NULL, NULL, NULL, NULL);
}

eOSState cMenuSetupViewer::HandleFilebrowser()
{
  if(SetupFilename)
     free(SetupFilename);
  SetupFilename=strdup(newFileNames[lastCurrent]);
  bSubMenuOk=false;
  return AddSubMenu(new cMenuViewerFilebrowser(newFileNames[lastCurrent], &bSubMenuOk));
}

eOSState cMenuSetupViewer::ProcessKey(eKeys Key)
{
  bool bHadSubMenu = HasSubMenu();
  eOSState state = cMenuSetupPage::ProcessKey(Key);

  if(!HasSubMenu())
     Update();

  if (state == osUnknown) {
     switch (Key) {
        case kRed:   if((lastCurrent>=0) && strlen(gSyslogConf))
                        return HandleFilebrowser();
                     break;
        case kGreen: if((lastCurrent>=0) && strlen(gSyslogConf))
                        return AddSubMenu(new cMenuViewerSyslog(tr("Select a syslog entry ..."), newFileNames[lastCurrent], gSyslogConf, &bSubMenuOk));
                     break;
        case kYellow:if(lastCurrent>=0) {
                        strcpy(newFileNames[lastCurrent], "");
                        Update();
                     }
        default:     break;
     }
  }

  if (bHadSubMenu && !HasSubMenu() && (lastCurrent>=0)) // return from submenu
  {
     isyslog("cMenuSetupViewer::ProcessKey, %s", newFileNames[lastCurrent]);
//     strn0cpy(newFileNames[lastCurrent], SetupFilename, MaxFileName);
     Update();
  }
  return state;
}

void cMenuSetupViewer::Store(void)
{char txt[256];

  SetupStore("LinesToRead", LinesToRead = newLinesToRead);
  SetupStore("Filter", vdrFilter = (bool)newvdrFilter);
  SetupStore("Order", bOrder = (bool)newiOrder);

  for(int i=0;i<MAX_VIEWER_FILES;i++){
     sprintf(txt, "FileName%d", i);
     asprintf(&FileNames[i], "%s", newFileNames[i]);
     SetupStore(txt, FileNames[i]);
  }
}

// --- cViewerByLine -------------------------------------------------------
class cViewerByLine : public cOsdMenu 
{
  bool *noUpdate;
  const char *m_szText;
public:
  cViewerByLine(const char *Title, const char *Text, int cur, int count, bool* noupdate, eDvbFont Font = fontOsd);
  virtual eOSState ProcessKey(eKeys Key);
  virtual void Display(void);
};

//--------------------------------------------------------------------------------------------------
cViewerByLine::cViewerByLine(const char *Title, const char *Text, int cur, int count, bool *noupdate, eDvbFont Font)
: cOsdMenu(Title)
, m_szText(Text)
{
  noUpdate = noupdate;
  SetHelp(NULL, (cur>0)? "<<":NULL, (cur<(count-1))? ">>":NULL, NULL);
}

void cViewerByLine::Display(void)
{
  cOsdMenu::Display();
  DisplayMenu()->SetText(m_szText, true);
}


eOSState cViewerByLine::ProcessKey(eKeys nKey)
{
 switch (nKey) {
    case kUp|k_Repeat:
    case kUp:
    case kDown|k_Repeat:
    case kDown:
    case kLeft|k_Repeat:
    case kLeft:
    case kRight|k_Repeat:
    case kRight:
                  DisplayMenu()->Scroll(NORMALKEY(nKey) == kUp || NORMALKEY(nKey) == kLeft, NORMALKEY(nKey) == kLeft || NORMALKEY(nKey) == kRight);
                  return osContinue;
    default: break;
    }

  eOSState nState = cOsdMenu::ProcessKey(nKey);

  if (nState == osUnknown) {
     switch (nKey) {
       case kOk:     return osBack;
       case kYellow: *noUpdate=true;
                     cRemote::Put(kDown);
                     cRemote::Put(kOk);
                     return osBack;
       case kGreen:  *noUpdate=true;
                     cRemote::Put(kUp);
                     cRemote::Put(kOk);
                     return osBack;
       default:      nState = osContinue;
       }
     }
  return nState;
}

// --- cViewer -------------------------------------------------------
class cViewer : public cOsdMenu {
private:
  char Filename[MaxFileName+1];
  char SearchString[MaxFileName+1];
  bool vdrOnly;
  bool noUpdate;
  eOSState Update(void);
  bool GetFileContent(void);
  bool GetFileContentFilter(const char *cmd);
  eOSState ShowLineDetails(void);
  eOSState Search(void);
  eOSState Open(void);
protected:
public:
  cViewer(void);
  ~cViewer(void);
  virtual eOSState ProcessKey(eKeys Key);
  };

cViewer::cViewer(void):cOsdMenu(tr("simple syslog-viewer"))
{
  noUpdate = false;
  if(bDirectCall) {
     strn0cpy(Filename, FileNames[0], MaxFileName);
     vdrFilter = true;
  }
  else {
     vdrFilter = false;
     strn0cpy(Filename, gFileName, MaxFileName);
  }
  strn0cpy(SearchString, "vdr: \\[", MaxFileName);
  if(!strlen(Filename))
     Skins.Message(mtInfo, tr("No File specified! Please select one in the setup."));
  Update();
}

cViewer::~cViewer(void)
{
}

eOSState cViewer::Update(void)
{
  Clear();
  GetFileContent();
  Display();
  SetHelp(tr("Open"), tr("Refresh"), vdrFilter? tr("All"):tr("VDR"), tr("Search"));
  return osContinue;
}

bool cViewer::GetFileContent(void)
{
  char *cmd = NULL;
  if(vdrFilter)
    asprintf(&cmd, "grep '%s' '%s'| %s --lines=%d", SearchString, Filename, bOrder? "tail":"head", LinesToRead);
  else
    asprintf(&cmd, "%s --lines=%d '%s'", bOrder? "tail":"head", LinesToRead, Filename);

  GetFileContentFilter(cmd);
  free(cmd);
  return true;
}


bool cViewer::GetFileContentFilter(const char *cmd)
{
   bool bRet=false;
   cOsdItem *pLastItem=NULL;
   cOsdItem *pItem=NULL;

   FILE *p = popen(cmd, "r");
   if (p) {
      char *s;
      cReadLine ReadLine;
      while ((s = ReadLine.Read(p)) != NULL) {
         pItem=new cOsdItem(s);
         if(bOrder) {
            Ins(pItem, true, pLastItem);
            pLastItem=pItem;
         }
         else
	    Add(pItem);
      }
      bRet=true;
   }
   pclose(p);
   SetCurrent(pLastItem);
   return bRet;
}

eOSState cViewer::ShowLineDetails(void)
{char *buf;

  cMessageContent cMsg;
  if(cMsg.ParseMessageLine(Get(Current())->Text())) {
     asprintf(&buf, "%s:\t%s\n%s:\t%s\n%s:\t%s\n\n%s:\n%s",
                    tr("Time"), cMsg.msgDate,
                    tr("Computer"), cMsg.msgComputer,
                    tr("Process"), cMsg.msgProcess,
                    tr("Text"), cMsg.msgText);
  }
  else
     asprintf(&buf, "%s", Get(Current())->Text());
  return AddSubMenu(new cViewerByLine(tr("Show Details ..."), buf, Current(), Count(), &noUpdate));
}

eOSState cViewer::Search(void)
{
  return AddSubMenu(new cMenuSearch(tr("Enter search string ..."), SearchString));
}

eOSState cViewer::Open(void)
{
  return AddSubMenu(new cMenuViewerOpen(tr("Select a file to view ..."), Filename, FileNames, gSyslogConf));
}

eOSState cViewer::ProcessKey(eKeys Key)
{
  bool bHadSubMenu = HasSubMenu();
  eOSState state = cOsdMenu::ProcessKey(Key);

  if (state == osUnknown) {
     if (HasSubMenu())
         return osContinue;

     switch (Key) {
     case kRed:    state=Open();
                   break;
     case kYellow: vdrFilter=!vdrFilter;
     case kGreen:  state=Update();
                   break;
     case kBlue:   state=Search();
                   break;
     case kOk:     if (Count()>0)
                      state=ShowLineDetails();
                   break;
     case k0:      bOrder=!bOrder;
                   state=Update();
                   break;
     default:      break;
     }
  }

  if (!noUpdate && bHadSubMenu && !HasSubMenu()) { // return from submenu
     vdrFilter = false;
     if(!strcmp(Filename, FileNames[0])) {
        bOrder = true;
        vdrFilter = true;
     }
     else {
        bOrder = false;
        vdrFilter = false;
     }
     noUpdate = false;
     Update();
     Display();
  }
  return state;
}

// --- cPluginViewer -------------------------------------------------------
cPluginViewer::cPluginViewer(void)
{
  // 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!

  for(int i=0;i<MAX_VIEWER_FILES;i++){
     FileNames[i]=(char *)malloc(MaxFileName+1);
     strcpy(FileNames[i], "");
  }

  LinesToRead = 30;
  vdrFilter = true;
  bOrder = true;
  strcpy(gSyslogConf, "");
  if (access("/etc/syslog-ng/syslog-ng.conf", F_OK) == 0) { // the directory doesn't exist until now
     strn0cpy(gSyslogConf, "/etc/syslog-ng/syslog-ng.conf", MaxFileName);
  }
  else
     if (access("/etc/syslog.conf", F_OK) == 0) { // the directory doesn't exist until now
        strn0cpy(gSyslogConf, "/etc/syslog.conf", MaxFileName);
     }
  isyslog("cPluginViewer using syslog: %s", gSyslogConf);
}

cPluginViewer::~cPluginViewer()
{
  // Clean up after yourself!
  bDirectCall=true;
  if(gFileName)
     free(gFileName);
  gFileName=NULL;

  for(int i=0;i<MAX_VIEWER_FILES;i++){
     free(FileNames[i]);
  }
}

const char *cPluginViewer::CommandLineHelp(void)
{
  // Return a string that describes all known command line options.
  return NULL;
}

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

bool cPluginViewer::Initialize(void)
{
  // Initialize any background activities the plugin shall perform.
  return true;
}

bool cPluginViewer::Start(void)
{
  // Start any background activities the plugin shall perform.
  RegisterI18n(Phrases);
  return true;
}

void cPluginViewer::Housekeeping(void)
{
  // Perform any cleanup or other regular tasks.
}

cOsdObject *cPluginViewer::MainMenuAction(void)
{
  return new cViewer();
}

cMenuSetupPage *cPluginViewer::SetupMenu(void)
{
  return new cMenuSetupViewer;
}

bool cPluginViewer::SetupParse(const char *Name, const char *Value)
{
  // Parse your own setup parameters and store their values.
  if      (!strcasecmp(Name, "FileName0"))       strn0cpy(FileNames[0], Value, MaxFileName);
  else if (!strcasecmp(Name, "LinesToRead"))     LinesToRead = atoi(Value);
  else if (!strcasecmp(Name, "Filter"))          vdrFilter = atoi(Value);
  else if (!strcasecmp(Name, "Order"))           bOrder = atoi(Value);
  else {
     char txt[256];
     int i;
     for(i=1;i<MAX_VIEWER_FILES;i++) {
       sprintf(txt,"FileName%d", i);
       if (!strcasecmp(Name, txt)){
                                                 strn0cpy(FileNames[i], Value, MaxFileName);
                                                 break;
       }
     }
     if(i==MAX_VIEWER_FILES)
        return false;
  }
  return true;
}

const char **cPluginViewer::SVDRPHelpPages(void)
{
  static const char *HelpPages[] = {
    "VIEW file\n"
    "    Shows the given file.",
    "QUIT\n"
    "    Close viewer plugin.",
    NULL
    };
  return HelpPages;
}

cString cPluginViewer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
{
  bOrder = false;
  if (strcasecmp(Command, "VIEW") == 0) {
     // we use the default reply code here
     if (*Option) {

// check if file is there and accessable
        if (access(Option, R_OK) == -1) { // the file directory doesn't exist or is not accessable
           ReplyCode = 504;
           esyslog("Viewer-plugin: %d, %s", errno, strerror(errno));
           return cString::sprintf("%d, %s", errno, strerror(errno));
        }
        if(gFileName)
           free(gFileName);
        gFileName=strdup(Option);
     }
     else {
        ReplyCode = 504;
        return cString("Please give a file!");
     }

     bDirectCall=false;
     cRemote::CallPlugin("viewer");
     return cString::sprintf("Opening viewer plugin with \"%s\"", Option);
     }
  else if (strcasecmp(Command, "QUIT") == 0) {
     cRemote::Put(kBack);
     return cString("Closing viewer plugin");
     }
  return NULL;
}

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

