/*
	Neutrino-GUI  -   DBoxII-Project

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

	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, see <http://www.gnu.org/licenses/>.
*/

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

#include <iostream>

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

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

#include <gui/widget/icons.h>
#include <gui/widget/buttons.h>
#include <gui/widget/hintbox.h>
#include <gui/widget/msgbox.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 <driver/fontrenderer.h>
#include <zapit/satconfig.h>
#include <zapit/getservices.h>
#include <eitd/sectionsd.h>
#include <system/helpers.h>

#include <algorithm>
#include <sstream>

extern CBouquetList *bouquetList;

int sizes[EpgPlus::NumberOfSizeSettings];

time_t EpgPlus::duration = 0;

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

/* negative size means "screen width in percent" */
static EpgPlus::SizeSetting sizeSettingTable[] =
{
	{ EpgPlus::EPGPlus_channelentry_width, -15 }, /* 15 percent of screen width */
	{ EpgPlus::EPGPlus_separationline_thickness, 1 }
};

static bool bigfont = false;
static int current_bouquet;

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

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

EpgPlus::Header::~Header()
{
	if (this->head)
	{
		delete this->head;
		this->head = NULL;
	}
}

void EpgPlus::Header::init()
{
	font = g_Font[SNeutrinoSettings::FONT_TYPE_MENU_TITLE];
}

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

	if (this->head == NULL)
	{
		this->head = new CComponentsHeader();
		this->head->setContextButton(CComponentsHeader::CC_BTN_HELP);
		this->head->enableClock(true, "%H:%M", "%H %M", true);
		this->head->enableShadow(CC_SHADOW_RIGHT | CC_SHADOW_CORNER_TOP_RIGHT | CC_SHADOW_CORNER_BOTTOM_RIGHT, -1, true);

	}

	if (this->head)
	{
		if (g_settings.channellist_show_channellogo)
		{
			// ensure to have clean background
			this->logo = this->head->getChannelLogoObject();
			this->logo->hide();
			this->logo->allowPaint(false);
		}
		this->head->setDimensionsAll(this->x, this->y, this->width, this->font->getHeight());
		this->head->setCaption(caption);
		this->head->paint(CC_SAVE_SCREEN_NO);
	}
}

void EpgPlus::Header::paintChannelLogo(const CZapitChannel * Channel)
{
	if (!g_settings.channellist_show_channellogo)
		return;

	if (this->head)
	{
		this->logo->hide();
		this->logo->clearSavedScreen();
		if (Channel)
		{
			this->head->setChannelLogo(Channel->getChannelID(), Channel->getName());
		}
		this->logo->allowPaint(true);
		this->logo->paint();
	}
}

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

