#include <string>
#include <vector>
#include <sstream>
#include <algorithm>
#include <vdr/channels.h>
#include <vdr/device.h>
#include "tools.h"
#include "searchtimer.h"

// -- cTVGuideSearchTimer -----------------------------------------------------------------
cTVGuideSearchTimer::cTVGuideSearchTimer(void) {
    strTimer = "";
    ID = -1;
    searchString = "";
    useTime = false;
    startTime = 0000;
    stopTime = 2359;
    useChannel = false;
#if VDRVERSNUM >= 20301
    {
    LOCK_CHANNELS_READ;
    channelMin = Channels->GetByNumber(cDevice::CurrentChannel());
    channelMax = Channels->GetByNumber(cDevice::CurrentChannel());
    }
#else
    channelMin = Channels.GetByNumber(cDevice::CurrentChannel());
    channelMax = Channels.GetByNumber(cDevice::CurrentChannel());
#endif
    channelGroup = "";
    useCase = false;
    mode = 0;
    useTitle = true;
    useSubtitle = true;
    useDescription = true;
    useDuration = false;
    minDuration = 0;
    maxDuration = 2359;
    useAsSearchTimer = true;
    useDayOfWeek = false;
    dayOfWeek = 0;
    directory = "";
    useEpisode = 0;
    priority = 50;
    lifetime = 99;
    marginStart = 5;
    marginStop = 5;
    useVPS = false;
    action = 0;
    useExtEPGInfo = 0;
    extEPGInfoValues = "";
    avoidRepeats = 1;
    compareTitle = 1;
    compareSubtitle = 1;
    compareSummary = 1;
    compareSummaryMatchInPercent = 90;
    compareDate = 0;
    allowedRepeats = 1;
    catvaluesAvoidRepeat = 0;
    repeatsWithinDays = 0;
    delAfterDays = 0;
    recordingsKeep = 0;
    switchMinsBefore = 1;
    pauseOnNrRecordings = 0;
    blacklistMode = 0;
    blacklists = "";
    fuzzyTolerance = 1;
    useInFavorites = 0;
    menuTemplate = 0;
    delMode = 0;
    delAfterCountRecs = 0;
    delAfterDaysOfFirstRec = 0;
    useAsSearchTimerFrom = 0;
    useAsSearchTimerTil = 0;
    ignoreMissingEPGCats = 0;
    unmuteSoundOnSwitch = 0;
    contentsFilter = "";
}

cTVGuideSearchTimer::~cTVGuideSearchTimer(void) {
}

bool cTVGuideSearchTimer::operator < (const cTVGuideSearchTimer& other) const {
    std::string searchStringOther = other.GetSearchString();
    searchStringOther = StrToLowerCase(searchStringOther);
    std::string thisSearchString = StrToLowerCase(searchString);
    int comp = thisSearchString.compare(searchStringOther);
    if (comp < 0)
        return true;
    return false;
}


void cTVGuideSearchTimer::SetTemplate(std::string tmpl) {
    std::stringstream searchTimerString;
    searchTimerString << "0:";
    searchTimerString << tmpl;
    strTimer = searchTimerString.str();
}

