/*
 * SI section filter to gather subtitles information. 
 * cSubFilter has been derived from VDR's cPatFilter
 *
 * $Id: subfilter.c,v 1.3 2006/01/21 12:51:50 opj Exp $
 */

#include "receiver.h"
#include "subfilter.h"
#include "replay.h"
#include "configuration.h"
#include <libsi/section.h>
#include <libsi/descriptor.h>
#include <vdr/device.h>
#include <vdr/thread.h>
#include <vdr/status.h>
#include <vdr/i18n.h>
#include <vdr/dvbsub.h>
#include <vdr/rcontroller.h>
#include <vdr/channels.h>
#include <vdr/tools.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include "menu.h"


class cOsdObject;

#define PMT_SCAN_TIMEOUT  10 // seconds

#define NO_SUBTITLES 0xFFFF 
#define USE_PREFERRED_LANGUAGES -1

class tSubtitleStream : public cListObject
{
 public:
    int languageIndex;
    int  type;
    int  pid;
    int  compositionPage;
    int  ancillaryPage;
    tSubtitleStream(int LanguageIndex, int Type, int Pid, int CompositionPage, int AncillaryPage);
    tSubtitleStream() { languageIndex = -1;}

    static tSubtitleStream FromString(const char *s);
    const char* ToString(void);

    bool operator==(const tSubtitleStream& right) const; 
};

tSubtitleStream::tSubtitleStream(int LanguageIndex, int Pid, int Type, int CompositionPage, int AncillaryPage)
    :languageIndex(LanguageIndex),type(Type), pid(Pid), compositionPage(CompositionPage), ancillaryPage(AncillaryPage)
{
}


tSubtitleStream tSubtitleStream::FromString(const char *s)
{
    char* langbuf = NULL;

    int pid;
    int type;
    int compositionPage;
    int ancillaryPage;

    int fields = sscanf(s, "%a[^=]=%d+%d+%d+%d", &langbuf, &pid, &compositionPage, &ancillaryPage, &type);

    int languageIndex = -1;
    if (langbuf)
	languageIndex = I18nLanguageIndex(langbuf);
    free(langbuf);

    if (fields == 5)
    {
	return tSubtitleStream(languageIndex, pid, type, compositionPage, ancillaryPage);
    }
    else
    {
	return tSubtitleStream();
    }
}

const char* tSubtitleStream::ToString(void)
{
    static char buffer[256];
    
    const char* langCode = I18nLanguageCode(languageIndex);
    
    if (!langCode)
	return NULL;

    char lang[4];
    lang[0] =  langCode[0];
    lang[1] =  langCode[1];
    lang[2] =  langCode[2];
    lang[3] = 0;


    snprintf(buffer, sizeof(buffer), "%s=%d+%d+%d+%d", lang, pid, compositionPage, ancillaryPage, type);

    return buffer;

}

bool tSubtitleStream::operator==(const tSubtitleStream& right) const
{
    return languageIndex==right.languageIndex && pid == right.pid &&
	type == right.type && compositionPage == right.compositionPage &&
	ancillaryPage == right.ancillaryPage;
}



// ---- cSubtitleChannel

class cSubtitleChannel : public cListObject
{
 private:
    tChannelID channelId;
    cList<tSubtitleStream> streams;
    int userLanguage;
    static char* buffer;
 public:
    cSubtitleChannel(){channelId = tChannelID::InvalidID;}
    cSubtitleChannel(tChannelID ChannelId);

    // Parse from String
    bool Parse(const char *s);
    const char* ToText();

    bool Save(FILE* file);

    // Adds a subtitle stream
    void AddStream(tSubtitleStream* Stream);

    //Returns true if Primary Language was available
    const tSubtitleStream* GetPrimary() const;

    //Returns true if Secondary Language was available
    const tSubtitleStream* GetSecondary() const;

    //Returns true if User selected language was available
    const tSubtitleStream* GetUser(void) const;

    // Returns true if requested language was available
    const tSubtitleStream* GetByLanguage(int Language) const;

    tChannelID GetId() const {return channelId;} 

    int GetUserLanguage() const {return userLanguage;} 
    void SetUserLanguage(int Language){userLanguage = Language;}

};

