#include "epgsearchext.h"
#include "epgsearchcfg.h"
#include "epgsearchcats.h"
#include "tools.h"
#include <vdr/tools.h>
#include "menu_searchresults.h"
#include "menu_dirselect.h" 
#include "changrp.h"

#include <math.h>
#ifdef HAVE_PCREPOSIX
#include <pcreposix.h>
#else
#include <regex.h>
#endif

char *SearchModes[5];
char *DaysOfWeek[8];
char *UseChannelSel[3];
const char *AllowedChars = " abcdefghijklmnopqrstuvwxyz0123456789-.#~\\^$[]|()*+?{}/:%";

int     q_manual_update; // data has change --> question for manual update
cSearchExts SearchExts;
cSearchExtCats SearchExtCats;
cChannelGroups ChannelGroups;


// -- cSearchExt -----------------------------------------------------------------

char *cSearchExt::buffer = NULL;

cSearchExt::cSearchExt(void)
{
  *search = 0;
  options = 1;
  useTime = false;
  startTime = 0000;
  stopTime = 2359;
  useChannel = false;
  channelMin = Channels.GetByNumber(cDevice::CurrentChannel());
  channelMax = Channels.GetByNumber(cDevice::CurrentChannel());
  channelGroup = NULL; 
  useCase = false;
  mode = 0;
  useTitle = true;
  useSubtitle = true;
  useDescription = true;
  useDuration = false;
  useAsSearchTimer = false;
  useDayOfWeek = false;
  DayOfWeek = 0;
  buffer = NULL;
  *directory = 0;
  useEpisode = 0;
  Priority = EPGSearchConfig.DefPriority;
  Lifetime = EPGSearchConfig.DefLifetime;
  MarginStart = EPGSearchConfig.DefMarginStart;
  MarginStop = EPGSearchConfig.DefMarginStop;
  useVPS = false;
  announceOnly = false;
  useExtEPGInfo = false;
  catvalues = (char**) malloc(SearchExtCats.Count() * sizeof(char*));
  cSearchExtCat *SearchExtCat = SearchExtCats.First();
  int index = 0;
  while (SearchExtCat) 
  {
      catvalues[index] = (char*)malloc(MaxFileName);
      *catvalues[index] = 0;
      SearchExtCat = SearchExtCats.Next(SearchExtCat);
      index++;
  }
}

cSearchExt::~cSearchExt(void)
{
  if (buffer) {
    free(buffer);
    buffer = NULL;
  }
}

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

bool cSearchExt::operator< (const cListObject &ListObject)
{
  cSearchExt *SE = (cSearchExt *)&ListObject;
  return strcasecmp(search, SE->search) < 0;
}

const char *cSearchExt::ToText(void)
{
  char tmp_Start[5] = "";
  char tmp_Stop[5] = "";
  char tmp_minDuration[5] = "";
  char tmp_maxDuration[5] = "";
  char* tmp_chanSel = NULL;
  char* tmp_search = NULL;
  char* tmp_catvalues = NULL;

  free(buffer);
  if (mode < 4) // not a regular expression  
      asprintf(&tmp_search, "%s", search);
  else if (mode ==4)
  {
      asprintf(&tmp_search, "%s", search);
      while(strstr(tmp_search, "|"))
	  tmp_search = strreplace(tmp_search, "|", "!^pipe^!"); // ugly: replace with something, that should not happen to be part of a regular expression 
  }
  strreplace(tmp_search, ':', '|');
  strreplace(directory, ':', '|');

  if (useTime)
  {
    sprintf(tmp_Start, "%04d", startTime);
    sprintf(tmp_Stop, "%04d", stopTime);
  }
  if (useDuration)
  {
    sprintf(tmp_minDuration, "%04d", minDuration);
    sprintf(tmp_maxDuration, "%04d", maxDuration);
  }

  if (useChannel==1)
  {
      if (channelMin->Number() < channelMax->Number())
	  asprintf(&tmp_chanSel, "%s|%s", CHANNELSTRING(channelMin), CHANNELSTRING(channelMax));
      else
	  asprintf(&tmp_chanSel, "%s", CHANNELSTRING(channelMin));
  }
  if (useChannel==2)
  {
      int channelGroupNr = ChannelGroups.GetIndex(channelGroup);
      if (channelGroupNr == -1)
      {
	  esyslog("EPGSearch: channel group %s does not exist!", channelGroup);
	  useChannel = 0;
      }
      else
	  tmp_chanSel = strdup(channelGroup);
  }

  if (useExtEPGInfo)
  {
      cSearchExtCat *SearchExtCat = SearchExtCats.First();
      int index = 0;
      while (SearchExtCat) 
      {
	  char* catvalue = NULL;
	  asprintf(&catvalue, "%s", catvalues[index]);
	  while(strstr(catvalue, ":"))
	      catvalue = strreplace(catvalue, ":", "!^colon^!"); // ugly: replace with something, that should not happen to be part ofa category value
	  while(strstr(catvalue, "|"))
	      catvalue = strreplace(catvalue, "|", "!^pipe^!"); // ugly: replace with something, that should not happen to be part of a regular expression 

	  if (index == 0)
	      asprintf(&tmp_catvalues, "%d#%s", SearchExtCat->id, catvalue);
	  else
	  {
	      char* temp = tmp_catvalues;	      
	      asprintf(&tmp_catvalues, "%s|%d#%s", tmp_catvalues, SearchExtCat->id, catvalue);
	      free(temp);
	  }
	  SearchExtCat = SearchExtCats.Next(SearchExtCat);	 
	  index++;
	  free(catvalue);
      }
      
  }

  asprintf(&buffer, "%s:%d:%s:%s:%d:%s:%d:%d:%d:%d:%d:%d:%s:%s:%d:%d:%d:%d:%s:%d:%d:%d:%d:%d:%d:%d:%s\n",
	   tmp_search,
	   useTime,
	   tmp_Start,
	   tmp_Stop,
	   useChannel,
	   useChannel>0?tmp_chanSel:"0",
	   useCase,
	   mode,
	   useTitle,
	   useSubtitle,
	   useDescription,
	   useDuration,
	   tmp_minDuration,
	   tmp_maxDuration,
	   useAsSearchTimer,
	   useDayOfWeek,
	   DayOfWeek,
	   useEpisode,
	   directory,
	   Priority,
	   Lifetime,
	   MarginStart,
	   MarginStop,
	   useVPS,
	   announceOnly,
	   useExtEPGInfo,
	   useExtEPGInfo?tmp_catvalues:"");

  strreplace(directory, '|', ':');

  if (tmp_chanSel)
      free(tmp_chanSel);
  if (tmp_search)
      free(tmp_search);
  if (tmp_catvalues)
      free(tmp_catvalues);


  return buffer;
}

