/*
 * loadepg.c: A plugin for the Video Disk Recorder
 *
 * See the README file for copyright information and how to reach the author.
 *
 * $Id$
 */

#include "loadepg.h"

//-- cLoadepgFilter -------------------------------------------------------

cLoadepgFilter::cLoadepgFilter( void )
{
    Set( 0x14, 0x73 );
    Set( 0xd3, 0x91 );
    Set( 0xd3, 0x92 );
    Set( 0xd2, 0x90 );
    Set( 0xd3, 0x90 );
}

void cLoadepgFilter::Process( u_short Pid, u_char Tid, const u_char *Data, int Length )
{
    if( Status.Plugin == STATUS_INIT )
    {
	fprintf( stderr, "scan provider: %s\n", EpgProviderTitles[MenuItem] );
	fprintf( stderr, "found %i equivalences channels\n", NumEpgEquivChannels );
	Status.Plugin = STATUS_EXEC_LOAD;
	Status.EpgTime = STATUS_START_LOAD;
	Status.EpgChannels = STATUS_NULL;
	Status.EpgThemes = STATUS_NULL;
	Status.EpgPrograms = STATUS_NULL;
	Status.EpgSummaries = STATUS_NULL;
	VdrEquivChannelId = -1;
	NumEpgChannels = 0;
        Timeout = time( NULL ) + TIMEOUT;
    }
    
    if( Status.Plugin != STATUS_NULL && Status.Plugin != STATUS_ACTIVE )
    {
	// Get time
	if( Pid == 0x14 && Tid == 0x73 )
	{
	    GetEpgTime( Data, Length );
	}
	
        // Get channels
	if( Pid == 0xd3 && Tid == 0x91 )
	{
	    GetEpgChannels( Data, Length );
	}
	
	// Get themes
	if( Pid == 0xd3 && Tid == 0x92 )
	{
	    GetEpgThemes( Data, Length );
	}
	
	// Get programs
	if( Pid == 0xd2 && Tid == 0x90 )
	{
	    GetEpgPrograms( Data, Length );
	}
	
	//Get summaries
	if( Pid == 0xd3 && Tid == 0x90 )
	{
	    GetEpgSummaries( Data, Length );
	}
	
	if( Status.EpgTime == STATUS_STOP_LOAD && Status.EpgChannels == STATUS_STOP_LOAD && Status.EpgThemes == STATUS_STOP_LOAD && Status.EpgPrograms == STATUS_STOP_LOAD && Status.EpgSummaries == STATUS_STOP_LOAD )
	{
	    Status.Plugin = STATUS_LOADEPG;
	}
    }
    if( Status.Plugin == STATUS_EXEC_LOAD )
    {
      if( NumEpgChannels == 0 && time( NULL ) > Timeout )
      {
        fprintf( stderr, "timeout: no data epg available\n" );
        Status.Plugin = STATUS_TIMEOUT;
        Timeout = time( NULL ) + ( 10 * TIMEOUT );
      }
    }
}

time_t cLoadepgFilter::LocalTime2Utc( time_t Time )
{
    struct tm *TmTime;
    TmTime = gmtime( &Time );
    TmTime->tm_isdst = -1;
    return mktime( TmTime );
}

time_t cLoadepgFilter::Utc2LocalTime( time_t Time )
{
    return ( 2 * Time ) - LocalTime2Utc( Time );
}

void cLoadepgFilter::GetEpgTime( const unsigned char *Data, int Length )
{
    if( Status.EpgTime == STATUS_START_LOAD )
    {
	struct tm *TmTime;
	time_t Time;
	Time = Utc2LocalTime( time( NULL ) - 86400 );
	TmTime = gmtime( &Time );
	Yesterday = TmTime->tm_wday;
	TmTime->tm_hour = 0;
	TmTime->tm_min = 0;
	TmTime->tm_sec = 0;
	TmTime->tm_isdst = -1;
	YesterdayEpoch = Utc2LocalTime( mktime( TmTime ) );
	CurrentTime = time( NULL );
	Status.EpgTime = STATUS_STOP_LOAD;
	Status.EpgChannels = STATUS_START_LOAD;
    }
}

void cLoadepgFilter::GetEpgChannels( const unsigned char *Data, int Length )
{
    if( Status.EpgChannels == STATUS_START_LOAD )
    {
	int Id, Sid, Tid, Nid, Rid;
	char *Dummy;
	char FileName[255];
	FILE *File;
	EpgChannelType *EpgChannelData = ( EpgChannelType * ) ( Data + 4 );
	NumEpgChannels = ( Length - 4 ) / sizeof( EpgChannelType );
	if( NumEpgChannels > EPG_CHANNELS )
	{
	    fprintf( stderr, "error stream data, epg channels > %i\n", EPG_CHANNELS );
	    Status.Plugin = STATUS_END;
	    Status.EpgTime = STATUS_STOP_LOAD;
	    Status.EpgChannels = STATUS_STOP_LOAD;
	    Status.EpgThemes = STATUS_STOP_LOAD;
	    Status.EpgPrograms = STATUS_STOP_LOAD;
	    Status.EpgSummaries = STATUS_STOP_LOAD;
	}
	memset( FileName, 0, sizeof( FileName ) );
	snprintf( FileName, sizeof( FileName ), "/tmp/%s.channels", EpgProviderTitles[MenuItem] );
	File = fopen( FileName, "w" );
	if( sscanf( EpgProviderChannels[MenuItem], "%a[^:]:%a[^:]:%a[^:]:%a[^:]:%a[^:]", &Dummy, &Dummy, &Dummy, &EpgSourceName, &Dummy ) == 5 )
	{
	    EpgSourceId = cSource::FromString( EpgSourceName );
	}
	for( Id = 0; Id < NumEpgChannels; Id ++ )
	{
	    Sid = ( ( EpgChannelData->ChannelIdHigh << 8 ) | EpgChannelData->ChannelIdLow ) & 0xffff;
	    Tid = ( ( EpgChannelData->TransponderIdHigh << 8 ) | EpgChannelData->TransponderIdLow ) & 0xffff;
	    Nid = ( ( EpgChannelData->NetworkIdHigh << 8 ) | EpgChannelData->NetworkIdLow ) & 0xffff;
	    Rid = 0;
	    tChannelID EpgChannelId = tChannelID( EpgSourceId, Nid, Tid, Sid, Rid );
	    cChannel *EpgChannel = Channels.GetByChannelID( EpgChannelId, true );
	    memset( EpgChannels[Id], 0, sizeof( EpgChannels[Id] ) );
	    if( EpgChannel )
	    {
		snprintf( EpgChannels[Id], sizeof( EpgChannels[Id] ), "%s-%i-%i-%i-%i %s", EpgSourceName, EpgChannel->Nid(), EpgChannel->Tid(), EpgChannel->Sid(), EpgChannel->Rid(), EpgChannel->Name() );
		if( File != NULL )
		{
		    fprintf( File, "%s\n", EpgChannels[Id] );
		}
	    }
	    EpgChannelData ++;
	}
	if( File != NULL )
	{
	    fclose( File );
	    fprintf( stderr, "epg channels list saved in %s\n", FileName );
	}
	fprintf( stderr, "found %i channels\n", NumEpgChannels );
	NumChannels = NumEpgChannels + NumEpgEquivChannels;
	Status.EpgChannels = STATUS_STOP_LOAD;
	Status.EpgThemes = STATUS_START_LOAD;
    }
}