char* cSubtitleChannel::buffer = NULL;

cSubtitleChannel::cSubtitleChannel(tChannelID ChannelId)
{
    channelId = ChannelId;
    userLanguage = USE_PREFERRED_LANGUAGES;
}

const char* cSubtitleChannel::ToText()
{
    if (!channelId.Valid() || streams.Count() == 0)
	return NULL;
    
    free(buffer);

    asprintf(&buffer, "%s:%d:", *channelId.ToString(), userLanguage);
    bool found = false;
    bool first = true;
    for (tSubtitleStream* stream = streams.First(); stream; stream = streams.Next(stream))
    {
	if (stream->languageIndex != -1)
	{
	    found = true;
	    char* buffer2 = NULL;
	    asprintf(&buffer2, first?"%s%s":"%s,%s",buffer,stream->ToString());
	    free(buffer);
	    buffer = buffer2;
	    first = false;
	}
    }
    if (!found)
      return NULL;
    
    return buffer;
}

bool cSubtitleChannel::Save(FILE* file)
{
    const char* string = ToText();
    if (string)
	fprintf(file, "%s\n", string);
    return true;
}


bool cSubtitleChannel::Parse(const char *s)
{
    char* cidbuf = NULL;
    char* streamsbuf = NULL;

    int fields = sscanf(s, "%a[^:]:%d:%a[^\n]", &cidbuf, &userLanguage, &streamsbuf);

    bool ret = false;

    if (fields == 3)
    {
	channelId = tChannelID::FromString(cidbuf);
	if (!channelId.Valid())
	{
	    ret = false;
	}
	else
	{
	    char *streamstr, *tok;
	    char *p = streamsbuf;
	    while ((streamstr = strtok_r(p, ",", &tok)) != NULL)
	    {
		tSubtitleStream stream = tSubtitleStream::FromString(streamstr);
		if (stream.languageIndex != -1)
		{
		    tSubtitleStream* newStream = new tSubtitleStream(stream);
		    AddStream(newStream);
		}
		p = NULL;
	    }
	    ret = (streams.Count() > 0);
	}
    }
    else
    {
	ret = false;
    }

    free(cidbuf);
    free(streamsbuf);

    return ret;
}

void cSubtitleChannel::AddStream(tSubtitleStream* Stream)
{

    streams.Add(Stream);
}

const tSubtitleStream* cSubtitleChannel::GetPrimary() const
{
    return GetByLanguage(gSubtitlesConfiguration.language);
}

const tSubtitleStream* cSubtitleChannel::GetSecondary() const
{
    return GetByLanguage(gSubtitlesConfiguration.language2);
}

const tSubtitleStream* cSubtitleChannel::GetUser() const
{
    return GetByLanguage(userLanguage);
}


const tSubtitleStream* cSubtitleChannel::GetByLanguage(int Language) const
{

    if (Language < 0 || Language > I18nNumLanguages)
	return false;

    for (tSubtitleStream* stream = streams.First(); stream; stream = streams.Next(stream))
    {

	if (Language == stream->languageIndex)
	{ 
	    return stream;
	}
    }
    return NULL;
}


using namespace RecordingPatch;

// ---- cSubtitlesChangedHandler
class cSubtitlesChangedHandler : public cStatus
{
 private:
    tChannelID currentChannelId;

    cSubtitlesReceiver *selectedReceiver;
    cSubtitlesReceiver *primaryReceiver;
    cSubtitlesReceiver *secondaryReceiver;
    cDvbSubtitlesReplay *primaryReplay;
    cDvbSubtitlesReplay *secondaryReplay;
    tSubtitleStream currentPrimary;
    tSubtitleStream currentSecondary;
    tSubtitleStream currentSelected;
    cDevice *device;
    cMutex mutex;
    void HandleReceiver(cSubtitlesReceiver*& Receiver, int Priority, 
			const cSubtitleChannel* newChannel, 
			int Language, tSubtitleStream& currentStream);
 protected:
#if VDRVERSNUM && VDRVERSNUM < 10338
    virtual void Replaying(const cControl *Control, const char *Name);
#else
    virtual void Replaying(const cControl *Control, const char *Name,
			   const char *FileName, bool On);
#endif

