/*                                                                  -*- c++ -*-
Copyright (C) 2004-2013 Christian Wieninger

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

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html

The author can be reached at cwieninger@gmx.de

The project's page is at http://winni.vdr-developer.org/epgsearch
*/

#include <vector>
#include <iostream>
#include <sstream>
#include <string>
#include "uservars.h"
#include "menu_whatson.h"
#include "epgsearchext.h"
#include "menu_event.h"
#include "menu_myedittimer.h"
#include "menu_searchresults.h"
#include "menu_search.h"
#include "menu_commands.h"
#include "epgsearchcfg.h"
#include "switchtimer.h"
#include "epgsearchcats.h"
#include "conflictcheck.h"
#include <math.h>
#include <vdr/menu.h>
#include "menu_conflictcheck.h"
#include "templatefile.h"
#include "menu_deftimercheckmethod.h"
#include "timerstatus.h"

#define HOURS(x) ((x)/100)
#define MINUTES(x) ((x)%100)

#define SKIPHOURS 20
#define HOURS2SECS(x) (x*60*60)

extern int exitToMainMenu;
extern bool isUTF8;
int gl_InfoConflict = 0;

// --- cMenuMyScheduleItem ------------------------------------------------------
cMenuMyScheduleItem::cMenuMyScheduleItem(const cTimers *Timers, const cEvent *Event, const cChannel *Channel, showMode Mode, cMenuTemplate* MenuTemplate)
{
    event = Event;
    channel = Channel;
    mode = Mode;
    timerMatch = tmNone;
    isRemote = false;
    inSwitchList = false;
    timerActive = false;
    menuTemplate = MenuTemplate;
    Update(Timers, true);
}

