#define __STL_CONFIG_H
#include <fstream>
#include <iostream>
#include <vdr/menu.h>
#include <vdr/videodir.h>
#include "displaymenuview.h"
#include "../config.h"
#include "../libcore/helpers.h"
#include "../libcore/timers.h"
#include "../services/scraper2vdr.h"

cDisplayMenuView::cDisplayMenuView(cTemplateView *tmplView, bool menuInit) : cView(tmplView) {
    if (menuInit)
        SetFadeTime(tmplView->GetNumericParameter(ptFadeTime));
    else
        SetFadeTime(0);
    cat = mcUndefined;
    buttonTexts = NULL;
}

cDisplayMenuView::~cDisplayMenuView() {
    CancelSave();
    FadeOut();
}

bool cDisplayMenuView::DrawBackground(void) {
    if (!ViewElementImplemented(veBackground)) {
        return false;
    }
    map < string, string > stringTokens;
    map < string, int > intTokens;

    DrawViewElement(veBackground, &stringTokens, &intTokens);
    return true;
}

bool cDisplayMenuView::DrawHeader(void) {
    if (!ViewElementImplemented(veHeader)) {
        return false;
    }

    map < string, string > stringTokens;
    map < string, int > intTokens;

    stringTokens.insert(pair<string,string>("title", menuTitle));
    stringTokens.insert(pair<string,string>("vdrversion", VDRVERSION));

    //check for standard menu entries
    bool hasIcon = false;
    string icon = imgCache->GetIconName(menuTitle, cat);
    if (imgCache->MenuIconExists(icon))
        hasIcon = true;
    stringTokens.insert(pair<string,string>("icon", icon));
    intTokens.insert(pair<string,int>("hasicon", hasIcon));

    //Disc Usage
    string vdrUsageString = *cVideoDiskUsage::String();
    int discUsage = cVideoDiskUsage::UsedPercent();
    bool discAlert = (discUsage > 95) ? true : false;
    string freeTime = *cString::sprintf("%02d:%02d", cVideoDiskUsage::FreeMinutes() / 60, cVideoDiskUsage::FreeMinutes() % 60);
    int freeGB = cVideoDiskUsage::FreeMB() / 1024;

    intTokens.insert(pair<string, int>("usedpercent", discUsage));
    intTokens.insert(pair<string, int>("freepercent", 100-discUsage));
    intTokens.insert(pair<string, int>("discalert", discAlert));
    intTokens.insert(pair<string, int>("freegb", freeGB));
    stringTokens.insert(pair<string,string>("freetime", freeTime));
    stringTokens.insert(pair<string,string>("vdrusagestring", vdrUsageString));

    ClearViewElement(veHeader);
    DrawViewElement(veHeader, &stringTokens, &intTokens);
    return true;
}

bool cDisplayMenuView::DrawDateTime(void) {
    if (!ViewElementImplemented(veDateTime)) {
        return false;
    }

    map < string, string > stringTokens;
    map < string, int > intTokens;
    
    if (!SetDate(stringTokens, intTokens)) {
        return false;
    }

    ClearViewElement(veDateTime);
    DrawViewElement(veDateTime, &stringTokens, &intTokens);
    return true;
}

bool cDisplayMenuView::DrawTime(void) {
    if (!ViewElementImplemented(veTime)) {
        return false;
    }
    
    map < string, string > stringTokens;
    map < string, int > intTokens;

    if (!SetTime(stringTokens, intTokens)) {
        return false;
    }    

    ClearViewElement(veTime);
    DrawViewElement(veTime, &stringTokens, &intTokens);

    return true;
}

