/*
	Neutrino-GUI  -   DBoxII-Project

	Copyright (C) 2001 Steffen Hehn 'McClean'
	Copyright (C) 2004 Martin Griep 'vivamiga'
	Copyright (C) 2009-2014 Stefan Seyfried

	License: GPL

	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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>

#include <global.h>
#include <neutrino.h>

#include <gui/infoviewer.h>
#include <gui/epgplus.h>
#include <gui/epgview.h>
#include <sectionsdclient/sectionsdclient.h>
#include <timerdclient/timerdclient.h>

#include <gui/components/cc.h>
#include <gui/widget/icons.h>
#include <gui/widget/buttons.h>
#include <gui/widget/messagebox.h>
#include <gui/widget/stringinput.h>
#include "bouquetlist.h"

#include <zapit/client/zapittools.h>
#include <driver/rcinput.h>
#include <driver/screen_max.h>
#include <driver/fade.h>
#include <zapit/satconfig.h>
#include <zapit/getservices.h>
#include <eitd/sectionsd.h>

#include <algorithm>
#include <sstream>

#define COL_MENUCONTENT_P1                 COL_MENUCONTENT_TEXT_PLUS_1
#define COL_MENUCONTENT_P2                 COL_MENUCONTENT_TEXT_PLUS_2
extern CBouquetList *bouquetList;

Font * fonts[EpgPlus::NumberOfFontSettings];
int sizes[EpgPlus::NumberOfSizeSettings];

time_t EpgPlus::duration = 0;

int EpgPlus::horGap1Height = 0;
int EpgPlus::horGap2Height = 0;
int EpgPlus::verGap1Width = 0;
int EpgPlus::verGap2Width = 0;

int EpgPlus::horGap1Color = 0;
int EpgPlus::horGap2Color = 0;
int EpgPlus::verGap1Color = 0;
int EpgPlus::verGap2Color = 0;

int EpgPlus::sliderWidth = 0;
int EpgPlus::channelsTableWidth = 0;

static EpgPlus::FontSetting fontSettingTable[] = {
	{ EpgPlus::EPGPlus_header_font,				"Regular",	27 },
	{ EpgPlus::EPGPlus_timeline_fonttime,			"Bold",		16 },
	{ EpgPlus::EPGPlus_timeline_fontdate,			"Bold",		14 },
	{ EpgPlus::EPGPlus_channelentry_font,			"Bold",		16 },
	{ EpgPlus::EPGPlus_channelevententry_font,		"Regular",	16 },
	{ EpgPlus::EPGPlus_footer_fontbouquetchannelname,	"Bold",		24 },
	{ EpgPlus::EPGPlus_footer_fonteventdescription,		"Regular",	16 },
	{ EpgPlus::EPGPlus_footer_fonteventshortdescription,	"Regular",	16 },
	{ EpgPlus::EPGPlus_footer_fontbuttons,			"Regular",	16 },
};

/* negative size means "screen width in percent" */
static EpgPlus::SizeSetting sizeSettingTable[] = {
	{EpgPlus::EPGPlus_channelentry_width, -15 }, /* 15 percent of screen width */
	{EpgPlus::EPGPlus_channelentry_separationlineheight, 2},
	{EpgPlus::EPGPlus_slider_width, 15},
	{EpgPlus::EPGPlus_horgap1_height, 4},
	{EpgPlus::EPGPlus_horgap2_height, 4},
	{EpgPlus::EPGPlus_vergap1_width, 4},
	{EpgPlus::EPGPlus_vergap2_width, 4},
};

static bool bigfont = false;
static int current_bouquet;

Font *EpgPlus::Header::font = NULL;

EpgPlus::Header::Header (CFrameBuffer * pframeBuffer, int px, int py, int pwidth)
{
	this->frameBuffer = pframeBuffer;
	this->x = px;
	this->y = py;
	this->width = pwidth;
}

EpgPlus::Header::~Header()
{
}

void EpgPlus::Header::init()
{
	font = fonts[EPGPlus_header_font];
}

void EpgPlus::Header::paint(const char * Name)
{
	std::string head = Name ? Name : g_Locale->getText (LOCALE_EPGPLUS_HEAD);

	CComponentsHeader header(this->x, this->y, this->width, this->font->getHeight()+4, head);
	header.paint(CC_SAVE_SCREEN_NO);
}

int EpgPlus::Header::getUsedHeight()
{
	return font->getHeight() + 8 ;
}

Font *EpgPlus::TimeLine::fontTime = NULL;
Font *EpgPlus::TimeLine::fontDate = NULL;

EpgPlus::TimeLine::TimeLine (CFrameBuffer * pframeBuffer, int px, int py, int pwidth, int pstartX, int pdurationX)
{
	this->frameBuffer = pframeBuffer;
	this->x = px;
	this->y = py;
	this->width = pwidth;
	this->startX = pstartX;
	this->durationX = pdurationX;
}

void EpgPlus::TimeLine::init()
{
	fontTime = fonts[EPGPlus_timeline_fonttime];
	fontDate = fonts[EPGPlus_timeline_fontdate];
}

EpgPlus::TimeLine::~TimeLine()
{
}

void EpgPlus::TimeLine::paint (time_t startTime, int pduration)
{
	this->clearMark();

	int xPos = this->startX;

	this->currentDuration = pduration;
	int numberOfTicks = this->currentDuration / (60 * 60) * 2;
	int tickDist = (this->durationX) / numberOfTicks;
	time_t tickTime = startTime;
	bool toggleColor = false;

	// display date of begin
	this->frameBuffer->paintBoxRel (this->x, this->y, this->width, this->fontTime->getHeight()
					, toggleColor ? COL_MENUCONTENT_PLUS_2 : COL_MENUCONTENT_PLUS_1);

	this->fontDate->RenderString (this->x + 4, this->y + this->fontDate->getHeight()
				      , this->width, EpgPlus::getTimeString (startTime, "%d-%b") , COL_MENUCONTENT_TEXT);

	// paint ticks
	for (int i = 0; i < numberOfTicks; ++i, xPos += tickDist, tickTime += pduration / numberOfTicks) {
		int xWidth = tickDist;
		if (xPos + xWidth > this->x + width)
			xWidth = this->x + width - xPos;

		this->frameBuffer->paintBoxRel (xPos, this->y, xWidth, this->fontTime->getHeight()
						, toggleColor ? COL_MENUCONTENT_PLUS_1 : COL_MENUCONTENT_PLUS_2);

		std::string timeStr = EpgPlus::getTimeString (tickTime, "%H");

		int textWidth = this->fontTime->getRenderWidth (timeStr);

		this->fontTime->RenderString (xPos - textWidth - 4, this->y + this->fontTime->getHeight()
					      , textWidth, timeStr, toggleColor ? COL_MENUCONTENT_P1 : COL_MENUCONTENT_P2);

		timeStr = EpgPlus::getTimeString (tickTime, "%M");
		textWidth = this->fontTime->getRenderWidth (timeStr);
		this->fontTime->RenderString (xPos + 4, this->y + this->fontTime->getHeight()
					      , textWidth, timeStr, toggleColor ? COL_MENUCONTENT_P1 : COL_MENUCONTENT_P2);

		toggleColor = !toggleColor;
	}
}

void EpgPlus::TimeLine::paintGrid()
{
	int xPos = this->startX;
	int numberOfTicks = this->currentDuration / (60 * 60) * 2;
	int tickDist = (this->durationX) / numberOfTicks;
	// paint ticks
	for (int i = 0; i < numberOfTicks; ++i, xPos += tickDist) {
		// display a line for the tick
		this->frameBuffer->paintVLineRel (xPos, this->y, this->fontTime->getHeight(), COL_MENUCONTENT_PLUS_5);
	}
}