bool cMenuMyScheduleItem::Update(const cTimers* Timers, bool Force)
{
    if (!menuTemplate)
        return false;

    const char* menutemplate = menuTemplate->MenuTemplate();
    if (!menutemplate || strlen(menutemplate) == 0)
        return false;

    bool result = false;

    eTimerMatch OldTimerMatch = timerMatch;
    bool OldIsRemote = isRemote;
    bool OldInSwitchList = inSwitchList;
    bool OldtimerActive = timerActive;
    bool hasMatch = false;
    const cTimer* timer = NULL;
    if (event) timer = Timers->GetMatch(event, &timerMatch);
    if (event) inSwitchList = (SwitchTimers.InSwitchList(event) != NULL);
    if (timer) hasMatch = true;
    if (timer) timerActive = timer->HasFlags(tfActive);
    if (timer) isRemote = timer->Remote();

    if (Force || timerMatch != OldTimerMatch || inSwitchList != OldInSwitchList
        || isRemote != OldIsRemote || timerActive != OldtimerActive) {
        char szProgressPart[Utf8BufSize(12)] = "";
        char szProgressPartT2S[12] = "";
        time_t now = time(NULL);
        if (channel) {
            if (event) {
                time_t startTime = event->StartTime();
                if ((now - event->StartTime()) >= 0 || strstr(menutemplate, "%time%") != NULL) {
                    int frac = 0;
                    if (mode == showNow) {
                        int dur = event->Duration();
                        if (dur != 0)
                            frac = ((now - startTime) * 8 + (dur >> 1)) / dur;
                    }
                    if (mode == showNext)
                        frac = ((30 * 60 - std::min((time_t)30 * 60, startTime - now)) * 8 + 15 * 60) / (30 * 60);

                    frac = std::min(8, std::max(0, frac));

                    szProgressPartT2S[0] = '[';
                    memset(szProgressPartT2S + 1, '|', frac);
                    memset(szProgressPartT2S + 1 + frac , ' ', 8 - frac);
                    szProgressPartT2S[9] = ']';
                    szProgressPartT2S[10] = 0;

                    if (!isUTF8) {
                        szProgressPart[0] = ICON_BAR_OPEN;
                        memset(szProgressPart + 1, ICON_BAR_EMPTY, 6);
                        szProgressPart[7] = ICON_BAR_CLOSE;
                        szProgressPart[8] = 0;
                        memset(szProgressPart, ICON_BAR_FULL, frac ? frac : sizeof(szProgressPart));
                    } else {
#if defined(__GNUC__) && __GNUC__ < 3 && __GNUC_MINOR__ < 96
#else
                        std::stringstream buffer;
                        buffer << ICON_BAR_OPEN_UTF8;
                        for (int i = 0; i < 8; i++) buffer << (i < frac ? ICON_BAR_FULL_UTF8 : ICON_BAR_EMPTY_UTF8);
                        buffer << ICON_BAR_CLOSE_UTF8;
                        char* temp = strdup(buffer.str().c_str());
                        sprintf(szProgressPart, "%s", temp);
                        free(temp);
#endif
                    }
                } else {
                    strncpy(szProgressPart, *event->GetTimeString(), 12);
                    szProgressPart[11] = 0;
                    memcpy(szProgressPartT2S, szProgressPart, 12);
                }
            }
        }

        char t[Utf8BufSize(2)], v[Utf8BufSize(2)], r[Utf8BufSize(2)];
        char szStatus[Utf8BufSize(4)];
        szStatus[3] = 0;
        t[1] = v[1] = r[1] = 0;

        if (EPGSearchConfig.WarEagle) {
            if (!isUTF8) {
                t[0] = event && hasMatch ? (timerMatch == tmFull) ? ((timer && timer->Recording()) ? ICON_REC : (timerActive ? ICON_CLOCK : ICON_TIMER_INACT)) : (timerActive ? ICON_CLOCK_HALF : ' ') : ' ';
                v[0] = event && event->Vps() && (event->Vps() - event->StartTime()) ? ICON_VPS : ' ';
                r[0] = event && event->IsRunning() ? ICON_RUNNING : ' ';
            } else {
#if defined(__GNUC__) && __GNUC__ < 3 && __GNUC_MINOR__ < 96
#else
                sprintf(t, "%s", (event && hasMatch ? (timerMatch == tmFull) ? ((timer && timer->Recording()) ? ICON_REC_UTF8 : (timerActive ? ICON_CLOCK_UTF8 : ICON_TIMER_INACT_UTF8)) : (timerActive ? ICON_CLOCK_HALF_UTF8 : " ") : " "));
                sprintf(v, "%s", event && event->Vps() && (event->Vps() - event->StartTime()) ? ICON_VPS_UTF8 : " ");
                sprintf(r, "%s", (event && event->IsRunning() ? ICON_RUNNING_UTF8 : " "));
#endif
            }
        } else {
            t[0] = event && hasMatch ? (timerMatch == tmFull) ? ((timer && timer->Recording()) ? 'R' : (timerActive ? 'T' : 'i')) : (timerActive ? 't' : ' ') : ' ';
            v[0] = event && event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' ';
            r[0] = event && event->IsRunning() ? '*' : ' ';
        }

        if (event && inSwitchList) {
            cSwitchTimer* s = SwitchTimers.InSwitchList(event);
            t[0] = (s && s->mode == 1) ? 's' : 'S';
        }
        if (EPGSearchConfig.WarEagle && isUTF8) {
            std::stringstream buffer;
            buffer << t << v << r;
            char* temp = strdup(buffer.str().c_str());
            sprintf(szStatus, "%s", temp);
            free(temp);
        } else {
            szStatus[0] = t[0];
            szStatus[1] = v[0];
            szStatus[2] = r[0];
        }

        char* buffer = strdup(menutemplate);
        strreplace(buffer, '|', '\t');

        char* title = NULL;
        title = strdup(event ? event->Title() : tr(">>> no info! <<<"));

        title = strreplacei(title, ":", "%colon%"); // assume a title has the form "a?b:c",
        // we need to replace the colon to avoid misinterpretation of the expression as a condition
        buffer = strreplacei(buffer, "%title%", title);
        free(title);

        if (channel) {
            char szChannelNr[6] = "";
            snprintf(szChannelNr, 6, "%d", channel->Number());
            buffer = strreplacei(buffer, "%chnr%", szChannelNr);
            buffer = strreplacei(buffer, "%chsh%", channel->ShortName(true));
            buffer = strreplacei(buffer, "%chlng%", channel->Name());
            buffer = strreplacei(buffer, "%progr%", szProgressPart);
            buffer = strreplacei(buffer, "%progrT2S%", szProgressPartT2S);
        }

        // parse the epxression and evaluate it
        cVarExpr varExpr(buffer);
        char* tmp = strdup(varExpr.Evaluate(event).c_str());
        free(buffer);
        buffer = tmp;

        buffer = strreplacei(buffer, "$status$", szStatus);
        buffer = strreplacei(buffer, "$t_status$", t);
        buffer = strreplacei(buffer, "$v_status$", v);
        buffer = strreplacei(buffer, "$r_status$", r);

        buffer = FixSeparators(buffer, '~');
        buffer = FixSeparators(buffer, ':');
        buffer = FixSeparators(buffer, '-');

        SetText(buffer, false);

        if (gl_InfoConflict == 0 && EPGSearchConfig.checkTimerConflAfterTimerProg && !Force && timer && ((timerMatch && timerMatch != OldTimerMatch) || (isRemote != OldIsRemote))) {
            cConflictCheck C;
            C.Check();
            if (C.TimerInConflict(timer))
                gl_InfoConflict = 1;
        }
        return true;
    }
    return result;
}

void cMenuMyScheduleItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable)
{
    bool withDate = (channel == NULL); // search for a better way to determine this
    if (!DisplayMenu->SetItemEvent(event, Index, Current, Selectable, channel, withDate, timerMatch))
        DisplayMenu->SetItem(Text(), Index, Current, Selectable);
}

// --- cMenuMyScheduleSepItem ------------------------------------------------------
cMenuMyScheduleSepItem::cMenuMyScheduleSepItem(const cTimers *Timers, const cEvent *Event, const cChannel *Channel)
    : cMenuMyScheduleItem(Timers, Event, Channel, showNow, NULL)
{
    event = Event;
    channel = Channel;
    dummyEvent = NULL;
    SetSelectable(false);
    Update(Timers, true);
}

cMenuMyScheduleSepItem::~cMenuMyScheduleSepItem()
{
    if (dummyEvent)
        delete dummyEvent;
}

bool cMenuMyScheduleSepItem::Update(const cTimers *Timers, bool Force)
{
    if (channel)
        SetText(cString::sprintf("%s\t %s %s", MENU_SEPARATOR_ITEMS, channel->Name(), MENU_SEPARATOR_ITEMS));
    else if (event) {
        dummyEvent = new cEvent(0);
        dummyEvent->SetTitle(cString::sprintf("%s\t %s %s", MENU_SEPARATOR_ITEMS, GETDATESTRING(event), MENU_SEPARATOR_ITEMS));
        SetText(dummyEvent->Title());
    }
    return true;
}

void cMenuMyScheduleSepItem::SetMenuItem(cSkinDisplayMenu *DisplayMenu, int Index, bool Current, bool Selectable)
{
    bool withDate = (channel == NULL); // search for a better way to determine this
    if (!DisplayMenu->SetItemEvent(dummyEvent, Index, Current, Selectable, channel, withDate, timerMatch))
        DisplayMenu->SetItem(Text(), Index, Current, Selectable);
}


// --- cMenuWhatsOnSearch ----------------------------------------------------------

int cMenuWhatsOnSearch::currentChannel = 0;
showMode cMenuWhatsOnSearch::currentShowMode = showNow;
const cChannel *cMenuWhatsOnSearch::scheduleChannel = NULL;
extern const char *ShowModes[];
cList<cShowMode> cMenuWhatsOnSearch::showModes;
time_t cMenuWhatsOnSearch::seekTime = 0;
int cMenuWhatsOnSearch::shiftTime = 0;

cMenuWhatsOnSearch::cMenuWhatsOnSearch(int CurrentChannelNr)
    : cOsdMenu("", GetTab(1), GetTab(2), GetTab(3), GetTab(4), GetTab(5))
{
    if (currentShowMode == showNow)
        SetMenuCategory(mcScheduleNow);
    else if (currentShowMode == showNext)
        SetMenuCategory(mcScheduleNext);
    else
        SetMenuCategory(mcSchedule);

    helpKeys = -1;
    shiftTime = 0;

    CreateShowModes();

    LoadSchedules();

    currentChannel = CurrentChannelNr;

    SetHelpKeys();
}

cMenuWhatsOnSearch::~cMenuWhatsOnSearch()
{
}

