#include "tools.h"
#include "epgsearchext.h"
#include "epgsearchcats.h"
#include "epgsearchcfg.h"
#include "svdrpclient.h"
#include "distance.h"
#include "afuzzy.h"

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

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

#define MATCHLIMIT 0.9

int CompareEventTime(const void *p1, const void *p2)
{
  return (int)((*(cSearchResult **)p1)->event->StartTime() - (*(cSearchResult **)p2)->event->StartTime());
}

int CompareEventChannel(const void *p1, const void *p2)
{
  return (int)( Channels.GetByChannelID((*(cSearchResult **)p1)->event->ChannelID())->Number() - Channels.GetByChannelID((*(cSearchResult **)p2)->event->ChannelID())->Number());
}

char* IndentMenuItem(const char* szString)
{
    char* szIndented = NULL;
    asprintf(&szIndented, "  %s", szString);
    return szIndented;
}

// based on Nordlicht's EPG-Plugin
void PatchFont(eDvbFont DvbFont)
{
 const cFont *font = cFont::GetFont(DvbFont);
 int num_rows_global = font->Height();
 int num_rows = num_rows_global+2;
 int i,j,num_char;
 
 cFont::tPixelData* font_data = new cFont::tPixelData[225*(num_rows)];
 
 for (i = 0; i < 225; i++)
  for (j = 0; j < num_rows; j++)
   font_data[(i*num_rows)+j]=0x0000000000000000;
   
 font_data[0+0]=6;//font->CharData(0)->width;
 font_data[0+1]=num_rows_global;
 
 for(num_char=33, i = 1; num_char<256; i++, num_char++)
 {
  const cFont::tCharData* char_data = font->CharData(num_char);
  font_data[i*num_rows+0] = (num_char>=127 && num_char<=129)?6:char_data->width;
  font_data[i*num_rows+1] = char_data->height;

  if(num_char==130)
  {
   if(DvbFont==fontOsd)
   {
    font_data[i*num_rows+ 0]=6;
    font_data[i*num_rows+ 1]=23;
    font_data[i*num_rows+ 8]=0x3f; // ## ####
    font_data[i*num_rows+ 9]=0x3f; // ## ####
    font_data[i*num_rows+10]=0x30; // ## ....
    font_data[i*num_rows+11]=0x30; // ## ....
    font_data[i*num_rows+12]=0x30; // ## ....
    font_data[i*num_rows+13]=0x30; // ## ....
    font_data[i*num_rows+14]=0x30; // ## ....
    font_data[i*num_rows+15]=0x30; // ## ....
    font_data[i*num_rows+16]=0x30; // ## ....
    font_data[i*num_rows+17]=0x30; // ## ....
    font_data[i*num_rows+18]=0x30; // ## ....
    font_data[i*num_rows+19]=0x30; // ## ....
    font_data[i*num_rows+20]=0x30; // ## ....
    font_data[i*num_rows+21]=0x30; // ## ....
    font_data[i*num_rows+22]=0x30; // ## ....
    font_data[i*num_rows+23]=0x3f; // ## ####
    font_data[i*num_rows+24]=0x3f; // ## ####
   }
   else
   {
    font_data[i*num_rows+ 0]=5;
    font_data[i*num_rows+ 1]=19;
    font_data[i*num_rows+ 6]=0x1f; // # ####
    font_data[i*num_rows+ 7]=0x1f; // # ####
    font_data[i*num_rows+ 8]=0x18; // # #...
    font_data[i*num_rows+ 9]=0x18; // # #...
    font_data[i*num_rows+10]=0x18; // # #...
    font_data[i*num_rows+11]=0x18; // # #...
    font_data[i*num_rows+12]=0x18; // # #...
    font_data[i*num_rows+13]=0x18; // # #...
    font_data[i*num_rows+14]=0x18; // # #...
    font_data[i*num_rows+15]=0x18; // # #...
    font_data[i*num_rows+16]=0x18; // # #...
    font_data[i*num_rows+17]=0x18; // # #...
    font_data[i*num_rows+18]=0x18; // # #...
    font_data[i*num_rows+19]=0x1f; // # #...
    font_data[i*num_rows+20]=0x1f; // # ####
   }
  }
  else if(num_char==131)
  {
   if(DvbFont==fontOsd)
   {
    font_data[i*num_rows+ 0]=6;
    font_data[i*num_rows+ 1]=23;
    font_data[i*num_rows+ 8]=0x3f; // ## ####
    font_data[i*num_rows+ 9]=0x3f; // ## ####
    font_data[i*num_rows+10]=0x0;  // .. ....
    font_data[i*num_rows+11]=0x0;  // .. ....
    font_data[i*num_rows+12]=0x0;  // .. ....
    font_data[i*num_rows+13]=0x0;  // .. ....
    font_data[i*num_rows+14]=0x0;  // .. ....
    font_data[i*num_rows+15]=0x0;  // .. ....
    font_data[i*num_rows+16]=0x0;  // .. ....
    font_data[i*num_rows+17]=0x0;  // .. ....
    font_data[i*num_rows+18]=0x0;  // .. ....
    font_data[i*num_rows+19]=0x0;  // .. ....
    font_data[i*num_rows+20]=0x0;  // .. ....
    font_data[i*num_rows+21]=0x0;  // .. ....
    font_data[i*num_rows+22]=0x0;  // .. ....
    font_data[i*num_rows+23]=0x3f; // ## ####
    font_data[i*num_rows+24]=0x3f; // ## ####
   }
   else
   {
    font_data[i*num_rows+ 0]=5;
    font_data[i*num_rows+ 1]=19;
    font_data[i*num_rows+ 6]=0x1f; // # ####
    font_data[i*num_rows+ 7]=0x1f; // # ####
    font_data[i*num_rows+ 8]=0x0;  // . ....
    font_data[i*num_rows+ 9]=0x0;  // . ....
    font_data[i*num_rows+10]=0x0;  // . ....
    font_data[i*num_rows+11]=0x0;  // . ....
    font_data[i*num_rows+12]=0x0;  // . ....
    font_data[i*num_rows+13]=0x0;  // . ....
    font_data[i*num_rows+14]=0x0;  // . ....
    font_data[i*num_rows+15]=0x0;  // . ....
    font_data[i*num_rows+16]=0x0;  // . ....
    font_data[i*num_rows+17]=0x0;  // . ....
    font_data[i*num_rows+18]=0x0;  // . ....
    font_data[i*num_rows+19]=0x1f; // # ####
    font_data[i*num_rows+20]=0x1f; // # ####
   }
  }
  else if(num_char==132)
  {
   if(DvbFont==fontOsd)
   {
    font_data[i*num_rows+ 0]=6;
    font_data[i*num_rows+ 1]=23;
    font_data[i*num_rows+ 8]=0x3f; // ## ####
    font_data[i*num_rows+ 9]=0x3f; // ## ####
    font_data[i*num_rows+10]=0x03; // .. ..##
    font_data[i*num_rows+11]=0x03; // .. ..##
    font_data[i*num_rows+12]=0x03; // .. ..##
    font_data[i*num_rows+13]=0x03; // .. ..##
    font_data[i*num_rows+14]=0x03; // .. ..##
    font_data[i*num_rows+15]=0x03; // .. ..##
    font_data[i*num_rows+16]=0x03; // .. ..##
    font_data[i*num_rows+17]=0x03; // .. ..##
    font_data[i*num_rows+18]=0x03; // .. ..##
    font_data[i*num_rows+19]=0x03; // .. ..##
    font_data[i*num_rows+20]=0x03; // .. ..##
    font_data[i*num_rows+21]=0x03; // .. ..##
    font_data[i*num_rows+22]=0x03; // .. ..##
    font_data[i*num_rows+23]=0x3f; // ## ####
    font_data[i*num_rows+24]=0x3f; // ## ####
   }
   else
   {
    font_data[i*num_rows+ 0]=5;
    font_data[i*num_rows+ 1]=19;
    font_data[i*num_rows+ 6]=0x1f; // # ####
    font_data[i*num_rows+ 7]=0x1f; // # ####
    font_data[i*num_rows+ 8]=0x03; // . ..##
    font_data[i*num_rows+ 9]=0x03; // . ..##
    font_data[i*num_rows+10]=0x03; // . ..##
    font_data[i*num_rows+11]=0x03; // . ..##
    font_data[i*num_rows+12]=0x03; // . ..##
    font_data[i*num_rows+13]=0x03; // . ..##
    font_data[i*num_rows+14]=0x03; // . ..##
    font_data[i*num_rows+15]=0x03; // . ..##
    font_data[i*num_rows+16]=0x03; // . ..##
    font_data[i*num_rows+17]=0x03; // . ..##
    font_data[i*num_rows+18]=0x03; // . ..##
    font_data[i*num_rows+19]=0x1f; // # ####
    font_data[i*num_rows+20]=0x1f; // # ####
   }
  }
  else if(num_char==127)
  {
   if(DvbFont==fontOsd)
   {
    font_data[i*num_rows+ 0]=6;
    font_data[i*num_rows+ 1]=23;
    font_data[i*num_rows+ 8]=0x3f; // ## ####
    font_data[i*num_rows+ 9]=0x3f; // ## ####
    font_data[i*num_rows+10]=0x3f; // ## ####
    font_data[i*num_rows+11]=0x3f; // ## ####
    font_data[i*num_rows+12]=0x3f; // ## ####
    font_data[i*num_rows+13]=0x3f; // ## ####
    font_data[i*num_rows+14]=0x3f; // ## ####
    font_data[i*num_rows+15]=0x3f; // ## ####
    font_data[i*num_rows+16]=0x3f; // ## ####
    font_data[i*num_rows+17]=0x3f; // ## ####
    font_data[i*num_rows+18]=0x3f; // ## ####
    font_data[i*num_rows+19]=0x3f; // ## ####
    font_data[i*num_rows+20]=0x3f; // ## ####
    font_data[i*num_rows+21]=0x3f; // ## ####
    font_data[i*num_rows+22]=0x3f; // ## ####
    font_data[i*num_rows+23]=0x3f; // ## ####
    font_data[i*num_rows+24]=0x3f; // ## ####
   }
   else
   {
    font_data[i*num_rows+ 0]=5;
    font_data[i*num_rows+ 1]=19;
    font_data[i*num_rows+ 6]=0x1f; // ######
    font_data[i*num_rows+ 7]=0x1f; // # ####
    font_data[i*num_rows+ 8]=0x1f; // # ####
    font_data[i*num_rows+ 9]=0x1f; // # ####
    font_data[i*num_rows+10]=0x1f; // # ####
    font_data[i*num_rows+11]=0x1f; // # ####
    font_data[i*num_rows+12]=0x1f; // # ####
    font_data[i*num_rows+13]=0x1f; // # ####
    font_data[i*num_rows+14]=0x1f; // # ####
    font_data[i*num_rows+15]=0x1f; // # ####
    font_data[i*num_rows+16]=0x1f; // # ####
    font_data[i*num_rows+17]=0x1f; // # ####
    font_data[i*num_rows+18]=0x1f; // # ####
    font_data[i*num_rows+19]=0x1f; // # ####
    font_data[i*num_rows+20]=0x1f; // # ####
   }
  }
  else if (char_data->width > 0)
  {
   for(j = 0; j<(int)char_data->height; j++)
   {
    cFont::tPixelData Data = (cFont::tPixelData)char_data->lines[j];    
    font_data[(i*num_rows)+2+j] = Data;
   }
  }
 }
 
 cFont::SetFont(DvbFont, font_data);
}