void EpgPlus::TimeLine::paintMark (time_t startTime, int pduration, int px, int pwidth)
{
	// clear old mark
	this->clearMark();

	// paint new mark
	this->frameBuffer->paintBoxRel (px, this->y + this->fontTime->getHeight()
					, pwidth, this->fontTime->getHeight() , COL_MENUCONTENTSELECTED_PLUS_0);

	// display start time before mark
	std::string timeStr = EpgPlus::getTimeString (startTime, "%H:%M");
	int textWidth = this->fontTime->getRenderWidth (timeStr);

	this->fontTime->RenderString (px - textWidth, this->y + this->fontTime->getHeight() + this->fontTime->getHeight()
				      , textWidth, timeStr, COL_MENUCONTENT_TEXT);

	// display end time after mark
	timeStr = EpgPlus::getTimeString (startTime + pduration, "%H:%M");
	textWidth = fontTime->getRenderWidth (timeStr);

	if (px + pwidth + textWidth < this->x + this->width) {
		this->fontTime->RenderString (px + pwidth, this->y + this->fontTime->getHeight() + this->fontTime->getHeight()
					      , textWidth, timeStr, COL_MENUCONTENT_TEXT);
	} else if (textWidth < pwidth - 10) {
		this->fontTime->RenderString (px + pwidth - textWidth, this->y + this->fontTime->getHeight() + this->fontTime->getHeight()
					      , textWidth, timeStr, COL_MENUCONTENTSELECTED_TEXT);
	}
}

void EpgPlus::TimeLine::clearMark()
{
	this->frameBuffer->paintBoxRel (this->x, this->y + this->fontTime->getHeight()
					, this->width, this->fontTime->getHeight() , COL_MENUCONTENT_PLUS_0);
}

int EpgPlus::TimeLine::getUsedHeight()
{
	return std::max (fontDate->getHeight(), fontTime->getHeight())
	       + fontTime->getHeight();
}

Font *EpgPlus::ChannelEventEntry::font = NULL;
int EpgPlus::ChannelEventEntry::separationLineHeight = 0;

EpgPlus::ChannelEventEntry::ChannelEventEntry (const CChannelEvent * pchannelEvent, CFrameBuffer * pframeBuffer, TimeLine * ptimeLine, Footer * pfooter, int px, int py, int pwidth)
{
	// copy neccessary?
	if (pchannelEvent != NULL)
		this->channelEvent = *pchannelEvent;

	this->frameBuffer = pframeBuffer;
	this->timeLine = ptimeLine;
	this->footer = pfooter;
	this->x = px;
	this->y = py;
	this->width = pwidth;
}

void EpgPlus::ChannelEventEntry::init()
{
	font = fonts[EPGPlus_channelevententry_font];
	separationLineHeight = sizes[EPGPlus_channelentry_separationlineheight];
}

EpgPlus::ChannelEventEntry::~ChannelEventEntry()
{
}

bool EpgPlus::ChannelEventEntry::isSelected (time_t selectedTime) const
{
	return (selectedTime >= this->channelEvent.startTime) && (selectedTime < this->channelEvent.startTime + time_t (this->channelEvent.duration));
}

void EpgPlus::ChannelEventEntry::paint (bool pisSelected, bool toggleColor)
{
	this->frameBuffer->paintBoxRel (this->x, this->y, this->width, this->font->getHeight()
			, this->channelEvent.description.empty()? COL_MENUCONTENT_PLUS_0 : (pisSelected ? COL_MENUCONTENTSELECTED_PLUS_0 : (toggleColor ? COL_MENUCONTENT_PLUS_1 : COL_MENUCONTENT_PLUS_2)));

	this->font->RenderString (this->x + 2, this->y + this->font->getHeight()
				  , this->width - 4 > 0 ? this->width - 4 : 0, this->channelEvent.description, pisSelected ? COL_MENUCONTENTSELECTED_TEXT : (toggleColor ? COL_MENUCONTENT_P1 : COL_MENUCONTENT_P2));

	// paint the separation line
	if (separationLineHeight > 0) {
		this->frameBuffer->paintBoxRel (this->x, this->y + this->font->getHeight()
						, this->width, this->separationLineHeight, COL_MENUCONTENT_PLUS_5);
	}

	if (pisSelected) {
		if (this->channelEvent.description.empty()) {	// dummy channel event
			this->timeLine->clearMark();
		} else {
			this->timeLine->paintMark (this->channelEvent.startTime, this->channelEvent.duration, this->x, this->width);
		}

		CShortEPGData shortEpgData;

		bool ret = CEitManager::getInstance()->getEPGidShort(this->channelEvent.eventID, &shortEpgData);
		this->footer->paintEventDetails (this->channelEvent.description, ret ? shortEpgData.info1 : "");

		this->timeLine->paintGrid();
	}
}

int EpgPlus::ChannelEventEntry::getUsedHeight()
{
	return font->getHeight() + separationLineHeight;
}

Font *EpgPlus::ChannelEntry::font = NULL;
int EpgPlus::ChannelEntry::separationLineHeight = 0;

EpgPlus::ChannelEntry::ChannelEntry (const CZapitChannel * pchannel, int pindex, CFrameBuffer * pframeBuffer, Footer * pfooter, CBouquetList * pbouquetList, int px, int py, int pwidth)
{
	this->channel = pchannel;

	if (pchannel != NULL) {
		std::stringstream pdisplayName;
		//pdisplayName << pindex + 1 << " " << pchannel->getName();
		pdisplayName << pchannel->number << " " << pchannel->getName();

		this->displayName = pdisplayName.str();
	}

	this->index = pindex;

	this->frameBuffer = pframeBuffer;
	this->footer = pfooter;
	this->bouquetList = pbouquetList;

	this->x = px;
	this->y = py;
	this->width = pwidth;
}

void EpgPlus::ChannelEntry::init()
{
	font = fonts[EPGPlus_channelentry_font];
	separationLineHeight = sizes[EPGPlus_channelentry_separationlineheight];
}

EpgPlus::ChannelEntry::~ChannelEntry()
{
	for (TCChannelEventEntries::iterator It = this->channelEventEntries.begin();
			It != this->channelEventEntries.end(); ++It) {
		delete *It;
	}
	this->channelEventEntries.clear();
}

void EpgPlus::ChannelEntry::paint (bool isSelected, time_t selectedTime)
{
	this->frameBuffer->paintBoxRel (this->x, this->y, this->width, this->font->getHeight(),
			isSelected ? COL_MENUCONTENTSELECTED_PLUS_0 : COL_MENUCONTENT_PLUS_0);

	this->font->RenderString (this->x + 2, this->y + this->font->getHeight(),
			this->width - 4, this->displayName, isSelected ? COL_MENUCONTENTSELECTED_TEXT : COL_MENUCONTENT_TEXT);

	if (isSelected) {
#if 0
		for (uint32_t i = 0; i < this->bouquetList->Bouquets.size(); ++i) {
			CBouquet *bouquet = this->bouquetList->Bouquets[i];
			for (int j = 0; j < bouquet->channelList->getSize(); ++j) {

				if ((*bouquet->channelList)[j]->number == this->channel->number) {
					this->footer->setBouquetChannelName (bouquet->channelList->getName()
									     , this->channel->getName()
									    );

					bouquet = NULL;
					break;
				}
			}
			if (bouquet == NULL)
				break;
		}
#endif
		if(this->channel->pname) {
			this->footer->setBouquetChannelName(this->channel->pname, this->channel->getName());
		} else {
			this->footer->setBouquetChannelName(
					CServiceManager::getInstance()->GetSatelliteName(this->channel->getSatellitePosition()),
					this->channel->getName());
		}
	}
	// paint the separation line
	if (separationLineHeight > 0) {
		this->frameBuffer->paintBoxRel (this->x, this->y + this->font->getHeight(),
				this->width, this->separationLineHeight, COL_MENUCONTENT_PLUS_5);
	}

	bool toggleColor = false;
	for (TCChannelEventEntries::iterator It = this->channelEventEntries.begin();
			It != this->channelEventEntries.end();
			++It) {
		(*It)->paint (isSelected && (*It)->isSelected (selectedTime), toggleColor);

		toggleColor = !toggleColor;
	}
}