bool cSearchExt::Parse(const char *s)
{
  char *line;
  char *pos;
  char *pos_next;
  int parameter = 1;
  int valuelen; 
  char value[MaxFileName];

  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:  strcpy(search, value);
	      break;
	    case 2:  useTime = atoi(value);
	      break;
	    case 3:  startTime = atoi(value);
		break;
	    case 4:  stopTime = atoi(value);
	      break;
	    case 5:  useChannel = atoi(value);
		break;
	    case 6:  
		if (useChannel == 0)
		{
		    channelMin = NULL;
		    channelMax = NULL;
		}
		else if (useChannel == 1)
		{
		    int minNum=0, maxNum=0;
		    int fields = sscanf(value, "%d-%d", &minNum, &maxNum);
		    if (fields == 0) // stored with ID
		    {
			char *channelMinbuffer = NULL;
			char *channelMaxbuffer = NULL;
			int channels = sscanf(value, "%a[^|]|%a[^|]", &channelMinbuffer, &channelMaxbuffer);
			channelMin = Channels.GetByChannelID(tChannelID::FromString(channelMinbuffer), true, true);
			if (!channelMin) 
			{
			    esyslog("EPGSearch - ERROR: channel %s not defined", channelMinbuffer);
			    free(line);
			    return false;
			}
			if (channels == 1)
			    channelMax = channelMin;
			else
			{
			    channelMax = Channels.GetByChannelID(tChannelID::FromString(channelMaxbuffer), true, true);
			    if (!channelMax) 
			    {
				esyslog("EPGSearch - ERROR: channel %s not defined", channelMaxbuffer);
				free(line);
				return false;
			    }
			}

			free(channelMinbuffer);
			free(channelMaxbuffer);
		    }
		    else // stored with Number
		    {
			if (fields == 1)
			    maxNum = minNum;
			channelMin = Channels.GetByNumber(minNum);
			channelMax = Channels.GetByNumber(maxNum);
			if ((!channelMin && minNum>0) ||
			    (!channelMax && maxNum>0))
			{
			    free(line);
			    return false;
			}
		    }
		}
		else if (useChannel == 2)
		    channelGroup = strdup(value);
	      break;
	    case 7:  useCase = atoi(value);
	      break;
	    case 8:  mode = atoi(value);
		break;
	    case 9: useTitle = atoi(value);
		break;
	    case 10: useSubtitle = atoi(value);
		break;
	    case 11: useDescription = atoi(value);
		break;
	    case 12: useDuration = atoi(value);
		break;
	    case 13: minDuration = atoi(value);
		break;
	    case 14: maxDuration = atoi(value);
		break;
	    case 15: useAsSearchTimer = atoi(value);
			break;
	    case 16: useDayOfWeek = atoi(value);
			break;
	    case 17: DayOfWeek = atoi(value);
			break;
	    case 18: useEpisode = atoi(value);
			break;
	    case 19:  strcpy(directory, value);
	      break;
	    case 20: Priority = atoi(value);
			break;
	    case 21: Lifetime = atoi(value);
			break;
	    case 22: MarginStart = atoi(value);
			break;
	    case 23: MarginStop = atoi(value);
			break;
	    case 24: useVPS = atoi(value);
			break;
	    case 25: announceOnly = atoi(value);
			break;
	    case 26: useExtEPGInfo = atoi(value);
			break;
	    case 27: 
	    {
		if (!ParseExtEPGValues(value))
		{
		    esyslog("EPGSearch - ERROR reading ext. EPG values");
		    free(line);
		    return false;
		}
		break;
	    }
	    break;

        } //switch
      }
      parameter++;
    }
    if (*pos) pos++;
  } //while

  strreplace(directory, '|', ':');
  strreplace(search, '|', ':');
  while(strstr(search, "!^pipe^!"))
      strreplace(search, "!^pipe^!", "|");

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