 public:
    cSubtitlesChangedHandler(cDevice* device=NULL);
    ~cSubtitlesChangedHandler();
    void SubtitlingUpdate(const cSubtitleChannel* newChannel);
    void ChannelChange(cDevice* device, const cSubtitleChannel* newChannel);

};

cSubtitlesChangedHandler::cSubtitlesChangedHandler(cDevice* Device)
{
    selectedReceiver = NULL;
    primaryReceiver = NULL;
    secondaryReceiver = NULL;
    primaryReplay = NULL;
    secondaryReplay = NULL;
    device = Device;
}

cSubtitlesChangedHandler::~cSubtitlesChangedHandler()
{
    delete primaryReceiver;
    delete secondaryReceiver;
    delete selectedReceiver;
    delete primaryReplay;
    delete secondaryReplay;
}

#if VDRVERSNUM && VDRVERSNUM < 10338
void cSubtitlesChangedHandler::Replaying(const cControl *Control, const char *Name)
#else
void cSubtitlesChangedHandler::Replaying(const cControl *Control, const char *Name, const char *FileName, bool On)
#endif
{
    cMutexLock lock(&mutex);
#if VDRVERSNUM && VDRVERSNUM < 10338
    if (Name)
#else
    if (On)
#endif
    {
	// replaying starts
	DELETENULL(primaryReceiver);
	DELETENULL(secondaryReceiver);
	DELETENULL(selectedReceiver);

	DELETENULL(primaryReplay);
	DELETENULL(secondaryReplay);
	currentPrimary = tSubtitleStream();
	currentSecondary = tSubtitleStream();

	primaryReplay = new cDvbSubtitlesReplay(1000,0x28);
	secondaryReplay = new cDvbSubtitlesReplay(900,0x29);
    }
    else
    {
	// replaying stops
	DELETENULL(primaryReplay);
	DELETENULL(secondaryReplay);
    }

}

void cSubtitlesChangedHandler::HandleReceiver(cSubtitlesReceiver*& Receiver, int Priority, const cSubtitleChannel* newChannel, int Language, tSubtitleStream& currentStream)
{
    const tSubtitleStream* newStream;
    if ((newStream=newChannel->GetByLanguage(Language)) != NULL)
    {
	if (!(*newStream == currentStream))
	{
	    if (Receiver != NULL)
	    {
		DELETENULL(Receiver);
		
	    }
	    Receiver = new cSubtitlesReceiver( newStream->pid, Priority );
	    Receiver->Start();
	    device->AttachReceiver(Receiver);
	    currentStream = *newStream;
	}
    }
    else
    {
	if (Receiver != NULL)
	{
	    DELETENULL(Receiver);
	    currentStream = tSubtitleStream();
	}
    }
}


void cSubtitlesChangedHandler::SubtitlingUpdate(const cSubtitleChannel* newChannel)
{    

    if (!newChannel || primaryReplay || secondaryReplay)
	return;

    cMutexLock lock(&mutex);

    if (device != NULL && newChannel->GetId()==currentChannelId)
    {
	if (newChannel->GetUserLanguage() == USE_PREFERRED_LANGUAGES)
	{
	    HandleReceiver(primaryReceiver, 1000, newChannel, gSubtitlesConfiguration.language,
			   currentPrimary);
	    HandleReceiver(secondaryReceiver,900, newChannel,gSubtitlesConfiguration.language2,
			   currentSecondary);
	    DELETENULL(selectedReceiver);currentSelected = tSubtitleStream();
	}
	else if (newChannel->GetUserLanguage() == NO_SUBTITLES)
	{
	    DELETENULL(primaryReceiver);currentPrimary = tSubtitleStream();
	    DELETENULL(secondaryReceiver);currentSecondary = tSubtitleStream();
	    DELETENULL(selectedReceiver);currentSelected = tSubtitleStream();
	}
	else
	{
	    DELETENULL(primaryReceiver);currentPrimary = tSubtitleStream();
	    DELETENULL(secondaryReceiver);currentSecondary = tSubtitleStream();
	    HandleReceiver(selectedReceiver, 1100, newChannel, newChannel->GetUserLanguage(), currentSelected);
	}
    }
}