int EpgPlus::ChannelEntry::getUsedHeight()
{
	return font->getHeight() + separationLineHeight;
}

Font *EpgPlus::Footer::fontBouquetChannelName = NULL;
Font *EpgPlus::Footer::fontEventDescription = NULL;
Font *EpgPlus::Footer::fontEventShortDescription = NULL;
Font *EpgPlus::Footer::fontButtons = NULL;
int EpgPlus::Footer::color = 0;

EpgPlus::Footer::Footer (CFrameBuffer * pframeBuffer, int px, int py, int pwidth, int /*height*/)
{
	this->frameBuffer = pframeBuffer;
	this->x = px;
	this->y = py;
	this->width = pwidth;
 	this->buttonHeight = g_Font[SNeutrinoSettings::FONT_TYPE_INFOBAR_SMALL]->getHeight()+8; //TODO get height from buttons
}

EpgPlus::Footer::~Footer()
{
}

void EpgPlus::Footer::init()
{
	fontBouquetChannelName = fonts[EPGPlus_footer_fontbouquetchannelname];
	fontEventDescription = fonts[EPGPlus_footer_fonteventdescription];
	fontEventShortDescription = fonts[EPGPlus_footer_fonteventshortdescription];
	fontButtons = fonts[EPGPlus_footer_fontbuttons];
}

void EpgPlus::Footer::setBouquetChannelName (const std::string & newBouquetName, const std::string & newChannelName)
{
	this->currentBouquetName = newBouquetName;
	this->currentChannelName = newChannelName;
}

int EpgPlus::Footer::getUsedHeight()
{
	return fontBouquetChannelName->getHeight() + fontEventDescription->getHeight()
	       + fontEventShortDescription->getHeight() /* + fontButtons->getHeight()*/;
}

void EpgPlus::Footer::paintEventDetails (const std::string & description, const std::string & shortDescription)
{
	int yPos = this->y;

	int height = this->fontBouquetChannelName->getHeight();

	// clear the region
	this->frameBuffer->paintBoxRel (this->x, yPos, this->width, height, COL_MENUCONTENT_PLUS_0);

	yPos += height;

	// display new text
	this->fontBouquetChannelName->RenderString (this->x + 10, yPos, this->width - 20, this->currentBouquetName + " : " + this->currentChannelName, COL_MENUHEAD_TEXT);

	height = this->fontEventDescription->getHeight();

	// clear the region
	this->frameBuffer->paintBoxRel (this->x, yPos, this->width, height, COL_MENUCONTENT_PLUS_0);

	yPos += height;

	// display new text
	this->fontEventDescription->RenderString (this->x + 10, yPos, this->width - 20, description, COL_MENUHEAD_TEXT);

	height = this->fontEventShortDescription->getHeight();

	// clear the region
	this->frameBuffer->paintBoxRel (this->x, yPos, this->width, height, COL_MENUCONTENT_PLUS_0);

	yPos += height;

	// display new text
	this->fontEventShortDescription->RenderString (this->x + 10, yPos, this->width - 20, shortDescription, COL_MENUHEAD_TEXT);
}

struct button_label buttonLabels[] = {
	{NEUTRINO_ICON_BUTTON_RED, LOCALE_EPGPLUS_ACTIONS},
	{NEUTRINO_ICON_BUTTON_GREEN, LOCALE_EPGPLUS_PREV_BOUQUET /*LOCALE_EPGPLUS_PAGE_UP*/},
	{NEUTRINO_ICON_BUTTON_YELLOW, LOCALE_EPGPLUS_NEXT_BOUQUET /*LOCALE_EPGPLUS_PAGE_DOWN*/},
	{NEUTRINO_ICON_BUTTON_BLUE, LOCALE_EPGPLUS_OPTIONS},
	{NEUTRINO_ICON_BUTTON_INFO_SMALL, LOCALE_EPGMENU_EVENTINFO}
};

void EpgPlus::Footer::paintButtons (button_label * pbuttonLabels, int numberOfButtons)
{
	int buttonWidth = (this->width);
	int yPos = this->y + this->getUsedHeight();
	::paintButtons (this->x, yPos, buttonWidth, numberOfButtons, pbuttonLabels, buttonWidth, buttonHeight);
}

EpgPlus::EpgPlus()
{
	selectedChannelEntry = NULL;
	this->init();
}

EpgPlus::~EpgPlus()
{
	this->free();
}