char* cSearchExt::BuildFile(const cEvent* pEvent)
{
    char* file = NULL;

   if (!pEvent)
	return file;

    if (useEpisode)
    {
	char* pFile = NULL;
	if (!isempty(pEvent->ShortText()))
	    asprintf(&pFile, "%s~%s", pEvent->Title(), pEvent->ShortText());
	else
	    asprintf(&pFile, "%s~%s-%s", pEvent->Title(), GETDATESTRING(pEvent), GETTIMESTRING(pEvent));

	if (file) free(file);
	file = strdup(pFile);
	free(pFile);
    }
    else
	asprintf(&file, "%s", pEvent->Title());

    if (!isempty(directory))
    {
	char* pFile = NULL;

	if (strstri(directory, "%title%") == NULL &&
	    strstri(directory, "%subtitle%") == NULL)
	    asprintf(&pFile, "%s~%s", directory, file?file:"");
	else
	    // ignore existing title and subtitle in file if already used as variables in directory
	    asprintf(&pFile, "%s", directory);

	// replace ext. EPG variables
	cSearchExtCat* SearchExtCat = SearchExtCats.First();
	while (SearchExtCat) 
	{
	    char* tmp = NULL;
	    // search the category enclosed in %
	    asprintf(&tmp, "%%%s%%", SearchExtCat->name);
	    if (strstri(pFile, tmp))
	    {
		char* value = GetExtEPGValue(pEvent, SearchExtCat);
		if (value)
		    pFile = strreplacei(pFile, tmp, value);
	    }
	    free(tmp);
	    SearchExtCat = SearchExtCats.Next(SearchExtCat);
	}

	// replace Title, Subtitle, Channel vars
	pFile = strreplacei(pFile, "%title%", pEvent->Title()?pEvent->Title():"?");

	char* Subtitle = NULL;
	if (!isempty(pEvent->ShortText()))
	    asprintf(&Subtitle, "%s", pEvent->ShortText());
	else
	    asprintf(&Subtitle, "%s-%s", GETDATESTRING(pEvent), GETTIMESTRING(pEvent));
	pFile = strreplacei(pFile, "%subtitle%", Subtitle);
	free(Subtitle);
	
	cChannel *channel = Channels.GetByChannelID(pEvent->ChannelID(), true);
	if (channel)
	    pFile = strreplacei(pFile, "%channel%", CHANNELNAME(channel));

	if (file) free(file);
	file = strdup(pFile);
	free(pFile);
    }    
// replace some special chars
    if (file)
    {
	while(strstr(file, ":")) file = strreplace(file, ':', '|');
	while(strstr(file, " ~")) file = strreplace(file, " ~", "~");
	while(strstr(file, "~ ")) file = strreplace(file, "~ ", "~");
    }
    return file;
}

bool cSearchExt::ParseExtEPGValues(const char *s)
{
  char *line;
  char *pos;
  char *pos_next;
  int valuelen; 
  char value[MaxFileName];

  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;
	if (!ParseExtEPGEntry(value))
	{
	    esyslog("EPGSearch - ERROR reading ext. EPG value: %s", value);
	    free(line);
	    return false;
	}
      }
    }
    if (*pos) pos++;
  } //while

  free(line);
  return true;
}

bool cSearchExt::ParseExtEPGEntry(const char *s)
{
  char *line;
  char *pos;
  char *pos_next;
  int parameter = 1;
  int valuelen; 
  char value[MaxFileName];
  int currentid = -1;

  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:  
		currentid = atoi(value);
	      break;
	    case 2:  
		if (currentid > -1)
		{
		    int index = SearchExtCats.GetIndexFromID(currentid);
		    if (index > -1)
		    {
			while(strstr(value, "!^colon^!"))
			    strreplace(value, "!^colon^!", ":");
			while(strstr(value, "!^pipe^!"))
			    strreplace(value, "!^pipe^!", "|");
			strcpy(catvalues[index], value);
		    }
		}
	      break;
        } //switch
      }
      parameter++;
    }
    if (*pos) pos++;
  } //while

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

bool cSearchExt::Save(FILE *f)
{
  return fprintf(f, "%s", ToText()) > 0;
}