void cSubtitlesChangedHandler::ChannelChange(cDevice* Device, const cSubtitleChannel* newChannel)
{

    cMutexLock lock(&mutex);


    // if we're replaying
    if (primaryReplay || secondaryReplay)
	return;

    DELETENULL(selectedReceiver);
    DELETENULL(primaryReceiver);
    DELETENULL(secondaryReceiver);
    currentPrimary = tSubtitleStream();
    currentSecondary = tSubtitleStream();
    currentSelected = tSubtitleStream();

    if (!newChannel || !Device)
    {
      currentChannelId = tChannelID::InvalidID;
      return;
    }

    currentChannelId = newChannel->GetId();
    device = Device;
    SubtitlingUpdate(newChannel);

}

static cSubtitlesChangedHandler SubHandler;

// cSubtitleChannels
class cSubtitleChannels : public cConfig<cSubtitleChannel>,
			  public cRwLock, 
			  public iPidQuery
{
 private:
    
 public:
    cSubtitleChannels();
    virtual ~cSubtitleChannels() {};
    void AddUpdate(cSubtitleChannel* Channel);
    const cSubtitleChannel* GetById(tChannelID ChannelId) const;
    bool Has(tChannelID ChannelId) const;
    void SetUserLanguage(tChannelID ChannelId, int Language);


    // iPidQuery
    virtual int GetPidByChannel( int DevNr, const cChannel* Channel, int Language );
};

int cSubtitleChannels::GetPidByChannel( int DevNr, const cChannel* Channel, int Language )
{

    if (  !gSubtitlesConfiguration.recording || Channel == NULL)
	return 0;

    time_t start = time(NULL);

    // wait max. 2 seconds for the SI information
    while (!Has(Channel->GetChannelID()) && (time(NULL)-start)<2)
    {
	usleep(10000);
    }

    const cSubtitleChannel* channel = GetById(Channel->GetChannelID());

    if (!channel)
    {  
        return 0;
    }

    const tSubtitleStream* stream;

    if ((Language == 1 && ((stream=channel->GetPrimary())!=NULL)) || 
 	(Language == 2 && ((stream=channel->GetSecondary())!=NULL)))
    {
	return stream->pid;
    }
    else
    {
	return 0;
    }
}


cSubtitleChannels::cSubtitleChannels()
{
    DvbSubtitlesRecording.Subscribe( this );
}


void cSubtitleChannels::AddUpdate(cSubtitleChannel* Channel)
{

    for (cSubtitleChannel* channel = First(); channel; channel = Next(channel))
    {
	if (channel->GetId() == Channel->GetId())
	{
	    int userLanguage = channel->GetUserLanguage();
	    Del(channel);
	    Channel->SetUserLanguage(userLanguage);
	    Add(Channel);
	    SubHandler.SubtitlingUpdate(Channel);
	    return;
	}
    }
    
    Add(Channel);
    SubHandler.SubtitlingUpdate(Channel);
}

const cSubtitleChannel* cSubtitleChannels::GetById(tChannelID ChannelId) const
{

    for (cSubtitleChannel* channel = First(); channel; channel = Next(channel))
    {
	if (channel->GetId() == ChannelId)
	{
	    return channel;
	}
    };
    return NULL;
}

bool cSubtitleChannels::Has(tChannelID ChannelId) const
{
    return (GetById(ChannelId) != 0);
}


void cSubtitleChannels::SetUserLanguage(tChannelID ChannelId, int Language)
{
    for (cSubtitleChannel* channel = First(); channel; channel = Next(channel))
    {
	if (channel->GetId() == ChannelId)
	{
	    if (channel->GetUserLanguage() != Language)
	    {
		channel->SetUserLanguage(Language);
		SubHandler.SubtitlingUpdate(channel);
	    }
	    return;
	}
    };

    cSubtitleChannel* channel= new cSubtitleChannel(ChannelId);
    channel->SetUserLanguage(Language);
    Add(channel);
}

static cSubtitleChannels SubChannels;

void LoadChannels(const char* FileName)
{
    SubChannels.Load(FileName);
}

void SaveChannels(void)
{
    SubChannels.Save();
}


// cSubtitlesStatus