void cLoadepgFilter::GetEpgThemes( const unsigned char *Data, int Length )
{
    if( Status.EpgThemes == STATUS_START_LOAD )
    {
	int Id, Theme, Offset;
	const unsigned char *EpgThemesIndex = Data + 3;
	typedef unsigned char EpgThemeType[15];
	EpgThemeType *EpgThemeData = ( EpgThemeType * ) ( Data + 19 );
	Theme = 0;
	Offset = 0;
	NumThemes = ( Length - 19 ) / 15;
	if( NumThemes > EPG_THEMES )
	{
	    fprintf( stderr, "error stream data, epg themes > %i\n", EPG_THEMES );
	    Status.Plugin = STATUS_END;
	    Status.EpgTime = STATUS_STOP_LOAD;
	    Status.EpgChannels = STATUS_STOP_LOAD;
	    Status.EpgThemes = STATUS_STOP_LOAD;
	    Status.EpgPrograms = STATUS_STOP_LOAD;
	    Status.EpgSummaries = STATUS_STOP_LOAD;
	}
	for( Id = 0; Id < NumThemes; Id ++ )
	{
	    if( EpgThemesIndex[Theme] == Id )
	    {
		Offset = ( Offset + 15 ) & 0xf0;
		Theme ++;
	    }
	    memcpy( EpgThemes[Offset], EpgThemeData, 15 );
	    EpgThemes[Offset][15] = 0;
	    CleanString( EpgThemes[Offset] );
	    Offset ++;
	    EpgThemeData ++;
	}
	fprintf( stderr, "found %i themes\n", NumThemes );
	Status.EpgThemes = STATUS_STOP_LOAD;
	Status.EpgPrograms = STATUS_START_LOAD;
	Status.EpgSummaries = STATUS_START_LOAD;
    }
}

static int PatternSortEpgPrograms( const void *a, const void *b )
{
    struct EpgProgram *EpgProgramA = ( struct EpgProgram * ) a;
    struct EpgProgram *EpgProgramB = ( struct EpgProgram * ) b;
    if( EpgProgramA->ChannelId > EpgProgramB->ChannelId )
    {
        return 1;
    }
    if( EpgProgramA->ChannelId < EpgProgramB->ChannelId )
    {
        return -1;
    }
    if( EpgProgramA->ChannelId == EpgProgramB->ChannelId )
    {
        if( EpgProgramA->Time > EpgProgramB->Time )
	{
	   return 1;
	}
        if( EpgProgramA->Time < EpgProgramB->Time )
	{
	   return -1;
	}
    }
    return 0;
}