#if VDRVERSNUM <= 10310    
time_t LastModifiedTime(const char *FileName)
{
  struct stat fs;
  if (stat(FileName, &fs) == 0)
     return fs.st_mtime;
  return 0;
}
#endif

bool MatchesSearchMode(char* szTest, char* searchText, int mode, const char* delim, int tolerance)
{
    if (strlen(szTest) > 0)
    {
	if (mode == 0) // substring
	    return (strstr(szTest, searchText) != NULL); 
	else if (mode == 1 || mode == 2) // AND or OR
	{ 
	    bool bTesting = false;
 	    char *pstrSearchToken, *pptr;
	    bool bFirst=true;
	    char *pstrSearch=strdup(searchText);
	    pstrSearchToken=strtok_r(pstrSearch, delim, &pptr);
	    while(pstrSearchToken) 
	    {
		if(szTest && strstr(szTest, pstrSearchToken)) 
		{ 
		    if(mode==1) 
		    { // means AND
			if(bFirst) 
			{
			    bTesting=true;
			    bFirst=false;
			} 
			else
			    bTesting&=true;
		    }
		    else
			bTesting|=true;
		} 
		else 
		{// not found!!
		    if(mode==1) 
		    { // means AND
			bTesting=false;
			bFirst=false;
		    }
		}
		pstrSearchToken=strtok_r(NULL, delim, &pptr);
	    }
	    free(pstrSearch);
	    return bTesting;
	}
	else if (mode == 3) // match exactly
	{
	    if (strcmp(szTest, searchText) == 0)
		return true;
	    else
		return false;
	} 
	else if (mode == 4)
	{
	    regex_t re;
	    bool bTesting = true;
	    if ( 0 == regcomp(&re, searchText, REG_EXTENDED) )
	    {
		if ( REG_NOMATCH == regexec( &re, szTest, 0, NULL, 0))
		{
		    regfree(&re);
		    return false;
		}
		else
		    bTesting = true;
		regfree(&re);
	    }
	    else
		bTesting = false;
	    return bTesting;
	}	
	else if (mode == 5)
	{
	    AFUZZY af = { NULL, NULL, NULL, NULL, NULL, NULL };
	    afuzzy_init(searchText, tolerance, 0, &af);
	    /* Checking substring */
	    int res = afuzzy_checkSUB(szTest, &af);
	    afuzzy_free(&af);
	    return (res > 0);
	}
    }
    return false;
}