Font *EpgPlus::TimeLine::font = NULL;
int EpgPlus::TimeLine::separationLineThickness = 0;

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()
{
	font = g_Font[SNeutrinoSettings::FONT_TYPE_EPG_DATE];
	separationLineThickness = sizes[EPGPlus_separationline_thickness];
}

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->font->getHeight(),
					COL_MENUCONTENT_PLUS_0);

	this->font->RenderString(this->x + OFFSET_INNER_MID, this->y + this->font->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->font->getHeight(),
						toggleColor ? COL_MENUCONTENT_PLUS_0 : COL_MENUCONTENT_PLUS_1);

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

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

		this->font->RenderString(xPos - textWidth - OFFSET_INNER_MIN, this->y + this->font->getHeight(),
						textWidth, timeStr, COL_MENUCONTENT_TEXT);

		timeStr = EpgPlus::getTimeString(tickTime, "%M");
		textWidth = this->font->getRenderWidth(timeStr);
		this->font->RenderString(xPos + OFFSET_INNER_MIN, this->y + this->font->getHeight(),
						textWidth, timeStr, COL_MENUCONTENT_TEXT);

		toggleColor = !toggleColor;
	}

	// shadow
	this->frameBuffer->paintBoxRel(this->x + this->width, this->y + OFFSET_SHADOW, OFFSET_SHADOW, this->getUsedHeight(), COL_SHADOW_PLUS_0);
}

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->font->getHeight(), COL_MENUCONTENTDARK_PLUS_0);
	}
}

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

	// paint new mark
	CProgressBar pbbar = CProgressBar(px, this->y + this->font->getHeight(), pwidth, this->font->getHeight());
	pbbar.setActiveColor(COL_MENUCONTENTSELECTED_PLUS_0);
	pbbar.setType(CProgressBar::PB_TIMESCALE);

	time_t currentTime;
	time(&currentTime);
	if ((currentTime > _startTime) && (currentTime < _startTime + pduration))
	{
		pbbar.setValues((currentTime - _startTime), pduration);
	}
	else
	{
		pbbar.setValues(0, pduration);
	}
	pbbar.paint();

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

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

	// display end time
	timeStr = EpgPlus::getTimeString(_startTime + pduration, "%H:%M");
	textWidth = font->getRenderWidth(timeStr);
	int textX = 0;

	if (px + pwidth + textWidth + OFFSET_INNER_MIN < this->x + this->width)
	{
		// display end time after mark
		textX = px + pwidth + OFFSET_INNER_MIN;
	}
	else if (textWidth < pwidth - 2*OFFSET_INNER_MIN)
	{
		// display end time before mark
		textX = px + pwidth - textWidth - OFFSET_INNER_MIN;
	}

	if (textX)
		this->font->RenderString(textX, this->y + this->font->getHeight() + this->font->getHeight(), textWidth, timeStr, COL_MENUCONTENT_TEXT);

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

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

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

int EpgPlus::TimeLine::getUsedHeight()
{
	return 2*font->getHeight() + separationLineThickness;
}

Font *EpgPlus::ChannelEventEntry::font = NULL;
int EpgPlus::ChannelEventEntry::separationLineThickness = 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 = g_Font[SNeutrinoSettings::FONT_TYPE_EPGPLUS_ITEM];
	separationLineThickness = sizes[EPGPlus_separationline_thickness];
}

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)
{
	bool selected = this->channelEvent.description.empty() ? false : pisSelected;

	fb_pixel_t color;
	fb_pixel_t bgcolor;

	getItemColors(color, bgcolor, selected, false, toggleColor, true /* toggle enlighten */);

	this->frameBuffer->paintBoxRel(this->x, this->y, this->width, this->font->getHeight(), bgcolor);

	this->font->RenderString(this->x + OFFSET_INNER_SMALL, this->y + this->font->getHeight(),
					this->width - 2*OFFSET_INNER_SMALL > 0 ? this->width - 2*OFFSET_INNER_SMALL : 0, this->channelEvent.description, color);

	// paint the separation lines
	if (separationLineThickness > 0)
	{
		// left side
		this->frameBuffer->paintBoxRel(this->x, this->y,
					this->separationLineThickness, this->font->getHeight(), COL_MENUCONTENTDARK_PLUS_0);

		// bottom
		this->frameBuffer->paintBoxRel(this->x, this->y + this->font->getHeight(),
					this->width, this->separationLineThickness, COL_MENUCONTENTDARK_PLUS_0);
	}

	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() + separationLineThickness;
}

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

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

	this->displayNumber = "";
	this->displayName = "";
	if (pchannel != NULL)
	{
		this->displayNumber = to_string(pchannel->number);
		this->displayName = pchannel->getName();
	}

	this->index = pindex;

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

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

	this->detailsLine = NULL;
}

void EpgPlus::ChannelEntry::init()
{
	font = g_Font[SNeutrinoSettings::FONT_TYPE_EPGPLUS_ITEM];
	separationLineThickness = sizes[EPGPlus_separationline_thickness];
}

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

	if (this->detailsLine)
	{
		this->detailsLine->kill();
		delete this->detailsLine;
		this->detailsLine = NULL;
	}
}