/*
    0 - unique search timer id
    1 - the search term
    2 - use time? 0/1
    3 - start time in HHMM
    4 - stop time in HHMM
    5 - use channel? 0 = no,  1 = Interval, 2 = Channel group, 3 = FTA only
    6 - if 'use channel' = 1 then channel id[|channel id] in VDR format,
        one entry or min/max entry separated with |, if 'use channel' = 2
        then the channel group name
    7 - match case? 0/1
    8 - search mode:
        0 - the whole term must appear as substring
        1 - all single terms (delimiters are blank,',', ';', '|' or '~')
            must exist as substrings.
        2 - at least one term (delimiters are blank, ',', ';', '|' or '~')
            must exist as substring.
        3 - matches exactly
        4 - regular expression   
    9 - use title? 0/1
    10 - use subtitle? 0/1
    11 - use description? 0/1
    12 - use duration? 0/1
    13 - min duration in hhmm
    14 - max duration in hhmm
    15 - use as search timer? 0/1
    16 - use day of week? 0/1
    17 - day of week (0 = Sunday, 1 = Monday...;
         -1 Sunday, -2 Monday, -4 Tuesday, ...; -7 Sun, Mon, Tue)
    18 - use series recording? 0/1
    19 - directory for recording
    20 - priority of recording
    21 - lifetime of recording
    22 - time margin for start in minutes
    23 - time margin for stop in minutes
    24 - use VPS? 0/1
    25 - action:
         0 = create a timer
         1 = announce only via OSD (no timer)
         2 = switch only (no timer)
         3 = announce via OSD and switch (no timer)
         4 = announce via mail
    26 - use extended EPG info? 0/1
    27 - extended EPG info values. This entry has the following format
         (delimiter is '|' for each category, '#' separates id and value):
         1 - the id of the extended EPG info category as specified in
             epgsearchcats.conf
         2 - the value of the extended EPG info category
             (a ':' will be translated to "!^colon^!", e.g. in "16:9")
    28 - avoid repeats? 0/1
    29 - allowed repeats
    30 - compare title when testing for a repeat? 0/1
    31 - compare subtitle when testing for a repeat? 0/1/2
         0 - no
         1 - yes
         2 - yes, if present
    32 - compare description when testing for a repeat? 0/1
    33 - compare extended EPG info when testing for a repeat?
         This entry is a bit field of the category IDs.
    34 - accepts repeats only within x days
    35 - delete a recording automatically after x days
    36 - but keep this number of recordings anyway
    37 - minutes before switch (if action = 2)
    38 - pause if x recordings already exist
    39 - blacklist usage mode (0 none, 1 selection, 2 all)
    40 - selected blacklist IDs separated with '|'
    41 - fuzzy tolerance value for fuzzy searching
    42 - use this search in favorites menu (0 no, 1 yes)
    43 - id of a menu search template
    44 - auto deletion mode (0 don't delete search timer, 1 delete after given
         count of recordings, 2 delete after given days after first recording)
    45 - count of recordings after which to delete the search timer
    46 - count of days after the first recording after which to delete the search
         timer
    47 - first day where the search timer is active (see parameter 16)
    48 - last day where the search timer is active (see parameter 16)
    49 - ignore missing EPG categories? 0/1
    50 - unmute sound if off when used as switch timer
    51 - percentage of match when comparing the summary of two events (with 'avoid repeats')
    52 - HEX representation of the content descriptors, each descriptor ID is represented with 2 chars
    53 - compare date when testing for a repeat? (0=no, 1=same day, 2=same week, 3=same month)
*/
bool cTVGuideSearchTimer::Parse(bool readTemplate) {
    splitstring s(strTimer.c_str());
    std::vector<std::string> values = s.split(':', 1);
    int numValues = values.size();
    if (numValues < 12)
        return false;
    for (int value = 0; value < numValues; value++) {
    	switch (value) {
            case 0:
                if (!readTemplate)
                    ID = atoi(values[value].c_str());
                break;
            case 1:
                if (!readTemplate) {
                    std::string searchStringMasked = values[value];
                    std::replace(searchStringMasked.begin(), searchStringMasked.end(), '|', ':');
                    searchString = searchStringMasked;
                }
                break;
            case 2:
                useTime = atoi(values[value].c_str());
                break;
            case 3:
                if (useTime) {
                    startTime = atoi(values[value].c_str());
                }
                break;
            case 4:
                if (useTime) {
                    stopTime = atoi(values[value].c_str());
                }
                break;
            case 5:
                useChannel = atoi(values[value].c_str());
                break;
            case 6:
                if (useChannel == 0) {
                    channelMin = NULL;
                    channelMax = NULL;
                } else if (useChannel == 1) {
                    int minNum = 0, maxNum = 0;
                    int fields = sscanf(values[value].c_str(), "%d-%d", &minNum, &maxNum);
                    if (fields == 0) { // stored with ID
                        char *channelMinbuffer = NULL;
                        char *channelMaxbuffer = NULL;
                        int channels = sscanf(values[value].c_str(), "%m[^|]|%m[^|]", &channelMinbuffer, &channelMaxbuffer);
#if VDRVERSNUM >= 20301
                        LOCK_CHANNELS_READ;
                        channelMin = Channels->GetByChannelID(tChannelID::FromString(channelMinbuffer), true, true);
#else
                        channelMin = Channels.GetByChannelID(tChannelID::FromString(channelMinbuffer), true, true);
#endif
                        if (!channelMin) {
                            esyslog("ERROR: channel '%s' not defined", channelMinbuffer);
                            channelMin = channelMax = NULL;
                            useChannel = 0;
                        }
                        if (channels == 1)
                            channelMax = channelMin;
                        else {
#if VDRVERSNUM >= 20301
                            channelMax = Channels->GetByChannelID(tChannelID::FromString(channelMaxbuffer), true, true);
#else
                            channelMax = Channels.GetByChannelID(tChannelID::FromString(channelMaxbuffer), true, true);
#endif
                            if (!channelMax) {
                                esyslog("ERROR: channel '%s' not defined", channelMaxbuffer);
                                channelMin = channelMax = NULL;
                                useChannel = 0;
                            }
                        }
                        free(channelMinbuffer);
                        free(channelMaxbuffer);
                    }
                } else if (useChannel == 2)
                    channelGroup = values[value];
                break;
            case 7:
           		useCase = atoi(values[value].c_str());
                break;
            case 8:  
                mode = atoi(values[value].c_str());
                break;
            case 9: 
                useTitle = atoi(values[value].c_str());
                break;
            case 10: 
                useSubtitle = atoi(values[value].c_str());
                break;
            case 11: 
                useDescription = atoi(values[value].c_str());
                break;
            case 12: 
                useDuration = atoi(values[value].c_str());
                break;
            case 13: 
                minDuration = atoi(values[value].c_str());
                break;
            case 14: 
                maxDuration = atoi(values[value].c_str());
                break;
            case 15: 
                useAsSearchTimer = atoi(values[value].c_str());
                break;
            case 16: 
                useDayOfWeek = atoi(values[value].c_str());
                break;
            case 17: 
                dayOfWeek = atoi(values[value].c_str());
                break;
            case 18: 
                useEpisode = atoi(values[value].c_str());
                break;
            case 19:  
                directory = values[value];
                break;
            case 20: 
                priority = atoi(values[value].c_str());
                break;
            case 21: 
                lifetime = atoi(values[value].c_str());
                break;
            case 22: 
                marginStart = atoi(values[value].c_str());
                break;
            case 23: 
                marginStop = atoi(values[value].c_str());
                break;
            case 24: 
                useVPS = atoi(values[value].c_str());
                break;
            case 25: 
                action = atoi(values[value].c_str());
                break;
            case 26: 
                useExtEPGInfo = atoi(values[value].c_str());
                break;
            case 27: 
                extEPGInfoValues = values[value];
                break;
            case 28: 
                avoidRepeats = atoi(values[value].c_str());
                break;
            case 29: 
                allowedRepeats = atoi(values[value].c_str());
                break;
            case 30: 
                compareTitle = atoi(values[value].c_str());
                break;
            case 31: 
                compareSubtitle = atoi(values[value].c_str());
                break;
            case 32: 
                compareSummary = atoi(values[value].c_str());
                break;
            case 33: 
                catvaluesAvoidRepeat = atol(values[value].c_str());
                break;
            case 34: 
                repeatsWithinDays = atoi(values[value].c_str());
                break;
            case 35: 
                delAfterDays = atoi(values[value].c_str());
                break;
            case 36:  
                recordingsKeep = atoi(values[value].c_str());
                break;
            case 37: 
                switchMinsBefore = atoi(values[value].c_str());
                break;
            case 38: 
                pauseOnNrRecordings = atoi(values[value].c_str());
                break;
            case 39: 
                blacklistMode = atoi(values[value].c_str());
                break;
            case 40:
                blacklists = values[value];
                break;
            case 41: 
                fuzzyTolerance = atoi(values[value].c_str());
                break;
            case 42: 
                useInFavorites = atoi(values[value].c_str());
                break;
            case 43: 
                menuTemplate = atoi(values[value].c_str());
                break;
            case 44: 
                delMode = atoi(values[value].c_str());
                break;
            case 45: 
                delAfterCountRecs = atoi(values[value].c_str());
                break;
            case 46: 
                delAfterDaysOfFirstRec = atoi(values[value].c_str());
                break;
            case 47:
                useAsSearchTimerFrom = atol(values[value].c_str());
                break;
            case 48:
                useAsSearchTimerTil = atol(values[value].c_str());
                break;
            case 49:
                ignoreMissingEPGCats = atoi(values[value].c_str());
                break;
            case 50:
                unmuteSoundOnSwitch = atoi(values[value].c_str());
                break;
            case 51:
                compareSummaryMatchInPercent = atoi(values[value].c_str());
                break;
            case 52:
                contentsFilter = values[value];
                break;
            case 53:
                compareDate = atoi(values[value].c_str());
                break;
            default:
                break;
        }
    }
    return true;
}