void cLoadepgFilter::GetEpgPrograms( const unsigned char *Data, int Length )
{
    EpgProgramData = ( EpgProgramType * ) Data;
    if( Length == EPG_PROGRAM_LENGTH )
    {
	if( EpgProgramData->Day == 7 )
	{
	    EpgProgramData->Day = 0;
	}
	if( EpgProgramData->Hours > 15 )
	{
	    EpgProgramData->Hours = EpgProgramData->Hours - 4;
	}
	else if( EpgProgramData->Hours > 7 )
	{
	    EpgProgramData->Hours = EpgProgramData->Hours - 2;
	}
	else
	{
	    if( EpgProgramData->Day == 6 )
	    {
	        //EpgProgramData->Day = 0;
	    }
	    else
	    {
	        //EpgProgramData->Day = Day + 1;
	    }
	}
        if( Status.EpgPrograms == STATUS_START_LOAD )
	{
	    if( EpgProgramData->ChannelId != 0xff )
	    {
	        NumEpgPrograms = 0;
	        memset( ListEpgPrograms, 0, sizeof( ListEpgPrograms ) );
	        memcpy( EpgProgramInitial, EpgProgramData, EPG_PROGRAM_LENGTH );
	        Status.EpgPrograms = STATUS_EXEC_LOAD;
	        goto EndGetEpgPrograms;
	    }
	}
	if( Status.EpgPrograms == STATUS_EXEC_LOAD )
	{
	    if( EpgProgramData->ChannelId != 0xff )
	    {
	        ( EpgPrograms + NumEpgPrograms )->ChannelId = EpgProgramData->ChannelId;
		( EpgPrograms + NumEpgPrograms )->ThemeId = EpgProgramData->ThemeId;
	        ( EpgPrograms + NumEpgPrograms )->Time = ( ( EpgProgramData->Day - Yesterday ) * 86400 ) + ( EpgProgramData->Hours * 3600 ) + ( EpgProgramData->Minutes * 60 );
		if( ( EpgPrograms + NumEpgPrograms )->Time < ( 6 * 3600 ) )
		{
		    ( EpgPrograms + NumEpgPrograms )->Time = ( EpgPrograms + NumEpgPrograms )->Time + ( 7 * 86400 );
		}
		( EpgPrograms + NumEpgPrograms )->Time = ( EpgPrograms + NumEpgPrograms )->Time + YesterdayEpoch;
		( EpgPrograms + NumEpgPrograms )->Time = cLoadepgFilter::LocalTime2Utc( ( EpgPrograms + NumEpgPrograms )->Time );
		( EpgPrograms + NumEpgPrograms )->SummaryAvailable = EpgProgramData->SummaryAvailable;
		( EpgPrograms + NumEpgPrograms )->Duration = HILO16( EpgProgramData->Duration ) * 60;
		memcpy( ( EpgPrograms + NumEpgPrograms )->Title, &EpgProgramData->Title, 23 );
		( EpgPrograms + NumEpgPrograms )->Title[23] = 0;
		CleanString( ( EpgPrograms + NumEpgPrograms )->Title );
		( EpgPrograms + NumEpgPrograms )->PpvId = HILO32( EpgProgramData->PpvId );
		( EpgPrograms + NumEpgPrograms )->ProgramId = HILO32( EpgProgramData->ProgramId );
		ListEpgPrograms[( EpgPrograms + NumEpgPrograms )->ChannelId] ++;
		NumEpgPrograms ++;
	        if( NumEpgPrograms > EPG_PROGRAMS )
	        {
	            fprintf( stderr, "error stream data, epg programs > %i\n", EPG_PROGRAMS );
	            Status.Plugin = STATUS_END;
	            Status.EpgTime = STATUS_STOP_LOAD;
	            Status.EpgChannels = STATUS_STOP_LOAD;
	            Status.EpgThemes = STATUS_STOP_LOAD;
	            Status.EpgPrograms = STATUS_STOP_LOAD;
	            Status.EpgSummaries = STATUS_STOP_LOAD;
	        }
	        if( ! memcmp( EpgProgramInitial, Data, EPG_PROGRAM_LENGTH ) )
	        {
		    qsort( &EpgPrograms[0], NumEpgPrograms, sizeof( EpgPrograms[0] ), &PatternSortEpgPrograms );
		    fprintf( stderr, "found %i programs\n", NumEpgPrograms );
	            Status.EpgPrograms = STATUS_STOP_LOAD;
	        }
	    }
	}
    }
    EndGetEpgPrograms:;
}

static int PatternSortEpgSummaries( const void *a, const void *b )
{
    struct EpgSummary *EpgSummaryA = ( struct EpgSummary * ) a;
    struct EpgSummary *EpgSummaryB = ( struct EpgSummary * ) b;
    if( EpgSummaryA->ProgramId > EpgSummaryB->ProgramId )
    {
        return 1;
    }
    if( EpgSummaryA->ProgramId < EpgSummaryB->ProgramId )
    {
        return -1;
    }
    return 0;
}

void cLoadepgFilter::GetEpgSummaries( const unsigned char *Data, int Length )
{
    EpgSummaryData = ( EpgSummaryType * ) Data;
    if( EpgSummaryData->Byte7 == 0xff && EpgSummaryData->Byte8 == 0xff && EpgSummaryData->Byte9 == 0xff && EpgSummaryData->NumReplays < EPG_SUMMARY_NUM_REPLAYS && Length > EPG_SUMMARY_LENGTH )
    {
	if( Status.EpgSummaries == STATUS_START_LOAD )
	{
	    NumEpgSummaries = 0;
	    memcpy( EpgSummaryInitial, EpgSummaryData, EPG_SUMMARY_LENGTH );
	    Status.EpgSummaries = STATUS_EXEC_LOAD;
	    goto EndGetEpgSummaries;
	}
	if( Status.EpgSummaries == STATUS_EXEC_LOAD )
	{
	    ( EpgSummaries + NumEpgSummaries )->ProgramId = HILO32( EpgSummaryData->ProgramId );
	    TextOffset = EPG_SUMMARY_LENGTH + ( EpgSummaryData->NumReplays * EPG_SUMMARY_REPLAY_LENGTH );
	    TextLength = Length + 1 - TextOffset;
	    ( EpgSummaries + NumEpgSummaries )->Text = ( unsigned char * ) malloc( TextLength );
	    memcpy( ( EpgSummaries + NumEpgSummaries )->Text, &Data[TextOffset], TextLength );
	    ( EpgSummaries + NumEpgSummaries )->Text[TextLength-1] = 0;
	    
	    CleanString( ( EpgSummaries + NumEpgSummaries )->Text );
	    NumEpgSummaries ++;
	    if( NumEpgSummaries > EPG_SUMMARIES )
	    {
	        fprintf( stderr, "error stream data, epg summaries > %i\n", EPG_SUMMARIES );
	        Status.Plugin = STATUS_END;
	        Status.EpgTime = STATUS_STOP_LOAD;
	        Status.EpgChannels = STATUS_STOP_LOAD;
	        Status.EpgThemes = STATUS_STOP_LOAD;
	        Status.EpgPrograms = STATUS_STOP_LOAD;
	        Status.EpgSummaries = STATUS_STOP_LOAD;
	    }
	    if( ! memcmp( EpgSummaryInitial, EpgSummaryData, EPG_SUMMARY_LENGTH ) )
	    {
		qsort( &EpgSummaries[0], NumEpgSummaries, sizeof( EpgSummaries[0] ), &PatternSortEpgSummaries );
		fprintf( stderr, "found %i summaries\n", NumEpgSummaries );
		Status.EpgSummaries = STATUS_STOP_LOAD;
	    }
	}
    }
    EndGetEpgSummaries:;
}

