<%pre>

#include <livefeatures.h>
#include <setup.h>
#include <tools.h>
#include <timers.h>
#include <epg_events.h>

using namespace vdrlive;

struct SchedEntry {
	std::string title;
	std::string short_description;
	std::string description;
	std::string description_trunc;
	std::string start;
	std::string end;
	std::string day;
	std::string epgid;
	bool truncated;
	bool has_timer;
	int start_row;
	int row_count;
};

std::string				channel_groups_setting;
std::vector<std::string>		channel_groups_names;
std::vector< std::vector<int> >		channel_groups_numbers;
std::vector<std::string>		times_names;
std::vector<time_t>			times_start;
</%pre>
<%args>
	int channel = -1;
	unsigned int time_para = 0;
</%args>
<%session scope="global">
	bool logged_in(false);
</%session>
<%request scope="global">
	unsigned int channel_group=0;
	unsigned int time_selected=0;
</%request>
<%include>page_init.eh</%include>
<%cpp>
	if (!logged_in && LiveSetup().UseAuth()) return reply.redirect("login.html");
	pageTitle = tr("MultiSchedule");

#if VDRVERSNUM < 20301
	ReadLock channelsLock( Channels );
	if ( !channelsLock )
		throw HtmlError( tr("Couldn't aquire access to channels, please try again later.") );
#endif