void ToLower(char* szText)
{
    if (!szText)
	return;
    for (int loop = 0; szText[loop] !=0; loop++)
	szText[loop] = tolower(szText[loop]);
}

char* GetExtEPGValue(const cEvent* e, cSearchExtCat* SearchExtCat)
{
    if (!e || !SearchExtCat)
	return NULL;
    return GetExtEPGValue(e->Description(), SearchExtCat->name);
}

char* GetExtEPGValue(const char* description, const char* catname)
{
    if (isempty(description))
	return NULL;
    char* tmp = NULL;
    
    // search the category, must be at beginnig of a line
    asprintf(&tmp, "\n%s: ", catname);
    char* descr = strdup(description);
    char* cat = NULL;
    if ((cat = strstr(descr, tmp)) == NULL)
    {
	free(descr);
	free(tmp);
	return NULL;
    }
    else
	cat++; // skip linefeed

    // search the value to appear before the next line feed or end 
    char* end = strchr(cat, '\n');
    int endpos = strlen(cat);
    if (end)
	endpos = end - cat;
    cat[endpos] = 0;

    char* value = NULL;
    asprintf(&value, "%s", cat + strlen(tmp)-1);
    free(descr);
    free(tmp);

    return value;
}

#if VDRVERSNUM > 10343 