void cLoadepgFilter::CleanString( unsigned char *String )
{
    unsigned char *Src;
    unsigned char *Dst;
    int Spaces;
    Src = String;
    Dst = String;
    Spaces = 0;
    while( *Src )
    {
        if( *Src < 0x20 || ( *Src > 0x7e && *Src < 0xa0 ) )
	{
	    *Src = 0x20;
	}
	if( *Src == 0x20 )
	{
	    Spaces ++;
	}
	else
	{
	    Spaces = 0;
	}
	if( Spaces < 2 )
	{
	    *Dst = *Src;
	    *Dst ++;
	}
        *Src ++;
    }
    if( Spaces > 0 )
    {
        Dst --;
	*Dst = 0;
    }
    else
    {
        *Dst = 0;
    }
}

//-------------------------------------------------------------------------

//-- cLoadepgOsd ----------------------------------------------------------

cLoadepgOsd::cLoadepgOsd( void )
{
    Osd = NULL;
    Filter = new cLoadepgFilter();
    cDevice::GetDevice( LoadepgConfig.DeviceNumber - 1 )->AttachFilter( Filter );
    Status.Plugin = STATUS_ACTIVE;
    Status.EpgTime = STATUS_NULL;
    Status.EpgChannels = STATUS_NULL;
    Status.EpgThemes = STATUS_NULL;
    Status.EpgPrograms = STATUS_NULL;
    Status.EpgSummaries = STATUS_NULL;
    MenuItem = 0;
    Timeout = 0;
    CheckLoadepgChannel();
    VdrChannelId = GetCurrentChannelId();
}

cLoadepgOsd::~cLoadepgOsd( void )
{
    memset( &Status, STATUS_NULL, sizeof( struct Status ) );
    Status.Plugin = STATUS_NULL;
    Status.Svdrp = STATUS_NULL;
    MenuItem = 0;
    cDevice::GetDevice( LoadepgConfig.DeviceNumber - 1 )->Detach( Filter );
    delete Filter;
    delete Osd;
}

void cLoadepgOsd::Show( void )
{
    Osd = cOsdProvider::NewOsd( 160, 88 );
    if( Osd )
    {
	tArea Area = { 0, 0, 399, 399, 4 };
	Osd->SetAreas( &Area, 1 );
    }
    Osd->DrawRectangle( 0, 0, 399, 399, clrTransparent );
    DrawOsd();
    if( Status.Plugin == STATUS_ACTIVE && Status.Svdrp != STATUS_NULL )
    {
      cRemote::Put( kOk );
    }
}

eOSState cLoadepgOsd::ProcessKey( eKeys Key )
{
    eOSState state = cOsdObject::ProcessKey( Key );
    StatusKey = 0;
    if( state == osUnknown )
    {
	switch( Key )
	{
	    case kOk:
		StatusKey = 1;
		if( Status.Plugin == STATUS_ACTIVE )
		{
		    if( Status.Svdrp != STATUS_NULL )
		    {
		      MenuItem = Status.Svdrp - 1;
		    }
		    EpgChannelId = NewChannel( EpgProviderChannels[MenuItem] );
		    if( EpgChannelId != 0 )
		    {
			if( SwitchToChannelId( EpgChannelId ) == false )
			{
			  Status.Plugin = STATUS_NULL;
			  Status.Svdrp = STATUS_NULL;
			  return osEnd;
			}
		    }
		    else
		    {
			Status.Plugin = STATUS_NULL;
			Status.Svdrp = STATUS_NULL;
			return osEnd;
		    }
		    Status.Plugin = STATUS_INIT;
		    Osd->DrawRectangle( 0, 0, 399, 399, clrTransparent );
		    DrawOsd();
		}
		break;
	    case kUp:
		StatusKey = 1;
		if( Status.Plugin == STATUS_ACTIVE )
		{
		    MenuItem = MenuItem - 1;
		    if( MenuItem < 0 )
		    {
			MenuItem = 0;
		    }
		}
		break;
	    case kDown:
		StatusKey = 1;
		if( Status.Plugin == STATUS_ACTIVE )
		{
		    MenuItem = MenuItem + 1;
		    if( MenuItem >= EpgProviders )
		    {
			MenuItem = EpgProviders - 1;
		    }
		}
		break;
	    case kBack:
		StatusKey = 1;
		if( Status.Plugin != STATUS_NULL && Status.Plugin != STATUS_ACTIVE )
		{
		    if( SwitchToChannelId( VdrChannelId ) )
		    {
			DelChannel( EpgChannelId );
		    }
		}
		Status.Plugin = STATUS_NULL;
		Status.Svdrp = STATUS_NULL;
		Osd->DrawRectangle( 0, 0, 399, 399, clrTransparent );
		return osEnd;
	    default:
		break;
	}
	Actions();
	state = osContinue;
	if( Status.Plugin == STATUS_END )
	{
	    if( SwitchToChannelId( VdrChannelId ) )
	    {
		DelChannel( EpgChannelId );
	    }
	    Status.Plugin = STATUS_NULL;
	    Status.Svdrp = STATUS_NULL;
	    Osd->DrawRectangle( 0, 0, 399, 399, clrTransparent );
	    return osEnd;
	}
	if( Status.Plugin == STATUS_TIMEOUT )
	{
	  DrawOsd();
	  if( Timeout == 0 )
	  {
	    Timeout = time( NULL ) + 4;
	  }
	  else if( time( NULL ) > Timeout )
	  {
	    Status.Plugin = STATUS_END;
	  }
	}
    }
    return state;
}

static int PatternSearchSummary( const void *a, const void *b )
{
    struct EpgSummary *EpgSummaryA = ( struct EpgSummary * ) a;
    struct EpgSummary *EpgSummaryB = ( struct EpgSummary * ) b;
    if( EpgSummaryA->ProgramId > EpgSummaryB->ProgramId )
    {
        return 1;
    }
    if( EpgSummaryA->ProgramId < EpgSummaryB->ProgramId )
    {
        return -1;
    }
    return 0;
}