std::string cTVGuideSearchTimer::BuildSearchString(void) {
    std::stringstream search;
    // 0 - 2
    if (ID > -1)
        search << ID << ":";
    else
        search << ":";
    std::string searchStringMasked = searchString;
    std::replace(searchStringMasked.begin(), searchStringMasked.end(), ':', '|');
    search << searchStringMasked << ":"; 
    search << useTime << ":";
    
    // 3 - 6
    if (useTime) {
        search << *cString::sprintf("%04d", startTime) << ":";
        search << *cString::sprintf("%04d", stopTime) << ":";
    } else {
        search << "::";
    }

    search << useChannel << ":";
    if (useChannel == 1) {
        if (channelMin && channelMax) {
            if (channelMin->Number() < channelMax->Number())
                search << std::string(channelMin->GetChannelID().ToString()) << "|" << std::string(channelMax->GetChannelID().ToString()) << ":";
            else
                search << std::string(channelMin->GetChannelID().ToString()) << ":";
        } else {
            search << "0:";
        }
    } else if (useChannel == 2) {
        search << channelGroup << ":";
    } else {
        search << "0:";
    }
    // 7 - 14
    search << useCase << ":";
    search << mode << ":";
    search << useTitle << ":";
    search << useSubtitle << ":";
    search << useDescription << ":";
    search << useDuration << ":";
    if (useDuration) {
        search << *cString::sprintf("%04d", minDuration) << ":";
        search << *cString::sprintf("%04d", maxDuration) << ":";
    } else {
        search << "::";
    }
    //15 - 53
    search << useAsSearchTimer << ":";
    search << useDayOfWeek << ":";
    search << dayOfWeek << ":";
    search << useEpisode << ":";
    search << directory << ":";
    search << priority << ":";
    search << lifetime << ":";
    search << marginStart << ":";
    search << marginStop << ":";
    search << useVPS << ":";
    search << action << ":";
    search << useExtEPGInfo << ":";
    search << extEPGInfoValues << ":";
    search << avoidRepeats << ":";
    search << allowedRepeats << ":";
    search << compareTitle << ":";
    search << compareSubtitle << ":";
    search << compareSummary << ":";
    search << catvaluesAvoidRepeat << ":";
    search << repeatsWithinDays << ":";
    search << delAfterDays << ":";
    search << recordingsKeep << ":";
    search << switchMinsBefore << ":";
    search << pauseOnNrRecordings << ":";
    search << blacklistMode << ":";
    search << blacklists << ":";
    search << fuzzyTolerance << ":";
    search << useInFavorites << ":";
    search << menuTemplate << ":";
    search << delMode << ":";
    search << delAfterCountRecs << ":";
    search << delAfterDaysOfFirstRec << ":";
    search << useAsSearchTimerFrom << ":";
    search << useAsSearchTimerTil << ":";
    search << ignoreMissingEPGCats << ":";
    search << unmuteSoundOnSwitch << ":";
    search << compareSummaryMatchInPercent << ":";
    search << contentsFilter << ":";
    search << compareDate;

    strTimer = search.str();
    return strTimer;
}