#ifdef USE_GRAPHTFT
const char* cMenuWhatsOnSearch::MenuKind()
{
    if (currentShowMode == showNow)  return "MenuEpgsWhatsOnNow";
    if (currentShowMode == showNext) return "MenuEpgsWhatsOnNext";
    if (currentShowMode > showNext)  return "MenuEpgsWhatsOnElse";
    else return "MenuWhatsOnElse";
}

void cMenuWhatsOnSearch::Display(void)
{
    cOsdMenu::Display();

    if (Count() > 0) {
        int i = 0;

        for (cOsdItem *item = First(); item; item = Next(item))
            cStatus::MsgOsdEventItem(!item->Selectable() ? 0 :
                                     ((cMenuMyScheduleItem*)item)->event,
                                     item->Text(), i++, Count());
    }
}
#endif /* GRAPHTFT */

int cMenuWhatsOnSearch::GetTab(int Tab)
{
    if (currentShowMode == showNow)
        return cTemplFile::GetTemplateByName("MenuWhatsOnNow")->Tab(Tab - 1);
    if (currentShowMode == showNext)
        return cTemplFile::GetTemplateByName("MenuWhatsOnNext")->Tab(Tab - 1);
    if (currentShowMode > showNext)
        return cTemplFile::GetTemplateByName("MenuWhatsOnElse")->Tab(Tab - 1);
    else
        return 0;
}

void cMenuWhatsOnSearch::LoadSchedules()
{
    Clear();
    eventObjects.Clear();

    //   time_t SeekTime;
    cString szTitle;
    cShowMode* mode = GetShowMode(currentShowMode);
    if (!mode) return;

    if (shiftTime != 0) {
        if (currentShowMode == showNow || currentShowMode == showNext)
            seekTime = time(NULL);
        else {
            if (mode)
                seekTime = GetTimeT(mode->GetTime());
            if (seekTime < time(NULL)) seekTime += HOURS2SECS(24);
        }
        seekTime += shiftTime * 60;

        struct tm tm_r;
        time_t now = time(NULL);

        tm tm_seek = *localtime_r(&seekTime, &tm_r);
        tm tm_now = *localtime_r(&now, &tm_r);
        if (tm_seek.tm_mday != tm_now.tm_mday)
            szTitle = cString::sprintf("%s - %s", tr("Overview"), DAYDATETIME(seekTime));
        else
            szTitle = cString::sprintf("%s - %02d:%02d", tr("Overview"), tm_seek.tm_hour, tm_seek.tm_min);
    } else {
        seekTime = GetTimeT(mode->GetTime());
        if (seekTime < time(NULL) && currentShowMode != showNow && currentShowMode != showNext) {
            seekTime += HOURS2SECS(24);
            szTitle = cString::sprintf("%s - %s (%s)", tr("Overview"), mode->GetDescription(), *WeekDayName(seekTime));
        } else
            szTitle = cString::sprintf("%s - %s", tr("Overview"), mode->GetDescription());
    }
    SetTitle(szTitle);

    cMenuTemplate* currentTemplate = NULL;
    if (currentShowMode == showNow)
        currentTemplate = cTemplFile::GetTemplateByName("MenuWhatsOnNow");
    if (currentShowMode == showNext)
        currentTemplate = cTemplFile::GetTemplateByName("MenuWhatsOnNext");
    if (currentShowMode > showNext)
        currentTemplate = cTemplFile::GetTemplateByName("MenuWhatsOnElse");

    int maxChannel = EPGSearchConfig.maxChannelMenuNow;
    if (currentChannel > maxChannel)
        maxChannel = 0;

    LOCK_TIMERS_READ; // needed in MyScheduleItem
    LOCK_CHANNELS_READ;
    for (const cChannel *Channel = Channels->First(); Channel; Channel = Channels->Next(Channel)) {
        if (!Channel->GroupSep()) {
            if (maxChannel && Channel->Number() > maxChannel) break;
            if (EPGSearchConfig.showRadioChannels == 0 && ISRADIO(Channel))
                continue;

            LOCK_SCHEDULES_READ;
            const cSchedule *Schedule;
            Schedule = Schedules->GetSchedule(Channel);
            const cEvent *Event = NULL;
            if (Schedule) {
                if (shiftTime != 0)
                    Event = Schedule->GetEventAround(seekTime);
                else {
                    switch (currentShowMode) {
                    default:
                    case showNow:
                        Event = Schedule->GetPresentEvent();
                        break;
                    case showNext:
                        Event = Schedule->GetFollowingEvent();
                        break;
                    case showUserMode1:
                    case showUserMode2:
                    case showUserMode3:
                    case showUserMode4:
                        Event = Schedule->GetEventAround(seekTime);
                        break;
                    }
                }
            }
            if (!EPGSearchConfig.showEmptyChannels && !Event)
                continue;

            Add(new cMenuMyScheduleItem(Timers, Event, Channel, currentShowMode, currentTemplate), Channel->Number() == currentChannel);
            if (Event) eventObjects.Add(Event);
        } else {
            if (EPGSearchConfig.showChannelGroups && strlen(Channel->Name()))
                Add(new cMenuMyScheduleSepItem(NULL, NULL, Channel));
        }
    }
}