bool cDisplayMenuView::DrawColorButtons(void) {
    if (!ViewElementImplemented(veButtons)) {
        return false;
    }
    map < string, string > stringTokens;
    map < string, int > intTokens;

    stringTokens.insert(pair<string,string>("red", buttonTexts ? buttonTexts[0] : ""));
    stringTokens.insert(pair<string,string>("green", buttonTexts ? buttonTexts[1]: ""));
    stringTokens.insert(pair<string,string>("yellow", buttonTexts ? buttonTexts[2]: ""));
    stringTokens.insert(pair<string,string>("blue", buttonTexts ? buttonTexts[3] : ""));

    int colorKeys[4] = { Setup.ColorKey0, Setup.ColorKey1, Setup.ColorKey2, Setup.ColorKey3 };

    for (int button = 1; button < 5; button++) {
        string red = *cString::sprintf("red%d", button);
        string green = *cString::sprintf("green%d", button);
        string yellow = *cString::sprintf("yellow%d", button);
        string blue = *cString::sprintf("blue%d", button);
        bool isRed = false;
        bool isGreen = false;
        bool isYellow = false;
        bool isBlue = false;
        switch (colorKeys[button-1]) {
            case 0:
                isRed = true;
                break;
            case 1:
                isGreen = true;
                break;
            case 2:
                isYellow = true;
                break;
            case 3:
                isBlue = true;
                break;
            default:
                break;
        }
        intTokens.insert(pair<string, int>(red, isRed));
        intTokens.insert(pair<string, int>(green, isGreen));
        intTokens.insert(pair<string, int>(yellow, isYellow));
        intTokens.insert(pair<string, int>(blue, isBlue));
    }

    ClearViewElement(veButtons);
    DrawViewElement(veButtons, &stringTokens, &intTokens);
    return true;
}

bool cDisplayMenuView::DrawMessage(eMessageType type, const char *text) {
    if (!ViewElementImplemented(veMessage)) {
        return false;
    }
    if (!text) {
        ClearViewElement(veMessage);
        return true;
    }
    map < string, string > stringTokens;
    map < string, int > intTokens;

    intTokens.insert(pair<string, int>("status",  (type == mtStatus)  ? true : false));
    intTokens.insert(pair<string, int>("info",    (type == mtInfo)    ? true : false));
    intTokens.insert(pair<string, int>("warning", (type == mtWarning) ? true : false));
    intTokens.insert(pair<string, int>("error",   (type == mtError)   ? true : false));
    stringTokens.insert(pair<string,string>("text", text));
    
    ClearViewElement(veMessage);
    DrawViewElement(veMessage, &stringTokens, &intTokens);
    return true;
}

void cDisplayMenuView::DrawScrollbar(int numMax, int numDisplayed, int offset) {
    if (!ViewElementImplemented(veScrollbar)) {
        return;
    }
    map < string, string > stringTokens;
    map < string, int > intTokens;
    if (numDisplayed < 1)
        return;
    
    int barHeight = 0;
    if (numDisplayed < numMax)
        barHeight = 1000;
    else
        barHeight = (double)numMax * 1000 / (double)numDisplayed;
    
    int barOffset = (double)offset * 1000 / (double)numDisplayed;

    intTokens.insert(pair<string, int>("height", barHeight));
    intTokens.insert(pair<string, int>("offset", barOffset));

    ClearViewElement(veScrollbar);
    DrawViewElement(veScrollbar, &stringTokens, &intTokens);
}

bool cDisplayMenuView::BackgroundImplemented(void) {
    if (!ViewElementImplemented(veBackground)) {
        return false;
    }
    return true;
}


void cDisplayMenuView::Action(void) {
    SetInitFinished();
    FadeIn();
    DoFlush();
}

/************************************************************************
* cDisplayMenuMainView
************************************************************************/

cDisplayMenuMainView::cDisplayMenuMainView(cTemplateView *tmplView, bool menuInit) : cDisplayMenuView(tmplView, menuInit) {
    initial = true;
    lastSystemLoad = 0.0;
    InitDevices();
}

cDisplayMenuMainView::~cDisplayMenuMainView() {
    CancelSave();
    FadeOut();
}

void cDisplayMenuMainView::DrawStaticViewElements(void) {
    DrawTimers();
    DrawDiscUsage();
    DrawTemperatures();
    DrawCurrentSchedule();
    DrawCurrentWeather();
    DrawCustomTokens();
}

bool cDisplayMenuMainView::DrawDynamicViewElements(void) {
    bool loadChanged = DrawLoad();
    bool devicesChanged = DrawDevices();
    initial = false;
    return loadChanged || devicesChanged;

}