char* GetAuxValue(const char* aux, const char* name)
{
    if (isempty(aux))
	return NULL;
    
    char* descr = strdup(aux);
    char* beginaux = strstr(descr, "<epgsearch>");
    char* endaux = strstr(descr, "</epgsearch>");
    if (!beginaux || !endaux) return NULL;
    strn0cpy(descr, beginaux + strlen("<epgsearch>"), endaux - (beginaux + strlen("<epgsearch>")) + 1);

    // first try with new syntax (complete xml)
    char catname[100] = "";
    sprintf(catname, "<%s>", name);
    char* cat = NULL;
    if ((cat = strstr(descr, catname)) != NULL)
    {
	// search the value to appear before the next line feed or end 
	char* end = strstr(cat, "</");
	int endpos = strlen(cat);
	if (end)
	    endpos = end - cat;
	else
	{
	    free(descr);	
	    return NULL;
	}
	cat[endpos] = 0;
	
	char* value = NULL;
	asprintf(&value, "%s", cat + strlen(name)+2);
	free(descr);
	
	return value;
    }

    // second try with old syntax
    cat = NULL;
    if ((cat = strstr(descr, name)) == NULL)
    {
	free(descr);
	return NULL;
    }

    // search the value to appear before the next line feed or end 
    char* end = strchr(cat, '|');
    int endpos = strlen(cat);
    if (end)
	endpos = end - cat;
    cat[endpos] = 0;

    char* value = NULL;
    asprintf(&value, "%s", cat + strlen(name)+2); // 2 for ": "
    free(descr);

    return value;
}