time_t cMenuWhatsOnSearch::GetTimeT(int iTime)
{
    struct tm tm_r;
    time_t t = time(NULL);
    tm* tmnow = localtime_r(&t, &tm_r);
    tmnow->tm_hour = HOURS(iTime);
    tmnow->tm_min = MINUTES(iTime);
    return mktime(tmnow);
}

showMode cMenuWhatsOnSearch::GetNextMode()
{
    showMode nextShowMode = currentShowMode;
    cShowMode* Mode = GetShowMode(currentShowMode);
    if (Mode) {
        cShowMode* ModeNext = showModes.Next(Mode);
        if (ModeNext == NULL)
            nextShowMode = showNow;
        else
            nextShowMode = ModeNext->GetMode();
    } else // no mode found? fall back to 'now'
        nextShowMode = showNow;
    return nextShowMode;
}

void cMenuWhatsOnSearch::CreateShowModes()
{
    showModes.Clear();

    cShowMode* ModeNow = new cShowMode(showNow, trVDR("Button$Now"));
    showModes.Add(ModeNow);
    cShowMode* ModeNext = new cShowMode(showNext, trVDR("Button$Next"));
    showModes.Add(ModeNext);

    time_t now = time(NULL);
    for (int i = showUserMode1; i < showModeMax; i++) {
        if (!EPGSearchConfig.ShowModes[i].GetUsage())
            continue;

        time_t SeekTime = GetTimeT(EPGSearchConfig.ShowModes[i].GetTime());
        if (SeekTime < now)
            SeekTime += HOURS2SECS(24);
        if (SeekTime - now > HOURS2SECS(SKIPHOURS))
            continue;
        cShowMode* Mode = new cShowMode((showMode)i, EPGSearchConfig.ShowModes[i].GetDescription(),
                                        1, EPGSearchConfig.ShowModes[i].GetTime(), SeekTime);
        showModes.Add(Mode);
    }
    if (EPGSearchConfig.showFavoritesMenu) {
        cShowMode* ModeFav = new cShowMode(showFavorites, tr("Button$Favorites"));
        showModes.Add(ModeFav);
    }
    showModes.Sort();
}

cShowMode* cMenuWhatsOnSearch::GetShowMode(showMode mode)
{
    for (cShowMode *showMode = showModes.First(); showMode; showMode = showModes.Next(showMode))
        if (mode == showMode->GetMode())
            return showMode;
    return NULL;
}

const cChannel *cMenuWhatsOnSearch::ScheduleChannel(const cChannel *force_channel)
{
    const cChannel *ch = force_channel ? force_channel : scheduleChannel;
    scheduleChannel = NULL;
    return ch;
}


eOSState cMenuWhatsOnSearch::Switch(void)
{
    cMenuMyScheduleItem *item = (cMenuMyScheduleItem *)Get(Current());
    if (item && item->channel) {
        if (cDevice::PrimaryDevice()->SwitchChannel(item->channel, true))
            return osEnd;
    }
    INFO(trVDR("Can't switch channel!"));
    return osContinue;
}