void cDisplayMenuMainView::DrawTimers(void) {
    if (!ViewElementImplemented(veTimers)) {
        return;
    }

    map < string, string > stringTokens;
    map < string, int > intTokens;

    map < string, vector< map< string, string > > > timerLoopTokens;
    vector< map< string, string > > timers;

    cGlobalSortedTimers SortedTimers;// local and remote timers
    int numTimers = SortedTimers.Size();

    intTokens.insert(pair<string, int>("numtimers", numTimers));

    int numTimerConflicts = SortedTimers.NumTimerConfilicts();
    intTokens.insert(pair<string, int>("numtimerconflicts", numTimerConflicts));

    for (int i=0; i<15; i++) {
        stringstream name;
        name << "timer" << i+1 << "exists";
        if (i < numTimers) {
            intTokens.insert(pair<string, int>(name.str(), true));
        } else {
            intTokens.insert(pair<string, int>(name.str(), false));
        }
    }

    for (int i = 0; i < numTimers; i++) {
        if (i >=15)
            break;
        map< string, string > timerVals;
        const cTimer *Timer = SortedTimers[i];
        const cEvent *event = Timer->Event();
        if (event) {
            timerVals.insert(pair< string, string >("timers[title]", event->Title()));
        } else {
            const char *File = Setup.FoldersInTimerMenu ? NULL : strrchr(Timer->File(), FOLDERDELIMCHAR);
            if (File && strcmp(File + 1, TIMERMACRO_TITLE) && strcmp(File + 1, TIMERMACRO_EPISODE))
                File++;
            else
                File = Timer->File();
            timerVals.insert(pair< string, string >("timers[title]", File));            
        }
        const cChannel *channel = Timer->Channel();
        if (channel) {
            timerVals.insert(pair< string, string >("timers[channelname]", channel->Name()));
            stringstream chanNum;
            chanNum << channel->Number();
            timerVals.insert(pair< string, string >("timers[channelnumber]", chanNum.str()));
            string channelID = *(channel->GetChannelID().ToString());
            timerVals.insert(pair< string, string >("timers[channelid]", channelID));
            bool logoExists = imgCache->LogoExists(channelID);
            timerVals.insert(pair< string, string >("timers[channellogoexists]", logoExists ? "1" : "0"));
        } else {
            timerVals.insert(pair< string, string >("timers[channelname]", ""));
            timerVals.insert(pair< string, string >("timers[channelnumber]", "0"));
            timerVals.insert(pair< string, string >("timers[channelid]", ""));  
            timerVals.insert(pair< string, string >("timers[channellogoexists]", "0"));
        }
        
        timerVals.insert(pair< string, string >("timers[recording]", Timer->Recording() ? "1" : "0"));

        cString timerDate("");
        if (Timer->Recording()) {
            timerDate = cString::sprintf("-%s", *TimeString(Timer->StopTime()));
        } else {
            time_t Now = time(NULL);
            cString Today = WeekDayName(Now);
            cString Time = TimeString(Timer->StartTime());
            cString Day = WeekDayName(Timer->StartTime());
            if (Timer->StartTime() > Now + 6 * SECSINDAY) {
                time_t ttm = Timer->StartTime();
                struct tm * timerTime = localtime(&ttm);
                timerDate = cString::sprintf("%02d.%02d %s", timerTime->tm_mday, timerTime->tm_mon + 1, *Time);
            } else if (strcmp(Day, Today) != 0)
                timerDate = cString::sprintf("%s %s", *Day, *Time);
            else
                timerDate = Time;
            if (Timer->Flags() & tfVps)
                timerDate = cString::sprintf("VPS %s", *timerDate);
        }
        timerVals.insert(pair< string, string >("timers[datetime]", *timerDate));

        timers.push_back(timerVals);
    }

    timerLoopTokens.insert(pair< string, vector< map< string, string > > >("timers", timers));

    ClearViewElement(veTimers);
    DrawViewElement(veTimers, &stringTokens, &intTokens, &timerLoopTokens);
}