#define MAX_CHANNELS 10
#define MAX_DAYS 3
#define MAX_HOURS 8
#define MINUTES_PER_ROW 5
#define CHARACTERS_PER_ROW 30

    if ( ( channel_groups_setting.compare(LiveSetup().GetChannelGroups()) != 0 ) || ( channel_groups_numbers.size() == 0 ) )
    {
#if VDRVERSNUM >= 20301
        LOCK_CHANNELS_READ;
#endif
        // build the groups of channels to display
        std::string channelGroups=LiveSetup().GetChannelGroups();
        if ( channelGroups.empty() )
        {
            // setup default channel groups
           int lastChannel = LiveSetup().GetLastChannel();
           if ( lastChannel == 0 )
#if VDRVERSNUM >= 20301
               lastChannel = Channels->MaxNumber();
#else
               lastChannel = Channels.MaxNumber();
#endif
           std::stringstream groups;
           int i = 0;
#if VDRVERSNUM >= 20301
           for (cChannel *channel = (cChannel *)Channels->First(); channel && (channel->Number() <= lastChannel); channel = (cChannel *)Channels->Next(channel))
#else
           for (cChannel *channel = Channels.First(); channel && (channel->Number() <= lastChannel); channel = Channels.Next(channel))
#endif
           {
               if (channel->GroupSep())
                   continue;
               groups << channel->Number();
               if ( (++i % 5) == 0 )
                   groups << ";";
               else
                   groups << ",";
           }
           channelGroups = groups.str();
           LiveSetup().SetChannelGroups( channelGroups );
        }
        channel_groups_names.clear();
        channel_groups_numbers.clear();
        channel_groups_setting = channelGroups;
        size_t groupSep;
        std::string thisGroup = "";
        while ( ! channelGroups.empty() )
        {
            groupSep = channelGroups.find(';');
            thisGroup = channelGroups.substr(0, groupSep );
            if ( groupSep != channelGroups.npos )
                channelGroups.erase(0, groupSep+1 );
            else
                channelGroups="";

            int cur_group_count=0;
            channel_groups_names.push_back( std::string() );
            channel_groups_numbers.push_back( std::vector<int>() );
            while ( !thisGroup.empty() )
            {
                std::string thisChannel;
                try {
                    size_t channelSep = thisGroup.find(',');
                    thisChannel = thisGroup.substr(0, channelSep );
                    if ( cur_group_count++ != 0 )
                        channel_groups_names.back() += std::string( " - " );
		    else
			channel_groups_names.back() += thisChannel += std::string( ": " );
                    if ( channelSep != thisGroup.npos )
                        thisGroup.erase( 0, channelSep+1 );
                    else
                        thisGroup = "";
                    int channel_no = lexical_cast< int > (thisChannel);
#if VDRVERSNUM >= 20301
                    cChannel* Channel = (cChannel *)Channels->GetByNumber( channel_no );
#else
                    cChannel* Channel = Channels.GetByNumber( channel_no );
#endif
                    if ( !Channel )
                    {
                        esyslog("live: could not find channel no '%s'.", thisChannel.c_str() );
                        continue;
                    }
                    channel_groups_names.back() += std::string( Channel->Name() );
                    channel_groups_numbers.back().push_back( Channel->Number() );
                    if ( cur_group_count>=MAX_CHANNELS )
                    {
                        // start new group if group gets too large
                        cur_group_count=0;
                        channel_groups_names.push_back( std::string() );
                        channel_groups_numbers.push_back( std::vector<int>() );
                    }
                }
                catch ( const bad_lexical_cast & )
                {
                    esyslog("live: could not convert '%s' into a channel number", thisChannel.c_str());
                    continue;
                }
            }
        }
    }

	if ( channel < 0 )
	{
		if (cDevice::CurrentChannel())
		{
            // find group corresponding to current channel
            int curGroup =0;
			int curChannel = cDevice::CurrentChannel();
            for ( std::vector< std::vector<int> >::iterator grIt = channel_groups_numbers.begin();
                  grIt != channel_groups_numbers.end() && channel < 0; ++grIt, ++curGroup )
            {
                for ( std::vector<int>::iterator chIt = (*grIt).begin();
                       chIt != (*grIt).end() && channel < 0; ++ chIt )
                {
                    if ( *chIt == curChannel )
                        channel_group = channel = curGroup;
                }
            }
            // if nothing is found, fall back to group 0
            if ( channel < 0 )
                channel = 0;
		}
		else
		{
            channel_group = channel;
		}
	}

	if ( channel >= (int)channel_groups_numbers.size() )
		channel = 0;
	channel_group = channel;
	{
		// build time list
		times_names.clear();
		times_start.clear();

		// calculate time of midnight (localtime) and convert back to GMT
		time_t now = (time(NULL)/3600)*3600;
		time_t now_local = time(NULL);
		struct tm tm_r;
		if ( localtime_r( &now_local, &tm_r ) == 0 ) {
			std::stringstream builder;
			builder << "cannot represent timestamp " << now_local << " as local time";
			throw std::runtime_error( builder.str() );
		}
		tm_r.tm_hour=0;
		tm_r.tm_min=0;
		tm_r.tm_sec=0;
		time_t midnight = mktime( &tm_r );

		// add four 8h steps per day to the time list
		for (int i=0; i<4*MAX_DAYS ; i++ )
		{
			times_start.push_back( midnight + MAX_HOURS*3600*i );
		}
		std::vector< std::string > parts = StringSplit( LiveSetup().GetTimes(), ';' );
		std::vector< time_t > offsets;
		std::vector< std::string >::const_iterator part = parts.begin();
		for ( ; part != parts.end(); ++part )
		{
			try {
				unsigned int sep = (*part).find(':');
				std::string hour = (*part).substr(0, sep );
				if ( sep == (*part).npos )
				{
					esyslog("live: Error parsing time '%s'", (*part).c_str() );
					continue;
				}
				std::string min = (*part).substr(sep+1, (*part).npos );
				offsets.push_back( lexical_cast<time_t>( hour )*60*60 + lexical_cast<time_t>( min ) *60 );
			}
			catch ( const bad_lexical_cast & ) {
				esyslog("live: Error parsing time '%s'", part->c_str() );
			};
		};
		// add the time of the favourites to the time list
		for (int i=0; i< MAX_DAYS ; i++ )
		{
			std::vector< time_t >::const_iterator offset = offsets.begin();
			for ( ; offset != offsets.end(); ++offset )
			{
				times_start.push_back( midnight + 24*3600*i + *offset );
			}
		}
		// add now
		times_start.push_back( now );
		// sort the times
		std::sort( times_start.begin(), times_start.end() );
		// delete every time which has already passed
		while ( *times_start.begin()< now )
			times_start.erase(times_start.begin() );

		// build the corresponding names
		for ( std::vector< time_t >::const_iterator start = times_start.begin();
			  start != times_start.end(); ++start )
		{
			times_names.push_back(FormatDateTime( tr("%A, %x"), *start)
								  +std::string(" ")+ FormatDateTime( tr("%I:%M %p"), *start) );
		}
		// the first time is now
		times_names[0]=tr("Now");

		if ( time_para >= times_names.size() )
			time_para = times_names.size()-1;
		time_selected=time_para;
	}
