/***************************************************************************
                          commands.c  -  description
                             -------------------
    begin                : Sa Jan 15 2005
    copyright            : (C) 2005 by Holger Brunn
    email                : holger.brunn@stud.uni-karlsruhe.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <malloc.h>
#include <string.h>
#include <fnmatch.h>
#include <sys/wait.h>
#include <vdr/tools.h>
#include <vdr/thread.h>
#include <vdr/menuitems.h>
#include "commands.h"
#ifdef ROASTER_PLUGIN_BUILD
#include "menu-threads.h"
#endif
#include "statebag.h"

cRoasterCommand::cRoasterCommand(cRoasterStatebag* Statebag)
{
  Name=   NULL;
  Pattern=NULL;
  Confirm=false;
  UseDestination=false;
  UseCurrentFile=false;
  UseSelectedFiles=false;
  UseNewDestination=false;
  Synchronous=false;
  Menu=true;
  RemoveFinished=false;
  PatternIsShellscript=false;
  State=osContinue;
  this->Statebag=Statebag;
}

cRoasterCommand::~cRoasterCommand()
{
  if(Name)    free(Name);
  if(Pattern) free(Pattern);
}

bool cRoasterCommand::Matches(const char* Filename)
{
  bool Match=true;
  
  if(Statebag && (Statebag->GetSelectedFiles()->Count() > 0))
  {
    for(cStringContainer* i=Statebag->GetSelectedFiles()->First(); i; i=Statebag->GetSelectedFiles()->Next(i))
    {
      Match&=MatchesSingleFile(i->GetObject());
      if(!Match)
      {
        break;
      }
    }
  }
  else
  {
    Match=MatchesSingleFile(Filename);
  }
  return Match;
}

bool cRoasterCommand::MatchesSingleFile(const char* Filename)
{
  if(!Filename)
  {
    return false;
  }
  if(PatternIsShellscript)
  {
    cCommandParser* Parser=new cCommandParser(Pattern);
    Parser->AddReplacement('f', Filename);
    cString Command=Parser->Parse();
    D(fprintf(stderr, "Trying to execute %s\n", *Command));
    int Status=SystemExec(*Command);
    D(fprintf(stderr, "Executed \"%s\": %d\n", *Command, Status));
    return Status == 0;
  }
  else
  {
    if(Pattern[0]!='/')
    {
      char* filename_tmp=NULL;
      Filename=(Filename && (filename_tmp=strrchr(Filename, '/'))) ? filename_tmp + 1 : NULL;
    }
    if(Filename==NULL)
    {
      return false;
    }
    return fnmatch(Pattern, Filename, FNM_FILE_NAME | FNM_EXTMATCH) != FNM_NOMATCH;
  }
}

char* cRoasterCommand::GetName()
{
  return Name;
}

bool cRoasterCommand::Execute(cOsdMenu* Menu, char* DestinationFile, char* CurrentFile)
{
  return true;
}

eKeys cRoasterCommand::GetHotkey()
{
  return kNone;
}

#ifdef ROASTER_PLUGIN_BUILD

/*
  Implementation cConfigCommand
*/

cRoasterConfigCommand::cRoasterConfigCommand(cRoasterStatebag* Statebag) : cRoasterCommand(Statebag)
{
   Command=NULL;
};

char* cRoasterConfigCommand::FlagHandler(const char* OrgString, const char* CurrentPos, const cCommandParser::cHandlerParameters* Params)
{
  *((bool*)Params->Data)=true;
  return NULL;
}