void EpgPlus::createChannelEntries (int selectedChannelEntryIndex)
{
	for (TChannelEntries::iterator It = this->displayedChannelEntries.begin();
			It != this->displayedChannelEntries.end(); ++It) {
		delete *It;
	}
	this->displayedChannelEntries.clear();

	this->selectedChannelEntry = NULL;

	if (selectedChannelEntryIndex < this->channelList->getSize()) {
		for (;;) {
			if (selectedChannelEntryIndex < this->channelListStartIndex) {
				this->channelListStartIndex -= this->maxNumberOfDisplayableEntries;
				if (this->channelListStartIndex < 0)
					this->channelListStartIndex = 0;
			} else if (selectedChannelEntryIndex >= this->channelListStartIndex + this->maxNumberOfDisplayableEntries) {
				this->channelListStartIndex += this->maxNumberOfDisplayableEntries;
			} else
				break;
		}

		int yPosChannelEntry = this->channelsTableY;
		int yPosEventEntry = this->eventsTableY;

		for (int i = this->channelListStartIndex; (i < this->channelListStartIndex + this->maxNumberOfDisplayableEntries)
				&& (i < this->channelList->getSize());
				++i, yPosChannelEntry += this->entryHeight, yPosEventEntry += this->entryHeight) {

			CZapitChannel * channel = (*this->channelList)[i];

			ChannelEntry *channelEntry = new ChannelEntry (channel, i, this->frameBuffer, this->footer, this->bouquetList, this->channelsTableX + 2, yPosChannelEntry, this->channelsTableWidth);
//printf("Going to get getEventsServiceKey for %llx\n", (channel->getChannelID() & 0xFFFFFFFFFFFFULL));
			CChannelEventList channelEventList;
			CEitManager::getInstance()->getEventsServiceKey(channel->getEpgID(), channelEventList);
//printf("channelEventList size %d\n", channelEventList.size());

			int xPosEventEntry = this->eventsTableX;
			int widthEventEntry = 0;
			time_t lastEndTime = this->startTime;

			CChannelEventList::const_iterator lastIt (channelEventList.end());
			//for (CChannelEventList::const_iterator It = channelEventList.begin(); (It != channelEventList.end()) && (It->startTime < (this->startTime + this->duration)); ++It)
			for (CChannelEventList::const_iterator It = channelEventList.begin(); It != channelEventList.end(); ++It)
			{
//if(0x2bc000b004b7ULL == (channel->getChannelID() & 0xFFFFFFFFFFFFULL)) printf("*** Check1 event %s event start %ld this start %ld\n", It->description.c_str(), It->startTime, (this->startTime + this->duration));
				if (!(It->startTime < (this->startTime + this->duration)) )
					continue;
				if ((lastIt == channelEventList.end()) || (lastIt->startTime != It->startTime)) {
					int startTimeDiff = It->startTime - this->startTime;
					int endTimeDiff = this->startTime + time_t (this->duration) - It->startTime - time_t (It->duration);
//if(0x2bc000b004b7ULL == (channel->getChannelID() & 0xFFFFFFFFFFFFULL)) printf("*** Check event %s\n", It->description.c_str());
					if ((startTimeDiff >= 0) && (endTimeDiff >= 0)) {
						// channel event fits completely in the visible part of time line
						startTimeDiff = 0;
						endTimeDiff = 0;
					} else if ((startTimeDiff < 0) && (endTimeDiff < 0)) {
						// channel event starts and ends outside visible part of the time line but covers complete visible part
					} else if ((startTimeDiff < 0) && (endTimeDiff < this->duration)) {
						// channel event starts before visible part of the time line but ends in the visible part
						endTimeDiff = 0;
					} else if ((endTimeDiff < 0) && (startTimeDiff < this->duration)) {
						// channel event ends after visible part of the time line but starts in the visible part
						startTimeDiff = 0;
					} else if (startTimeDiff > 0) {	// channel event starts and ends after visible part of the time line => break the loop
//if(0x2bc000b004b7ULL == (channel->getChannelID() & 0xFFFFFFFFFFFFULL)) printf("*** break 1\n");
						break;
					} else {				// channel event starts and ends after visible part of the time line => ignore the channel event
//if(0x2bc000b004b7ULL == (channel->getChannelID() & 0xFFFFFFFFFFFFULL)) printf("*** continue 1 startTimeDiff %ld endTimeDiff %ld\n", startTimeDiff, endTimeDiff);
						continue;
					}

					if (lastEndTime < It->startTime) {	// there is a gap between last end time and new start time => fill it with a new event entry

						CChannelEvent channelEvent;
						channelEvent.startTime = lastEndTime;
						channelEvent.duration = It->startTime - channelEvent.startTime;

						ChannelEventEntry *channelEventEntry = new ChannelEventEntry (&channelEvent, this->frameBuffer, this->timeLine, this->footer, this->eventsTableX + ((channelEvent.startTime - this->startTime) * this->eventsTableWidth) / this->duration, yPosEventEntry, (channelEvent.duration * this->eventsTableWidth) / this->duration + 1);
						channelEntry->channelEventEntries.push_back (channelEventEntry);
					}
					// correct position
					xPosEventEntry = this->eventsTableX + ((It->startTime - startTimeDiff - this->startTime) * this->eventsTableWidth) / this->duration;

					// correct width
					widthEventEntry = ((It->duration + startTimeDiff + endTimeDiff) * this->eventsTableWidth) / this->duration + 1;

					if (widthEventEntry < 0)
						widthEventEntry = 0;

					if (xPosEventEntry + widthEventEntry > this->eventsTableX + this->eventsTableWidth)
						widthEventEntry = this->eventsTableX + this->eventsTableWidth - xPosEventEntry;

					ChannelEventEntry *channelEventEntry = new ChannelEventEntry (&(*It) , this->frameBuffer, this->timeLine, this->footer, xPosEventEntry, yPosEventEntry, widthEventEntry);

					channelEntry->channelEventEntries.push_back (channelEventEntry);
					lastEndTime = It->startTime + It->duration;
				}
				lastIt = It;
			}

			if (lastEndTime < this->startTime + time_t (this->duration)) {	// there is a gap between last end time and end of the timeline => fill it with a new event entry

				CChannelEvent channelEvent;
				channelEvent.startTime = lastEndTime;
				channelEvent.duration = this->startTime + this->duration - channelEvent.startTime;

				ChannelEventEntry *channelEventEntry = new ChannelEventEntry (&channelEvent, this->frameBuffer, this->timeLine, this->footer, this->eventsTableX + ((channelEvent.startTime - this->startTime) * this->eventsTableWidth) / this->duration, yPosEventEntry, (channelEvent.duration * this->eventsTableWidth) / this->duration + 1);
				channelEntry->channelEventEntries.push_back (channelEventEntry);
			}
			this->displayedChannelEntries.push_back (channelEntry);
		}

		this->selectedChannelEntry = this->displayedChannelEntries[selectedChannelEntryIndex - this->channelListStartIndex];
	}
}

void EpgPlus::init()
{
	frameBuffer = CFrameBuffer::getInstance();
#if 0
	currentViewMode = ViewMode_Scroll;
	currentSwapMode = SwapMode_ByPage;
#endif
	usableScreenWidth = frameBuffer->getScreenWidthRel();
	usableScreenHeight = frameBuffer->getScreenHeightRel();
	std::string FileName = std::string (g_settings.font_file);
	for (size_t i = 0; i < NumberOfFontSettings; ++i) {
		int size = fontSettingTable[i].size;
		if (bigfont && (fontSettingTable[i].settingID == EpgPlus::EPGPlus_channelentry_font ||
				fontSettingTable[i].settingID == EpgPlus::EPGPlus_channelevententry_font)) {
			size = size * 3 / 2; /* increase font size for channel name and event title */
		}
		std::string family = g_fontRenderer->getFamily (FileName.c_str());
		Font *font = g_fontRenderer->getFont(family.c_str(), fontSettingTable[i].style, size);

		if (font == NULL)
			font = g_fontRenderer->getFont(family.c_str(), "Regular", size);

		fonts[i] = font;
	}

	for (size_t i = 0; i < NumberOfSizeSettings; ++i) {
		int size = sizeSettingTable[i].size;
		if (size < 0) /* size < 0 == width in percent x -1 */
			size = usableScreenWidth * size / -100;
		sizes[i] = size;
	}

	Header::init();
	TimeLine::init();
	ChannelEntry::init();
	ChannelEventEntry::init();
	Footer::init();

	channelsTableWidth = sizes[EPGPlus_channelentry_width];
	sliderWidth = sizes[EPGPlus_slider_width];

	horGap1Height = sizes[EPGPlus_horgap1_height];
	horGap2Height = sizes[EPGPlus_horgap2_height];
	verGap1Width = sizes[EPGPlus_vergap1_width];
	verGap2Width = sizes[EPGPlus_vergap2_width];

	int headerHeight = Header::getUsedHeight();
	int timeLineHeight = TimeLine::getUsedHeight();
	this->entryHeight = ChannelEntry::getUsedHeight();

        int icol_w, icol_h, h2;

        CFrameBuffer::getInstance()->getIconSize(NEUTRINO_ICON_BUTTON_RED, &icol_w, &icol_h);
	CFrameBuffer::getInstance()->getIconSize(NEUTRINO_ICON_BUTTON_HELP, &icol_w, &h2);
// 	if(icol_h < h2)
// 		icol_h = h2;

	int buttonHeight = g_Font[SNeutrinoSettings::FONT_TYPE_INFOBAR_SMALL]->getHeight()+8; //TODO get height from buttons/*std::max (icol_h+8, fonts[EPGPlus_footer_fontbuttons]->getHeight());*/
	int footerHeight = Footer::getUsedHeight() + buttonHeight;

	this->maxNumberOfDisplayableEntries = (this->usableScreenHeight - headerHeight - timeLineHeight - horGap1Height - horGap2Height - footerHeight) / this->entryHeight;

	this->usableScreenHeight = headerHeight + timeLineHeight + horGap1Height + this->maxNumberOfDisplayableEntries * this->entryHeight + horGap2Height + footerHeight;	// recalc deltaY
	this->usableScreenX = getScreenStartX(this->usableScreenWidth);
	this->usableScreenY = getScreenStartY(this->usableScreenHeight);

	this->headerX = this->usableScreenX;
	this->headerY = this->usableScreenY;
	this->headerWidth = this->usableScreenWidth;

	this->timeLineX = this->usableScreenX;
	this->timeLineY = this->usableScreenY + headerHeight;
	this->timeLineWidth = this->usableScreenWidth;

	this->horGap1X = this->usableScreenX;
	this->horGap1Y = this->timeLineY + timeLineHeight;
	this->horGap1Width = this->usableScreenWidth;

	this->footerX = usableScreenX;
	this->footerY = this->usableScreenY + this->usableScreenHeight - footerHeight;
	this->footerWidth = this->usableScreenWidth;

	this->horGap2X = this->usableScreenX;
	this->horGap2Y = this->footerY - horGap2Height;
	this->horGap2Width = this->usableScreenWidth;

	this->channelsTableX = this->usableScreenX;
	this->channelsTableY = this->timeLineY + timeLineHeight + horGap1Height;
	this->channelsTableHeight = this->maxNumberOfDisplayableEntries * entryHeight;

	this->verGap1X = this->channelsTableX + channelsTableWidth;
	this->verGap1Y = this->channelsTableY;
	this->verGap1Height = this->channelsTableHeight;

	this->eventsTableX = this->channelsTableX + channelsTableWidth + verGap1Width;
	this->eventsTableY = this->channelsTableY;
	this->eventsTableWidth = this->usableScreenWidth - this->channelsTableWidth - this->sliderWidth - verGap1Width - verGap2Width;
	this->eventsTableHeight = this->channelsTableHeight;

	this->sliderX = this->usableScreenX + this->usableScreenWidth - this->sliderWidth;
	this->sliderY = this->eventsTableY;
	this->sliderHeight = this->channelsTableHeight;

	this->verGap2X = this->sliderX - verGap2Width;
	this->verGap2Y = this->channelsTableY;
	this->verGap2Height = this->channelsTableHeight;

	this->channelListStartIndex = 0;
	this->startTime = 0;
	this->duration = 2 * 60 * 60;

	this->refreshAll = false;
	this->currentViewMode = ViewMode_Scroll;
	this->currentSwapMode = SwapMode_ByBouquet; //SwapMode_ByPage;

	this->header = new Header (this->frameBuffer, this->headerX, this->headerY, this->headerWidth);

	this->timeLine = new TimeLine (this->frameBuffer, this->timeLineX, this->timeLineY, this->timeLineWidth, this->eventsTableX, this->eventsTableWidth);

	this->footer = new Footer (this->frameBuffer, this->footerX, this->footerY, this->footerWidth, buttonHeight);
}