eOSState cMenuWhatsOnSearch::Record(void)
{
    cMenuMyScheduleItem *item = (cMenuMyScheduleItem *)Get(Current());
    if (item) {
        LOCK_TIMERS_WRITE;
        Timers->SetExplicitModify();
        if (item->timerMatch == tmFull) {
            eTimerMatch tm = tmNone;
            cTimer *timer = Timers->GetMatch(item->event, &tm);
            if (timer) {
                if (EPGSearchConfig.useVDRTimerEditMenu)
                    return AddSubMenu(new cMenuEditTimer(timer));
                else
                    return AddSubMenu(new cMenuMyEditTimer(timer, false, item->event, item->channel));
            }
        }

        cTimer *timer = NULL;
        if (item->event) {
            timer = new cTimer(item->event);
            PrepareTimerFile(item->event, timer);
        } else
            timer = new cTimer(false, false, item->channel);

        cTimer *t = Timers->GetTimer(timer);
        if (EPGSearchConfig.onePressTimerCreation == 0 || t || !item->event || (!t && item->event && item->event->StartTime() - (Setup.MarginStart + 2) * 60 < time(NULL))) {
            if (t) {
                delete timer;
                timer = t;
            }
            timer->SetFlags(tfActive);
            if (EPGSearchConfig.useVDRTimerEditMenu)
                return AddSubMenu(new cMenuEditTimer(timer, !t));
            else
                return AddSubMenu(new cMenuMyEditTimer(timer, !t, item->event, item->channel));
        } else {
            string fullaux = "";
            string aux = "";
            if (item->event) {
                const cEvent* event = item->event;
                int bstart = event->StartTime() - timer->StartTime();
                int bstop = timer->StopTime() - event->EndTime();
                int checkmode = DefTimerCheckModes.GetMode(timer->Channel());
                aux = UpdateAuxValue(aux, "channel", NumToString(timer->Channel()->Number()) + " - " + CHANNELNAME(timer->Channel()));
                aux = UpdateAuxValue(aux, "update", checkmode);
                aux = UpdateAuxValue(aux, "eventid", event->EventID());
                aux = UpdateAuxValue(aux, "bstart", bstart);
                aux = UpdateAuxValue(aux, "bstop", bstop);
                fullaux = UpdateAuxValue(fullaux, "epgsearch", aux);
            }
#ifdef USE_PINPLUGIN
            aux = "";
            aux = UpdateAuxValue(aux, "protected", timer->FskProtection() ? "yes" : "no");
            fullaux = UpdateAuxValue(fullaux, "pin-plugin", aux);
#endif
            SetAux(timer, fullaux);
            if (Setup.SVDRPPeering && *Setup.SVDRPDefaultHost)
                timer->SetRemote(Setup.SVDRPDefaultHost);
            Timers->Add(timer);
            if (!HandleRemoteTimerModifications(timer)) {
                Timers->Del(timer);
                ERROR(tr("Epgsearch: RemoteTimerModifications failed"));
            } else {
                gl_timerStatusMonitor->SetConflictCheckAdvised();
                timer->Matches();
                Timers->SetModified();
                LogFile.iSysLog("timer %s added (active)", *timer->ToDescr());
            }

            if (HasSubMenu())
                CloseSubMenu();
            if (Update())
                Display();
            SetHelpKeys();
        }
    }
    return osContinue;
}

bool cMenuWhatsOnSearch::Update(void)
{
    bool result = false;
    LOCK_TIMERS_READ;
    for (cOsdItem *item = First(); item; item = Next(item)) {
        if (item->Selectable() && ((cMenuMyScheduleItem *)item)->Update(Timers))
            result = true;
    }
    return result;
}

eOSState cMenuWhatsOnSearch::Commands(eKeys Key)
{
    if (HasSubMenu() || Count() == 0)
        return osContinue;

    cMenuMyScheduleItem *mi = (cMenuMyScheduleItem *)Get(Current());
    if (mi) {
        if (!mi->event) {
            if (Key == k3)
                return Switch();
            else if (Key == k2)
                return Record();
            else
                return osContinue;
        }
        cMenuSearchCommands *menu;
        eOSState state = AddSubMenu(menu = new cMenuSearchCommands(tr("EPG Commands"), mi->event, true));
        if (Key != kNone)
            state = menu->ProcessKey(Key);
        return state;
    }
    return osContinue;
}