void cLoadepgOsd::SaveEpg( void )
{
    int ProgramId, BeginProgramId, LastProgramId, ChannelId;
    struct EpgSummary Key, *Result;
    FILE *File;
    VdrEquivChannelId = -1;
    if( ( File = fopen( "/tmp/epg.tmp", "w" ) ) != NULL )
    {
        BeginProgramId = 0;
	for( ChannelId = 0; ChannelId < NumEpgChannels; ChannelId ++ )
	{
	    LastProgramId = BeginProgramId + ListEpgPrograms[ChannelId+1];
	    if( strlen( EpgChannels[ChannelId] ) > 0 )
	    {
	        if( ListEpgPrograms[ChannelId+1] > 0 )
		{
		    BeginLoop:;
		    if( VdrEquivChannelId > -1 )
		    {
		        fprintf( File, "C %s\n", EpgEquivChannels[VdrEquivChannelId][1] );
			EpgEquivChannels[VdrEquivChannelId][0][0] = 0;
		    }
		    else
		    {
		        fprintf( File, "C %s\n", EpgChannels[ChannelId] );
		    }
		    for( ProgramId = BeginProgramId; ProgramId < LastProgramId; ProgramId ++ )
		    {
		        if( ( ( EpgPrograms + ProgramId )->Time + ( EpgPrograms + ProgramId )->Duration ) > ( unsigned int ) CurrentTime )
		        {
		            fprintf( File, "E %u %u %u 90\n", ( EpgPrograms + ProgramId )->ProgramId, ( unsigned int ) ( EpgPrograms + ProgramId )->Time, ( EpgPrograms + ProgramId )->Duration );
		            fprintf( File, "T %s\n", ( EpgPrograms + ProgramId )->Title );
		            fprintf( File, "S %s - %d\n", EpgThemes[( EpgPrograms + ProgramId )->ThemeId], ( ( EpgPrograms + ProgramId )->Duration / 60 ) );
			    if( ( EpgPrograms + ProgramId )->SummaryAvailable == 1 )
			    {
			        Key.ProgramId = ( EpgPrograms + ProgramId )->ProgramId;
			        Result = ( EpgSummary * ) bsearch( &Key, EpgSummaries, NumEpgSummaries, sizeof( EpgSummaries[0] ), &PatternSearchSummary );
				if( Result != NULL )
				{
				    fprintf( File, "D %s\n", Result->Text );
				}
			    }
			    fprintf( File, "e\n" );
		        }
		    }
		    fprintf( File, "c\n" );
		}
	    }
	    if( LoadepgConfig.UseFileEquivalences == true )
	    {
	        for( VdrEquivChannelId = 0; VdrEquivChannelId < NumEpgEquivChannels; VdrEquivChannelId ++ )
		{
		    if( ! memcmp( EpgChannels[ChannelId], EpgEquivChannels[VdrEquivChannelId][0], sizeof( EpgChannels[ChannelId] ) ) )
		    {
		        goto BeginLoop;
		    }
		}
	    }
	    VdrEquivChannelId = -1;
	    BeginProgramId = BeginProgramId + ListEpgPrograms[ChannelId+1];
	}
        fclose( File );
    }
    if( ( File = fopen( "/tmp/epg.tmp", "r" ) ) != NULL )
    {
	rewind( File );
	if( cSchedules::Read( File ) )
	{
	    cSchedules::Cleanup( true );
	}
	fclose( File );
    }
    fprintf( stderr, "epg saved\n" );
    Status.Plugin = STATUS_END;
}

void cLoadepgOsd::Actions( void )
{
    if( StatusKey == 1 )
    {
	DrawOsd();
	StatusKey = 0;
    }
    if( Status.Plugin == STATUS_LOADEPG )
    {
	SaveEpg();
    }
}