void EpgPlus::free()
{
	delete this->header;
	delete this->timeLine;
	delete this->footer;
	int i;
	for (i = 0; i < NumberOfFontSettings; ++i) {
		delete fonts[i];
	}
}

int EpgPlus::exec (CChannelList * pchannelList, int selectedChannelIndex, CBouquetList *pbouquetList)
{
	this->channelList = pchannelList;
	this->channelListStartIndex = int (selectedChannelIndex / maxNumberOfDisplayableEntries) * maxNumberOfDisplayableEntries;
	this->bouquetList = pbouquetList;

	int res = menu_return::RETURN_REPAINT;

	COSDFader fader(g_settings.theme.menu_Content_alpha);
	do {
		this->refreshFooterButtons = false;
		time_t currentTime = time (NULL);
		tm tmStartTime = *localtime (&currentTime);

		tmStartTime.tm_sec = 0;
		tmStartTime.tm_min = int (tmStartTime.tm_min / 15) * 15;

		this->startTime = mktime (&tmStartTime);
		this->selectedTime = this->startTime;
		this->firstStartTime = this->startTime;

		if (this->selectedChannelEntry != NULL) {
			selectedChannelIndex = this->selectedChannelEntry->index;
		}

		neutrino_msg_t msg;
		neutrino_msg_data_t data;

		this->createChannelEntries (selectedChannelIndex);

		if(!this->refreshAll)
			fader.StartFadeIn();

		this->refreshAll = false;

		this->header->paint(this->channelList->getName());

		this->footer->paintButtons (buttonLabels, sizeof (buttonLabels) / sizeof (button_label));

		this->paint();

		uint64_t timeoutEnd = CRCInput::calcTimeoutEnd (g_settings.timing[SNeutrinoSettings::TIMING_CHANLIST]);
		bool loop = true;
		while (loop) {
			g_RCInput->getMsgAbsoluteTimeout (&msg, &data, &timeoutEnd);

			if (msg <= CRCInput::RC_MaxRC)
				timeoutEnd = CRCInput::calcTimeoutEnd (g_settings.timing[SNeutrinoSettings::TIMING_CHANLIST]);


			if((msg == NeutrinoMessages::EVT_TIMER) && (data == fader.GetFadeTimer())) {
				if(fader.FadeDone())
					loop = false;
			}
			else if ((msg == CRCInput::RC_timeout) || (msg == (neutrino_msg_t) g_settings.key_channelList_cancel)) {
				if(fader.StartFadeOut()) {
					timeoutEnd = CRCInput::calcTimeoutEnd( 1 );
					msg = 0;
				} else
					loop = false;
			}
			else if (msg == CRCInput::RC_epg) {
				//fprintf(stderr, "RC_Epg, bigfont = %d\n", bigfont);
				hide();
				bigfont = !bigfont;
				free();
				init();
				refreshAll = true;
				break;
			}
			else if ((msg == CRCInput::RC_page_down)) {
				int selected = this->selectedChannelEntry->index;
				int prev_selected = selected;
				int step = this->maxNumberOfDisplayableEntries;
				int listSize = this->channelList->getSize();

				selected += step;
				if(selected >= listSize) {
					if((listSize - step -1 < prev_selected) && (prev_selected != (listSize - 1)))
						selected = listSize - 1;
					else if (((listSize / step) + 1) * step == listSize + step) // last page has full entries
						selected = 0;
					else
						selected = ((selected < (((listSize / step)+1) * step))) ? (listSize - 1) : 0;
				}

				this->createChannelEntries (selected);
				this->paint();
			}
			else if (msg == CRCInput::RC_page_up) {
				int selected = this->selectedChannelEntry->index;
				int prev_selected = selected;
				int step = this->maxNumberOfDisplayableEntries;

				selected -= step;
				if((prev_selected-step) < 0) {
					if(prev_selected != 0 && step != 1)
						selected = 0;
					else
						selected = this->channelList->getSize() - 1;
				}

				this->createChannelEntries (selected);
				this->paint();
			}
			if (msg == CRCInput::RC_yellow) {
				if (!bouquetList->Bouquets.empty()) {
					bool found = true;
					uint32_t nNext = (bouquetList->getActiveBouquetNumber()+1) % bouquetList->Bouquets.size();
//printf("**************************** EpgPlus::exec current bouquet %d new %d\n", bouquetList->getActiveBouquetNumber(), nNext);
					if(bouquetList->Bouquets[nNext]->channelList->isEmpty() ) {
						found = false;
						nNext = nNext < bouquetList->Bouquets.size()-1 ? nNext+1 : 0;
						for(uint32_t i = nNext; i < bouquetList->Bouquets.size(); i++) {
							if(!bouquetList->Bouquets[i]->channelList->isEmpty() ) {
								found = true;
								nNext = i;
								break;
							}
						}
					}
//printf("**************************** EpgPlus::exec found %d new %d\n", found, nNext);
					if(found) {
						pbouquetList->activateBouquet (nNext, false);
						this->channelList = bouquetList->Bouquets[nNext]->channelList;
						this->channelListStartIndex = int (channelList->getSelectedChannelIndex() / maxNumberOfDisplayableEntries) * maxNumberOfDisplayableEntries;
						this->createChannelEntries (channelList->getSelectedChannelIndex());
						this->header->paint(this->channelList->getName());
						this->paint();
					}
				}
			}
			else if (msg == CRCInput::RC_green) {
				if (!bouquetList->Bouquets.empty()) {
					bool found = true;
					int nNext = (bouquetList->getActiveBouquetNumber()+bouquetList->Bouquets.size()-1) % bouquetList->Bouquets.size();
					if(bouquetList->Bouquets[nNext]->channelList->isEmpty()) {
						found = false;
						nNext = nNext > 0 ? nNext-1 : bouquetList->Bouquets.size()-1;
						for(int i = nNext; i > 0; i--) {
							if(!bouquetList->Bouquets[i]->channelList->isEmpty()) {
								found = true;
								nNext = i;
								break;
							}
						}
					}
					if(found) {
						pbouquetList->activateBouquet (nNext, false);
						this->channelList = bouquetList->Bouquets[nNext]->channelList;
						this->channelListStartIndex = int (channelList->getSelectedChannelIndex() / maxNumberOfDisplayableEntries) * maxNumberOfDisplayableEntries;
						this->createChannelEntries (channelList->getSelectedChannelIndex());
						this->header->paint(this->channelList->getName());
						this->paint();
					}
				}
			} 
			else if (msg == CRCInput::RC_ok) {
				if (selectedChannelEntry)
					CNeutrinoApp::getInstance()->channelList->zapTo_ChannelID(selectedChannelEntry->channel->getChannelID());
				current_bouquet = bouquetList->getActiveBouquetNumber();
			} 
			else if (CRCInput::isNumeric (msg)) {
				this->hide();
				CNeutrinoApp::getInstance()->channelList->numericZap (msg);
//printf("numericZap: prev bouquet %d new %d\n", current_bouquet, bouquetList->getActiveBouquetNumber());
				current_bouquet = bouquetList->getActiveBouquetNumber();
				this->channelList = bouquetList->Bouquets[current_bouquet]->channelList;
				this->channelListStartIndex = int (channelList->getSelectedChannelIndex() / maxNumberOfDisplayableEntries) * maxNumberOfDisplayableEntries;
				g_InfoViewer->killTitle();

				int selectedChannelEntryIndex = this->channelList->getSelectedChannelIndex();
				if (selectedChannelEntryIndex < this->channelList->getSize()) {
					this->hide();
					this->createChannelEntries (selectedChannelEntryIndex);

					this->header->paint(this->channelList->getName());
					this->footer->paintButtons (buttonLabels, sizeof (buttonLabels) / sizeof (button_label));
					this->paint();
				}

			} 
			else if (msg == CRCInput::RC_up) {
				int selectedChannelEntryIndex = this->selectedChannelEntry->index;
				int prevSelectedChannelEntryIndex = selectedChannelEntryIndex;

				--selectedChannelEntryIndex;
				if (selectedChannelEntryIndex < 0) {
					selectedChannelEntryIndex = this->channelList->getSize() - 1;
				}


				int oldChannelListStartIndex = this->channelListStartIndex;

				this->channelListStartIndex = (selectedChannelEntryIndex / this->maxNumberOfDisplayableEntries) * this->maxNumberOfDisplayableEntries;

				if (oldChannelListStartIndex != this->channelListStartIndex) {
					this->createChannelEntries (selectedChannelEntryIndex);
					this->paint();
				} else {
					this->selectedChannelEntry = this->displayedChannelEntries[selectedChannelEntryIndex - this->channelListStartIndex];
					this->paintChannelEntry (prevSelectedChannelEntryIndex - this->channelListStartIndex);
					this->paintChannelEntry (selectedChannelEntryIndex - this->channelListStartIndex);
				}
			} 
			else if (msg == CRCInput::RC_down) {
				int selectedChannelEntryIndex = this->selectedChannelEntry->index;
				int prevSelectedChannelEntryIndex = this->selectedChannelEntry->index;

				selectedChannelEntryIndex = (selectedChannelEntryIndex + 1) % this->channelList->getSize();


				int oldChannelListStartIndex = this->channelListStartIndex;
				this->channelListStartIndex = (selectedChannelEntryIndex / this->maxNumberOfDisplayableEntries) * this->maxNumberOfDisplayableEntries;

				if (oldChannelListStartIndex != this->channelListStartIndex) {
					this->createChannelEntries (selectedChannelEntryIndex);
					this->paint();
				} else {
					this->selectedChannelEntry = this->displayedChannelEntries[selectedChannelEntryIndex - this->channelListStartIndex];
					this->paintChannelEntry (prevSelectedChannelEntryIndex - this->channelListStartIndex);
					this->paintChannelEntry (this->selectedChannelEntry->index - this->channelListStartIndex);
				}
			} 
			else if (msg == (neutrino_msg_t) CRCInput::RC_red) {
				CMenuWidget menuWidgetActions(LOCALE_EPGPLUS_ACTIONS, NEUTRINO_ICON_FEATURES);
				menuWidgetActions.enableFade(false);
				MenuTargetAddRecordTimer record(this);
				MenuTargetRefreshEpg refresh(this);
				MenuTargetAddReminder remind(this);
				if (!g_settings.minimode)
					menuWidgetActions.addItem (new CMenuForwarder (LOCALE_EPGPLUS_RECORD, true, NULL, &record, NULL, CRCInput::RC_red), false);
				menuWidgetActions.addItem (new CMenuForwarder (LOCALE_EPGPLUS_REFRESH_EPG, true, NULL, &refresh, NULL, CRCInput::RC_green), false);
				menuWidgetActions.addItem (new CMenuForwarder (LOCALE_EPGPLUS_REMIND, true, NULL, &remind, NULL, CRCInput::RC_yellow), false);
				if (selectedChannelEntry)
					menuWidgetActions.exec (NULL, "");

				this->refreshAll = true;
			} 
			else if (msg == (neutrino_msg_t) CRCInput::RC_blue) {

				CMenuWidget menuWidgetOptions(LOCALE_EPGPLUS_OPTIONS, NEUTRINO_ICON_FEATURES);
				menuWidgetOptions.enableFade(false);
				//menuWidgetOptions.addItem (new MenuOptionChooserSwitchSwapMode (this));
				menuWidgetOptions.addItem (new MenuOptionChooserSwitchViewMode (this));

				int result = menuWidgetOptions.exec (NULL, "");
				if (result == menu_return::RETURN_REPAINT) {
					this->refreshAll = true;
				} else if (result == menu_return::RETURN_EXIT_ALL) {
					this->refreshAll = true;
				}
			} 
			else if (msg == CRCInput::RC_left) {
				switch (this->currentViewMode) {
					case ViewMode_Stretch:
						{
							if (this->duration - 30 * 60 > 30 * 60) {
								this->duration -= 30 * 60;
								this->hide();
								this->refreshAll = true;
							}
						}
						break;
					case ViewMode_Scroll:
						{
							TCChannelEventEntries::const_iterator It = this->getSelectedEvent();

							if ((It != this->selectedChannelEntry->channelEventEntries.begin())
									&& (It != this->selectedChannelEntry->channelEventEntries.end())
							   ) {
								--It;
								this->selectedTime = (*It)->channelEvent.startTime + (*It)->channelEvent.duration / 2;
								if (this->selectedTime < this->startTime)
									this->selectedTime = this->startTime;

								this->selectedChannelEntry->paint (true, this->selectedTime);
							} else {
								if (this->startTime != this->firstStartTime) {

									if (this->startTime - this->duration > this->firstStartTime) {
										this->startTime -= this->duration;
									} else {
										this->startTime = this->firstStartTime;
									}

									this->selectedTime = this->startTime + this->duration - 1;	// select last event
									this->createChannelEntries (this->selectedChannelEntry->index);

									this->paint();
								}
							}
						}
						break;
				}
			} 
			else if (msg == CRCInput::RC_right) {
				switch (this->currentViewMode) {
					case ViewMode_Stretch:
						{
							if (this->duration + 30 * 60 < 4 * 60 * 60) {
								this->duration += 60 * 60;
								this->hide();
								this->refreshAll = true;
							}
						}
						break;

					case ViewMode_Scroll:
						{
							TCChannelEventEntries::const_iterator It = this->getSelectedEvent();

							if ((It != this->selectedChannelEntry->channelEventEntries.end() - 1)
									&& (It != this->selectedChannelEntry->channelEventEntries.end())) {
								++It;

								this->selectedTime = (*It)->channelEvent.startTime + (*It)->channelEvent.duration / 2;

								if (this->selectedTime > this->startTime + time_t (this->duration))
									this->selectedTime = this->startTime + this->duration;

								this->selectedChannelEntry->paint (true, this->selectedTime);
							} else {
								this->startTime += this->duration;
								this->selectedTime = this->startTime;
								this->createChannelEntries (this->selectedChannelEntry->index);

								this->paint();
							}
						}
						break;
				}
			} 
			else if (msg == CRCInput::RC_help || msg == CRCInput::RC_info) {
				TCChannelEventEntries::const_iterator It = this->getSelectedEvent();

				if (It != this->selectedChannelEntry->channelEventEntries.end()) {

					if ((*It)->channelEvent.eventID != 0) {
						this->hide();

						time_t startTime2 = (*It)->channelEvent.startTime;
						res = g_EpgData->show (this->selectedChannelEntry->channel->getChannelID(), (*It)->channelEvent.eventID, &startTime2);

						if (res == menu_return::RETURN_EXIT_ALL) {
							loop = false;
						} else {
							g_RCInput->getMsg (&msg, &data, 0);

							if ((msg != CRCInput::RC_red) && (msg != CRCInput::RC_timeout)) {
								// RC_red schlucken
								g_RCInput->postMsg (msg, data);
							}

							this->header->paint(this->channelList->getName());
							this->footer->paintButtons (buttonLabels, sizeof (buttonLabels) / sizeof (button_label));
							this->paint();
						}
					}
				}
			}
			else if (msg == CRCInput::RC_sat || msg == CRCInput::RC_favorites) {
				g_RCInput->postMsg (msg, 0);
				res = menu_return::RETURN_EXIT_ALL;
				loop = false;
			} else if (msg == NeutrinoMessages::EVT_SERVICESCHANGED || msg == NeutrinoMessages::EVT_BOUQUETSCHANGED) {
				g_RCInput->postMsg(msg, data);
				loop = false;
				res = menu_return::RETURN_EXIT_ALL;
			}
			else {
				if (CNeutrinoApp::getInstance()->handleMsg (msg, data) & messages_return::cancel_all) {
					loop = false;
					res = menu_return::RETURN_EXIT_ALL;
				}
			}

			if (this->refreshAll)
				loop = false;
			else if (this->refreshFooterButtons)
				this->footer->paintButtons (buttonLabels, sizeof (buttonLabels) / sizeof (button_label));
		}

		this->hide();

		fader.StopFade();
#if 0
		for (TChannelEntries::iterator It = this->displayedChannelEntries.begin();
				It != this->displayedChannelEntries.end(); It++) {
			delete *It;
		}
		this->displayedChannelEntries.clear();
#endif
	}
	while (this->refreshAll);
	for (TChannelEntries::iterator It = this->displayedChannelEntries.begin();
			It != this->displayedChannelEntries.end(); ++It) {
		delete *It;
	}
	this->displayedChannelEntries.clear();

	return res;
}