void cDisplayMenuMainView::DrawDiscUsage(void) {
    if (!ViewElementImplemented(veDiscUsage)) {
        return;
    }

    map < string, string > stringTokens;
    map < string, int > intTokens;

    string vdrUsageString = *cVideoDiskUsage::String();
    int discUsage = cVideoDiskUsage::UsedPercent();
    bool discAlert = (discUsage > 95) ? true : false;
    string freeTime = *cString::sprintf("%02d:%02d", cVideoDiskUsage::FreeMinutes() / 60, cVideoDiskUsage::FreeMinutes() % 60);
    int freeGB = cVideoDiskUsage::FreeMB() / 1024;

    intTokens.insert(pair<string, int>("usedpercent", discUsage));
    intTokens.insert(pair<string, int>("freepercent", 100-discUsage));
    intTokens.insert(pair<string, int>("discalert", discAlert));
    intTokens.insert(pair<string, int>("freegb", freeGB));
    stringTokens.insert(pair<string,string>("freetime", freeTime));
    stringTokens.insert(pair<string,string>("vdrusagestring", vdrUsageString));

    ClearViewElement(veDiscUsage);
    DrawViewElement(veDiscUsage, &stringTokens, &intTokens);
}

bool cDisplayMenuMainView::DrawLoad(void) {
    if (!ViewElementImplemented(veSystemLoad)) {
        return false;
    }

    map < string, string > stringTokens;
    map < string, int > intTokens;

    double systemLoad;
    if (getloadavg(&systemLoad, 1) > 0) {
        if (lastSystemLoad == systemLoad) {
            return false;
        }
        string load = *cString::sprintf("%.2f", systemLoad);
        int loadHand = systemLoad * 1000;
        int loadHandValue = 0;
        if (loadHand > 2500)
            loadHandValue = 2525;
        else {

            int loadHandDec = loadHand - (loadHand / 100) * 100;

            if (loadHandDec <= 12)
                loadHandDec = 0;
            else if (loadHandDec <= 37)
                loadHandDec = 25;
            else if (loadHandDec <= 62)
                loadHandDec = 50;
            else if (loadHandDec <= 87)
                loadHandDec = 75;
            else
                loadHandDec = 0;

            loadHandValue = loadHand / 100 * 100 + loadHandDec;
        }

        stringTokens.insert(pair<string,string>("load", load));
        intTokens.insert(pair<string,int>("loadhand", loadHandValue));
        lastSystemLoad = systemLoad;
    }

    ClearViewElement(veSystemLoad);
    DrawViewElement(veSystemLoad, &stringTokens, &intTokens);

    return true;
}

void cDisplayMenuMainView::DrawTemperatures(void) {
    if (!ViewElementImplemented(veTemperatures)) {
        return;
    }

    cString execCommand = cString::sprintf("cd \"%s/\"; \"%s/temperatures\"", SCRIPTFOLDER, SCRIPTFOLDER);
    system(*execCommand);

    string tempCPU, tempGPU;
    int cpu, gpu;

    cString itemFilename = cString::sprintf("%s/cpu", SCRIPTOUTPUTPATH );
    ifstream file(*itemFilename, ifstream::in);
    if( file.is_open() ) {
        std::getline(file, tempCPU);
        if (tempCPU.size() > 2) {
            cpu = atoi(tempCPU.substr(0,2).c_str());
        } else
            cpu = 0;
        file.close();
    } else {
        tempCPU = "0°C";
        cpu = 0;
    }

    itemFilename = cString::sprintf("%s/gpu", SCRIPTOUTPUTPATH );
    ifstream file2(*itemFilename, ifstream::in);
    if( file2.is_open() ) {
        std::getline(file2, tempGPU);
        if (tempGPU.size() > 2) {
            gpu = atoi(tempGPU.substr(0,2).c_str());
        } else
            gpu = 0;
        file2.close();
    } else {
        tempGPU = "0°C";
        gpu = 0;
    }

    map < string, string > stringTokens;
    map < string, int > intTokens;

    intTokens.insert(pair<string,int>("cputemp", cpu));
    intTokens.insert(pair<string,int>("gputemp", gpu));

    ClearViewElement(veTemperatures);
    DrawViewElement(veTemperatures, &stringTokens, &intTokens);
}