eOSState cMenuWhatsOnSearch::ExtendedSearch(void)
{
    return AddSubMenu(new cMenuEPGSearchExt());
}

void cMenuWhatsOnSearch::SetHelpKeys(bool Force)
{
    cMenuMyScheduleItem *item = (cMenuMyScheduleItem *)Get(Current());
    int NewHelpKeys = 0;
    if (item) {
        if (item->Selectable() && item->timerMatch == tmFull)
            NewHelpKeys = 2;
        else
            NewHelpKeys = 1;
    }

    bool hasTimer = (NewHelpKeys == 2);
    if (NewHelpKeys != helpKeys || Force) {
        showMode nextShowMode = GetNextMode();
        cShowMode* mode = GetShowMode(nextShowMode);
        const char* szButtonGreen = NULL;
        if (mode)
            szButtonGreen = mode->GetDescription();
        if (toggleKeys == 0)
            SetHelp((EPGSearchConfig.redkeymode == 0 ? (hasTimer ? trVDR("Button$Timer") : trVDR("Button$Record")) : tr("Button$Commands")),
                        szButtonGreen,
                        trVDR("Button$Schedule"),
                        EPGSearchConfig.bluekeymode == 0 ? (EPGSearchConfig.useOkForSwitch ? trVDR("Button$Info") : trVDR("Button$Switch")) : tr("Button$Search"));
        else
            SetHelp((EPGSearchConfig.redkeymode == 1 ? (hasTimer ? trVDR("Button$Timer") : trVDR("Button$Record")) : tr("Button$Commands")),
                        (EPGSearchConfig.toggleGreenYellow == 0 ? szButtonGreen : "<<"),
                        (EPGSearchConfig.toggleGreenYellow == 0 ? trVDR("Button$Schedule") : ">>"),
                        EPGSearchConfig.bluekeymode == 1 ? (EPGSearchConfig.useOkForSwitch ? trVDR("Button$Info") : trVDR("Button$Switch")) : tr("Button$Search"));
        helpKeys = NewHelpKeys;
    }
}

eOSState cMenuWhatsOnSearch::Shift(int iMinutes)
{
    shiftTime += iMinutes;
    cMenuMyScheduleItem *mi = (cMenuMyScheduleItem *)Get(Current());
    int TempChannel = currentChannel;
    if (mi) {
        currentChannel = mi->channel->Number();
        LOCK_CHANNELS_READ;
        scheduleChannel = Channels->GetByNumber(currentChannel);
    }
    LoadSchedules();
    Display();
    currentChannel = TempChannel;
    SetHelpKeys();
    return osContinue;
}

eOSState cMenuWhatsOnSearch::ShowSummary()
{
    if (Count()) {
        const cEvent *ei = ((cMenuMyScheduleItem *)Get(Current()))->event;
        if (ei) {
            const cChannel *channel;
            {
                LOCK_CHANNELS_READ;
                channel = Channels->GetByChannelID(ei->ChannelID(), true, true);
            }
            if (channel)
                return AddSubMenu(new cMenuEventSearch(ei, eventObjects, SurfModeChannel));
        }
    }
    return osContinue;
}

void cMenuWhatsOnSearch::UpdateCurrent()
{
    // navigation in summary could have changed current item, so update it
    cEventObj* cureventObj = eventObjects.GetCurrent();
    if (cureventObj && cureventObj->Event())
        for (cMenuMyScheduleItem *item = (cMenuMyScheduleItem *)First(); item; item = (cMenuMyScheduleItem *)Next(item))
            if (item->Selectable() && item->event == cureventObj->Event()) {
                cureventObj->Select(false);
                SetCurrent(item);
                Display();
                break;
            }
}