EpgPlus::TCChannelEventEntries::const_iterator EpgPlus::getSelectedEvent() const
{
	for (TCChannelEventEntries::const_iterator It = this->selectedChannelEntry->channelEventEntries.begin();
			It != this->selectedChannelEntry->channelEventEntries.end();
			++It) {
		if ((*It)->isSelected (this->selectedTime)) {
			return It;
		}
	}
	return this->selectedChannelEntry->channelEventEntries.end();
}

void EpgPlus::hide()
{
	this->frameBuffer->paintBackgroundBoxRel (this->usableScreenX, this->usableScreenY, this->usableScreenWidth, this->usableScreenHeight);
}

void EpgPlus::paintChannelEntry (int position)
{
	ChannelEntry *channelEntry = this->displayedChannelEntries[position];

	bool currentChannelIsSelected = false;
	if (this->channelListStartIndex + position == this->selectedChannelEntry->index) {
		currentChannelIsSelected = true;
	}
	channelEntry->paint (currentChannelIsSelected, this->selectedTime);
}

std::string EpgPlus::getTimeString (const time_t & time, const std::string & format)
{
	char tmpstr[256];
	struct tm *tmStartTime = localtime (&time);

	strftime (tmpstr, sizeof (tmpstr), format.c_str(), tmStartTime);
	return tmpstr;
}