char* GetAuxValue(cRecording *recording, const char* name)
{
    if (!recording || !recording->Info()) return NULL;
    return GetAuxValue(recording->Info()->Aux(), name);
}

char* GetAuxValue(cTimer *timer, const char* name)
{
    if (!timer || !timer->Aux()) return NULL;
    return GetAuxValue(timer->Aux(), name);
}

#endif

// replace s1 with s2 in s ignoring the case of s1
char *strreplacei(char *s, const char *s1, const char *s2)
{
    char *p = strcasestr(s, s1);
    if (p) {
	int of = p - s;
	int l  = strlen(s);
	int l1 = strlen(s1);
	int l2 = strlen(s2);
	if (l2 > l1)
	    s = (char *)realloc(s, strlen(s) + l2 - l1 + 1);
	if (l2 != l1)
	    memmove(s + of + l2, s + of + l1, l - of - l1 + 1);
	strncpy(s + of, s2, l2);
    }
    return s;
}

unsigned int strcharcount(char* source, char ch)
{
    unsigned int count;
    
    for (count = 0; *source; source++)
	if (*source == ch)
	    count++;
    return (count);
}

char *strreplaceall(char* source, char ch, char* replacement)
{
    int		replen;
    unsigned int	chnum;
    register char	*sptr, *nptr;
    char		*newval;
    
    chnum = strcharcount(source, ch);
    
    if (chnum == 0)
	return (NULL);
    
    replen = strlen(replacement);
    newval = (char*) malloc(strlen(source) + (chnum * replen) + 1);
    nptr = newval;
    for (sptr = source; *sptr; sptr++)
	if (*sptr == ch) {
	    strcpy(nptr, replacement);
	    nptr += replen;
	}
	else
	    *nptr++ = *sptr;
    *nptr = '\0';
    
    return (newval);
}

void sleepMSec(long ms)
{
#if VDRVERSNUM >= 10314
    cCondWait::SleepMs(ms);
#else
    usleep(1000 * ms);
#endif
}

void sleepSec(long s)
{
    sleepMSec(s * 1000);
}

bool SendViaSVDRP(const char* SVDRPcmd) 
{
    bool bSuccess = true;
    char *cmdbuf = NULL;
    if (EPGSearchConfig.useExternalSVDRP)
    {
	asprintf(&cmdbuf, "%s -p %d \"%s\"", 
		 SVDRPSendCmd,
		 EPGSearchConfig.SVDRPPort,
		 SVDRPcmd);

	FILE *p = popen(cmdbuf, "r");
	if (p)
	    pclose(p);
	else
	{
	    esyslog("EPGSearch: can't open pipe for command '%s'", cmdbuf);
	    bSuccess = false;
	}
    }
    else
    {
	asprintf(&cmdbuf, "%s", SVDRPcmd);
	cSVDRPClient client(EPGSearchConfig.SVDRPPort);
	if (!client.SendCmd(cmdbuf))
	{
	    esyslog("EPGSearch: command '%s' failed", cmdbuf);
	    bSuccess = false;
	}
    }
	    
    free(cmdbuf);
    return bSuccess;
}

void SendMsg(const char* Message) 
{
#if VDRVERSNUM < 10337
    char *cmdbuf = NULL;
    asprintf(&cmdbuf, "MESG %s", Message);
    SendViaSVDRP(cmdbuf);
    free(cmdbuf);
#else
    Skins.QueueMessage(mtInfo, Message, 5,0);
#endif
}


bool InEditMode(const char* ItemText, const char* ItemName, const char* ItemValue)
{
    bool bEditMode = true;
    // ugly solution to detect, if in edit mode
    char* value = strdup(ItemText);
    strreplace(value, ItemName, "");
    strreplace(value, ":\t", "");
    // for bigpatch
    strreplace(value, "\t", "");
    if (strlen(value) == strlen(ItemValue))
	bEditMode = false;
    free(value);
    return bEditMode;
}

// checks if the timer was triggered from a search timer and return a pointer to the search
cSearchExt* TriggeredFromSearchTimer(cTimer* timer)
{
#if VDRVERSNUM && VDRVERSNUM <= 10343
    if (!timer->Summary())
	return NULL;
    char* searchID = GetExtEPGValue(timer->Summary(), "S-ID");
#else
    char* searchID = GetAuxValue(timer, "S-ID");
#endif
    if (!searchID)
	return NULL;
    
    cSearchExt* search = SearchExts.GetSearchFromID(atoi(searchID));
    free(searchID);
    return search;
}