</%cpp>
<& pageelems.doc_type &>
<html>
	<head>
		<title>VDR Live - <$ pageTitle $></title>
		<& pageelems.stylesheets &>
		<& pageelems.ajax_js &>
	</head>
	<body onload="adjustHeader()" onresize="adjustHeader()">
		<& pageelems.logo &>
		<& menu active=("multischedule") component=("multischedule.channel_selection") &>
		<div class="inhalt">
<%cpp>
#if VDRVERSNUM < 20301
	cSchedulesLock schedulesLock;
	cSchedules const* schedules = cSchedules::Schedules( schedulesLock );
#endif

	time_t now = time(NULL);
	if ( time_para >= times_start.size() )
	    time_para = times_start.size()-1;
	time_t sched_start = (times_start[ time_para ]/300)*300;
	time_t max_hours;
	try {
		max_hours = lexical_cast<time_t>( LiveSetup().GetScheduleDuration() );
	}
	catch ( const bad_lexical_cast & )
	{
		esyslog("live: could not convert '%s' into a schedule duration", LiveSetup().GetScheduleDuration().c_str());
		max_hours = 8;
	};
	if (max_hours > 48)
	    max_hours = 48;

	time_t sched_end = sched_start + 60 * 60 * max_hours;
	int sched_end_row = ( sched_end - sched_start ) / 60 / MINUTES_PER_ROW;
	std::list<SchedEntry> table[MAX_CHANNELS];
	std::vector<std::string> channel_names(channel_groups_numbers[ channel ].size() );
	std::vector<tChannelID> channel_IDs(channel_groups_numbers[ channel ].size() );
	if ( channel >= (int)channel_groups_numbers.size() )
		channel = channel_groups_numbers.size()-1;
	//for ( int chan = 0; chan<MAX_CHANNELS; chan++)
	for ( unsigned int j = 0; j<channel_groups_numbers[ channel ].size(); j++)
	{
		int prev_row = -1;
		int chan = channel_groups_numbers[ channel ][ j ];

		cChannel* Channel;
#if VDRVERSNUM >= 20301
		{
			LOCK_CHANNELS_READ;
			Channel = (cChannel *)Channels->GetByNumber( chan );
		}
#else
		Channel = Channels.GetByNumber( chan );
#endif
		if ( ! Channel )
			continue;
		if ( Channel->GroupSep() || !Channel->Name() || !*Channel->Name() )
			continue;
		channel_names[ j ] = Channel->Name();
		channel_IDs[ j ] = Channel->GetChannelID();

		cSchedule const* Schedule;
#if VDRVERSNUM >= 20301
		{
			LOCK_SCHEDULES_READ;
			Schedule = Schedules->GetSchedule( Channel );
		}
#else
		Schedule = schedules->GetSchedule( Channel );
#endif
		if ( ! Schedule )
			continue;
		for (const cEvent *Event = Schedule->Events()->First(); Event;
			 Event = Schedule->Events()->Next(Event) )
		{
			if (Event->EndTime() <= sched_start )
				continue;
			if (Event->StartTime() >= sched_end )
				continue;

			EpgInfoPtr epgEvent = EpgEvents::CreateEpgInfo(Channel, Event);
			if ( prev_row < 0 && Event->StartTime() > sched_start + MINUTES_PER_ROW )
			{
				// insert dummy event at start
				table[ j ].push_back( SchedEntry() );
				SchedEntry &en=table[ j ].back();
				int event_start_row = (Event->StartTime() - sched_start) / 60 / MINUTES_PER_ROW;
				en.start_row = 0;
				en.row_count = event_start_row;
				if ( en.row_count < 1 )
					en.row_count = 1;
				// no title and no start time = dummy event
				en.title = "";
				en.start = "";
				prev_row = en.start_row + en.row_count;
			}
			table[ j ].push_back( SchedEntry() );
			SchedEntry &en=table[j].back();

			en.title = epgEvent->Title();
			en.short_description = epgEvent->ShortDescr();
			en.description = epgEvent->LongDescr();
			en.start = epgEvent->StartTime(tr("%I:%M %p"));
			en.end = epgEvent->EndTime(tr("%I:%M %p"));
			en.day = epgEvent->StartTime(tr("%A, %b %d %Y"));
			en.epgid = EpgEvents::EncodeDomId(Channel->GetChannelID(), Event->EventID());
			en.has_timer = LiveTimerManager().GetTimer(Event->EventID(), Channel->GetChannelID() ) != 0;

			en.start_row = prev_row > 0 ? prev_row : 0;
			int end_time = Schedule->Events()->Next(Event) ?
				Schedule->Events()->Next(Event)->StartTime() :
				Event->EndTime();
			if (end_time > sched_end)
				end_time = sched_end;
			int next_event_start_row = (end_time - sched_start) / 60 / MINUTES_PER_ROW;
			en.row_count = next_event_start_row - en.start_row;
			if ( en.row_count < 1 )
				en.row_count = 1;
			prev_row = en.start_row + en.row_count;

			// truncate description if too long
			en.truncated=false;
			en.description_trunc=StringWordTruncate( en.description,
													 CHARACTERS_PER_ROW*(en.row_count-2),
													 en.truncated );



		};
		if ( table[ j ].begin() == table[ j ].end() )
		{
			// no entries... create a single dummy entry
			table[ j ].push_back( SchedEntry() );
			SchedEntry &en=table[ j ].back();
			en.start_row = 0;
			en.row_count = sched_end_row;
			// no title and no start time = dummy event
			en.title = "";
			en.start = "";
		}
	}