bool cTVGuideSearchTimer::IsActive(void) {
    if (useAsSearchTimer)
        return true;
    return false;
}

int cTVGuideSearchTimer::GetNumTimers(void) {
    int numTimers = 0;
    if (ID < 0)
    	return numTimers;
#if VDRVERSNUM >= 20301
    LOCK_TIMERS_READ;
    for (const cTimer *timer = Timers->First(); timer; timer = Timers->Next(timer)) {
#else
    for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) {
#endif
        char* searchID = GetAuxValue(timer, "s-id");
        if (!searchID) continue;
      	if (ID == atoi(searchID))
        	numTimers++;
	    free(searchID);
    }
    return numTimers;
}

int cTVGuideSearchTimer::GetNumRecordings(void) {
    int numRecordings = 0;
    if (ID < 0)
        return numRecordings;
#if VDRVERSNUM >= 20301
    LOCK_RECORDINGS_READ;
    for (const cRecording *recording = Recordings->First(); recording; recording = Recordings->Next(recording)) {
#else
    for (cRecording *recording = Recordings.First(); recording; recording = Recordings.Next(recording)) {
#endif
        if (recording->IsEdited()) 
            continue;
        if (!recording->Info()) 
            continue;
        char* searchID = GetAuxValue(recording, "s-id");
        if (!searchID) continue;
        if (ID == atoi(searchID))
            numRecordings++;
        free(searchID);
    }
    return numRecordings;
}

void cTVGuideSearchTimer::GetSearchModes(std::vector<std::string> *searchModes) {
    searchModes->push_back(tr("whole term must appear"));
    searchModes->push_back(tr("all terms must exist"));
    searchModes->push_back(tr("one term must exist"));
    searchModes->push_back(tr("exact match"));
    searchModes->push_back(tr("regular expression"));
    searchModes->push_back(tr("fuzzy"));
}

void cTVGuideSearchTimer::GetUseChannelModes(std::vector<std::string> *useChannelModes) {
    useChannelModes->push_back(tr("No"));
    useChannelModes->push_back(tr("Interval"));
    useChannelModes->push_back(tr("Channel Group"));
    useChannelModes->push_back(tr("only FTA"));
}

void cTVGuideSearchTimer::GetCompareDateModes(std::vector<std::string> *compareDateModes) {
    compareDateModes->push_back(tr("No"));
    compareDateModes->push_back(tr("same day"));
    compareDateModes->push_back(tr("same week"));
    compareDateModes->push_back(tr("same month"));
}

void cTVGuideSearchTimer::GetSearchTimerModes(std::vector<std::string> *searchTimerModes) {
    searchTimerModes->push_back(tr("Record"));
    searchTimerModes->push_back(tr("Announce by OSD"));
    searchTimerModes->push_back(tr("Switch only"));
    searchTimerModes->push_back(tr("Announce and switch"));
    searchTimerModes->push_back(tr("Announce by mail"));
    searchTimerModes->push_back(tr("Inactive record"));
}

void cTVGuideSearchTimer::GetDelModes(std::vector<std::string> *delModes) {
    delModes->push_back(tr("no"));
    delModes->push_back(tr("count recordings"));
    delModes->push_back(tr("count days"));
}

void cTVGuideSearchTimer::Dump(void) {
    esyslog("tvguide searchtimer: strTimer: %s", strTimer.c_str());
    esyslog("tvguide searchtimer: ID: %d", ID);
    esyslog("tvguide searchtimer: searchString: %s", searchString.c_str());
    esyslog("tvguide searchtimer: useTime: %d", useTime);
    esyslog("tvguide searchtimer: startTime: %d", startTime);
    esyslog("tvguide searchtimer: stopTime: %d", stopTime);
    esyslog("tvguide searchtimer: useChannel: %d", useChannel);
    if (channelMin)
        esyslog("tvguide searchtimer: channelMin: %s", channelMin->Name());
    if (channelMax)
        esyslog("tvguide searchtimer: channelMax: %s", channelMax->Name());
    esyslog("tvguide searchtimer: channelGroup: %s", channelGroup.c_str());
    esyslog("tvguide searchtimer: useCase: %d", useCase);
    esyslog("tvguide searchtimer: mode: %d", mode);
    esyslog("tvguide searchtimer: useTitle: %d", useTitle);
    esyslog("tvguide searchtimer: useSubtitle: %d", useSubtitle);
    esyslog("tvguide searchtimer: useDescription: %d", useDescription);
}