void cLoadepgOsd::DrawOsd( void )
{
    int Id;
    char *ProviderName;
    const cFont *Font = cFont::GetFont( fontOsd );
    Margin = 10;
    Padding = 1;
    if( Osd )
    {
	if( Status.Plugin == STATUS_ACTIVE )
	{
	    LineHeight = Font->Height() + ( Padding * 2 );
	    LineWidth = Font->Width( tr( "LoadEPG" ) );
	    if( Font->Width( tr( "Select a provider and press Ok" ) ) > LineWidth )
	    {
		LineWidth = Font->Width( tr( "Select a provider and press Ok" ) );
	    }
	    LineWidth = LineWidth + ( Margin * 2 );
	    if( LineWidth > 399 )
	    {
		LineWidth = 399;
	    }
	    Left = ( 399 - LineWidth ) / 2;
	    Top = ( 399 - ( ( LineHeight * ( EpgProviders + 2 ) ) + ( 2 * Margin ) ) ) / 2;
	    
	    // Draw text top
	    TextTopX1 = Left;
	    TextTopY1 = Top;
	    TextTopX2 = TextTopX1 + LineWidth;
	    TextTopY2 = TextTopY1 + LineHeight;
	    Osd->DrawRectangle( TextTopX1, TextTopY1, TextTopX2, TextTopY2, clrBgTextTop );
	    Osd->DrawText( TextTopX1 + Margin, TextTopY1 + Padding, tr( "LoadEPG" ), clrFgTextTop, clrBgTextTop, Font );
	    
	    // Draw text scroll
	    TextScrollX1 = Left;
	    TextScrollX2 = TextScrollX1 + LineWidth;
	    Osd->DrawRectangle( TextScrollX1, TextTopY2, TextScrollX2, TextTopY2 + ( LineHeight * EpgProviders ) + ( 2 * Margin ), clrBgTextScroll );
	    for( Id = 0; Id < EpgProviders; Id ++ )
	    {
		TextScrollY1 = TextTopY2 + ( LineHeight * Id ) + Margin;
		TextScrollY2 = TextScrollY1 + LineHeight;
		asprintf( &ProviderName, "%i %s", ( Id + 1 ), EpgProviderTitles[Id] );
		if( MenuItem == Id )
		{
		    Osd->DrawRectangle( TextScrollX1 + Margin , TextScrollY1, TextScrollX2 - Margin, TextScrollY2, clrBgMenuItem );
		    //Osd->DrawText( TextScrollX1 + ( Margin * 2 ), TextScrollY1 + Padding, EpgProviderTitles[Id], clrFgMenuItem, clrBgMenuItem, Font );
		    Osd->DrawText( TextScrollX1 + ( Margin * 2 ), TextScrollY1 + Padding, ProviderName, clrFgMenuItem, clrBgMenuItem, Font );
		}
		else
		{
		    //Osd->DrawText( TextScrollX1 + ( Margin * 2 ), TextScrollY1 + Padding, EpgProviderTitles[Id], clrFgTextScroll, clrBgTextScroll, Font );
		    Osd->DrawText( TextScrollX1 + ( Margin * 2 ), TextScrollY1 + Padding, ProviderName, clrFgTextScroll, clrBgTextScroll, Font );
		}
		free( ProviderName );
	    }
	    
	    // Draw text bottom
	    TextBottomX1 = Left;
	    TextBottomY1 = TextScrollY2 + Margin;
	    TextBottomX2 = TextBottomX1 + LineWidth;
	    TextBottomY2 = TextBottomY1 + LineHeight;
	    Osd->DrawRectangle( TextBottomX1, TextBottomY1, TextBottomX2, TextBottomY2, clrBgTextBottom );
	    Osd->DrawText( TextBottomX1 + Margin, TextBottomY1 + Padding, tr( "Select a provider and press Ok" ), clrFgTextBottom, clrBgTextBottom, Font );
	    
	}
	if( Status.Plugin == STATUS_INIT )
	{
	    if( LoadepgConfig.DeviceNumber == 1 )
	    {
	        LineHeight = Font->Height() + ( Padding * 2 );
		LineWidth = 399;
		Left = 0;
		Top = ( 399 - ( LineHeight * 2 ) ) / 2;
	        
		TextTopX1 = Left;
		TextTopY1 = Top;
		TextTopX2 = TextTopX1 + LineWidth;
		TextTopY2 = TextTopY1 + LineHeight;
		Osd->DrawRectangle( TextTopX1, TextTopY1, TextTopX2, TextTopY2, clrBgTextTop );
		Osd->DrawText( TextTopX1 + Margin, TextTopY1 + Padding, tr( "LoadEPG" ), clrFgTextTop, clrBgTextTop, Font );
		TextBottomX1 = Left;
		TextBottomY1 = TextTopY2 + 2;
		TextBottomX2 = TextBottomX1 + LineWidth;
		TextBottomY2 = TextBottomY1 + LineHeight;
		Osd->DrawRectangle( TextBottomX1, TextBottomY1, TextBottomX2, TextBottomY2, clrBgTextBottom );
		Osd->DrawText( TextBottomX1 + ( LineWidth - Font->Width( tr( "Wait load epg data" ) ) ) / 2, TextBottomY1 + Padding, tr( "Wait load epg data" ), clrFgTextBottom, clrBgTextBottom, Font );
	    }
	    else
	    {
		LineHeight = Font->Height() + ( Padding * 2 );
		LineWidth = Font->Width( tr( "LoadEPG" ) ) + ( Margin * 2 );
		Left = 399 - ( LineWidth + 1 );
		Top = 399 - ( LineHeight + 1 );
		TextTopX1 = Left;
		TextTopY1 = Top;
		TextTopX2 = TextTopX1 + LineWidth;
		TextTopY2 = TextTopY1 + LineHeight;
		Osd->DrawRectangle( TextTopX1, TextTopY1, TextTopX2, TextTopY2, clrBgTextTop );
		Osd->DrawText( TextTopX1 + Margin, TextTopY1 + Padding, tr( "LoadEPG" ), clrFgTextTop, clrBgTextTop, Font );
	    }
	}
	if( Status.Plugin == STATUS_TIMEOUT )
	{
          LineHeight = Font->Height() + ( Padding * 2 );
          LineWidth = 399;
	  Left = 0;
	  Top = ( 399 - ( LineHeight * 2 ) ) / 2;
	  TextTopX1 = Left;
          TextTopY1 = Top;
          TextTopX2 = TextTopX1 + LineWidth;
          TextTopY2 = TextTopY1 + LineHeight;
	  Osd->DrawRectangle( 0, 0, 399, 399, clrTransparent );
	  Osd->DrawRectangle( TextTopX1, TextTopY1, TextTopX2, TextTopY2, clrRed );
          Osd->DrawText( TextTopX1 + ( LineWidth - Font->Width( tr( "No data epg available" ) ) ) / 2, TextTopY1 + Padding, tr( "No data epg available" ), clrWhite, clrRed, Font );
	}
	Osd->Flush();
    }
}

void cLoadepgOsd::CheckLoadepgChannel( void )
{
    int ChannelId;
    ChannelId = 1;
    while( ChannelId <= Channels.MaxNumber() )
    {
        cChannel *ChannelSearch = Channels.GetByNumber( ChannelId, 1 );
	if( ! strcmp( "LOADEPG_CHANNEL", ChannelSearch->Name() ) )
	{
	    cChannel *ChannelDevice = Channels.GetByNumber( cDevice::GetDevice( LoadepgConfig.DeviceNumber - 1 )->CurrentChannel() );
	    if( ChannelSearch->Number() == ChannelDevice->Number() )
	    {
	        if( ! SwitchToChannelId( 1 ) )
		{
		    fprintf( stderr, "deleting manually LOADEPG_CHANNEL channel\n" );
		}
	    }
	    if( ! DelChannel( ChannelSearch->Number() ) )
	    {
	        fprintf( stderr, "deleting manually LOADEPG_CHANNEL channel\n" );
	    }
	}
	ChannelId ++;
    }
}

int cLoadepgOsd::GetCurrentChannelId( void )
{
    cChannel *Channel = Channels.GetByNumber( cDevice::GetDevice( LoadepgConfig.DeviceNumber - 1 )->CurrentChannel() );
    if( Channel )
    {
        return Channel->Number();
    }
    return 0;
}