class cSubtitlesStatus : public cStatus
{
 protected:
    virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
};
void cSubtitlesStatus::ChannelSwitch(const cDevice *Device, int ChannelNumber)
{
    if (Device->IsPrimaryDevice() && ChannelNumber && 
	cDevice::PrimaryDevice()->CurrentChannel()==ChannelNumber)
    {
	cChannel *Channel = Channels.GetByNumber(ChannelNumber);
	cDevice* device = cDevice::PrimaryDevice()->ActualDevice();
	SubHandler.ChannelChange(device,SubChannels.GetById(Channel->GetChannelID()));
    }
}
static cSubtitlesStatus SubStatus;


// cLanguageItem

class cLanguageItem : public cOsdItem
{
 private:
    int language;
 public:
    cLanguageItem(const char *Text, int Language);
    int GetLanguage(){return language;}
};

cLanguageItem::cLanguageItem(const char *Text, int Language)
    :cOsdItem(Text), language(Language)
{
}


// cLanguageSelectionMenu

class cLanguageSelectionMenu : public cOsdMenu
{

 private:
    bool showAll;
    tChannelID channelId;

    void Setup(void);
    eOSState Select(void);
 public:
    cLanguageSelectionMenu();
    virtual eOSState ProcessKey(eKeys Key);
};

cLanguageSelectionMenu::cLanguageSelectionMenu()
    :cOsdMenu("")
{
    showAll = false;


    int currentChannel = cDevice::PrimaryDevice()->CurrentChannel();

    cChannel* channel = Channels.GetByNumber(currentChannel);

    char* titlebuf = NULL;
    asprintf(&titlebuf, "%s - %s", channel->Name(), tr("Choose Language"));
    cOsdMenu::SetTitle(titlebuf);
    free(titlebuf);
    channelId = channel->GetChannelID();

    Setup();
}

void cLanguageSelectionMenu::Setup(void)
{

    const cSubtitleChannel* schannel = SubChannels.GetById(channelId);
    
    if (!schannel) 
    {
	cSubtitleChannel* nchannel = new cSubtitleChannel(channelId);
	SubChannels.Add(nchannel);
	schannel = nchannel;
    }

    const char * const * languages = I18nLanguages();


    int userLanguage = schannel->GetUserLanguage();

    Clear();

    Add(new cLanguageItem(tr("No Subtitles"), NO_SUBTITLES), userLanguage == NO_SUBTITLES);

    Add(new cLanguageItem(tr("Automatic"), USE_PREFERRED_LANGUAGES), userLanguage == USE_PREFERRED_LANGUAGES);


    for (int i = 0; i < I18nNumLanguages; i++)
    {
	const tSubtitleStream* stream;
	stream = schannel->GetByLanguage(i);
	
	if (stream != NULL || showAll)
	{
	    Add(new cLanguageItem(languages[i],i), userLanguage == i);
	}
    
    }

    SetHelp(showAll?tr("Available"):tr("All"), NULL, NULL, NULL);
    Display();
}

eOSState cLanguageSelectionMenu::Select(void)
{

    cLanguageItem* item = (cLanguageItem*)Get(Current());
    if (item)
    {
	SubChannels.SetUserLanguage(channelId, item->GetLanguage());
    }

    return osEnd;

}

eOSState cLanguageSelectionMenu::ProcessKey(eKeys Key)
{

    eOSState state = cOsdMenu::ProcessKey(Key);

    if (state == osUnknown)
    {

	if (Key == kOk)
	{
	    state = Select();
	}
	else if (Key == kRed)
	{
	    showAll = !showAll;
	    Setup();
	}
    }

    return state;

};


cOsdObject* GetLanguageSelector()
{
    return new cLanguageSelectionMenu();
}




// ---- cSubFilter

cSubFilter::cSubFilter()
{
    pmtIndex = 0;
    pmtPid = 0;
    lastPmtScan = 0;
    numPmtEntries = 0;
    Set(0x00, 0x00);  // PAT
}

cSubFilter::~cSubFilter()
{
}


void cSubFilter::SetStatus(bool On)
{
    cFilter::SetStatus(On);
    pmtIndex = 0;
    pmtPid = 0;
    lastPmtScan = 0;
    numPmtEntries = 0;
    
}