cEvent * cSearchExt::GetEventBySearchExt(const cSchedule *schedules, const cEvent *Start) 
{
  cEvent *pe = NULL;
  cEvent *p1=schedules->Events()->First();
  // first we have to search for the Start item
  if(Start) {
     cEvent *p;
     for (p = schedules->Events()->First(); p && p!=Start; p = schedules->Events()->Next(p));
     if(p)
        p1=schedules->Events()->Next(p);
     }

  time_t tNow=time(NULL);
  char* szTest = NULL;
  char* searchText = strdup(search);

  int searchStart = 0, searchStop = 0;
  if (useTime)
  {
	searchStart = startTime;
	searchStop = stopTime;
	if (searchStop < searchStart)
		searchStop += 2400;
  }
  int minSearchDuration = 0;
  int maxSearchDuration = 0;
  if (useDuration)
  {
      minSearchDuration = minDuration/100*60 + minDuration%100;
      maxSearchDuration = maxDuration/100*60 + maxDuration%100;
  }

  for (cEvent *p = p1; p; p = schedules->Events()->Next(p))
  {
     if(!p) 
     {
        break;
     }

     if (szTest)
     {
	free(szTest);
	szTest = NULL;
     }

     // ignore events without title
     if (!p->Title() || strlen(p->Title()) == 0)
	 continue;

     asprintf(&szTest, "%s%s%s%s%s", (useTitle?p->Title():""), (useSubtitle||useDescription)?"~":"",
	      (useSubtitle?p->ShortText():""),useDescription?"~":"",
	      (useDescription?p->Description():""));     
	
     if (tNow < p->EndTime()) 
     {	    
        if (!useCase)
        {
	    ToLower(szTest);
	    ToLower(searchText);
        }
	
 	if (strlen(szTest) > 0)
	{
	    if (!MatchesSearchMode(szTest, searchText, mode," ,;|~"))
		continue;
	}
        if (useTime)
        {
		time_t tEvent = p->StartTime();
		struct tm tmEvent;
		localtime_r(&tEvent, &tmEvent);
		int eventStart = tmEvent.tm_hour*100 + tmEvent.tm_min;	
		int eventStart2 = tmEvent.tm_hour*100 + tmEvent.tm_min + 2400;	
		if ((eventStart < searchStart || eventStart > searchStop) &&
		    (eventStart2 < searchStart || eventStart2 > searchStop))
			continue;
        }
        if (useDuration)
        {
		int duration = p->Duration()/60;
		if (minSearchDuration > duration || maxSearchDuration < duration)
		    continue;
        }

	if (useDayOfWeek)
	{
	    time_t tEvent = p->StartTime();
	    struct tm tmEvent;
	    localtime_r(&tEvent, &tmEvent);
	    if (DayOfWeek >= 0 && DayOfWeek != tmEvent.tm_wday)
		continue;
	    if (DayOfWeek < 0)
	    {	      
		int iFound = 0;
		for(int i=0; i<7; i++)
		    if (abs(DayOfWeek) & (int)pow(2,i) && i == tmEvent.tm_wday)
		    {
			iFound = 1;
			break;
		    }
		if (!iFound)
		    continue;
	    }
	}
	if (useExtEPGInfo && !MatchesExtEPGInfo(p))
	    continue;
	pe=p;
	break;
     }
  }
  if (szTest)
	free(szTest);
  free(searchText);
  return pe;
}

bool cSearchExt::MatchesExtEPGInfo(const cEvent* e)
{
    if (!e || !e->Description())
	return false;
    cSearchExtCat* SearchExtCat = SearchExtCats.First();
    while (SearchExtCat) 
    {
	char* value = NULL;
	int index = SearchExtCats.GetIndexFromID(SearchExtCat->id);
	if (index > -1)
	    value = catvalues[index];
	if (value && strlen(value) > 0)
	{
	    char* testvalue = GetExtEPGValue(e, SearchExtCat);
	    if (!testvalue)
		return false;

	    // compare not case sensitive
	    char* valueLower = strdup(value);
	    ToLower(valueLower);
	    ToLower(testvalue);
	    if (!MatchesSearchMode(testvalue, valueLower, SearchExtCat->searchmode, ",;|~"))
	    {
		free(testvalue);
		free(valueLower);
		return false;
	    }
	    free(testvalue);
	    free(valueLower);
	}
	SearchExtCat = SearchExtCats.Next(SearchExtCat);
    }
    return true;
}

// -- cSearchExts ----------------------------------------------------------------

cSearchExts::cSearchExts(void)
{
}

// --- cMenuEditSearchExt --------------------------------------------------------


cMenuEditSearchExt::cMenuEditSearchExt(cSearchExt *SearchExt, bool New)
:cOsdMenu(tr("Edit search"),30)
{
    asprintf(&SearchModes[0], tr("phrase"));
    asprintf(&SearchModes[1], tr("all words"));
    asprintf(&SearchModes[2], tr("at least one word"));
    asprintf(&SearchModes[3], tr("match exactly"));
    asprintf(&SearchModes[4], tr("regular expression"));
    
    asprintf(&DaysOfWeek[0], WeekDayName(0));
    asprintf(&DaysOfWeek[1], WeekDayName(1));
    asprintf(&DaysOfWeek[2], WeekDayName(2));
    asprintf(&DaysOfWeek[3], WeekDayName(3));
    asprintf(&DaysOfWeek[4], WeekDayName(4));
    asprintf(&DaysOfWeek[5], WeekDayName(5));
    asprintf(&DaysOfWeek[6], WeekDayName(6));
    asprintf(&DaysOfWeek[7], tr("user defined"));
    
    asprintf(&UseChannelSel[0], tr("no"));
    asprintf(&UseChannelSel[1], tr("interval"));
    asprintf(&UseChannelSel[2], tr("channel group"));
    
    searchExt = SearchExt;
    addIfConfirmed = New;
    if (searchExt) 
    {
	data = *searchExt;
	useTime = data.useTime;
	UserDefDayOfWeek = 0;
	if (searchExt->DayOfWeek < 0)
	{
	    UserDefDayOfWeek = searchExt->DayOfWeek;
	    data.DayOfWeek = 7;
	}

	menuitemsChGr = NULL;
	channelGroupName = NULL;

	channelMin = channelMax = cDevice::CurrentChannel();
	channelGroupNr = 0;  
	if (data.useChannel==1)
	{
	    channelMin = data.channelMin->Number();
	    channelMax = data.channelMax->Number();
	}
	if (data.useChannel==2)
	{
	    channelGroupNr = ChannelGroups.GetIndex(data.channelGroup);
	    if (channelGroupNr == -1)
	    {
		free(data.channelGroup);
		data.channelGroup = NULL;
		channelGroupNr = 0; // no selection
	    }
	    else
	    {
		channelGroupName = strdup(data.channelGroup);
		channelGroupNr++;
	    }
	}

	catselection = NULL;
	if (SearchExtCats.Count() > 0)
	{
	    // fill an array, that stores the type of category (editable or browsable)
	    catselection = (int*) malloc(SearchExtCats.Count() * sizeof(int));
	    cSearchExtCat *SearchExtCat = SearchExtCats.First();
	    int index = 0;
	    while (SearchExtCat) 
	    {
		catselection[index] = 0;
		for(int i=0; i<SearchExtCat->nvalues; i++)
		    if (strcmp(data.catvalues[index], SearchExtCat->values[i]) == 0)
		    {
			catselection[index] = i;
			break;
		    }
		SearchExtCat = SearchExtCats.Next(SearchExtCat);
		index++;
	    }
	}
	
	Set();
    }
}

