/*
 * epgsearch.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/status.h>
#include <vdr/epg.h>
#include <getopt.h>
#include "menu_event.h"
#include "menu_main.h"
#include "menu_whatson.h"
#include "i18n.h"
#include "epgsearchsetup.h"
#include "epgsearchcfg.h"
#include "epgsearchext.h"
#include "epgsearchcats.h"
#include "menu_searchresults.h"
#include "searchtimer_thread.h"
#include "switchtimer_thread.h"
#include "menu_myedittimer.h"
#include "tools.h"
#include "rcfile.h"
#include "changrp.h"
#include "recstatus.h"
#include "recdone.h"
#include "blacklist.h"
#include "menu_search.h"
#include "templatefile.h"

static const char VERSION[]        = "0.9.16";
static const char DESCRIPTION[]    = "search the EPG for repeats and more";
const char *SVDRPSendCmd = "svdrpsend.pl";
char *ConfigDir = NULL;
char *LogFileName = NULL;
int PluginIndex = -1;
int updateForced = 0;
cLogFile LogFile;
int LogLevel = 0;


class cPluginEpgsearch : public cPlugin {
private:
public:
  cPluginEpgsearch(void);
  virtual ~cPluginEpgsearch();
  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 Initialize(void);
  virtual bool Start(void);
  virtual void Stop(void);
  virtual void Housekeeping(void);
  virtual const char *MainMenuEntry(void);
  virtual cOsdObject *MainMenuAction(void);
  virtual cMenuSetupPage *SetupMenu(void);
  virtual bool SetupParse(const char *Name, const char *Value);
  cOsdObject *DoInitialSearch(char* rcFilename);

#if VDRVERSNUM >= 10330 || defined(PATCH_CUSTOMSERVICES)
  bool Service(const char *Id, void *Data);
#endif
#if VDRVERSNUM >= 10331
  virtual const char **SVDRPHelpPages(void);
  virtual cString SVDRPCommand(const char *Cmd, const char *Option, int &ReplyCode);
#endif
};

cPluginEpgsearch::cPluginEpgsearch(void)
{
    gl_recStatusMonitor = NULL;
}

cPluginEpgsearch::~cPluginEpgsearch()
{
    delete gl_recStatusMonitor;
    cSearchTimerThread::Exit();
    cSwitchTimerThread::Exit();
}

const char *cPluginEpgsearch::CommandLineHelp(void)
{
  return "  -f file,  --svdrpsendcmd=file  the path to svdrpsend.pl for external\n"
         "                           SVDRP communitcation (default is internal\n"
         "                           communication)\n"
         "  -c path,  --config=path  to specify a specific config dir for epgsearch\n"
         "  -l file,  --logfile=file to specify a specific logfile for epgsearch\n"
         "  -v n,  --verbose=n       verbose level for logfile\n";
}

const char *cPluginEpgsearch::MainMenuEntry(void)
{
    if (EPGSearchConfig.hidemenu)
	return NULL;
    if (isempty(EPGSearchConfig.mainmenuentry))
	return tr("Program guide");
    else
	return EPGSearchConfig.mainmenuentry;   
}

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


  if (argc==5 && !strcmp(argv[0], "timermenu"))
  { // yes, this is an ugly hack!
      argv[1] = (char*) new cMenuMyEditTimer((cTimer*) argv[2], (bool) argv[3], (const cEvent*) argv[4]);
      return true;
  }

  if (argc==8 && !strcmp(argv[0], "searchepg"))
  {
      cSearchExt* SearchExt = new cSearchExt;
      strcpy(SearchExt->search,argv[2]);
      if (atoi(argv[3]) > 0)
      {
	  SearchExt->useChannel = true;
	  SearchExt->channelMin = Channels.GetByNumber(atoi(argv[3]));
	  SearchExt->channelMax = Channels.GetByNumber(atoi(argv[3]));
      }
      SearchExt->mode = atoi(argv[4]);
      SearchExt->useTitle = atoi(argv[5]);
      SearchExt->useSubtitle = atoi(argv[6]);
      SearchExt->useDescription = atoi(argv[7]);
      argv[1] = (char*) new cMenuSearchResults(SearchExt);
      return true;
  }

  static const struct option long_options[] = {
    { "svdrpsendcmd",          required_argument, NULL, 'f' },
    { "config",           required_argument, NULL, 'c' },
    { "logfile",           required_argument, NULL, 'l' },
    { "verbose",           required_argument, NULL, 'v' },
    { NULL }
  };

  int c=0,i=0;

  while ((c = getopt_long(argc, argv, "f:c:l:v:", long_options, &i)) != -1) {
        switch (c) {

          case 'f': 
	      SVDRPSendCmd = optarg;
	      EPGSearchConfig.useExternalSVDRP = 1;
	      break;
          case 'c': 
	      ConfigDir = optarg;
	      break;
          case 'l': 
	      LogFileName = optarg;
	      break;
          case 'v': 
	      LogLevel = atoi(optarg);
	      break;
          default:  return false;
          }
        }

  if (EPGSearchConfig.useExternalSVDRP && access(SVDRPSendCmd, F_OK) != 0)
  {
      esyslog("EPGSearch: ERROR - can't find SVDRPSend script: '%s'", SVDRPSendCmd);
      SVDRPSendCmd = NULL;
  }

  return true;
}

#if VDRVERSNUM >= 10330 || defined(PATCH_CUSTOMSERVICES)

#include "services.h"

bool cPluginEpgsearch::Service(const char *Id, void *Data)
{
  if (strcmp(Id, "schedule") == 0) 
      return (EPGSearchConfig.ReplaceOrgSchedule!=0);
 
  if (strcmp(Id, "Epgsearch-search-v1.0") == 0) {
     if (Data == NULL)
        return true;
      cSearchExt* SearchExt = new cSearchExt;
      
      Epgsearch_search_v1_0* searchData = (Epgsearch_search_v1_0*) Data;
      searchData->pResultMenu = NULL;
      strcpy(SearchExt->search,searchData->query);
      if (searchData->channelNr > 0)
      {
	  SearchExt->useChannel = true;
	  SearchExt->channelMin = Channels.GetByNumber(searchData->channelNr);
	  SearchExt->channelMax = Channels.GetByNumber(searchData->channelNr);
      }
      SearchExt->mode = searchData->mode;
      SearchExt->useTitle = searchData->useTitle;
      SearchExt->useSubtitle = searchData->useSubTitle;
      SearchExt->useDescription = searchData->useDescription;
      searchData->pResultMenu = new cMenuSearchResults(SearchExt);

      return true;
  }
  if (strcmp(Id, "Epgsearch-exttimeredit-v1.0") == 0) {
     if (Data == NULL)
        return true;
      
      Epgsearch_exttimeredit_v1_0* serviceData = (Epgsearch_exttimeredit_v1_0*) Data;
      serviceData->pTimerMenu = new cMenuMyEditTimer(serviceData->timer, serviceData->bNew, serviceData->event);

      return true;
  }
  if (strcmp(Id, "Epgsearch-updatesearchtimers-v1.0") == 0) {
     if (Data == NULL)
        return true;
     else
     {
	 Epgsearch_updatesearchtimers_v1_0* serviceData = (Epgsearch_updatesearchtimers_v1_0*) Data;
	 updateForced = serviceData->showMessage?2:1;
     }
     return true;
  }
  if (strcmp(Id, "Epgsearch-osdmessage-v1.0") == 0) {
     if (Data == NULL)
        return true;
     else
     {
	 Epgsearch_osdmessage_v1_0* serviceData = (Epgsearch_osdmessage_v1_0*) Data;
	 Skins.Message(serviceData->type, serviceData->message,5);
     }
     return true;
  }
  if (strcmp(Id, "Epgsearch-searchmenu-v1.0") == 0) {
     if (Data == NULL)
        return true;
      
      Epgsearch_searchmenu_v1_0* serviceData = (Epgsearch_searchmenu_v1_0*) Data;
      serviceData->pSearchMenu = new cMenuEPGSearchExt();

      return true;
  }
  if (strcmp(Id, "Epgsearch-searchresults-v1.0") == 0) {
     if (Data == NULL)
        return true;
      cSearchExt* SearchExt = new cSearchExt;
      
      Epgsearch_searchresults_v1_0* searchData = (Epgsearch_searchresults_v1_0*) Data;
      searchData->pResultList = NULL;
      strcpy(SearchExt->search,searchData->query);
      if (searchData->channelNr > 0)
      {
	  SearchExt->useChannel = true;
	  SearchExt->channelMin = Channels.GetByNumber(searchData->channelNr);
	  SearchExt->channelMax = Channels.GetByNumber(searchData->channelNr);
      }
      SearchExt->mode = searchData->mode;
      SearchExt->useTitle = searchData->useTitle;
      SearchExt->useSubtitle = searchData->useSubTitle;
      SearchExt->useDescription = searchData->useDescription;

      cSearchResults* results = SearchExt->Run();
      // transfer to result list
      if (results)
      {
	  results->SortBy(CompareEventTime);
	  searchData->pResultList = new cList<Epgsearch_searchresults_v1_0::cServiceSearchResult>;
	  cSearchResult *result = results->First();
	  while (result) 
	  {
	      searchData->pResultList->Add(new Epgsearch_searchresults_v1_0::cServiceSearchResult(result->event));
	      result = results->Next(result);
	  }
      }
      return true;
  }
  return false;
}
#endif

bool cPluginEpgsearch::Initialize(void)
{
  // Initialize any background activities the plugin shall perform.
  RegisterI18n(Phrases);

  EPGSearchConfig.ShowModes[showNow].SetDescription(tr("Button$Now"));
  EPGSearchConfig.ShowModes[showNext].SetDescription(tr("Button$Next"));

  return true;
}

bool cPluginEpgsearch::Start(void)
{
    if (LogFileName)
	LogFile.Open(LogFileName, LogLevel, Version());
    else
	LogFile.Open(AddDirectory(CONFIGDIR, "epgsearch.log"), LogLevel, Version());

    gl_recStatusMonitor = new cRecStatusMonitor;

    SearchExtCats.Load(AddDirectory(CONFIGDIR, "epgsearchcats.conf"), true);
    ChannelGroups.Load(AddDirectory(CONFIGDIR, "epgsearchchangrps.conf"), true);
    Blacklists.Load(AddDirectory(CONFIGDIR, "epgsearchblacklists.conf"));
    SearchExts.Load(AddDirectory(CONFIGDIR, "epgsearch.conf"));
    SearchTemplates.Load(AddDirectory(CONFIGDIR, "epgsearchtemplates.conf"));
    RecsDone.Load(AddDirectory(CONFIGDIR, "epgsearchdone.data"));
    SwitchTimers.Load(AddDirectory(CONFIGDIR, "epgsearchswitchtimers.conf"));
 
    cSearchTimerThread::Init();    
    cSwitchTimerThread::Init();    

    return true;
}

void cPluginEpgsearch::Stop(void)
{
    cSearchTimerThread::Exit();
    cSwitchTimerThread::Exit();
}

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

cOsdObject *cPluginEpgsearch::DoInitialSearch(char* rcFilename)
{
    cRCFile rcFile;
    if (rcFile.Load(rcFilename))
    {
	cSearchExt* SearchExt = new cSearchExt;
	strcpy(SearchExt->search,rcFile.Search);
	if (rcFile.ChannelNr != -1)
	{
	    SearchExt->useChannel = true;
	    SearchExt->channelMin = Channels.GetByNumber(rcFile.ChannelNr);
	    SearchExt->channelMax = Channels.GetByNumber(rcFile.ChannelNr);
	}
	SearchExt->mode = rcFile.SearchMode;
	SearchExt->useTitle = rcFile.UseTitle;
	SearchExt->useSubtitle = rcFile.UseSubtitle;
	SearchExt->useDescription = rcFile.UseDescr;
	remove(rcFilename);
	return new cMenuSearchResults(SearchExt);
    }
    else
	esyslog("EPGSearch: could not load %s", rcFilename);
	
    return NULL;
}

cOsdObject *cPluginEpgsearch::MainMenuAction(void)
{
    if (PluginIndex != -1)
    {
	cPlugin *p = cPluginManager::GetPlugin("timeline");
	PluginIndex = -1;
	if (p)
	    return p->MainMenuAction();
    }

    char* templateFilename = strdup(AddDirectory(CONFIGDIR, "epgsearchmenu.conf"));
    if (access(templateFilename, F_OK) == 0)
    {
	cTemplFile templFile;
	if (!templFile.Load(templateFilename))
	    esyslog("EPGSearch: could not load %s", templateFilename);
    }
    cTemplFile::PrepareDefaultTemplates();
    free(templateFilename);

    // Perform the action when selected from the main VDR menu.
    cOsdObject* pMenu = NULL;
    char* rcFilename = strdup(AddDirectory(CONFIGDIR, ".epgsearchrc"));
    if (access(rcFilename, F_OK) == 0)
	pMenu = DoInitialSearch(rcFilename);
    else
	pMenu = new cMenuSearchMain();
    
    free(rcFilename);
    return pMenu;
}

cMenuSetupPage *cPluginEpgsearch::SetupMenu(void)
{
  // Return a setup menu in case the plugin supports one.
  return new cMenuEPGSearchSetup;
}

bool cPluginEpgsearch::SetupParse(const char *Name, const char *Value)
{
  // Parse your own setup parameters and store their values.

  if	  (!strcasecmp(Name, "IsOrgSchedule")) return (EPGSearchConfig.ReplaceOrgSchedule == false);

  if      (!strcasecmp(Name, "HideMenu"))       EPGSearchConfig.hidemenu         = atoi(Value);
  if      (!strcasecmp(Name, "MainMenuEntry")) strcpy(EPGSearchConfig.mainmenuentry, Value);
  if      (!strcasecmp(Name, "ReplaceOrgSchedule"))  EPGSearchConfig.ReplaceOrgSchedule = atoi(Value);
  if      (!strcasecmp(Name, "StartMenu"))       EPGSearchConfig.StartMenu        = atoi(Value);
  if      (!strcasecmp(Name, "RedKeyMode"))     EPGSearchConfig.redkeymode       = atoi(Value);
  if      (!strcasecmp(Name, "BlueKeyMode"))     EPGSearchConfig.bluekeymode       = atoi(Value);
  if      (!strcasecmp(Name, "ShowProgress"))     EPGSearchConfig.showProgress   = atoi(Value);
  if      (!strcasecmp(Name, "ShowChannelNr"))     EPGSearchConfig.showChannelNr = atoi(Value);

  if      (!strcasecmp(Name, "ShowRadioChannels"))     EPGSearchConfig.showRadioChannels = atoi(Value);
  if      (!strcasecmp(Name, "OnePressTimerCreation"))     EPGSearchConfig.onePressTimerCreation = atoi(Value);
  
  if      (!strcasecmp(Name, "UserMode1Description"))   EPGSearchConfig.ShowModes[showUserMode1].SetDescription(Value);
  if      (!strcasecmp(Name, "UserMode1Time"))   EPGSearchConfig.ShowModes[showUserMode1].SetTime(atoi(Value));
  if      (!strcasecmp(Name, "UserMode1UseIt"))   EPGSearchConfig.ShowModes[showUserMode1].SetUsage(atoi(Value));

  if      (!strcasecmp(Name, "UserMode2Description"))   EPGSearchConfig.ShowModes[showUserMode2].SetDescription(Value);
  if      (!strcasecmp(Name, "UserMode2Time"))   EPGSearchConfig.ShowModes[showUserMode2].SetTime(atoi(Value));
  if      (!strcasecmp(Name, "UserMode2UseIt"))   EPGSearchConfig.ShowModes[showUserMode2].SetUsage(atoi(Value));

  if      (!strcasecmp(Name, "UserMode3Description"))   EPGSearchConfig.ShowModes[showUserMode3].SetDescription(Value);
  if      (!strcasecmp(Name, "UserMode3Time"))   EPGSearchConfig.ShowModes[showUserMode3].SetTime(atoi(Value));
  if      (!strcasecmp(Name, "UserMode3UseIt"))   EPGSearchConfig.ShowModes[showUserMode3].SetUsage(atoi(Value));

  if      (!strcasecmp(Name, "UserMode4Description"))   EPGSearchConfig.ShowModes[showUserMode4].SetDescription(Value);
  if      (!strcasecmp(Name, "UserMode4Time"))   EPGSearchConfig.ShowModes[showUserMode4].SetTime(atoi(Value));
  if      (!strcasecmp(Name, "UserMode4UseIt"))   EPGSearchConfig.ShowModes[showUserMode4].SetUsage(atoi(Value));

  if      (!strcasecmp(Name, "UseSearchTimers"))  EPGSearchConfig.useSearchTimers = atoi(Value);
  if      (!strcasecmp(Name, "UpdateIntervall"))  EPGSearchConfig.UpdateIntervall = atoi(Value);
  if      (!strcasecmp(Name, "SVDRPPort"))  EPGSearchConfig.SVDRPPort = atoi(Value);
  if      (!strcasecmp(Name, "CheckTimerConflicts"))  EPGSearchConfig.checkTimerConflicts = atoi(Value);

  if      (!strcasecmp(Name, "TimeIntervallFRFF"))  EPGSearchConfig.timeShiftValue = atoi(Value);
  if      (!strcasecmp(Name, "ToggleGreenYellow"))  EPGSearchConfig.toggleGreenYellow = atoi(Value);

  if      (!strcasecmp(Name, "DefPriority"))  EPGSearchConfig.DefPriority = atoi(Value);
  if      (!strcasecmp(Name, "DefLifetime"))  EPGSearchConfig.DefLifetime = atoi(Value);
  if      (!strcasecmp(Name, "DefMarginStart"))  EPGSearchConfig.DefMarginStart = atoi(Value);
  if      (!strcasecmp(Name, "DefMarginStop"))  EPGSearchConfig.DefMarginStop = atoi(Value);

  if      (!strcasecmp(Name, "IgnorePayTV"))  EPGSearchConfig.ignorePayTV = atoi(Value);

  if      (!strcasecmp(Name, "DefRecordingDir"))  strcpy(EPGSearchConfig.defrecdir, Value);
  if      (!strcasecmp(Name, "UseVDRTimerEditMenu"))  EPGSearchConfig.useVDRTimerEditMenu = atoi(Value);

  if      (!strcasecmp(Name, "ShowChannelGroups"))  EPGSearchConfig.showChannelGroups = atoi(Value); 
  if      (!strcasecmp(Name, "ShowDaySeparators"))  EPGSearchConfig.showDaySeparators = atoi(Value); 

  if      (!strcasecmp(Name, "ShowEmptyChannels"))  EPGSearchConfig.showEmptyChannels = atoi(Value); 

  if      (!strcasecmp(Name, "DefSearchTemplateID"))  EPGSearchConfig.DefSearchTemplateID = atoi(Value); 

  if      (!strcasecmp(Name, "AddSubtitleToTimerMode"))  EPGSearchConfig.addSubtitleToTimer = (addSubtitleToTimerMode) atoi(Value); 
  if      (!strcasecmp(Name, "SetupMode"))  EPGSearchConfig.SetupMode = atoi(Value); 
  
  return true;
}

#if VDRVERSNUM >= 10331
const char **cPluginEpgsearch::SVDRPHelpPages(void)
{
  static const char *HelpPages[] = {
    "LSTS [ ID ]\n"
    "    List searches.\n"
    "    If the optional numeric argument ID is passed,\n"
    "    only the search with the according ID is listed",  
    "NEWS <settings>\n"
    "    Add a new search\n",
    "DELS <ID>\n"
    "    Delete search with passed ID\n",
    "EDIS <settings>\n"
    "    Edit an existing search\n",
    "UPDS [ OSD ]\n"
    "    Update search timers.\n"
    "    If the optional keyword 'OSD' is passed, an OSD message\n"
    "    will inform about update completion.",  
    "UPDD\n"
    "    Reload epgsearchdone.data",
    "SETS <ON|OFF>\n"
    "    Activate or cancel the search timer background thread.",
    "FIND <settings>\n"
    "    Search the EPG for events and receive a result list.",
    NULL
    };
  return HelpPages;
}

cString cPluginEpgsearch::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
{
    if (strcasecmp(Command, "UPDS") == 0) 
    {
	updateForced = (Option && strcasecmp(Option, "OSD") == 0)?2:1;
	return cString("update triggered");
    }
    else if (strcasecmp(Command, "UPDD") == 0) 
    {
	if (RecsDone.Load(AddDirectory(CONFIGDIR, "epgsearchdone.data")))
	    return cString("reload successful");	    
	else
	    return cString("reload failed");	    
    }
    else if (strcasecmp(Command, "LSTS") == 0) 
    {
	if (*Option) 
	{
	    if (isnumber(Option))
	    {
		cSearchExt *search = SearchExts.GetSearchFromID(atol(Option));
		if (search)
		{
		    char* buffer = NULL;
		    asprintf(&buffer, "%s", search->ToText());
		    char* bufferRepl = strreplaceall(buffer, '%', "%%"); // '%' would be interpreted, so replace it with '%%'
		    if (bufferRepl)
		    {
			free(buffer);
			buffer = bufferRepl;
		    }			    
		    cString sBuffer = cString(buffer);
		    free(buffer);
		    return sBuffer;
		}

		else
		{
		    ReplyCode = 901;
		    return cString::sprintf("search id %s not defined", Option);
		}
	    }
	    else
	    {
		ReplyCode = 901;
		return cString::sprintf("Error in search ID \"%s\"", Option);	
	    }
	}
	else if (SearchExts.Count()>0) 
	{
	    cString sBuffer("");
	    for (int i = 0; i < SearchExts.Count(); i++) 
	    {
		cSearchExt* search = SearchExts.Get(i);
		if (search)
		{
		    cString sOldBuffer = sBuffer;
		    sBuffer = cString::sprintf("%s%s\n", *sOldBuffer, search->ToText());
		}
	    }
	    char* buffer = strdup(*sBuffer);
	    char* bufferRepl = strreplaceall(buffer, '%', "%%"); // '%' would be interpreted, so replace it with '%%'
	    if (bufferRepl)
	    {
		free(buffer);
		buffer = bufferRepl;
	    }			    
	    cString sResBuffer = cString(buffer);
	    free(buffer);
	    return sResBuffer;
	}
	else
	{
	    ReplyCode = 901;
	    return cString("no searches defined");	    	
	}
    }
    else if (strcasecmp(Command, "DELS") == 0) 
    {
	if (*Option) 
	{
	    if (isnumber(Option))
	    {
		cSearchExt *search = SearchExts.GetSearchFromID(atol(Option));
		if (search)
		{
		    LogFile.Log(1,"search '%s' deleted via SVDRP", search->search, Option);
		    SearchExts.Del(search);
		    SearchExts.Save();
		    RecsDone.RemoveSearchID(atol(Option));
		    return cString::sprintf("search id %s deleted", Option);
		}
		else
		{
		    ReplyCode = 901;
		    return cString::sprintf("search id %s not defined", Option);
		}
	    }
	    else
	    {
		ReplyCode = 901;
		return cString::sprintf("Error in search ID \"%s\"", Option);	
	    }
	}
	else
	{
	    ReplyCode = 901;
	    return cString("missing search ID");	    	
	}
    }
    else if (strcasecmp(Command, "NEWS") == 0) 
    {
	if (*Option) 
	{
	    cSearchExt* search = new cSearchExt;
	    if (search->Parse(Option))
	    {
		search->ID = SearchExts.GetNewID();
		LogFile.Log(1,"added search '%s' (%d) via SVDRP", search->search, search->ID);
		SearchExts.Add(search);
		SearchExts.Save();
		return cString::sprintf("search '%s' (with new ID %d) added", search->search, search->ID);
	    }
	    else
	    {
		ReplyCode = 901;
		delete search;
		return cString("Error in search settings");	
	    }
	}
	else
	{
	    ReplyCode = 901;
	    return cString("missing search settings");	    	
	}
    }
    else if (strcasecmp(Command, "EDIS") == 0) 
    {
	if (*Option) 
	{
	    cSearchExt* search = new cSearchExt;
	    if (search->Parse(Option))
	    {
		cSearchExt *searchTemp = SearchExts.GetSearchFromID(search->ID);
		if (searchTemp)
		{
		    searchTemp->Parse(Option);
		    LogFile.Log(1,"modified search '%s' (%d) deleted via SVDRP", searchTemp->search, searchTemp->ID);
		    SearchExts.Save();
		    return cString::sprintf("search '%s' with %d modified", searchTemp->search, searchTemp->ID);
		}
		else
		{
		    ReplyCode = 901;
		    int ID = search->ID;
		    delete search;
		    return cString::sprintf("search id %d does not exists", ID);
		}
	    }
	    else
	    {
		ReplyCode = 901;
		delete search;
		return cString("Error in search settings");	
	    }
	}
	else
	{
	    ReplyCode = 901;
	    return cString("missing search settings");	    	
	}
    }
    else if (strcasecmp(Command, "SETS") == 0) 
    {
	if (*Option) 
	{
	    if (strcasecmp(Option, "ON") == 0)
	    {
		if (cSearchTimerThread::m_Instance)
		{
		    ReplyCode = 901;
		    return cString("search timer thread already active!");	    	
		}
		else
		{
		    LogFile.Log(1,"search timer thread started via SVDRP");
		    cSearchTimerThread::Init();
		    return cString("search timer activated.");	    	
		}
	    }
	    else if (strcasecmp(Option, "OFF") == 0)
	    {
		if (!cSearchTimerThread::m_Instance)
		{
		    ReplyCode = 901;
		    return cString("search timer thread already inactive!");	    	
		}
		else
		{
		    LogFile.Log(1,"search timer thread canceled via SVDRP");
		    cSearchTimerThread::Exit();
		    return cString("search timer thread canceled.");	    	
		}
	    }
	    else
	    {
		ReplyCode = 901;
		return cString::sprintf("unknown option '%s'", Option);	    	
	    }
	}
	else
	{
	    ReplyCode = 901;
	    return cString("missing option <on|off>");	    	
	}
    }
    else if (strcasecmp(Command, "FIND") == 0) 
    {
	if (*Option) 
	{
	    cSearchExt* search = new cSearchExt;
	    if (search->Parse(Option))
	    {
		cSearchResults* results = search->Run();
		// transfer to result list
		cString sBuffer("");
		if (results)
		{
		    results->SortBy(CompareEventTime);
		    cSearchResult *result = results->First();
		    while (result) 
		    {
			const cEvent* pEvent = result->event;
			cTimer* Timer = new cTimer(pEvent);
			char *cmdbuf = NULL;

			static char bufStart[25];
			static char bufEnd[25];

			struct tm tm_r;    time_t eStart = pEvent->StartTime();
			time_t eStop = pEvent->EndTime();
			time_t start = eStart - (search->MarginStart * 60);
			time_t stop  = eStop + (search->MarginStop * 60);
			int Flags = Timer->Flags();
			if (search->useVPS && pEvent->Vps() && Setup.UseVps)
			{
			    start = pEvent->Vps();
			    stop = start + pEvent->Duration();
			}
			else
			    Flags = 1; // don't use VPS, if not set in this search

			strftime(bufStart, sizeof(bufStart), "%H%M", localtime_r(&start, &tm_r));
			strftime(bufEnd, sizeof(bufEnd), "%H%M", localtime_r(&stop, &tm_r));

#if VDRVERSNUM && VDRVERSNUM >= 10323
			asprintf(&cmdbuf, "NEWT %d:%d:%s:%s:%s:%d:%d:%s:%s", 
#else
		        asprintf(&cmdbuf, "NEWT %d:%d:%d:%s:%s:%d:%d:%s:%s", 
#endif
				 Flags,
				 Timer->Channel()->Number(),
#if VDRVERSNUM && VDRVERSNUM >= 10323
				 *Timer->PrintDay(start, Timer->WeekDays()),
#else
				 localtime_r(&start, &tm_r)->tm_mday,
#endif
				 bufStart,
				 bufEnd,
				 search->Priority, 
				 search->Lifetime, 
				 Timer->File(),
				 "");
				 
		      
        		cString sOldBuffer = sBuffer;
			sBuffer = cString::sprintf("%s%s\n", *sOldBuffer, cmdbuf);
			free(cmdbuf);
			delete(Timer);
			result = results->Next(result);
		    }
		    char* buffer = strdup(*sBuffer);
		    char* bufferRepl = strreplaceall(buffer, '%', "%%"); // '%' would be interpreted, so replace it with '%%'
		    if (bufferRepl)
		    {
			free(buffer);
			buffer = bufferRepl;
		    }			    
		    cString sResBuffer = cString(buffer);
		    free(buffer);
		    return sResBuffer;	    
		}
		else
		{
		    ReplyCode = 901;
		    delete search;
		    return cString("no results");	
		}
	    }
	    else
	    {
		ReplyCode = 901;
		delete search;
		return cString("Error in search settings");	
	    }
	}
	else
	{
	    ReplyCode = 901;
	    return cString("missing search settings");	    	
	}
    }
    return NULL;
}
#endif

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