void EpgPlus::ChannelEntry::paint(bool isSelected, time_t _selectedTime)
{
	fb_pixel_t color;
	fb_pixel_t bgcolor;

	int radius = isSelected ? RADIUS_MID : RADIUS_NONE;

	getItemColors(color, bgcolor, isSelected);

	this->frameBuffer->paintBoxRel(this->x, this->y, this->width, this->font->getHeight(), bgcolor, radius, CORNER_LEFT);

	int xPos = this->x + OFFSET_INNER_MID;
	int numberWidth = 0;

	if (g_settings.channellist_show_numbers)
	{
		// display channelnumber
		int xOffset = EpgPlus::channelNumberOffset - this->font->getRenderWidth(this->displayNumber);
		this->font->RenderString(xPos + xOffset, this->y + this->font->getHeight(), this->width - 2*OFFSET_INNER_MID, this->displayNumber, color);
		numberWidth = EpgPlus::channelNumberOffset + OFFSET_INNER_SMALL;
		xPos += numberWidth;
	}

	// display channelname
	this->font->RenderString(xPos, this->y + this->font->getHeight(), this->width - numberWidth - 2*OFFSET_INNER_MID, this->displayName, color);

	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 (separationLineThickness > 0)
	{
		this->frameBuffer->paintBoxRel(this->x, this->y + this->font->getHeight(),
						this->width, this->separationLineThickness, COL_MENUCONTENTDARK_PLUS_0);
	}

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

		toggleColor = !toggleColor;
	}

	// kill detailsline
	if (detailsLine)
	{
		detailsLine->kill();
		delete detailsLine;
		detailsLine = NULL;
	}

	// paint detailsline
	if (isSelected)
	{
		xPos		= this->x - DETAILSLINE_WIDTH;
		int yPosTop	= this->y + this->font->getHeight()/2;
		int yPosBottom	= this->footer->y + this->footer->getUsedHeight()/2;

		if (detailsLine == NULL)
			detailsLine = new CComponentsDetailsLine();

		detailsLine->setDimensionsAll(xPos, yPosTop, yPosBottom, this->font->getHeight()/2, this->footer->getUsedHeight() - RADIUS_LARGE*2);
		detailsLine->paint(false);

		this->header->paintChannelLogo(this->channel);
	}
}

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

Font *EpgPlus::Footer::fontBouquetChannelName = NULL;
Font *EpgPlus::Footer::fontEventDescription = NULL;
Font *EpgPlus::Footer::fontEventInfo1 = NULL;

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

	this->buttonHeight = pbuttonHeight;
	this->buttonY = this->y - OFFSET_INTER - OFFSET_SHADOW - this->buttonHeight;
}

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

void EpgPlus::Footer::init()
{
	fontBouquetChannelName = g_Font[SNeutrinoSettings::FONT_TYPE_EVENTLIST_ITEMSMALL];
	fontEventDescription = g_Font[SNeutrinoSettings::FONT_TYPE_EVENTLIST_ITEMLARGE];
	fontEventInfo1 = g_Font[SNeutrinoSettings::FONT_TYPE_EVENTLIST_EVENT];
}

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() + fontEventInfo1->getHeight() + 2*OFFSET_INNER_SMALL;
}

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

	// clear the whole footer
	this->frameBuffer->paintBoxRel(this->x + OFFSET_SHADOW, yPos + OFFSET_SHADOW, this->width, this->getUsedHeight(), COL_SHADOW_PLUS_0, RADIUS_LARGE);
	this->frameBuffer->paintBoxRel(this->x, yPos, this->width, this->getUsedHeight(), COL_MENUCONTENTDARK_PLUS_0, RADIUS_LARGE);
	this->frameBuffer->paintBoxFrame(this->x, yPos, this->width, this->getUsedHeight(), frame_thickness, COL_FRAME_PLUS_0, RADIUS_LARGE);

	// display bouquet and channel name
	yPos += OFFSET_INNER_SMALL + this->fontBouquetChannelName->getHeight();
	this->fontBouquetChannelName->RenderString(this->x + OFFSET_INNER_MID, yPos, this->width - 2*OFFSET_INNER_MID, this->currentBouquetName + ": " + this->currentChannelName, COL_MENUCONTENT_TEXT);

	// display event's descrition
	yPos += this->fontEventDescription->getHeight();
	this->fontEventDescription->RenderString(this->x + OFFSET_INNER_MID, yPos, this->width - 2*OFFSET_INNER_MID, description, COL_MENUCONTENT_TEXT);

	// display event's info1
	yPos += this->fontEventInfo1->getHeight();
	this->fontEventInfo1->RenderString(this->x + OFFSET_INNER_MID, yPos, this->width - 2*OFFSET_INNER_MID, info1, COL_MENUCONTENT_TEXT);
}