void cMenuEditSearchExt::Set()
{
    int current = Current();
    Clear();
    
    Add(new cMenuEditStrItem( tr("Search term"), data.search, sizeof(data.search), tr(AllowedChars)));
    Add(new cMenuEditStraItem(tr("Search mode"),     &data.mode, 5, SearchModes));
    Add(new cMenuEditBoolItem( tr("Match case"), &data.useCase, tr("no"), tr("yes")));
    Add(new cMenuEditBoolItem( tr("Use title"), &data.useTitle, tr("no"), tr("yes")));
    Add(new cMenuEditBoolItem( tr("Use subtitle"), &data.useSubtitle, tr("no"), tr("yes")));
    Add(new cMenuEditBoolItem( tr("Use description"), &data.useDescription, tr("no"), tr("yes")));
    
    
    // show Categories only if we have them
    if (SearchExtCats.Count() > 0)
    {
	Add(new cMenuEditBoolItem( tr("Use extended EPG info"), &data.useExtEPGInfo, tr("no"), tr("yes")));
	if (data.useExtEPGInfo)
	{
	    cSearchExtCat *SearchExtCat = SearchExtCats.First();
	    int index = 0;
	    while (SearchExtCat) 
	    {
		if (SearchExtCat->nvalues > 0)
		    Add(new cMenuEditStraItem(IndentMenuItem(SearchExtCat->menuname), &catselection[index], SearchExtCat->nvalues, SearchExtCat->values));
		else
		    Add(new cMenuEditStrItem( IndentMenuItem(SearchExtCat->menuname), data.catvalues[index], MaxFileName, tr(AllowedChars)));
		SearchExtCat = SearchExtCats.Next(SearchExtCat);
		index++;
	    }
	}
    }
    
    Add(new cMenuEditStraItem(tr("Use channel"), &data.useChannel, 3, UseChannelSel));
    if (data.useChannel==1)
    {
	Add(new cMenuEditChanItem(tr("  from channel"),      &channelMin));
	Add(new cMenuEditChanItem(tr("  to channel"),      &channelMax));
    }
    if (data.useChannel==2)
    {
	// create the char array for the menu display
	CreateMenuitemsChannelGroups();
	int oldchannelGroupNr = channelGroupNr;
	channelGroupNr = ChannelGroups.GetIndex(channelGroupName);
	if (channelGroupNr == -1)
	{
	    if (oldchannelGroupNr > 0 && oldchannelGroupNr <= ChannelGroups.Count()) // perhaps its name was changed
		channelGroupNr = oldchannelGroupNr;
	    else
		channelGroupNr = 0; // no selection
	}
	else
	    channelGroupNr++;
	Add(new cMenuEditStraItem(IndentMenuItem(tr("Channel group")), &channelGroupNr, ChannelGroups.Count()+1, menuitemsChGr));
    }
    
    Add(new cMenuEditBoolItem( tr("Use time"), &useTime, tr("no"), tr("yes")));
    if (useTime == true)
    {
	Add(new cMenuEditTimeItem(tr("  Start after"),        &data.startTime));
	Add(new cMenuEditTimeItem(tr("  Start before"),         &data.stopTime));
    }
    Add(new cMenuEditBoolItem( tr("Use duration"), &data.useDuration, tr("no"), tr("yes")));
    if (data.useDuration == true)
    {
	Add(new cMenuEditTimeItem(tr("  Min. duration"), &data.minDuration));
	Add(new cMenuEditTimeItem(tr("  Max. duration"), &data.maxDuration));
    }
    Add(new cMenuEditBoolItem( tr("Use day of week"), &data.useDayOfWeek, tr("no"), tr("yes")));
    if (data.useDayOfWeek)
	Add(new cMenuEditStraItem(tr("  Day of week"),     &data.DayOfWeek, 8, DaysOfWeek));
    
    Add(new cMenuEditBoolItem( tr("Use as search timer"), &data.useAsSearchTimer, tr("no"), tr("yes")));
    if (data.useAsSearchTimer)
    {
	Add(new cMenuEditBoolItem(IndentMenuItem(tr("Announce only (no timer)")), &data.announceOnly, tr("no"), tr("yes")));
	Add(new cMenuEditBoolItem( tr("  Series recording"), &data.useEpisode, tr("no"), tr("yes")));
	Add(new cMenuEditStrItem(IndentMenuItem(tr("Directory")), data.directory, sizeof(data.directory), tr(AllowedChars)));
	Add(new cMenuEditIntItem(IndentMenuItem(tr("Priority")), &data.Priority, 0, MAXPRIORITY));
	Add(new cMenuEditIntItem(IndentMenuItem(tr("Lifetime")), &data.Lifetime, 0, MAXLIFETIME));
	Add(new cMenuEditIntItem(IndentMenuItem(tr("Setup.Recording$Margin at start (min)")), &data.MarginStart, -INT_MAX, INT_MAX));
	Add(new cMenuEditIntItem(IndentMenuItem(tr("Setup.Recording$Margin at stop (min)")), &data.MarginStop, -INT_MAX, INT_MAX));
	Add(new cMenuEditBoolItem(IndentMenuItem(tr("VPS")), &data.useVPS, tr("no"), tr("yes")));
    }
    SetCurrent(Get(current));
}