cRoasterConfigCommand* cRoasterConfigCommand::Parse(char* Config, cRoasterStatebag* Statebag)
{
  cRoasterConfigCommand* Command=new cRoasterConfigCommand(Statebag);
  
  cConfigParser Parser=cConfigParser(Config);
  for(char* Text=Parser.First(); Text; Text=Parser.Next())
  {
    switch(Parser.GetFieldNumber())
    {
      case 0: //The name of this command
        if(Text[Parser.GetCurrentLength()-1]=='?')
        {
          Text[Parser.GetCurrentLength()-1]='\0';
          Command->Confirm=true;
          D(fprintf(stderr, "I will ask "));
        }
        D(fprintf(stderr, "[doing realloc] "));
        Command->Name=(char*)realloc(Command->Name, Parser.GetCurrentLength() + 1);
        D(fprintf(stderr, "[copying %s] ", Text));
        strcpy(Command->Name, Text);
        D(fprintf(stderr, "\"%s\" ", Command->Name));
        break;
      case 1: //The pattern of this command
        Command->PatternIsShellscript=(Text[0]=='!');
        Command->Pattern=(char*)realloc(Command->Pattern, Parser.GetCurrentLength() + 1);
        strcpy(Command->Pattern, Text + (Command->PatternIsShellscript ? 1 : 0));
        D(fprintf(stderr, "\"%s\" ", Command->Pattern));
        break;
      case 2: //The command string of this command
        Command->Command=(char*)realloc(Command->Command, Parser.GetCurrentLength() + 1);
        strcpy(Command->Command, Text);
        D(fprintf(stderr, "\"%s\" ", Command->Command));
        break;
      case 3:
        Command->Synchronous=strchr(Text, 's');
        Command->Menu=!strchr(Text, 'b');
        Command->RemoveFinished=strchr(Text, 'r');
        D(fprintf(stderr, " Sync:%d Menu:%d Remove:%d ", Command->Synchronous, Command->Menu, Command->RemoveFinished));
        break;
    }
  }
  
  D(fprintf(stderr, "\n"));
  
  //Set Use-Flags
  cCommandParser CommandParser(Command->Command, false, false);
  CommandParser.AddHandler('D', &cRoasterConfigCommand::FlagHandler, &Command->UseNewDestination);
  CommandParser.AddHandler('d', &cRoasterConfigCommand::FlagHandler, &Command->UseDestination);
  CommandParser.AddHandler('M', &cRoasterConfigCommand::FlagHandler, &Command->UseSelectedFiles);
  CommandParser.AddHandler('m', &cRoasterConfigCommand::FlagHandler, &Command->UseSelectedFiles);
  CommandParser.AddHandler('f', &cRoasterConfigCommand::FlagHandler, &Command->UseCurrentFile);
  CommandParser.Parse();
  
  D(fprintf(stderr, "NewDest %d, Dest %d, SelFiles %d, File %d\n", Command->UseNewDestination, Command->UseDestination, Command->UseSelectedFiles, Command->UseCurrentFile));
  
  return Command;
}

cRoasterConfigCommand::~cRoasterConfigCommand()
{
  if(Command) free(Command);
}

bool cRoasterConfigCommand::Execute(cOsdMenu* Menu, char* DestinationFile, char* CurrentFile)
{
  D(fprintf(stderr, "got dest %s and %d marked files\n", DestinationFile, Statebag->GetSelectedFiles()->Count()));
  cConfigCommandThread* Thread=new cConfigCommandThread(Statebag, DestinationFile, CurrentFile, this);
  if(Synchronous)
  {
    //TODO: Move this to roaster.c, implement some kind of wait function to wait until the thread is finished
    pid_t child_pid=fork();
    if(child_pid == 0)
    {
      int MaxPossibleFileDescriptors = getdtablesize();
      for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
        close(i);
      Thread->Execute();
    }
    else if(child_pid < 0)
    {
      LOG_ERROR;
    }
    else
    {
      int status;
      waitpid(child_pid, &status, 0);
      delete Thread;
    }
  }
  else
  {
    Statebag->GetThreads()->Add(new cThreadContainer(Thread));
  }
  return true;
}

char* cRoasterConfigCommand::GetCommand()
{
  return Command;
}

#endif