bool cLoadepgOsd::SwitchToChannelId( int ChannelId )
{
    cChannel *Channel = Channels.GetByNumber( ChannelId );
    if( Channel )
    {
	if( cDevice::GetDevice( LoadepgConfig.DeviceNumber - 1 )->SwitchChannel( Channel, true ) )
	{
	    return true;
	}
	else
	{
	    fprintf( stderr, "error switch to channel %i\n", ChannelId );
	    return false;
	}
    }
    else
    {
	fprintf( stderr, "error switch to channel %i\n", ChannelId );
	return false;
    }
}

int cLoadepgOsd::NewChannel( char *Data )
{
    cChannel Channel;
    if( Channel.Parse( Data ) )
    {
	if( Channels.HasUniqueChannelID( &Channel ) )
	{
	    cChannel *NewChannel = new cChannel;
	    *NewChannel = Channel;
	    Channels.Add( NewChannel );
	    Channels.ReNumber();
	    Channels.SetModified( true );
	    return NewChannel->Number();
	}
	else
	{
	    fprintf( stderr, "epg channel is not unique\n" );
	    return 0;
	}
    }
    else
    {
	fprintf( stderr, "epg channel parsing error\n" );
	return 0;
    }
}

bool cLoadepgOsd::DelChannel( int ChannelId )
{
    cChannel *Channel = Channels.GetByNumber( ChannelId );
    if( Channel )
    {
	Channels.Del( Channel );
	Channels.ReNumber();
	Channels.SetModified( true );
	return true;
    }
    else
    {
	fprintf( stderr, "error delete channel %i\n", ChannelId );
	return false;
    }
}

cLoadepgSetup::cLoadepgSetup( void )
{
    Clear();
    Add( new cMenuEditBoolItem( tr( "Use file channels equivalences" ), &LoadepgConfig.UseFileEquivalences, tr( "No" ), tr( "Yes" ) ) );
    Add( new cMenuEditIntItem( tr( "DVB-S device for scanning" ), &LoadepgConfig.DeviceNumber, 1, cDevice::NumDevices() ) );
    Display();
}

void cLoadepgSetup::Store( void )
{
    SetupStore( "UseFileEquivalences", LoadepgConfig.UseFileEquivalences );
    SetupStore( "DeviceNumber", LoadepgConfig.DeviceNumber );
}

//-------------------------------------------------------------------------

cPluginLoadepg::cPluginLoadepg(void)
{
  // Initialize any member variables here.
  // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL
  // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT!
  LoadepgConfig.UseFileEquivalences = false;
  LoadepgConfig.DeviceNumber = 1;
}

cPluginLoadepg::~cPluginLoadepg()
{
  // Clean up after yourself!
}

const char *cPluginLoadepg::CommandLineHelp(void)
{
  // Return a string that describes all known command line options.
  static char *HelpString = 0;
  free( HelpString );
  asprintf( &HelpString,
            "  -c PATH,  --config=PATH  to specify directory of configuration data\n"
	    "                           relative to VDR Plugin configuration directory\n"
	    "                           (default: \"/video/plugins\")\n"
	  );
  return HelpString;
}

bool cPluginLoadepg::ProcessArgs(int argc, char *argv[])
{
  // Implement command line argument processing here if applicable.
  ConfigPlugin = NULL;
  static struct option long_options[] = {
    { "config", required_argument, NULL, 'c' },
    { NULL }
  };
  int c;
  while( ( c = getopt_long( argc, argv, "c:", long_options, NULL ) ) != -1 )
  {
    switch( c )
    {
      case 'c':
        ConfigPlugin = ( char * ) malloc( sizeof ( char ) * ( strlen( optarg ) + 1 ) );
	snprintf( ConfigPlugin, ( strlen( optarg ) + 1 ), "%s", optarg );
	break;
      default:
        return false;
    }
  }
  return true;
}

bool cPluginLoadepg::Initialize(void)
{
  // Initialize any background activities the plugin shall perform.
  return true;
}

bool cPluginLoadepg::Start(void)
{
  // Start any background activities the plugin shall perform.
  RegisterI18n( LoadepgPhrases );
  memset( &Status, STATUS_NULL, sizeof( struct Status ) );
  Status.Plugin  = STATUS_NULL;
  return true;
}

void cPluginLoadepg::Stop(void)
{
  // Stop any background activities the plugin shall perform.
}

void cPluginLoadepg::Housekeeping(void)
{
  // Perform any cleanup or other regular tasks.
}