void cMenuEditSearchExt::CreateMenuitemsChannelGroups()
{
    if (menuitemsChGr)
	free(menuitemsChGr);
    menuitemsChGr = (char**) malloc((Count()+1) * sizeof(char*));
    cChannelGroup* ChannelGroup = ChannelGroups.First();
    menuitemsChGr[0] = strdup("");
    int index = 1;
    while (ChannelGroup) 
    {
	menuitemsChGr[index++] = ChannelGroup->name;
	ChannelGroup = ChannelGroups.Next(ChannelGroup);
    }
}

cMenuEditSearchExt::~cMenuEditSearchExt()
{
    if (searchExt && addIfConfirmed)
	delete searchExt; // apparently it wasn't confirmed
    if (catselection)
	free(catselection);
    if (menuitemsChGr)
	free(menuitemsChGr);
    if (channelGroupName)
	free(channelGroupName);
}

eOSState cMenuEditSearchExt::ProcessKey(eKeys Key)
{
    bool bHadSubMenu = HasSubMenu();

    int iTemp_useTime = useTime;
    int iTemp_useChannel = data.useChannel;
    int iTemp_useDuration = data.useDuration;
    int iTemp_useDayOfWeek = data.useDayOfWeek;
    int iTemp_useAsSearchTimer = data.useAsSearchTimer;
    int iTemp_useExtEPGInfo = data.useExtEPGInfo;
    
    eOSState state = cOsdMenu::ProcessKey(Key);
    
    if (useTime != iTemp_useTime ||
	iTemp_useChannel != data.useChannel ||
	iTemp_useDuration != data.useDuration ||
	iTemp_useDayOfWeek != data.useDayOfWeek ||
	iTemp_useAsSearchTimer != data.useAsSearchTimer ||
	iTemp_useExtEPGInfo != data.useExtEPGInfo)
    {
	Set();
	Display();
    }
    const char* ItemText = Get(Current())->Text();
    
    if (!HasSubMenu())
    {
	if (strlen(ItemText)>0 && strstr(ItemText, tr("  from channel")) == ItemText && ((Key >= k0 &&  Key <= k9) || Key == kLeft || Key == kRight))
	{
	    channelMax = channelMin;
	    Set();
	    Display();
	}
    }

    int iOnUserDefDayItem = 0;
    int iOnDirectoryItem = 0;
    int iOnUseChannelGroups = 0;
    int iOnChannelGroup = 0;
    if (!HasSubMenu() && strlen(ItemText)>0)
    {
	// check, if on an item of ext. EPG info
	int iOnExtCatItem = 0;
	cSearchExtCat *SearchExtCat = SearchExtCats.First();
	while (SearchExtCat) 
	{
	    if (SearchExtCat->nvalues == 0)
		if (strstr(ItemText, IndentMenuItem(SearchExtCat->menuname)) == ItemText)
		    iOnExtCatItem = 1;
	    SearchExtCat = SearchExtCats.Next(SearchExtCat);
	}
	
	if (strstr(ItemText, tr("  Day of week")) == ItemText)
	{
	    if (data.DayOfWeek == 7)
	    {
		SetHelp(tr("Edit"));
		iOnUserDefDayItem = 1;
	    }
	    else
		SetHelp(NULL);
	}
	else if (strstr(ItemText, IndentMenuItem(tr("Directory"))) == ItemText)
	{
	    if (!InEditMode(ItemText, IndentMenuItem(tr("Directory")), data.directory))
		SetHelp(NULL, NULL, NULL, tr("Select"));
	    iOnDirectoryItem = 1;
	}
	else if (strstr(ItemText, tr("Use channel")) == ItemText && data.useChannel == 2)
	{
	    SetHelp(NULL, NULL, NULL, tr("Setup"));
	    iOnUseChannelGroups = 1;
	}
	else if (strstr(ItemText, IndentMenuItem(tr("Channel group"))) == ItemText)
	{
	    SetHelp(NULL, NULL, NULL, tr("Setup"));
	    iOnChannelGroup = 1;
	}
	else if (strstr(ItemText, tr("Search term")) != ItemText && iOnExtCatItem == 0)
	    SetHelp(NULL, NULL, NULL, NULL);
    }
    if (state == osUnknown) {
	if (HasSubMenu())
	    return osContinue;
	switch (Key) {
	    case kOk: 
		if (data.useChannel==1) 
		{
		    cChannel *ch = Channels.GetByNumber(channelMin);
		    if (ch)
			data.channelMin = ch;
		    else 
		    {
			Skins.Message(mtError, tr("*** Invalid Channel ***"));
			break;
		    }
		    ch = Channels.GetByNumber(channelMax);
		    if (ch)
			data.channelMax = ch;
		    else 
		    {
			Skins.Message(mtError, tr("*** Invalid Channel ***"));
			break;
		    }
		    if (channelMin > channelMax)
		    {
			Skins.Message(mtError, tr("Please check channel criterions!"));
			return osContinue;
		    }
		}
		if (data.useChannel==2)
		    data.channelGroup = strdup(menuitemsChGr[channelGroupNr]);
		
		if ((data.useTitle || data.useSubtitle || data.useDescription) && 
		    (int(strlen(data.search)) < 3) && 
		    !Interface->Confirm(tr("Edit$Search text too short - use anyway?")))
		    break;
		
		data.useTime = useTime;
		
		if (searchExt) 
		{
		    *searchExt = data;
		    if (data.DayOfWeek == 7)
			searchExt->DayOfWeek = UserDefDayOfWeek;
		    
		    // transfer cat selection back to search
		    cSearchExtCat *SearchExtCat = SearchExtCats.First();
		    int index = 0;
		    while (SearchExtCat) 
		    {
			if (SearchExtCat->nvalues > 1)
			    strcpy(searchExt->catvalues[index], SearchExtCat->values[catselection[index]]);
			SearchExtCat = SearchExtCats.Next(SearchExtCat);
			index++;
		    }
		    
		    if (addIfConfirmed)
			SearchExts.Add(searchExt);
		    SearchExts.Save();
		    addIfConfirmed = false;
		}
		return osBack;
	    case kRed:
		if (iOnUserDefDayItem)
		    state = AddSubMenu(new cMenuEditDaysOfWeek(&UserDefDayOfWeek));
		break;
		
	    case kBlue:   
		if (iOnDirectoryItem && !InEditMode(ItemText, IndentMenuItem(tr("Directory")), data.directory))
		    state = AddSubMenu(new cMenuDirSelect(data.directory));
		if (iOnUseChannelGroups || iOnChannelGroup)
		{		    
		    if (channelGroupName)
			free(channelGroupName);
		    channelGroupName = strdup(menuitemsChGr[channelGroupNr]);
		    state = AddSubMenu(new cMenuChannelGroups(&channelGroupName));
		}
		break;
	    case kGreen:
	    case kYellow: state = osContinue;
	    default: break;
	}
    }
    if ((iOnUseChannelGroups || iOnChannelGroup) && bHadSubMenu && !HasSubMenu()) // return form Channelgroups setup
    {
	Set();
	Display();
    }
    return state;
}


 
// --- cMenuSearchExtItem ----------------------------------------------------------