</%cpp>
		<table class="mschedule" cellspacing="0" cellpadding="0">
<%cpp>
</%cpp>
	       <tr class=" topaligned ">
	       <td > <div class="boxheader"> <div><div><$ tr("Time") $></div></div> </div></td>
	       <td class="time spacer"> &nbsp; </td>
<%cpp>
                for ( unsigned int channel = 0; channel< channel_names.size() ; channel++)
                {
</%cpp>
	       <td> <div class="boxheader"> <div> <div><$ channel_names[channel] $> <# reply.sout() automatically escapes special characters to html entities #>
            <& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(channel_IDs[channel]) image="zap.png" alt="" &>
            <& pageelems.hls_channel channelId=(channel_IDs[channel]) &>
            <& pageelems.m3u_playlist_channel channelId=(channel_IDs[channel]) &>
            </div></div> </div></td>
	       <td class="time spacer"> &nbsp; </td>
<%cpp>
                }
</%cpp>
	       </tr>
<%cpp>
                bool odd=true;
                std::list<SchedEntry>::iterator  cur_event[ MAX_CHANNELS ];
                for (int i=0;i<MAX_CHANNELS;i++)
                        cur_event[i]=table[i].begin();
                for (int row = 0 ; row < sched_end_row; row++ )
		{
                       int minutes= ( (sched_start +  row * 60 * MINUTES_PER_ROW ) % 3600 ) / 60;
                       std::string row_class;
                       if ( minutes < MINUTES_PER_ROW )
                       {
                         // full hour, swap odd/even
                         odd = !odd;
                       };
                       if (  (sched_start +  row * 60 * MINUTES_PER_ROW ) <= now &&
                             (sched_start +  (row+1) * 60 * MINUTES_PER_ROW ) > now )
                       {
                         row_class +=" current_row ";
                       }
                       row_class += odd ? " odd " : " even ";

</%cpp>
		       <tr>
		       <td class=" time leftcol rightcol <$ row_class $>">
<%cpp>
			if (  minutes < MINUTES_PER_ROW )
			{
</%cpp>
		       <$ FormatDateTime( tr("%I:%M %p"), sched_start +  row * 60 * MINUTES_PER_ROW )  $>
<%cpp>
			}
			else
			{
</%cpp>
			 &nbsp;
<%cpp>
		        }
</%cpp>
		       </td>
<%cpp>
                        for ( unsigned int channel = 0; channel< channel_names.size() ; channel++)
                        {
                                // output spacer column
</%cpp>
		                <td class = " time spacer " > &nbsp; </td>
<%cpp>
                                if ( cur_event[channel] == table[channel].end()
                                     || cur_event[channel]->start_row != row )
				{
                                  // no new event in this channel, skip it
					SchedEntry &en=*(--table[channel].end());
					if ( row == 0 || en.start_row + en.row_count == row) {
</%cpp>
			<td class="event topaligned leftcol rightcol" rowspan="<$ sched_end_row - row $>">
                        <div class=" content1 " >
                         <div class= "start withmargin"><? row == 0 ? "No EPG available" ?><? row != 0 ? "EPG end" ?></div>
                        </div>
			</td>
<%cpp>
					}
                                  continue;
				}

                                SchedEntry &en=*cur_event[channel];
                                if (en.title.empty() && en.start.empty() )
                                {
                                  // empty dummy event
</%cpp>
			<td class="event topaligned leftcol rightcol" rowspan="<$ en.row_count $>">
                        <div class=" content1 " >
                         <div class= "start withmargin"><? en.row_count == sched_end_row ? "EPG empty" ?></div>
                        </div>
                        </td>
<%cpp>
				  ++cur_event[channel];
                                  continue;

                                }
                                // output an event cell
</%cpp>
			<td class="event topaligned leftcol rightcol <$ en.has_timer ? "has_timer" : "" $>" rowspan="<$ en.row_count $>">
                        <div class=" content1 " >
                        <div class=" tools1 " >
                          <& pageelems.event_timer epgid=(en.epgid) &>
<%cpp>
                           if (LiveFeatures<features::epgsearch>().Recent() ) {
</%cpp>
                          <a href="searchresults.html?searchplain=<$ StringUrlEncode(en.title) $>"><img src="<$ LiveSetup().GetThemedLink("img", "search.png") $>" alt="" <& tooltip.hint text=(tr("Search for repeats.")) &>></img></a>
<%cpp>
                           } else {
                             </%cpp><img src="img/transparent.png" width="16" height="16"><%cpp>
                           }
</%cpp>
                          <& pageelems.imdb_info_href title=(en.title) &>
                         </div><div class= "start withmargin"><$ en.start $></div>
			  <div class="title withmargin"><a <& tooltip.hint text=(StringEscapeAndBreak(tr("Click to view details."))) &><& tooltip.display domId=en.epgid &>><$ en.title $></a></div>
<%cpp>
                         if ( en.row_count>2 && !en.short_description.empty() )
                         {
</%cpp>
                          <div class="short withmargin"><$ en.short_description.empty() ? "&nbsp;" : en.short_description $></div>
<%cpp>
                         }
                         if ( en.row_count>3 && ! en.description_trunc.empty() )
                         {
</%cpp>
                          <div class="description withmargin"><$en.description_trunc$>...
<%cpp>
                           if ( en.truncated )
                           {
</%cpp>
                           <a <& tooltip.hint text=(StringEscapeAndBreak(tr("Click to view details."))) &><& tooltip.display domId=en.epgid &>> <$ tr("more") $></a>
<%cpp>
                           }
</%cpp>
                           </div>
<%cpp>
                         }
</%cpp>
                          </div></div>
                          </td>
<%cpp>
                          // move to next event for this channel
                          ++cur_event[channel];
                        }
</%cpp>
		       </tr>
<%cpp>
		}
