#include <stdlib.h>
#include <vector>

#include "config.h"
#include "services/epgsearch.h"
#include "services/remotetimers.h"

#include "tools.h"
#include "setup.h"

#include "tvguideosd.h"

cTvGuideOsd::cTvGuideOsd(void) {
    detailView = NULL;
    detailViewActive = false;
    activeGrid = NULL;
    timeLine = NULL;
    recMenuView = NULL;
    channelJumper = NULL;
}

cTvGuideOsd::~cTvGuideOsd() {
    delete timeManager;
    columns.Clear();
    if (config.displayStatusHeader) {
        delete statusHeader;
    }
    if (detailView)
        delete detailView;
    delete timeLine;
    delete channelGroups;
    delete footer;
    delete recMenuView;
    if (channelJumper)
        delete channelJumper;
    osdManager.deleteOsd();
}

void cTvGuideOsd::Show(void) {
    int start = cTimeMs::Now();
    bool ok = false;
    ok = osdManager.setOsd();
    if (ok) {
        bool themeChanged = config.LoadTheme();
        config.SetStyle();
        config.setDynamicValues();
        bool geoChanged = geoManager.SetGeometry(cOsd::OsdWidth(), cOsd::OsdHeight());
        if (themeChanged || geoChanged) {
            fontManager.DeleteFonts();
            fontManager.SetFonts();
            imgCache.Clear();
            imgCache.CreateCache();
        }
        osdManager.setBackground();
        timeManager = new cTimeManager();
        timeManager->Now();
        SwitchTimers.Load(AddDirectory(cPlugin::ConfigDirectory("epgsearch"), "epgsearchswitchtimers.conf"));
        recMenuView = new cRecMenuView();
        pRemoteTimers = cPluginManager::CallFirstService("RemoteTimers::RefreshTimers-v1.0", NULL);
        if (pRemoteTimers) {
            isyslog("tvguide: remotetimers-plugin is available");
        }
        if (config.useRemoteTimers && pRemoteTimers) {
            cString errorMsg;
            if (!pRemoteTimers->Service("RemoteTimers::RefreshTimers-v1.0", &errorMsg)) {
                esyslog("tvguide: %s", *errorMsg);
            }
        }
        drawOsd();
    }
    esyslog("tvguide: Rendering took %d ms", int(cTimeMs::Now()-start));
}