eOSState cMenuWhatsOnSearch::ProcessKey(eKeys Key)
{
    exitToMainMenu = 0;
    if (!HasSubMenu() && Key == kBack) {
        exitToMainMenu = 1;
        return osBack;
    }

    bool HadSubMenu = HasSubMenu();
    eOSState state = cOsdMenu::ProcessKey(Key);

    if (!HasSubMenu() && HadSubMenu) // navigation in summary could have changed current item, so update it
        UpdateCurrent();

    if (state == osUnknown) {
        switch (Key) {
        case kFastRew:
            if (!HasSubMenu())
                return Shift(-EPGSearchConfig.timeShiftValue);
            break;
        case kFastFwd:
            if (!HasSubMenu())
                return Shift(EPGSearchConfig.timeShiftValue);
            break;
        case kRecord:
        case kRed:
            if (HasSubMenu()) {
                UpdateCurrent();
                state = Record();
                break;
            }
            if (Count()) {
                if (EPGSearchConfig.redkeymode == toggleKeys)
                    state = Record();
                else {
                    cMenuMyScheduleItem *mi = (cMenuMyScheduleItem *)Get(Current());
                    if (mi && mi->Selectable()) {
                        if (mi->event)
                            return AddSubMenu(new cMenuSearchCommands(tr("EPG Commands"), mi->event));
                        else
                            return osContinue;
                    }
                }
            }
            break;

        case k0:
            if (!HasSubMenu()) {
                toggleKeys = 1 - toggleKeys;
                SetHelpKeys(true);
            }
            state = osContinue;
            break;
        case k1...k9:
            return Commands(Key);
        case kYellow:
            if (!HasSubMenu()) {
                if (toggleKeys == 0 || (toggleKeys == 1 && EPGSearchConfig.toggleGreenYellow == 0)) {
                    cMenuMyScheduleItem *mi = (cMenuMyScheduleItem *)Get(Current());
                    if (mi && mi->Selectable() && mi->channel) {
                        LOCK_SCHEDULES_READ;
                        const cSchedule *Schedule = Schedules->GetSchedule(mi->channel);
                        if (Schedule) {
                            time_t now = time(NULL);
                            bool hasFutureEvents = false;
                            for (const cEvent *e = Schedule->Events()->First(); e; e = Schedule->Events()->Next(e))
                                if (e->StartTime() > now) {
                                    hasFutureEvents = true;
                                    break;
                                }
                            if (!hasFutureEvents)
                                return osContinue;
                        } else
                            return osContinue;
                    }
                    state = osBack;
                    // continue with kGreen
                } else
                    return Shift(EPGSearchConfig.timeShiftValue);
            }
        case kGreen:
            if (!HasSubMenu()) {
                if (toggleKeys == 0 || (toggleKeys == 1 && EPGSearchConfig.toggleGreenYellow == 0)) {
                    if (Key == kYellow)
                        currentShowMode = showNow;
                    else
                        currentShowMode = GetNextMode();
                    cMenuMyScheduleItem *mi = (cMenuMyScheduleItem *)Get(Current());
                    if (mi && mi->Selectable()) {
                        currentChannel = mi->channel->Number();
                        LOCK_CHANNELS_READ;
                        scheduleChannel = Channels->GetByNumber(currentChannel);
                    }
                } else
                    return Shift(-EPGSearchConfig.timeShiftValue);
            }
            break;
        case kBlue:
            if (HasSubMenu()) {
                UpdateCurrent();
                return Switch();
            }
            if (EPGSearchConfig.bluekeymode == toggleKeys)
                return EPGSearchConfig.useOkForSwitch ? ShowSummary() : Switch();
            else
                return ExtendedSearch();
            break;
        case kOk: {
            cMenuMyScheduleItem *mi = (cMenuMyScheduleItem *)Get(Current());
            if (mi && mi->Selectable()) {
                if (!mi->event) // no EPG, so simply switch to channel
                    return Switch();
                else
                    return EPGSearchConfig.useOkForSwitch ? Switch() : ShowSummary();
            }
        }
        break;
        case kInfo:
            return ShowSummary();
            break;
        default:
            break;
        }
    }
    if (!HasSubMenu()) {
        if ((HadSubMenu || gl_TimerProgged) && Update()) {
            if (gl_TimerProgged) { // when using epgsearch's timer edit menu, update is delayed because of SVDRP
                gl_TimerProgged = 0;
                SetHelpKeys();
            }
            Display();
        }
        if (Key != kNone)
            SetHelpKeys();
        if (gl_InfoConflict) {
            gl_InfoConflict = 0;
            if (Interface->Confirm(tr("Timer conflict! Show?")))
                return AddSubMenu(new cMenuConflictCheck());
        }
    }

    return state;
}