void EpgPlus::paint()
{
	// clear
	//this->frameBuffer->paintBackgroundBoxRel (this->channelsTableX, this->channelsTableY, this->usableScreenWidth, this->channelsTableHeight);
	this->frameBuffer->paintBoxRel (this->channelsTableX, this->channelsTableY, this->usableScreenWidth, this->channelsTableHeight, COL_MENUCONTENT_PLUS_0);

	// paint the gaps
	this->frameBuffer->paintBoxRel (this->horGap1X, this->horGap1Y, this->horGap1Width, horGap1Height, horGap1Color);
	this->frameBuffer->paintBoxRel (this->horGap2X, this->horGap2Y, this->horGap2Width, horGap2Height, horGap2Color);
	this->frameBuffer->paintBoxRel (this->verGap1X, this->verGap1Y, verGap1Width, this->verGap1Height, verGap1Color);
	this->frameBuffer->paintBoxRel (this->verGap2X, this->verGap2Y, verGap2Width, this->verGap2Height, verGap2Color);

	// paint the time line
	timeLine->paint (this->startTime, this->duration);

	// paint the channel entries
	for (int i = 0; i < (int) this->displayedChannelEntries.size(); ++i) {
		this->paintChannelEntry (i);
	}

	// paint the time line grid
	this->timeLine->paintGrid();

	// paint slider
	this->frameBuffer->paintBoxRel (this->sliderX, this->sliderY, this->sliderWidth, this->sliderHeight, COL_MENUCONTENT_PLUS_0);

	int tmp = ((this->channelList->getSize() - 1) / this->maxNumberOfDisplayableEntries) + 1;
	int sliderKnobPosition = this->selectedChannelEntry == NULL ? 0 : (this->selectedChannelEntry->index / this->maxNumberOfDisplayableEntries);

	this->frameBuffer->paintBoxRel(this->sliderX + 2, this->sliderY + sliderKnobPosition * (sliderHeight-4)/tmp
					, this->sliderWidth - 4, (sliderHeight-4)/tmp, COL_MENUCONTENT_PLUS_3);
}

//  -- EPG+ Menue Handler Class
//  -- to be used for calls from Menue
//  -- (2004-03-05 rasc)

int CEPGplusHandler::exec (CMenuTarget * parent, const std::string & /*actionKey*/)
{
	int res = menu_return::RETURN_EXIT_ALL;
	EpgPlus *e;
	CChannelList *channelList;

	if (parent)
		parent->hide();

	e = new EpgPlus;

	//channelList = CNeutrinoApp::getInstance()->channelList;
	int bnum = bouquetList->getActiveBouquetNumber();
	current_bouquet = bnum;
	if(!bouquetList->Bouquets.empty() && !bouquetList->Bouquets[bnum]->channelList->isEmpty() )
		channelList = bouquetList->Bouquets[bnum]->channelList;
	else
		channelList = CNeutrinoApp::getInstance()->channelList;

	e->exec (channelList, channelList->getSelectedChannelIndex(), bouquetList);
	delete e;
//FIXME
//printf("CEPGplusHandler::exec old bouquet %d new %d current %d\n", bnum, bouquetList->getActiveBouquetNumber(), current_bouquet);
	bouquetList->activateBouquet(current_bouquet, false);

	return res;
}