struct button_label buttonLabels[] =
{
	{ NEUTRINO_ICON_BUTTON_RED, LOCALE_EPGPLUS_ACTIONS },
	{ NEUTRINO_ICON_BUTTON_GREEN, LOCALE_EPGPLUS_PREV_BOUQUET },
	{ NEUTRINO_ICON_BUTTON_YELLOW, LOCALE_EPGPLUS_NEXT_BOUQUET },
	{ 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);
	CComponentsFooter foot;
	foot.enableShadow(CC_SHADOW_ON, -1, true);
	foot.paintButtons(this->x, this->buttonY, buttonWidth, buttonHeight, numberOfButtons, pbuttonLabels, buttonWidth/numberOfButtons);
}

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->header, this->footer, this->bouquetList, this->channelsTableX, 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 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
					int 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];
	}

	// get largest channelnumber
	int n = 1;
	for (TChannelEntries::iterator It = this->displayedChannelEntries.begin();
			It != this->displayedChannelEntries.end();
			++It)
	{
		n = std::max(n, (*It)->channel->number);
	}
	channelNumberOffset = ChannelEntry::font->getRenderWidth(to_string(n));
}

void EpgPlus::init()
{
	frameBuffer = CFrameBuffer::getInstance();
#if 0
	currentViewMode = ViewMode_Scroll;
	currentSwapMode = SwapMode_ByPage;
#endif
	usableScreenWidth = frameBuffer->getScreenWidthRel();
	usableScreenHeight = frameBuffer->getScreenHeightRel();

	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;
	}

	if (entryFontSize == 0)
		entryFontSize = g_Font[SNeutrinoSettings::FONT_TYPE_EPGPLUS_ITEM]->getSize();

	// reset possible bigfont
	g_Font[SNeutrinoSettings::FONT_TYPE_EPGPLUS_ITEM]->setSize(entryFontSize);

	if (bigfont)
		g_Font[SNeutrinoSettings::FONT_TYPE_EPGPLUS_ITEM]->setSize((int)(entryFontSize * BIGFONT_FACTOR));

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

	channelsTableWidth = sizes[EPGPlus_channelentry_width];
	sliderWidth = SCROLLBAR_WIDTH;

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

	int buttonHeight = headerHeight;

	int footerHeight = Footer::getUsedHeight();

	this->maxNumberOfDisplayableEntries = (this->usableScreenHeight - headerHeight - timeLineHeight - buttonHeight - OFFSET_SHADOW - OFFSET_INTER - footerHeight - OFFSET_SHADOW) / this->entryHeight;
	this->bodyHeight = this->maxNumberOfDisplayableEntries * entryHeight;

	this->usableScreenHeight = headerHeight + timeLineHeight + this->bodyHeight + buttonHeight + OFFSET_SHADOW + OFFSET_INTER + footerHeight + OFFSET_SHADOW; // recalc deltaY
	this->usableScreenX = getScreenStartX(this->usableScreenWidth);
	if (this->usableScreenX < DETAILSLINE_WIDTH)
		this->usableScreenX = DETAILSLINE_WIDTH;
	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->footerX = usableScreenX;
	this->footerY = this->usableScreenY + this->usableScreenHeight - OFFSET_SHADOW - footerHeight;
	this->footerWidth = this->usableScreenWidth;

	this->channelsTableX = this->usableScreenX;
	this->channelsTableY = this->timeLineY + timeLineHeight;
	this->channelsTableHeight = this->bodyHeight;

	this->eventsTableX = this->channelsTableX + channelsTableWidth;
	this->eventsTableY = this->channelsTableY;
	this->eventsTableWidth = this->usableScreenWidth - this->channelsTableWidth - this->sliderWidth;
	this->eventsTableHeight = this->bodyHeight;

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

	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 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)
			{
				loop = false;
			}
			else if (msg == CRCInput::RC_help)
			{
				//fprintf(stderr, "RC_help, 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 == 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 == 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_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 (CNeutrinoApp::getInstance()->listModeKey(msg))
			{
				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();

	// reset possible bigfont
	g_Font[SNeutrinoSettings::FONT_TYPE_EPGPLUS_ITEM]->setSize(entryFontSize);

	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()
{
	if (this->header->head)
	{
		this->header->head->kill();
		delete this->header->head;
		this->header->head = NULL;
	}

	if (this->selectedChannelEntry->detailsLine) {
		this->selectedChannelEntry->detailsLine->kill();
		delete this->selectedChannelEntry->detailsLine;
		this->selectedChannelEntry->detailsLine = NULL;
	}

	this->frameBuffer->paintBackgroundBoxRel(this->usableScreenX, this->usableScreenY, this->usableScreenWidth + OFFSET_SHADOW, this->usableScreenHeight + OFFSET_SHADOW);
}

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 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
	int total_pages;
	int current_page;
	getScrollBarData(&total_pages, &current_page,
		this->channelList->getSize(),
		this->maxNumberOfDisplayableEntries,
		this->selectedChannelEntry == NULL ? 0 : this->selectedChannelEntry->index);

	paintScrollBar(this->sliderX, this->sliderY, this->sliderWidth, this->sliderHeight, total_pages, current_page, CC_SHADOW_RIGHT_CORNER_ALL);
}

// -- 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),
				CMsgBox::mbrBack, CMsgBox::mbBack, NEUTRINO_ICON_INFO);
		}
		else
			printf("timerd not available\n");
	}
	return menu_return::RETURN_EXIT_ALL;
}

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