bool cDisplayMenuMainView::DrawDevices(void) {
    if (!ViewElementImplemented(veDevices)) {
        return false;
    }

    map < string, string > stringTokens;
    map < string, int > intTokens;
    map < string, vector< map< string, string > > > deviceLoopTokens;
    vector< map< string, string > > devices;

    bool changed = SetDevices(initial, &intTokens, &devices);
    if (!changed)
        return false;

    deviceLoopTokens.insert(pair< string, vector< map< string, string > > >("devices", devices));
    
    ClearViewElement(veDevices);
    DrawViewElement(veDevices, &stringTokens, &intTokens, &deviceLoopTokens);
    return true;
}

void cDisplayMenuMainView::DrawCurrentSchedule(void) {
    if (!ViewElementImplemented(veCurrentSchedule)) {
        return;
    }

    cDevice *device = cDevice::PrimaryDevice();
    const cChannel *channel = NULL;
    if (!device->Replaying() || device->Transferring()) {
        channel = Channels.GetByNumber(device->CurrentChannel());
    }
    if (!channel)
        return;

    const cEvent *event = NULL;
    cSchedulesLock SchedulesLock;
    if (const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock))
        if (const cSchedule *Schedule = Schedules->GetSchedule(channel))
            event = Schedule->GetPresentEvent();
    if (!event)
        return;

    map < string, string > stringTokens;
    map < string, int > intTokens;

    stringTokens.insert(pair<string,string>("title", (event->Title())?event->Title():""));
    stringTokens.insert(pair<string,string>("subtitle", (event->ShortText())?event->ShortText():""));
    stringTokens.insert(pair<string,string>("start", *event->GetTimeString()));
    stringTokens.insert(pair<string,string>("stop", *event->GetEndTimeString()));
    intTokens.insert(pair<string,int>("duration", event->Duration() / 60));
    intTokens.insert(pair<string,int>("durationhours", event->Duration() / 3600));
    stringTokens.insert(pair<string,string>("durationminutes", *cString::sprintf("%.2d", (event->Duration() / 60)%60)));
    intTokens.insert(pair<string,int>("elapsed", (int)round((time(NULL) - event->StartTime())/60)));
    intTokens.insert(pair<string,int>("remaining", (int)round((event->EndTime() - time(NULL))/60)));

    int mediaWidth = 0;
    int mediaHeight = 0;
    string mediaPath = "";
    bool isBanner = false;
    int posterWidth = 0;
    int posterHeight = 0;
    string posterPath = "";
    bool hasPoster = false;
    int bannerWidth = 0;
    int bannerHeight = 0;
    string bannerPath = "";
    bool hasBanner = false;
    static cPlugin *pScraper = GetScraperPlugin();
    if (pScraper) {
        ScraperGetPosterBanner call;
        call.event = event;
        if (pScraper->Service("GetPosterBanner", &call)) {
            if ((call.type == tSeries) && call.banner.path.size() > 0) {
                mediaWidth = call.banner.width;
                mediaHeight = call.banner.height;
                mediaPath = call.banner.path;
                isBanner = true;
                bannerWidth = mediaWidth;
                bannerHeight = mediaHeight;
                bannerPath = mediaPath;
                hasBanner = true;
                ScraperGetPoster callPoster;
                callPoster.event = event;
                callPoster.recording = NULL;
                if (pScraper->Service("GetPoster", &callPoster)) {
                    posterWidth = callPoster.poster.width;
                    posterHeight = callPoster.poster.height;
                    posterPath = callPoster.poster.path;
                    hasPoster = true;
                }
            } else if (call.type == tMovie && call.poster.path.size() > 0 && call.poster.height > 0) {
                mediaWidth = call.poster.width;
                mediaHeight = call.poster.height;
                mediaPath = call.poster.path;
                posterWidth = call.poster.width;
                posterHeight = call.poster.height;
                posterPath = call.poster.path;
                hasPoster = true;
            }
        }
    }
    intTokens.insert(pair<string,int>("mediawidth", mediaWidth));
    intTokens.insert(pair<string,int>("mediaheight", mediaHeight));
    intTokens.insert(pair<string,int>("isbanner", isBanner));
    stringTokens.insert(pair<string,string>("mediapath", mediaPath));
    intTokens.insert(pair<string,int>("posterwidth", posterWidth));
    intTokens.insert(pair<string,int>("posterheight", posterHeight));
    stringTokens.insert(pair<string,string>("posterpath", posterPath));
    intTokens.insert(pair<string,int>("hasposter", hasPoster));
    intTokens.insert(pair<string,int>("bannerwidth", bannerWidth));
    intTokens.insert(pair<string,int>("bannerheight", bannerHeight));
    stringTokens.insert(pair<string,string>("bannerpath", bannerPath));
    intTokens.insert(pair<string,int>("hasbanner", hasBanner));

    ClearViewElement(veCurrentSchedule);
    DrawViewElement(veCurrentSchedule, &stringTokens, &intTokens);
}