EpgPlus::MenuTargetAddReminder::MenuTargetAddReminder (EpgPlus * pepgPlus) {
	this->epgPlus = pepgPlus;
}

int EpgPlus::MenuTargetAddReminder::exec (CMenuTarget * /*parent*/, const std::string & /*actionKey*/)
{
	TCChannelEventEntries::const_iterator It = this->epgPlus->getSelectedEvent();

	if ((It != this->epgPlus->selectedChannelEntry->channelEventEntries.end())
			&& (!(*It)->channelEvent.description.empty())
	   ) {
		if (g_Timerd->isTimerdAvailable()) {
			g_Timerd->addZaptoTimerEvent (this->epgPlus->selectedChannelEntry->channel->getChannelID(), (*It)->channelEvent.startTime - (g_settings.zapto_pre_time * 60), (*It)->channelEvent.startTime - ANNOUNCETIME - (g_settings.zapto_pre_time * 60), 0, (*It)->channelEvent.eventID, (*It)->channelEvent.startTime, 0);

			ShowMsg (LOCALE_TIMER_EVENTTIMED_TITLE, g_Locale->getText (LOCALE_TIMER_EVENTTIMED_MSG)
				    , CMessageBox::mbrBack, CMessageBox::mbBack, NEUTRINO_ICON_INFO);	// UTF-8
		} else
			printf ("timerd not available\n");
	}
	return menu_return::RETURN_EXIT_ALL;
}

EpgPlus::MenuTargetAddRecordTimer::MenuTargetAddRecordTimer (EpgPlus * pepgPlus) {
	this->epgPlus = pepgPlus;
}

int EpgPlus::MenuTargetAddRecordTimer::exec (CMenuTarget * /*parent*/, const std::string & /*actionKey*/)
{
	TCChannelEventEntries::const_iterator It = this->epgPlus->getSelectedEvent();

	if ((It != this->epgPlus->selectedChannelEntry->channelEventEntries.end())
			&& (!(*It)->channelEvent.description.empty())
	   ) {
		if (g_Timerd->isTimerdAvailable()) {

			g_Timerd->addRecordTimerEvent (this->epgPlus->selectedChannelEntry->channel->getChannelID(), (*It)->channelEvent.startTime, (*It)->channelEvent.startTime + (*It)->channelEvent.duration, (*It)->channelEvent.eventID, (*It)->channelEvent.startTime, (*It)->channelEvent.startTime - (ANNOUNCETIME + 120)
						       , TIMERD_APIDS_CONF, true);
			ShowMsg (LOCALE_TIMER_EVENTRECORD_TITLE, g_Locale->getText (LOCALE_TIMER_EVENTRECORD_MSG)
				    , CMessageBox::mbrBack, CMessageBox::mbBack, NEUTRINO_ICON_INFO);	// UTF-8
		} else
			printf ("timerd not available\n");
	}
	return menu_return::RETURN_EXIT_ALL;
}

EpgPlus::MenuTargetRefreshEpg::MenuTargetRefreshEpg (EpgPlus * pepgPlus) {
	this->epgPlus = pepgPlus;
}

int EpgPlus::MenuTargetRefreshEpg::exec (CMenuTarget * /*parent*/, const std::string & /*actionKey*/)
{
	this->epgPlus->refreshAll = true;
	return menu_return::RETURN_EXIT_ALL;
}

struct CMenuOptionChooser::keyval menuOptionChooserSwitchSwapModes[] = {
	{ EpgPlus::SwapMode_ByPage, LOCALE_EPGPLUS_BYPAGE_MODE },
	{ EpgPlus::SwapMode_ByBouquet, LOCALE_EPGPLUS_BYBOUQUET_MODE }
};

EpgPlus::MenuOptionChooserSwitchSwapMode::MenuOptionChooserSwitchSwapMode (EpgPlus * pepgPlus)
		:CMenuOptionChooser (LOCALE_EPGPLUS_SWAP_MODE, (int *) &pepgPlus->currentSwapMode, menuOptionChooserSwitchSwapModes, sizeof (menuOptionChooserSwitchSwapModes) / sizeof (CMenuOptionChooser::keyval)
				     , true, NULL, CRCInput::RC_yellow, NEUTRINO_ICON_BUTTON_YELLOW,0)
{
	this->epgPlus = pepgPlus;
	this->oldSwapMode = epgPlus->currentSwapMode;
	this->oldTimingMenuSettings = g_settings.timing[SNeutrinoSettings::TIMING_MENU];
}

EpgPlus::MenuOptionChooserSwitchSwapMode::~MenuOptionChooserSwitchSwapMode()
{
	g_settings.timing[SNeutrinoSettings::TIMING_MENU] = this->oldTimingMenuSettings;

	if (this->epgPlus->currentSwapMode != this->oldSwapMode) {
		switch (this->epgPlus->currentSwapMode) {
		case SwapMode_ByPage:
			buttonLabels[1].locale = LOCALE_EPGPLUS_PAGE_DOWN;
			buttonLabels[2].locale = LOCALE_EPGPLUS_PAGE_UP;
			break;
		case SwapMode_ByBouquet:
			buttonLabels[1].locale = LOCALE_EPGPLUS_PREV_BOUQUET;
			buttonLabels[2].locale = LOCALE_EPGPLUS_NEXT_BOUQUET;
			break;
		}
		this->epgPlus->refreshAll = true;
	}
}

int EpgPlus::MenuOptionChooserSwitchSwapMode::exec (CMenuTarget * parent)
{
	// change time out settings temporary
	g_settings.timing[SNeutrinoSettings::TIMING_MENU] = 1;

	CMenuOptionChooser::exec (parent);

	return menu_return::RETURN_REPAINT;
}

struct CMenuOptionChooser::keyval menuOptionChooserSwitchViewModes[] = {
	{ EpgPlus::ViewMode_Scroll, LOCALE_EPGPLUS_STRETCH_MODE },
	{ EpgPlus::ViewMode_Stretch, LOCALE_EPGPLUS_SCROLL_MODE }
};

EpgPlus::MenuOptionChooserSwitchViewMode::MenuOptionChooserSwitchViewMode (EpgPlus * epgPlus)
		:CMenuOptionChooser (LOCALE_EPGPLUS_VIEW_MODE, (int *) &epgPlus->currentViewMode, menuOptionChooserSwitchViewModes, sizeof (menuOptionChooserSwitchViewModes) / sizeof (CMenuOptionChooser::keyval)
				     , true, NULL, CRCInput::RC_blue, NEUTRINO_ICON_BUTTON_BLUE) {
	this->oldTimingMenuSettings = g_settings.timing[SNeutrinoSettings::TIMING_MENU];
}

EpgPlus::MenuOptionChooserSwitchViewMode::~MenuOptionChooserSwitchViewMode()
{
	g_settings.timing[SNeutrinoSettings::TIMING_MENU] = this->oldTimingMenuSettings;
}

int EpgPlus::MenuOptionChooserSwitchViewMode::exec (CMenuTarget * parent)
{
	// change time out settings temporary
	g_settings.timing[SNeutrinoSettings::TIMING_MENU] = 1;

	CMenuOptionChooser::exec (parent);

	return menu_return::RETURN_REPAINT;
}