</%cpp>
			<tr>
<%cpp>
			for ( unsigned int channel = 0; channel <= channel_names.size() ; channel++)
			{
</%cpp>
			<td class = " event leftcol rightcol bottomrow " > &nbsp; </td>
			<td class = " time spacer " > &nbsp; </td>
<%cpp>
			}
</%cpp>
			</tr>
			</table>
		</div>
	</body>
</html>
<%include>page_exit.eh</%include>

<%def channel_selection>
<form action="multischedule.html" method="get" id="channels">
   <span>
     <label for="channel"><$ tr("Channel") $>:&nbsp;<span class="bold"></span></label>
     <select name="channel" id="channel"  onchange="document.forms.channels.submit()" >
%	for ( unsigned int i = 0; i < channel_groups_names.size(); ++i ) {
%
		<option value="<$ i $>"
%                      if ( i == channel_group )
%                      {
			selected="selected"
%                      }
                        ><$ channel_groups_names[i] $></option>
%	}
     </select>
     <label for="time_para"><$ tr("Time") $>:&nbsp;<span class="bold"></span></label>
     <select name="time_para" id="time_para"  onchange="document.forms.channels.submit()" >
%	for ( unsigned int i = 0; i < times_names.size(); ++i ) {
%
		<option value="<$ i $>"
%                      if ( i == time_selected )
%                      {
			selected="selected"
%                      }
                        ><$ times_names[i] $></option>
%	}
     </select>
% //     <& pageelems.ajax_action_href action="switch_channel" tip=(tr("Switch to this channel.")) param=(Channel->GetChannelID()) image="zap.png" alt="" &>
   </span>
</form>
<span class="sep">|</span>
<a href="multischedule.html?time_para=<$ time_selected $>&channel=<$ std::max(0, ((int)channel_group - 1)) $>">« Prev</a>
<span class="sep">|</span>
<a href="multischedule.html?time_para=<$ time_selected $>&channel=<$ (channel_group + 1) $>">Next »</a>
</%def>