void cTvGuideOsd::drawOsd() {
    cPixmap::Lock();
    int numBack = config.numGrids / 2;
    int offset = 0;
    const cChannel *newStartChannel;
#if VDRVERSNUM >= 20301
    {
    LOCK_CHANNELS_READ;
    const cChannel *startChannel = Channels->GetByNumber(cDevice::CurrentChannel());
#else
    cChannel *startChannel = Channels.GetByNumber(cDevice::CurrentChannel());
#endif
    newStartChannel = startChannel;
#if VDRVERSNUM >= 20301
    for (; newStartChannel ; newStartChannel = Channels->Prev(newStartChannel)) {
#else
    for (; newStartChannel ; newStartChannel = Channels.Prev(newStartChannel)) {
#endif
        if (newStartChannel && !newStartChannel->GroupSep()) {
            offset++;
        }
        if (offset == numBack)
            break;
    }
    if (!newStartChannel)
#if VDRVERSNUM >= 20301
        newStartChannel = Channels->First();
    } //LOCK_CHANNELS_READ
#else
        newStartChannel = Channels.First();
#endif
    offset--;
    if (offset < 0)
        offset = 0;
    
    if (config.displayStatusHeader) {
        statusHeader = new cStatusHeader();
        statusHeader->Draw();
        statusHeader->ScaleVideo();
    }
    timeLine = new cTimeLine(timeManager);
    timeLine->DrawDateViewer();
    timeLine->DrawTimeline();
    timeLine->DrawClock();
    channelGroups = new cChannelGroups();
    channelGroups->ReadChannelGroups();
    footer = new cFooter(channelGroups);
    recMenuView->SetFooter(footer);
    footer->drawRedButton();
    if (config.channelJumpMode == eNumJump) {
        footer->drawGreenButton();
        footer->drawYellowButton();
    }
    footer->drawBlueButton(false);
    osdManager.flush();
    readChannels(newStartChannel);
    drawGridsChannelJump(offset);
    osdManager.flush();
    cPixmap::Unlock();
}

void cTvGuideOsd::readChannels(const cChannel *channelStart) {
    int i=0;
    bool foundEnough = false;
    columns.Clear();
    if (!channelStart)
        return;
#if VDRVERSNUM >= 20301
    const cChannels *channels;
    {
    LOCK_CHANNELS_READ;
    channels = Channels;
    }
#else
    cChannels *channels = &Channels;
#endif
    for (const cChannel *channel = channelStart; channel; channel = channels->Next(channel)) {
        if (!channel->GroupSep()) {
            if (channelGroups->IsInLastGroup(channel)) {
                break;
            }
            cChannelEpg *column = new cChannelEpg(i, channel, timeManager);
            if (column->readGrids()) {
                columns.Add(column);
                i++;
            } else {
                delete column;
            }
        }
        if (i == config.numGrids) {
            foundEnough = true;
            break;
        }
    }
    if (!foundEnough) {
        int numCurrent = columns.Count();
        int numBack = config.numGrids - numCurrent;
        int newChannelNumber = columns.First()->getChannel()->Number() - numBack;
        const cChannel *newStart = channels->GetByNumber(newChannelNumber);
        readChannels(newStart);
    }
}

void cTvGuideOsd::drawGridsChannelJump(int offset) {
    if (columns.Count() == 0)
        return;
    activeGrid = columns.Get(offset)->getActive();
    if (activeGrid)
        activeGrid->SetActive();
    if (config.displayStatusHeader) {
        statusHeader->DrawInfoText(activeGrid);
    }
    if (activeGrid && (config.channelJumpMode == eGroupJump)) {
        footer->UpdateGroupButtons(activeGrid->column->getChannel());
    }
    if (config.displayChannelGroups) {
        channelGroups->DrawChannelGroups(columns.First()->getChannel(), columns.Last()->getChannel());
    }
    for (cChannelEpg *column = columns.First(); column; column = columns.Next(column)) {
        column->createHeader();
        column->drawGrids();
    }
}

void cTvGuideOsd::drawGridsTimeJump(bool last) {
    if (columns.Count() == 0)
        return;
    cChannelEpg *colActive = NULL;
    if (activeGrid) {
        colActive = activeGrid->column;
    } else {
        colActive = columns.First();
    }
    for (cChannelEpg *column = columns.First(); column; column = columns.Next(column)) {
        column->clearGrids();
        column->readGrids();
        column->drawGrids();
    }
    activeGrid = colActive->getActive(last);
    if (activeGrid) {
        activeGrid->SetActive();
        activeGrid->Draw();
        if (config.displayStatusHeader) {
            statusHeader->DrawInfoText(activeGrid);
        }
    }
}

void cTvGuideOsd::setNextActiveGrid(cGridElement *next) {
    if (!next || !activeGrid) {
        return;
    }
    activeGrid->SetInActive();
    activeGrid->Draw(); 
    activeGrid = next;
    activeGrid->SetActive();
    activeGrid->Draw();
    if (config.displayStatusHeader) {
        statusHeader->DrawInfoText(activeGrid);
    }
}

void cTvGuideOsd::channelForward() {
    cChannelEpg *colRight = columns.Next(activeGrid->column);
    bool colAdded = false;
    if (!colRight) {
        const cChannel *channelRight = activeGrid->column->getChannel();
        const cChannels *channels;
#if VDRVERSNUM >= 20301
        {
        LOCK_CHANNELS_READ;
        channels = Channels;
        }
#else
        channels = &Channels;
#endif
        while (channelRight = channels->Next(channelRight)) {
            if (!channelRight->GroupSep()) {
                if (channelGroups->IsInLastGroup(channelRight)) {
                    break;
                }
                colRight = new cChannelEpg(config.numGrids - 1, channelRight, timeManager);
                if (colRight->readGrids()) {
                    break;
                } else {
                    delete colRight;
                    colRight = NULL;
                }
            }
        }
        if (colRight) {
            colAdded = true;
            if (columns.Count() == config.numGrids) {
                cChannelEpg *cFirst = columns.First();
                columns.Del(cFirst);
            }
            for (cChannelEpg *column = columns.First(); column; column = columns.Next(column)) {
                column->SetNum(column->GetNum() - 1);
                column->drawHeader();
                column->drawGrids();
            }
            columns.Add(colRight);
            colRight->createHeader();
            colRight->drawGrids();
        }
    }
    if (colRight) {
        cGridElement *right = colRight->getNeighbor(activeGrid);
        if (right) {
            setNextActiveGrid(right);
        }
    }
    if (config.displayChannelGroups && colAdded) {
        channelGroups->DrawChannelGroups(columns.First()->getChannel(), columns.Last()->getChannel());
    }
    if (activeGrid && (config.channelJumpMode == eGroupJump)) {
        footer->UpdateGroupButtons(activeGrid->column->getChannel());
    }
    osdManager.flush();
}

void cTvGuideOsd::channelBack() {
    cChannelEpg *colLeft = columns.Prev(activeGrid->column);
    bool colAdded = false;
    if (!colLeft) {
        const cChannel *channelLeft = activeGrid->column->getChannel();
        const cChannels *channels;
#if VDRVERSNUM >= 20301
	{
	LOCK_CHANNELS_READ;
	channels = Channels;
        }
#else
        channels = &Channels;
#endif
        while (channelLeft = channels->Prev(channelLeft)) {
            if (!channelLeft->GroupSep()) {
                colLeft = new cChannelEpg(0, channelLeft, timeManager);
                if (colLeft->readGrids()) {
                    break;
                } else {
                    delete colLeft;
                    colLeft = NULL;
                }
            }
        }
        if (colLeft) {
            colAdded = true;
            if (columns.Count() == config.numGrids) {
                cChannelEpg *cLast = columns.Last();
                columns.Del(cLast);
            }
            for (cChannelEpg *column = columns.First(); column; column = columns.Next(column)) {
                column->SetNum(column->GetNum() + 1);
                column->drawHeader();
                column->drawGrids();
            }
            columns.Ins(colLeft, columns.First());
            colLeft->createHeader();
            colLeft->drawGrids();
        }
    }

    if (colLeft) {
        cGridElement *left = colLeft->getNeighbor(activeGrid);
        if (left) {
            setNextActiveGrid(left);
        }
    }
    if (config.displayChannelGroups && colAdded) {
        channelGroups->DrawChannelGroups(columns.First()->getChannel(), columns.Last()->getChannel());
    }

    if (activeGrid && (config.channelJumpMode == eGroupJump)) {
        footer->UpdateGroupButtons(activeGrid->column->getChannel());
    }
    osdManager.flush();
}

void cTvGuideOsd::timeForward() {
    bool actionDone = false;
    if ((timeManager->GetEnd() - activeGrid->EndTime())/60 < 30 ) {
        ScrollForward();
        actionDone = true;
    }
    cGridElement *next = activeGrid->column->getNext(activeGrid);
    if (next) {
        if (   (next->EndTime() < timeManager->GetEnd())
            || ( (timeManager->GetEnd() - next->StartTime())/60 > 30 ) ) {
            setNextActiveGrid(next);
            actionDone = true;
        }
    }
    if (!actionDone) {
        ScrollForward();
    }
    osdManager.flush();
}

void cTvGuideOsd::ScrollForward() {
    timeManager->AddStep(config.stepMinutes);
    if (config.useHWAccel) {
        drawGridsTimeJump(true);
        timeLine->DrawDateViewer();
        timeLine->DrawClock();
        timeLine->DrawTimeline();
    } else {
        timeLine->DrawDateViewer();
        timeLine->DrawTimeline();
        for (cChannelEpg *column = columns.First(); column; column = columns.Next(column)) {
            column->AddNewGridsAtEnd();
            column->ClearOutdatedStart();
            column->drawGrids();
        }
    }
}

void cTvGuideOsd::timeBack() {
    bool actionDone = false;
    if ((activeGrid->StartTime() - timeManager->GetStart())/60 < 30 ) {
        ScrollBack();
        actionDone = true;
    }
    cGridElement *prev = activeGrid->column->getPrev(activeGrid);
    if (prev) {
        if (   (prev->StartTime() > timeManager->GetStart())
            || ( (prev->EndTime() - timeManager->GetStart())/60 > 30 )
            || ( prev->isFirst()) ) {
            setNextActiveGrid(prev);
            actionDone = true;
        }
    }
    if (!actionDone) {
        ScrollBack();
    }
    osdManager.flush();
}

void cTvGuideOsd::ScrollBack() {
    timeManager->DelStep(config.stepMinutes);
    if (config.useHWAccel) {
        drawGridsTimeJump();
        timeLine->DrawDateViewer();
        timeLine->DrawClock();
        timeLine->DrawTimeline();
    } else {
        timeLine->DrawDateViewer();
        timeLine->DrawTimeline();
        for (cChannelEpg *column = columns.First(); column; column = columns.Next(column)) {
            column->AddNewGridsAtStart();
            column->ClearOutdatedEnd();
            column->drawGrids();
        }   
    }
}

void cTvGuideOsd::processKeyUp() {
    if (!activeGrid) {
        return;
    }
    if (config.displayMode == eVertical) {
        timeBack();
    } else if (config.displayMode == eHorizontal) {
        channelBack();
    }
}

void cTvGuideOsd::processKeyDown() {
    if (!activeGrid) {
            return;
    }
    if (config.displayMode == eVertical) {
        timeForward();
    } else if (config.displayMode == eHorizontal) {
        channelForward();
    }
}

void cTvGuideOsd::processKeyLeft() {
    if (activeGrid == NULL)
        return;
    if (config.displayMode == eVertical) {
        channelBack();
    } else if (config.displayMode == eHorizontal) {
        timeBack();
    }
}

void cTvGuideOsd::processKeyRight() {
    if (activeGrid == NULL)
        return;
    if (config.displayMode == eVertical) {
        channelForward();
    } else if (config.displayMode == eHorizontal) {
        timeForward();
    }
}

void cTvGuideOsd::processKeyRed() {
    if  ((activeGrid == NULL) || activeGrid->isDummy())
        return;
    recMenuView->Start(activeGrid->GetEvent());
}

void cTvGuideOsd::processKeyGreen() {
    if (activeGrid == NULL)
        return;
    
    const cChannel *currentChannel = activeGrid->column->getChannel();
    const cChannel *firstChannel = columns.First()->getChannel();
    int currentCol = activeGrid->column->GetNum();
    const cChannel *prev = NULL;
    
    if (config.channelJumpMode == eGroupJump) {
        int prevNum = channelGroups->GetPrevGroupChannelNumber(currentChannel);
        if (prevNum) {
#if VDRVERSNUM >= 20301
            LOCK_CHANNELS_READ;
            prev = Channels->GetByNumber(prevNum);
#else
            prev = Channels.GetByNumber(prevNum);
#endif
        }    
    } else if (config.channelJumpMode == eNumJump) {
        int i = config.jumpChannels + 1;
#if VDRVERSNUM >= 20301
        LOCK_CHANNELS_READ;
        for (const cChannel *channel = firstChannel; channel; channel = Channels->Prev(channel)) {
#else
        for (const cChannel *channel = firstChannel; channel; channel = Channels.Prev(channel)) {
#endif
            if (!channel->GroupSep()) {
                prev = channel;
                i--;
            }
            if (i == 0)
                break;
        }
    }
    if (prev) {
        readChannels(prev);
        if (columns.Count() > 0) {
            if (config.channelJumpMode == eGroupJump)
                drawGridsChannelJump();
            else
                drawGridsChannelJump(currentCol);
        }
        osdManager.flush();
    }
}

void cTvGuideOsd::processKeyYellow() {
    if (activeGrid == NULL)
        return;
    const cChannel *currentChannel = activeGrid->column->getChannel();
    int currentCol = activeGrid->column->GetNum();
    const cChannel *firstChannel = columns.First()->getChannel();
    const cChannel *next = NULL;
    
    if (config.channelJumpMode == eGroupJump) {
        int nextNum = channelGroups->GetNextGroupChannelNumber(currentChannel);
        if (nextNum) {
#if VDRVERSNUM >= 20301
            LOCK_CHANNELS_READ;
            next = Channels->GetByNumber(nextNum);
#else
            next = Channels.GetByNumber(nextNum);
#endif
        }    
    } else if (config.channelJumpMode == eNumJump) {
        int i=0;
#if VDRVERSNUM >= 20301
        LOCK_CHANNELS_READ;
        for (const cChannel *channel = firstChannel; channel; channel = Channels->Next(channel)) {
#else
        for (const cChannel *channel = firstChannel; channel; channel = Channels.Next(channel)) {
#endif
            if (channelGroups->IsInLastGroup(channel)) {
                break;
            }
            if (!channel->GroupSep()) {
                next = channel;
                i++;
            }
            if (i == (config.jumpChannels+1)) {
                break;
            }
        }
    }
    if (next) {
        readChannels(next);
        if (columns.Count() > 0) {
            if (config.channelJumpMode == eGroupJump)
                drawGridsChannelJump();
            else
                drawGridsChannelJump(currentCol);
        }
        osdManager.flush();
    }
}

eOSState cTvGuideOsd::processKeyBlue(bool *alreadyUnlocked) {
    if (config.blueKeyMode == eBlueKeySwitch) {
        return ChannelSwitch(alreadyUnlocked);
    } else if (config.blueKeyMode == eBlueKeyEPG) {
        DetailedEPG();
    } else if (config.blueKeyMode == eBlueKeyFavorites) {
        recMenuView->StartFavorites();
    }
    return osContinue;
}

eOSState cTvGuideOsd::processKeyOk(bool *alreadyUnlocked) {
    if (config.blueKeyMode == eBlueKeySwitch) {
        DetailedEPG();
    } else if (config.blueKeyMode == eBlueKeyEPG) {
        return ChannelSwitch(alreadyUnlocked);
    } else if (config.blueKeyMode == eBlueKeyFavorites) {
        DetailedEPG();
    }
    return osContinue;
}

eOSState cTvGuideOsd::ChannelSwitch(bool *alreadyUnlocked) {
    if (activeGrid == NULL)
        return osContinue;
    const cChannel *currentChannel = activeGrid->column->getChannel();
    if (currentChannel) {
        cPixmap::Unlock();
        *alreadyUnlocked = true;
        cDevice::PrimaryDevice()->SwitchChannel(currentChannel, true);
        if (config.closeOnSwitch) {
            if (detailView) {
                delete detailView;
                detailView = NULL;
                detailViewActive = false;
            }
            return osEnd;
        }
    }
    return osContinue;
}

void cTvGuideOsd::DetailedEPG() {
    if (!activeGrid->isDummy()) {
        detailViewActive = true;
        detailView = new cDetailView(activeGrid->GetEvent(), footer);
        footer->SetDetailedViewMode();
        osdManager.flush();
        detailView->Start();
        osdManager.flush();
    }
}

void cTvGuideOsd::processNumKey(int numKey) {
    if (config.numkeyMode == 0) {
        //timely jumps with 1,3,4,6,7,9
        TimeJump(numKey);
    } else {
        //jump to specific channel
        ChannelJump(numKey);
    }
}

void cTvGuideOsd::TimeJump(int mode) {
    switch (mode) {
        case 1: {
            timeManager->DelStep(((config.displayMode == eVertical) ? config.bigStepHours : config.bigStepHoursHorizontal) * 60);
            }
            break;
        case 3: {
            timeManager->AddStep(((config.displayMode == eVertical) ? config.bigStepHours : config.bigStepHoursHorizontal) * 60);
            }
            break;
        case 4: {
            timeManager->DelStep(((config.displayMode == eVertical) ? config.hugeStepHours : config.hugeStepHoursHorizontal) * 60);
            }
            break;
        case 6: {
            timeManager->AddStep(((config.displayMode == eVertical) ? config.hugeStepHours : config.hugeStepHoursHorizontal) * 60);
            }
            break;
        case 7: {
            cTimeManager primeChecker;
            primeChecker.Now();
            time_t prevPrime = primeChecker.getPrevPrimetime(timeManager->GetStart());
            if (primeChecker.tooFarInPast(prevPrime))
                return;
            timeManager->SetTime(prevPrime);
            }
            break;
        case 9: {
            cTimeManager primeChecker;
            time_t nextPrime = primeChecker.getNextPrimetime(timeManager->GetStart());
            timeManager->SetTime(nextPrime);
            }
            break;
        default:
            return;
    }
    drawGridsTimeJump();
    timeLine->DrawDateViewer();
    timeLine->DrawClock();
    timeLine->DrawTimeline();
    osdManager.flush();
}

int cTvGuideOsd::GetLastValidChannel(void) {
    return channelGroups->GetLastValidChannel();
}

void cTvGuideOsd::ChannelJump(int num) {
    if (!channelJumper) {
        int lastValidChannel = GetLastValidChannel();
        channelJumper = new cChannelJump(channelGroups, lastValidChannel);
    }
    channelJumper->Set(num);
    channelJumper->DrawText();
    osdManager.flush();
}

void cTvGuideOsd::CheckTimeout(void) {
    if (!channelJumper)
        return;
    if (channelJumper->TimeOut()) {
        int newChannelNum = channelJumper->GetChannel(); 
        delete channelJumper;
        channelJumper = NULL;
        const cChannel *newChannel;
#if VDRVERSNUM >= 20301
        {
        LOCK_CHANNELS_READ;
        newChannel = Channels->GetByNumber(newChannelNum);
        }
#else
        newChannel = Channels.GetByNumber(newChannelNum);
#endif
        if (newChannel) {
            readChannels(newChannel);
            if (columns.Count() > 0) {
                drawGridsChannelJump();
            }
        }
        osdManager.flush();
    }
}

void cTvGuideOsd::SetTimers() {
    for (cChannelEpg *column = columns.First(); column; column = columns.Next(column)) {
        column->SetTimers();
    }
}

eOSState cTvGuideOsd::ProcessKey(eKeys Key) {
    eOSState state = osContinue;
    cPixmap::Lock();
    bool alreadyUnlocked = false;
    if (recMenuView->isActive()) {
        state = recMenuView->ProcessKey(Key);
        if (state == osEnd) {
            SetTimers();                
            osdManager.flush();
        }
        state = osContinue;
    } else if (detailViewActive) {
        if ((Key & ~k_Repeat) == kRed) {
            delete detailView;
            detailView = NULL;
            detailViewActive = false;
            processKeyRed();
        } else if ((Key & ~k_Repeat) == kBlue) {
            delete detailView;
            detailView = NULL;
            detailViewActive = false;
            if ((config.blueKeyMode == eBlueKeySwitch) || (config.blueKeyMode == eBlueKeyFavorites)) {
                state = ChannelSwitch(&alreadyUnlocked);
            } else {
                osdManager.flush();
                state = osContinue;
            }
        } else if ((Key & ~k_Repeat) == kOk && (config.blueKeyMode == eBlueKeyEPG)) {
            delete detailView;
            detailView = NULL;
            detailViewActive = false;
            state = ChannelSwitch(&alreadyUnlocked);
        } else {
            state = detailView->ProcessKey(Key);
            if (state == osEnd) {
                delete detailView;
                detailView = NULL;
                detailViewActive = false;
                osdManager.flush();
                state = osContinue;
            }
        }
    } else {
        switch (Key & ~k_Repeat) {
            case kUp:       processKeyUp(); break;
            case kDown:     processKeyDown(); break;
            case kLeft:     processKeyLeft(); break;
            case kRight:    processKeyRight(); break;
            case kRed:      processKeyRed(); break;
            case kGreen:    processKeyGreen(); break;
            case kYellow:   processKeyYellow(); break;
            case kBlue:     state = processKeyBlue(&alreadyUnlocked); break;
            case kOk:       state = processKeyOk(&alreadyUnlocked); break;
            case kBack:     state = osEnd; break;    
            case k0 ... k9: processNumKey(Key - k0); break;
            case kFastRew:  TimeJump(1); break; // Doesnt work, if used from timeshiftmode
            case kFastFwd:  TimeJump(3); break;
            case kPrev:     TimeJump(4); break;
            case kNext:     TimeJump(6); break;
            case kNone:     if (channelJumper) CheckTimeout(); break;
            default:        break;
        }
        if (timeLine->DrawClock()) {
            osdManager.flush();
        }
    }
    if (!alreadyUnlocked) {
        cPixmap::Unlock();
    }
    return state;
}

void cTvGuideOsd::dump() {
    esyslog("tvguide: ------Dumping Content---------");
    activeGrid->debug();
//    int i=1;
    for (cChannelEpg *col = columns.First(); col; col = columns.Next(col)) {
        col->dumpGrids();
    }
}