void cDisplayMenuMainView::DrawCurrentWeather(void) {
    if (!ViewElementImplemented(veCurrentWeather)) {
        return;
    }
    map < string, string > stringTokens;
    map < string, int > intTokens;
    if (!SetCurrentWeatherTokens(stringTokens, intTokens)){
        ClearViewElement(veCurrentWeather);
        return;
    }
    
    ClearViewElement(veCurrentWeather);
    DrawViewElement(veCurrentWeather, &stringTokens, &intTokens);
}

void cDisplayMenuMainView::DrawCustomTokens(void) {
    if (!ViewElementImplemented(veCustomTokens)) {
        return;
    }
    if (!tmplView)
        return;
    map < string, string > stringTokens = tmplView->GetCustomStringTokens();
    map < string, int > intTokens = tmplView->GetCustomIntTokens();
    DrawViewElement(veCustomTokens, &stringTokens, &intTokens);
}

/************************************************************************
* cDisplayMenuSchedulesView
************************************************************************/

cDisplayMenuSchedulesView::cDisplayMenuSchedulesView(cTemplateView *tmplView, eMenuCategory menuCat, bool menuInit) : cDisplayMenuView(tmplView, menuInit) {
    cat = menuCat;
    channel = NULL;
    isEpgSearchFavoritesMenu = false;
}

cDisplayMenuSchedulesView::~cDisplayMenuSchedulesView() {
    CancelSave();
    FadeOut();
}

bool cDisplayMenuSchedulesView::DrawHeader(void) {
    if (!ViewElementImplemented(veHeader)) {
        return false;
    }

    map < string, string > stringTokens;
    map < string, int > intTokens;

    intTokens.insert(pair<string,int>("whatson", (cat == mcSchedule)&&(!isEpgSearchFavoritesMenu) ? true: false));
    intTokens.insert(pair<string,int>("whatsonnow", (cat == mcScheduleNow) ? true: false));
    intTokens.insert(pair<string,int>("whatsonnext", (cat == mcScheduleNext) ? true: false));
    intTokens.insert(pair<string,int>("whatsonfavorites", isEpgSearchFavoritesMenu ? true: false));

    stringTokens.insert(pair<string,string>("title", menuTitle));
    stringTokens.insert(pair<string,string>("vdrversion", VDRVERSION));
    if (channel) {
        stringTokens.insert(pair<string,string>("channelnumber", *cString::sprintf("%d", channel->Number())));
        stringTokens.insert(pair<string,string>("channelname", channel->Name()));
        string channelID = *(channel->GetChannelID().ToString());
        stringTokens.insert(pair<string,string>("channelid", channelID));
        intTokens.insert(pair<string, int>("channellogoexists", imgCache->LogoExists(channelID)));
    } else {
        stringTokens.insert(pair<string,string>("channelnumber", ""));
        stringTokens.insert(pair<string,string>("channelname", ""));
        stringTokens.insert(pair<string,string>("channelid", ""));
        intTokens.insert(pair<string, int>("channellogoexists", 0));        
    }
    bool hasIcon = false;
    string icon = imgCache->GetIconName(menuTitle, cat);
    if (icon.size() > 0)
        hasIcon = true;

    stringTokens.insert(pair<string,string>("icon", icon));
    intTokens.insert(pair<string,int>("hasicon", hasIcon));
    ClearViewElement(veHeader);
    DrawViewElement(veHeader, &stringTokens, &intTokens);
    return true;
}