class cMenuSearchExtItem : public cOsdItem {
  private:
public:
  cSearchExt* searchExt;
  cMenuSearchExtItem(cSearchExt* SearchExt);
  int Compare(const cListObject &ListObject) const;
  void Set(void);
  };

cMenuSearchExtItem::cMenuSearchExtItem(cSearchExt* SearchExt)
{
  searchExt = SearchExt;
  Set();
}

void cMenuSearchExtItem::Set(void)
{
  char *buffer = NULL;
  char *tmp[]  = { "", "", "", "", "" };

  if (searchExt->useAsSearchTimer)
      asprintf(&tmp[0], ">");

  if (searchExt->search && strlen(searchExt->search) > 0)
      asprintf(&tmp[1], "\t%s", searchExt->search);
  else
      asprintf(&tmp[1], "\t*");

  if (searchExt->useChannel == 1)
  {
      if (searchExt->channelMin != searchExt->channelMax)
	  asprintf(&tmp[2], "\t%d - %d", searchExt->channelMin->Number(),searchExt->channelMax->Number());
      else
	  asprintf(&tmp[2], "\t%.12s", (searchExt->useChannel?CHANNELNAME(searchExt->channelMin):""));
  }
  if (searchExt->useChannel == 2)
  {
      asprintf(&tmp[2], "\t%s", searchExt->channelGroup);
  }

  if (searchExt->useTime)
  {
	  asprintf(&tmp[3], "\t%02d:%02d", searchExt->startTime / 100, searchExt->startTime % 100);
	  asprintf(&tmp[4], "\t%02d:%02d", searchExt->stopTime / 100, searchExt->stopTime % 100);
  }
  else
  {
	  asprintf(&tmp[3], "\t--:--");
	  asprintf(&tmp[4], "\t--:--");
  }
	
  asprintf(&buffer, "%s%s%s%s%s", tmp[0], tmp[1], tmp[2], tmp[3], tmp[4]);
  SetText(buffer, false);
}