bool CopyFile(char* file_path_from,  char* file_path_to)
{
    FILE* f_from;		/* stream of source file. */
    FILE* f_to;			/* stream of target file. */
    char buf[MAXPARSEBUFFER+1];   /* input buffer.          */

    /* open the source and the target files. */
    f_from = fopen(file_path_from, "r");
    if (!f_from) {
	esyslog("Cannot open source file: %s", file_path_from);
	return false;
    }
    f_to = fopen(file_path_to, "w+");
    if (!f_to) {
	esyslog("Cannot open target file: %s", file_path_to);
	return false;
    }

    /* copy source file to target file, line by line. */
    while (fgets(buf, MAXPARSEBUFFER+1, f_from)) {
	if (fputs(buf, f_to) == EOF) {  /* error writing data */
	    esyslog("Error writing to target file: %s", file_path_to);
	    return false;
	}
    }
    if (!feof(f_from)) { /* fgets failed _not_ due to encountering EOF */
	esyslog("Error reading from source file: %s", file_path_from);
	return false;
    }

    /* close source and target file streams. */
    if (fclose(f_from) == EOF) {
	esyslog("Error when closing source file: %s", file_path_from);
	return false;
	fprintf(stderr, "Error when closing source file: ");
	perror("");
    }
    if (fclose(f_to) == EOF) {
	esyslog("Error when closing target file: %s", file_path_to);
	return false;
    }
    return true;
}

double FuzzyMatch(const char* s1, const char* s2, int maxLength)
{
    Distance D;
    int dist = D.LD (s1, s2, maxLength);
    double fMaxLength = max(strlen(s1), strlen(s2));
    return (fMaxLength - dist)/fMaxLength;
}

bool DescriptionMatches(const char* eDescr, const char* rDescr)
{
    if (eDescr == NULL && rDescr == NULL) return true;
    if (eDescr == NULL && rDescr != NULL) return false;
    if (eDescr != NULL && rDescr == NULL) return false;
    int l_eDescr = strlen(eDescr);
    int l_rDescr = strlen(rDescr);
    if (l_eDescr == l_rDescr && strcmp(eDescr, rDescr) == 0) return true;

    // partial match:
    // first check the length, should only be different at MATCHLIMIT
    int minLength = min(l_eDescr, l_rDescr);
    int maxLength = max(l_eDescr, l_rDescr);
    if (double(minLength)/double(maxLength) < MATCHLIMIT)
	return false;

    // last try with Levenshtein Distance, only compare the first 1000 chars 
    double fMatch = FuzzyMatch(eDescr, rDescr, 1000);
    if (fMatch > MATCHLIMIT)
    {
	LogFile.Log(2,"match is: %.2f %%", fMatch*100);
	return true;
    }
    return false;
}

const cEvent* GetEvent(cTimer* timer)
{
    const cEvent* event = NULL;
    const cChannel *channel = timer->Channel();
    time_t Time = timer->StartTime() + (timer->StopTime() - timer->StartTime()) / 2;
    for (int seconds = 0; seconds <= 3; seconds++) 
    {
	{
	    cSchedulesLock SchedulesLock;
	    const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
	    if (Schedules) {
		const cSchedule *Schedule = Schedules->GetSchedule(channel->GetChannelID());
		if (Schedule) {
		    event = Schedule->GetEventAround(Time);
		    if (event) return event; 
		}
	    }
	}
	if (seconds == 0)
	    LogFile.Log(2,"waiting for EPG info...");
	sleep(1);
    }
    LogFile.Log(1,"no EPG info available");
    return NULL;
}