/*
  Implementation of cRoasterDestinationContainerCommand
*/
cRoasterDestinationContainerCommand::cRoasterDestinationContainerCommand(cRoasterCommand* Command, char* CurrentFile, cRoasterStatebag* Statebag)
  : cRoasterCommand(Statebag)
{
  this->Command=Command;
  UseSelectedFiles=Command->UsesSelectedFiles();
  UseNewDestination=Command->UsesNewDestination();
  UseCurrentFile=Command->UsesCurrentFile();
  Menu=Command->ShowMenu();
  Synchronous=Command->IsSynchronous();
  RemoveFinished=Command->RemoveWhenFinished();
  this->CurrentFile=strdup(CurrentFile);
  DestinationFile=NULL;
  Name=strcpyrealloc(Name, tr("OK"));
}
cRoasterDestinationContainerCommand::~cRoasterDestinationContainerCommand()
{
  if(CurrentFile) free(CurrentFile);
  if(DestinationFile) free(DestinationFile);
}
bool cRoasterDestinationContainerCommand::Execute(cOsdMenu* Menu, char* DestinationFile, char* CurrentFile)
{
  if(this->DestinationFile) free(this->DestinationFile);
  this->DestinationFile=strdup(CurrentFile);
  
  for(cOsdItem* i=Menu->First(); i!=NULL; i=Menu->cOsdMenu::Next(i))
  {
    if(dynamic_cast<cOsdItemNewFileEntry*>(i))
    {
      ((cOsdMenuRoaster*)Menu)->Del(i->Index());
    }
  }
  return Command->Execute(Menu, this->DestinationFile, this->CurrentFile);
}
bool cRoasterDestinationContainerCommand::Matches(const char* Filename)
{
  return Command ? Command->MatchesDestination(Filename) : false;
}

/*
  Implementation of cRoasterDestinationAbortCommand
*/
cRoasterDestinationAbortCommand::cRoasterDestinationAbortCommand(cRoasterStatebag* Statebag)
  : cRoasterCommand(Statebag)
{
  Name=strcpyrealloc(Name, tr("Abort"));
}
bool cRoasterDestinationAbortCommand::Execute(cOsdMenu* Menu, char* DestinationFile, char* CurrentFile)
{
  ((cOsdMenuRoaster*)Menu)->Task=taskBrowse;
  for(cOsdItem* i=Menu->First(); i!=NULL; i=Menu->cOsdMenu::Next(i))
  {
    if(dynamic_cast<cOsdItemNewFileEntry*>(i))
    {
      ((cOsdMenuRoaster*)Menu)->Del(i->Index());
    }
  }
  Menu->Display();
  return true;
}
bool cRoasterDestinationAbortCommand::Matches(const char* Filename)
{
  return true;
}

/*
  Implementation of cRoasterDestinationNewCommand
*/
cRoasterDestinationNewCommand::cRoasterDestinationNewCommand(cRoasterStatebag* Statebag)
  : cRoasterCommand(Statebag)
{
  Name=strcpyrealloc(Name, tr("New"));
}
bool cRoasterDestinationNewCommand::Execute(cOsdMenu* Menu, char* DestinationFile, char* CurrentFile)
{
  char* val=(char*)malloc(255);
  sprintf(val, tr("New file"));
  cOsdItemNewFileEntry* NewItem=new cOsdItemNewFileEntry(val, *(Statebag->CurrentDirectory));
  Menu->Add(NewItem, true);
  ((cOsdMenuRoaster*)Menu)->DisplayCurrent(true);
  NewItem->ProcessKey(kNone);
  Menu->Display();
  return true;
}
bool cRoasterDestinationNewCommand::Matches(const char* Filename)
{
  return true;
}

#ifdef ROASTER_PLUGIN_BUILD

/*
  Implementation of cRoasterCommandThreadList
*/
cRoasterCommandThreadList::cRoasterCommandThreadList(cRoasterStatebag* Statebag) : cRoasterCommand(Statebag)
{
  Name=strcpyrealloc(Name, tr("Status"));
}

bool cRoasterCommandThreadList::Execute(cOsdMenu* Menu, char* DestinationFile, char* SelectedFile)
{
  cOsdMenuRoaster* FbMenu=(cOsdMenuRoaster*)Menu;
  FbMenu->AddSubMenu(new cOsdMenuThreadList(Statebag));
  return true;
}
bool cRoasterCommandThreadList::Matches(const char* Filename)
{
  return true;
}

#endif