int cMenuSearchExtItem::Compare(const cListObject &ListObject) const
{
  cMenuSearchExtItem *p = (cMenuSearchExtItem *)&ListObject;
  return strcasecmp(searchExt->search, p->searchExt->search);
}

// --- cMenuEPGSearchExt ----------------------------------------------------------
cMenuEPGSearchExt::cMenuEPGSearchExt()
:cOsdMenu(tr("Search"), 2, 20, 12, 6, 5)
{
  cSearchExt *SearchExt = SearchExts.First();
  while (SearchExt) {
    Add(new cMenuSearchExtItem(SearchExt));
    SearchExt = SearchExts.Next(SearchExt);
  }

  SetHelp(tr("Edit"), tr("New"), tr("Delete"), tr("Button$Start"));
  Sort();
  Display();
}

cSearchExt *cMenuEPGSearchExt::CurrentSearchExt(void)
{
    cMenuSearchExtItem *item = (cMenuSearchExtItem *)Get(Current());
    return item ? item->searchExt : NULL;
}


eOSState cMenuEPGSearchExt::New(void)
{
  if (HasSubMenu())
    return osContinue;
  return AddSubMenu(new cMenuEditSearchExt(new cSearchExt, true));
}

eOSState cMenuEPGSearchExt::Delete(void)
{
  cSearchExt *curSearchExt = CurrentSearchExt();
  if (curSearchExt) {
    if (Interface->Confirm(tr("Edit$Delete search?"))) {
      SearchExts.Del(curSearchExt);
      SearchExts.Save();
      cOsdMenu::Del(Current());
      Display();
    }
  }
  return osContinue;
}

eOSState cMenuEPGSearchExt::Search(void)
{
    return AddSubMenu(new cMenuSearchResults(CurrentSearchExt()));
}


eOSState cMenuEPGSearchExt::OnOffSearchtimer(void)
{
  cSearchExt* search = CurrentSearchExt();
  if (search) 
  {
     search->useAsSearchTimer = search->useAsSearchTimer?0:1;
     SearchExts.Save();
     RefreshCurrent();
     DisplayCurrent(true);
  }
  return osContinue;
}

eOSState cMenuEPGSearchExt::ProcessKey(eKeys Key)
{
  int SearchNumber = HasSubMenu() ? Count() : -1;
  eOSState state = cOsdMenu::ProcessKey(Key);
  if (state == osUnknown) {
    switch (Key) {
	case k0:
	    OnOffSearchtimer();
	    break;	    
	case kOk:
	case kBlue:
	    if (HasSubMenu())
		return osContinue;
	    if (CurrentSearchExt())
		state = Search();
	    else
		state = osContinue;
	    break;
	case kRed:
 	  if (HasSubMenu())
	    return osContinue;
	  if (CurrentSearchExt())
	      state = AddSubMenu(new cMenuEditSearchExt(CurrentSearchExt()));
	  else
	      state = osContinue;
	  break;
     	case kGreen: state = New(); break;
        case kYellow: state = Delete(); break;
      default: break;
    }
  }
  if (SearchNumber >= 0 && !HasSubMenu()) 
  {      
      cSearchExt* search = SearchExts.Get(SearchNumber);
      if (search)       // a newly created search was confirmed with Ok
	  Add(new cMenuSearchExtItem(search));
      // always update all entries, since channel group names may have changed and affect other searches
      Sort();
      for(int i=0; i<Count(); i++)
      {
	  cMenuSearchExtItem *item = (cMenuSearchExtItem *)Get(i);
	  if (item)
	  {
	      item->Set();
	      if (item->searchExt == search)
		  SetCurrent(item);
	  }
      }
      Display();
  }

  return state;
}

cMenuEditDaysOfWeek::cMenuEditDaysOfWeek(int* DaysOfWeek, int Offset, bool Negate)
:cOsdMenu(tr("Edit user defined days of week"),30)
{
    int i=0;
    offset = Offset;
    negate = Negate;
    pDaysOfWeek = DaysOfWeek;

    if (negate)
	*pDaysOfWeek = -*pDaysOfWeek;

    for(i=0; i<7; i++)
	Days[(i+offset)%7]=((*pDaysOfWeek) & (int)pow(2,i))?1:0;
    for(i=0; i<7; i++)
	Add(new cMenuEditBoolItem( WeekDayName((i+1)%7), &Days[(i+1)%7], tr("no"), tr("yes")));

    SetHelp(NULL);
}

eOSState cMenuEditDaysOfWeek::ProcessKey(eKeys Key)
{
    int i=0;
    eOSState state = cOsdMenu::ProcessKey(Key);
    if (state == osUnknown) {
	switch (Key) {
	    case kOk:
		*pDaysOfWeek = 0;
		for(i=0; i<7; i++)
		    *pDaysOfWeek += Days[i]?(int)pow(2,(i+7-offset)%7):0;
		if (negate)
		    *pDaysOfWeek = -*pDaysOfWeek;
#if VDRVERSNUM && VDRVERSNUM < 10323
		*pDaysOfWeek |= 0x80000000;
#endif
		state = osBack;
	    default: break;
	}
    }
    return state;
}