// this extracts the real description from a given epg entry cutting all that looks like a category line
// we asume, that a category has a name not longer than MAXCATNAMELENGTH and a value not longer than 
// MAXCATVALUELENGTH (so in most cases e.g. the 'cast' category will stay part of the description). name and 
// value are separated with ': '
#define MAXCATNAMELENGTH 40
#define MAXCATVALUELENGTH 60
char* GetRawDescription(const char* descr)
{
    if (!descr) return NULL;

    char* rawDescr = NULL;
    const char* tmp = descr;    
    while(tmp)
    {
        // extract a single line
	char* line = strdup(tmp);
	char* lf = strchr(line, '\n');
	if (lf) *lf = 0;
	// check for category
	char* delim = strstr(line, ": ");
	if (!delim || 
	    (delim && (delim - line > MAXCATNAMELENGTH || strlen(line) - (delim - line) + 2 > MAXCATVALUELENGTH)))
	    if (strlen(line) > 0)
	    {
		if (rawDescr)
		{
		    char* tmp = strdup(rawDescr);
		    free(rawDescr); 
		    rawDescr = NULL;
		    asprintf(&rawDescr, "%s%s", tmp, line);
		    free(tmp);
		}
		else
		    rawDescr = strdup(line);
	    }
	
	if (lf) tmp += strlen(line)+1; else tmp = NULL;
	free(line);
    }
    return rawDescr;
}

void PrepareTimerFile(const cEvent* event, cTimer* timer)
{
    if (EPGSearchConfig.addSubtitleToTimer == addSubtitleNever)
	return;
    if (event && !isempty(event->ShortText())) // add subtitle if present
    {
	if (EPGSearchConfig.addSubtitleToTimer == addSubtitleSmart)
	{
	    if (event->Duration() > 80*60)
		return;
	}
	char tmp[MaxFileName] = "";
	sprintf(tmp, "%s~%s", event->Title(), event->ShortText());
	timer->SetFile(tmp);
    }
    if (event && strlen(EPGSearchConfig.defrecdir) > 0)
    {
	char directory[MaxFileName] = "";
	strn0cpy(directory, EPGSearchConfig.defrecdir,sizeof(directory));
	strcat(directory, "~");
	strcat(directory, timer->File());

	if (strchr(directory,'%'))
	{
	    char* tmpDirectory = strdup(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 (strcasestr(tmpDirectory, tmp))
		{
		    char* value = GetExtEPGValue(event, SearchExtCat);
		    if (value)
		    {
			char* tmp_dir = strdup(tmpDirectory);
			tmp_dir = strreplacei(tmp_dir, tmp, value);
			tmpDirectory = strcpyrealloc(tmpDirectory, tmp_dir);
			free(tmp_dir);
			free(value);
		    }
		}
		free(tmp);
		SearchExtCat = SearchExtCats.Next(SearchExtCat);
	    }

	    char* tmp_dir = strdup(tmpDirectory);
	    tmp_dir = strreplacei(tmp_dir, "%Title%", !isempty(event->Title())?event->Title():"?");
	    tmpDirectory = strcpyrealloc(tmpDirectory, tmp_dir);
	    free(tmp_dir);
	    char* Subtitle = NULL;
	    if (!isempty(event->ShortText()))
		Subtitle = strdup(event->ShortText());
	    else
		asprintf(&Subtitle, "%s-%s", GETDATESTRING(event), GETTIMESTRING(event));
	    tmp_dir = strdup(tmpDirectory);
	    tmp_dir = strreplacei(tmp_dir, "%Subtitle%", Subtitle);
	    tmpDirectory = strcpyrealloc(tmpDirectory, tmp_dir);
	    free(tmp_dir);
	    free(Subtitle);
	    cChannel *channel = Channels.GetByChannelID(event->ChannelID(), true);
	    if (channel)
		tmpDirectory = strreplacei(tmpDirectory, "%channel%", CHANNELNAME(channel));
	    
	    strn0cpy(directory, tmpDirectory, sizeof(directory));
	    free(tmpDirectory);
	}
	if (strchr(directory, '%') == NULL) // only set directory to new value if all categories could have been replaced
	    timer->SetFile(directory);
    }
}