void cSubFilter::Trigger(void)
{
    numPmtEntries = 0;
}

bool cSubFilter::PmtVersionChanged(int PmtPid, int Sid, int Version)
{
    uint64_t v = Version;
    v <<= 32;
    uint64_t id = (PmtPid | (Sid << 16)) & 0x00000000FFFFFFFFLL;
    for (int i = 0; i < numPmtEntries; i++) 
    {
	if ((pmtVersion[i] & 0x00000000FFFFFFFFLL) == id) {
	    bool Changed = (pmtVersion[i] & 0x000000FF00000000LL) != v;
	    if (Changed)
		pmtVersion[i] = id | v;
	    return Changed;
	}
    }
    if (numPmtEntries < MAXPMTENTRIES)
	pmtVersion[numPmtEntries++] = id | v;
    return true;
}
    
void cSubFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length)
{

    if (Pid == 0x00) 
    {
	if (Tid == 0x00) 
	{
	    if (pmtPid && time(NULL) - lastPmtScan > PMT_SCAN_TIMEOUT) 
	    {
		Del(pmtPid, 0x02);
		pmtPid = 0;
		pmtIndex++;
		lastPmtScan = time(NULL);
	    }
	    if (!pmtPid) 
	    {
		SI::PAT pat(Data, false);
		if (!pat.CheckCRCAndParse())
		    return;
		SI::PAT::Association assoc;
		int Index = 0;
		for (SI::Loop::Iterator it; pat.associationLoop.getNext(assoc, it); ) 
		{
		    if (!assoc.isNITPid()) 
		    {
			if (Index++ == pmtIndex) 
			{
			    pmtPid = assoc.getPid();
			    Add(pmtPid, 0x02);
			    break;
			}
		    }
		}
		if (!pmtPid)
		    pmtIndex = 0;
	    }
	}
    }
    else if (Pid == pmtPid && Tid == SI::TableIdPMT && Source() && Transponder()) 
    {


	SI::PMT pmt(Data, false);
	if (!pmt.CheckCRCAndParse())
	    return;
	if (!PmtVersionChanged(pmtPid, pmt.getTableIdExtension(), pmt.getVersionNumber())) 
	{
	    lastPmtScan = 0; // this triggers the next scan
	    return;
	}

	cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), pmt.getServiceId());
	
	if (Channel == NULL)
	    return;
	else
	{
	    SI::PMT::Stream stream;
	    cSubtitleChannel* schannel = new cSubtitleChannel(Channel->GetChannelID());
	    bool gotSubtitles = false;
	    for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); ) 
	    {
	      //		stream = pmt.streamLoop.getNext(it);
		switch (stream.getStreamType()) 
		{
		    case 5: // STREAMTYPE_13818_PRIVATE
		    case 6: // STREAMTYPE_13818_PES_PRIVATE
		    {
			SI::Descriptor *d;
			for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) 
			{
			    switch (d->getDescriptorTag()) {
				case SI::SubtitlingDescriptorTag:
				{
				    gotSubtitles = true;
				    SI::SubtitlingDescriptor *sd = (SI::SubtitlingDescriptor *)d;
				    SI::Loop::Iterator sit;
				    
				    SI::SubtitlingDescriptor::Subtitling sss;
				    while (sd->subtitlingLoop.getNext(sss, sit))
				    {
					if (sss.languageCode[0] == 0)
					    continue; // otherwise we get incomplete streams without language code
					tSubtitleStream *subStream = new tSubtitleStream(I18nLanguageIndex(sss.languageCode),stream.getPid(), sss.getSubtitlingType(),   sss.getCompositionPageId(), sss.getAncillaryPageId());
					schannel->AddStream(subStream);
				    }
				}
				break;
				default: ;
			    }
			    delete d;
			}
		    }
		    break;
		}
	    }
	    if (gotSubtitles && SubChannels.Lock(true,10)) 
	    {
		SubChannels.AddUpdate(schannel);
		SubChannels.Unlock();
	    }
	    else
	    {
		delete schannel;
	    }
	}
	lastPmtScan = 0; // this triggers the next scan
    }
}