static bool sortByDateTime(const CChannelEvent& a, const CChannelEvent& b)
{
	return a.startTime < b.startTime;
}

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()))
	{
		bool doRecord = true;
		if (g_settings.recording_already_found_check)
		{
			CHintBox loadBox(LOCALE_RECORDING_ALREADY_FOUND_CHECK, LOCALE_MOVIEBROWSER_SCAN_FOR_MOVIES);
			loadBox.paint();
			CMovieBrowser moviebrowser;
			const char *rec_title = (*It)->channelEvent.description.c_str();
			bool already_found = moviebrowser.gotMovie(rec_title);
			loadBox.hide();
			if (already_found)
			{
				printf("already found in moviebrowser: %s\n", rec_title);
				char message[1024];
				snprintf(message, sizeof(message)-1, g_Locale->getText(LOCALE_RECORDING_ALREADY_FOUND), rec_title);
				doRecord = (ShowMsg(LOCALE_RECORDING_ALREADY_FOUND_CHECK, message, CMsgBox::mbrYes, CMsgBox::mbYes | CMsgBox::mbNo) == CMsgBox::mbrYes);
			}
		}
		if (g_Timerd->isTimerdAvailable() && doRecord)
		{
			CChannelEventList evtlist;
			CEitManager::getInstance()->getEventsServiceKey(this->epgPlus->selectedChannelEntry->channel->channel_id, evtlist);
			sort(evtlist.begin(),evtlist.end(),sortByDateTime);
			CFollowScreenings m(this->epgPlus->selectedChannelEntry->channel->channel_id,
				(*It)->channelEvent.startTime,
				(*It)->channelEvent.startTime + (*It)->channelEvent.duration,
				(*It)->channelEvent.description, (*It)->channelEvent.eventID, TIMERD_APIDS_CONF, true, "", &evtlist);
			m.exec(NULL, "");
		}
		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;
}