bool EventsMatch(const cEvent* event1, const cEvent* event2, bool compareTitle, bool compareSubtitle, bool compareSummary, unsigned long catvaluesAvoidRepeat)
{
    if (!event1 || !event2) return false;
    if (event1 == event2) return true;

    const char* Title1    = event1->Title();
    const char* Subtitle1 = event1->ShortText();
    const char* Title2    = event2->Title();
    const char* Subtitle2 = event2->ShortText();

    bool match = false;
    if ((!compareTitle || (!Title1 && !Title2) || (Title1 && Title2 && strcmp(Title2, Title1) == 0)) &&
	(!compareSubtitle || (!Subtitle1 && !Subtitle2) || (Subtitle1 && Subtitle2 && strcmp(Subtitle2, Subtitle1) == 0)))
    {	
	const char* Descr1    = event1->Description();
	const char* Descr2    = event2->Description();
	if (compareSummary)
	{
	    char* rawDescr1 = GetRawDescription(Descr1);
	    char* rawDescr2 = GetRawDescription(Descr2);
	    match = DescriptionMatches(rawDescr1, rawDescr2);
	    free(rawDescr1);
	    free(rawDescr2);
	    if (!match) return false;
	}
	if (catvaluesAvoidRepeat != 0) // check categories
	{
	    bool bCatMatch = ((Descr1 && Descr2) || (!Descr1 && !Descr2));
	    cSearchExtCat *SearchExtCat = SearchExtCats.First();
	    int index = 0;
	    while (catvaluesAvoidRepeat > 0 && SearchExtCat && bCatMatch) 	    
	    {
		if (catvaluesAvoidRepeat & (1<<index))
		{
		    char* CatValue1 = GetExtEPGValue(Descr1, SearchExtCat->name);
		    char* CatValue2 = GetExtEPGValue(Descr2, SearchExtCat->name);
		    if ((!CatValue1 && CatValue2) ||
			(!CatValue2 && CatValue1) ||
			(CatValue1 && CatValue2 && strcmp(CatValue1, CatValue2) != 0))
			bCatMatch = false;
		    free(CatValue1);
		    free(CatValue2);
		}
		SearchExtCat = SearchExtCats.Next(SearchExtCat);
		index++;
	    }		    
	    if (bCatMatch)
		match = true;
	}
	else
	    match = true;
    }
    return match;
}

int ChannelNrFromEvent(const cEvent* pEvent)
{
    if (!pEvent)
	return -1;
    cChannel* channel = Channels.GetByChannelID(pEvent->ChannelID());
    if (!channel)
	return -1;
    else
	return channel->Number();						
}

int ChannelNrFromRecDone(cRecDone* recDone)
{
    if (!recDone)
	return -1;
    cChannel* channel = Channels.GetByChannelID(recDone->channelID);
    if (!channel)
	return -1;
    else
	return channel->Number();						
}

#if VDRVERSNUM <= 10324    
const char* TimeString(time_t t)
{
  static char buf[25] = "";
  struct tm tm_r;
  strftime(buf, sizeof(buf), "%R", localtime_r(&t, &tm_r));
  return buf;
}
#endif

#if VDRVERSNUM < 10318    
// --- cReadLine -------------------------------------------------------------
cReadLine::cReadLine(void)
{
  size = 0;
  buffer = NULL;
}

cReadLine::~cReadLine()
{
  free(buffer);
}

char *cReadLine::Read(FILE *f)
{
  int n = getline(&buffer, &size, f);
  if (n > 0) {
     n--;
     if (buffer[n] == '\n')
        buffer[n] = 0;
     return buffer;
     }
  return NULL;
}
#endif

void DelTimer(int index)
{
    char *cmdbuf = NULL;    
    asprintf(&cmdbuf, "DELT %d", index);    
    LogFile.Log(2, "delete timer %d", index);
    SendViaSVDRP(cmdbuf);
	      
    free(cmdbuf); 
}

char* FixSeparators(char* buffer, char sep)
{
    char tmp[5];
    sprintf(tmp, "%c\t", sep);
    buffer = strreplacei(buffer, tmp, "\t");
    sprintf(tmp, "%c \t", sep);
    buffer = strreplacei(buffer, tmp, "\t");
    sprintf(tmp, "%c ", sep);
    if (strstr(buffer, tmp) == buffer + strlen(buffer) - 2) buffer[strlen(buffer) - 2] = 0;
    if (buffer[strlen(buffer)-1] == sep) buffer[strlen(buffer) - 1] = 0;

    sprintf(tmp, "\t%c", sep);
    buffer = strreplacei(buffer, tmp, "\t");
    sprintf(tmp, "\t %c", sep);
    buffer = strreplacei(buffer, tmp, "\t");
    sprintf(tmp, "\t  %c", sep);
    buffer = strreplacei(buffer, tmp, "\t");
    return buffer;
}