cOsdObject *cPluginLoadepg::MainMenuAction(void)
{
  // Perform the action when selected from the main VDR menu.
  FILE *File;
  char Buffer[1024];
  char *Line;
  char *Dummy;
  char *Channel1;
  char *Channel2;
  char *SourceName1;
  char *SourceName2;
  int SourceId1, Nid1, Tid1, Sid1, Rid1;
  int SourceId2, Nid2, Tid2, Sid2, Rid2;
  EpgProviders = 0;
  if( ConfigPlugin )
  {
    asprintf( &Filename, "%s/%s", ConfigPlugin, "loadepg.conf" );
  }
  else
  {
    asprintf( &Filename, "%s/%s", cPlugin::ConfigDirectory(), "loadepg.conf" );
  }
  File = fopen( Filename, "r" );
  if( File )
  {
    while( ( Line = fgets( Buffer, sizeof( Buffer ), File ) ) != NULL )
    {
	if( ( strlen( Buffer ) - 1 ) >= 0 && Buffer[ strlen( Buffer ) - 1 ] == '\n' )
	{
	    Buffer[ strlen( Buffer ) - 1 ] = 0;
	}
	Line = compactspace( skipspace( stripspace( Line ) ) );
	if( ! isempty( Line ) )
	{
	    if( sscanf( Line, "PROVIDER=%a[^:]:%a[^\n]", &EpgProviderTitles[EpgProviders], &EpgProviderChannels[EpgProviders] ) == 2 )
	    {
		EpgProviderTitles[EpgProviders] = compactspace( skipspace( stripspace( EpgProviderTitles[EpgProviders] ) ) );
		EpgProviderChannels[EpgProviders] = compactspace( skipspace( stripspace( EpgProviderChannels[EpgProviders] ) ) );		asprintf( &EpgProviderChannels[EpgProviders], "LOADEPG_CHANNEL:%s:0:0:0:0:1234:1234:1234:1234", EpgProviderChannels[EpgProviders] );
		EpgProviders ++;
	    }
	}
    }
    fclose( File );
  }
  else
  {
    fprintf( stderr, "open file config \"%s\" failed\n", Filename );
    return NULL;
  }
  NumEpgEquivChannels = 0;

  if( LoadepgConfig.UseFileEquivalences == true )
  {
    if( ConfigPlugin )
    {
      asprintf( &Filename, "%s/%s", ConfigPlugin, "loadepg.equiv" );
    }
    else
    {
      asprintf( &Filename, "%s/%s", cPlugin::ConfigDirectory(), "loadepg.equiv" );
    }
    File = fopen( Filename, "r" );
    if( File )
    {
	while( ( Line = fgets( Buffer, sizeof( Buffer ), File ) ) != NULL )
	{
	    if( ( strlen( Buffer ) - 1 ) >= 0 && Buffer[ strlen( Buffer ) - 1 ] == '\n' )
	    {
		Buffer[ strlen( Buffer ) - 1 ] = 0;
	    }
	    Line = compactspace( skipspace( stripspace( Line ) ) );
	    if( ! isempty( Line ) )
	    {
		if( sscanf( Line, "%a[^ ] %a[^ ] %a[^ ]\n", &Channel1, &Channel2, &Dummy ) == 3 )
		{
		    if( sscanf( Channel1, "%a[^-] -%i-%i-%i-%i", &SourceName1, &Nid1, &Tid1, &Sid1, &Rid1 ) >= 4 )
		    {
			SourceId1 = cSource::FromString( SourceName1 );
			tChannelID ChannelId1 = tChannelID( SourceId1, Nid1, Tid1, Sid1, Rid1 );
			cChannel *EpgChannel = Channels.GetByChannelID( ChannelId1, true );
			if( sscanf( Channel2, "%a[^-] -%i-%i-%i-%i", &SourceName2, &Nid2, &Tid2, &Sid2, &Rid2 ) >= 4 )
			{
			    SourceId2 = cSource::FromString( SourceName2 );
		    	    tChannelID ChannelId2 = tChannelID( SourceId2, Nid2, Tid2, Sid2, Rid2 );
			    cChannel *EpgEquivChannel = Channels.GetByChannelID( ChannelId2, true );
			    if( EpgChannel && EpgEquivChannel )
			    {
				snprintf( EpgEquivChannels[NumEpgEquivChannels][0], sizeof( EpgEquivChannels[NumEpgEquivChannels][0] ), "%s-%i-%i-%i-%i %s", SourceName1, EpgChannel->Nid(), EpgChannel->Tid(), EpgChannel->Sid(), EpgChannel->Rid(), EpgChannel->Name());
				snprintf( EpgEquivChannels[NumEpgEquivChannels][1], sizeof( EpgEquivChannels[NumEpgEquivChannels][0] ), "%s-%i-%i-%i-%i %s", SourceName1, EpgEquivChannel->Nid(), EpgEquivChannel->Tid(), EpgEquivChannel->Sid(), EpgEquivChannel->Rid(), EpgEquivChannel->Name());
				//fprintf( stderr, "%s-%i-%i-%i-%i %s\n", SourceName1, EpgEquivChannel->Nid(), EpgEquivChannel->Tid(), EpgEquivChannel->Sid(), EpgEquivChannel->Rid(), EpgEquivChannel->Name());
				NumEpgEquivChannels ++;
			    }
			    else
			    {
				fprintf( stderr, "error in loadepg.equiv for: %s\n", Line );
			    }
			}
		    }
		}
	    }
	}
	fclose( File );
    }
    else
    {
	fprintf( stderr, "open file equivalences \"%s\" failed\n", Filename );
	return NULL;
    }
  }
  return new cLoadepgOsd();
}

cMenuSetupPage *cPluginLoadepg::SetupMenu(void)
{
  // Return a setup menu in case the plugin supports one.
  return new cLoadepgSetup();
}

bool cPluginLoadepg::SetupParse(const char *Name, const char *Value)
{
  // Parse your own setup parameters and store their values.
  if( !strcasecmp( Name, "UseFileEquivalences" ) )
  {
    LoadepgConfig.UseFileEquivalences = atoi( Value );
  }
  else if( !strcasecmp( Name, "DeviceNumber" ) )
  {
    LoadepgConfig.DeviceNumber = atoi( Value );
  }
  else
  {
    return false;
  }
  return true;
}

bool cPluginLoadepg::Service(const char *Id, void *Data)
{
  // Handle custom service requests from other plugins
  return false;
}

const char **cPluginLoadepg::SVDRPHelpPages(void)
{
  // Return help text for SVDRP commands this plugin implements
  static const char *HelpPages[] = {
    "LOAD <PROVIDER_NUMBER>\n"
    "    Load epg data.",
    NULL
  };
  return HelpPages;
}

cString cPluginLoadepg::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
{
  // Process SVDRP commands this plugin implements
  if( strcasecmp( Command, "LOAD" ) == 0 )
  {
    if( Status.Plugin == STATUS_NULL )
    {
      Status.Svdrp = atoi( Option );
      if( Status.Svdrp >= 1 && Status.Svdrp <= EPG_PROVIDERS )
      {
        cRemote::CallPlugin( "loadepg" );
	ReplyCode = 250;
        return cString( tr( "Wait load epg data" ) );
      }
      else
      {
        ReplyCode = 500;
        return cString( tr( "Invalid provider number" ) );
      }
    }
    else
    {
      ReplyCode = 500;
      return cString( tr( "The plugin is already active" ) );
    }
  }
  return NULL;
}


VDRPLUGINCREATOR(cPluginLoadepg); // Don't touch this!
