/*
 * See the README file for copyright information and how to reach the author.
 *
 */

#include "common.h"

//#define STOP_ON_CHANGE
#define MANAGE_BUFFER

class cRMDeviceItem
{
    public:
        eSetupItem item;
        const char* name;
};

const char* kPvrSource="C";
const int kPvrCa=0xA2;
const int kPvrCaAlternates[]={ 0xA0, 0xA1 };

// try to keep at least this amount of data in tsBuffer
#define TS_BUFFER_DEFAULT_FILL_SIZE (250*1024)

// when switching channel read at least this amount of data before
// start streaming to avoid running out of data
#define TS_BUFFER_STARTUP_FILL_SIZE (-30*1024 + TS_BUFFER_DEFAULT_FILL_SIZE)

// total size of buffer for TS packets
#define TS_BUFFER_SIZE (KILOBYTE(1600))

// when running out of data read at least this amount of data before
// start streaming again
#define TS_BUFFER_DATA_LOW_FILL_SIZE (32*1024)

#define INITIAL_TS_PACKET_LIMIT (30)

/* values that are stored in the config file */
static const eSetupItem configItems[]=
{
    eSetupVolume,
    eSetupAudioBitrate,
    eSetupBrightness,
    eSetupContrast,
    eSetupHue,
    eSetupSaturation,
    eSetupVideoBitrateAverage,
    eSetupVideoBitratePeak,
    eSetupVbr,
    eSetupVideoInput,
    eSetupVideoStandard,
    eSetupResolutionHorizontal,
    eSetupResolutionVertical
};

cRMDevice::cRMDevice(int _index)
{
    deviceIndex=_index;

    tsBuffer=new cRingBufferLinear(TS_BUFFER_SIZE, TS_SIZE, false, "PVRUSB2TS");
    tsBuffer->SetTimeouts(100, 100);
    delivered=false;

    deviceClosedTime.tv_sec=0;
    deviceClosedTime.tv_usec=0;

    dvrClosedTime.tv_sec=0;
    dvrClosedTime.tv_usec=0;

    videoDeviceFD=-1;
    scanner=NULL;
    reader=NULL;
    dvrOpen=false;

    tsPacketLimit=INITIAL_TS_PACKET_LIMIT;
}

cRMDevice::~cRMDevice()
{
    StopChannelScan();
    StopReader();

    CloseVideoDevice();

    tsBufferMutex.Lock();
    delete tsBuffer;
    tsBuffer=NULL;
    tsBufferMutex.Unlock();

    if(PvrUsb2) {
        PvrUsb2->DeviceDestroyed(this);
    }
}

bool cRMDevice::Setup(cRMSetup* _setup)
{
    for(int i=0; i<ENTRIES(configItems, eSetupItem); i++) {
        eSetupItem item=configItems[i];
        SetValue(item, _setup->Value(item, MinValue(item), MaxValue(item)));
    }
    return true;
}

bool cRMDevice::OpenDvr()
{
    cPvrUsb2Plugin::LogDebug("OpenDvr");

    StopChannelScan();
    CheckReader();

    dvrOpen=true;
    dvrJustOpened=true;

    ClearTSBuffer();
#ifdef MANAGE_BUFFER    
    if(!Wait4TSBuffer2Fill(TS_BUFFER_STARTUP_FILL_SIZE)) {
        cPvrUsb2Plugin::LogError("Not enough data in TS Buffer. Proceed anyway.");
    }
#endif

    return true;
}

void cRMDevice::CloseDvr()
{
    if(!PvrUsb2) {
        return;
    }
    cPvrUsb2Plugin::LogDebug("CloseDvr");
    gettimeofday(&dvrClosedTime, NULL);
#ifdef STOP_ON_CHANGE    
    StopReader();
#endif
    dvrOpen=false;
}

bool cRMDevice::IsReady2Close()
{
    if(IsDvrOpen()) {
        return false;
    }
    timeval time;
    gettimeofday(&time, NULL);
    timeval timeLimit=dvrClosedTime;
    // wait at least 3 second(s) after closing of dvr before closing
    // video device
    timeLimit.tv_sec+=3;
    if(timercmp(&timeLimit, &time, <)) {
        return true;
    }
    return false;
}

bool cRMDevice::OpenVideoDevice()
{
    if(VideoDeviceFD()>=0) {
        return true;
    }

    if(!VideoDeviceName()) {
        cPvrUsb2Plugin::LogError("No video device name set.");
        return false;
    }
    cPvrUsb2Plugin::LogDebug("Open video device: %s.", VideoDeviceName());

    Setup(&PvrUsb2->setup);

    // wait at least a second between two openings of /dev/video
    timeval time;
    gettimeofday(&time, NULL);
    timeval timeLimit=deviceClosedTime;
    timeLimit.tv_sec+=1;
    if(timercmp(&timeLimit, &time, >)) {
        cCondWait::SleepMs(1000);
    }

    videoDeviceFD=open(VideoDeviceName(), O_RDONLY);
    if(VideoDeviceFD()<0) {
        cPvrUsb2Plugin::LogError("Unable to open video device: '%s'.",
                VideoDeviceName());
        return false;
    }

    tsPacketCounter=0;

    return true;
}

bool cRMDevice::CloseVideoDevice()
{
    if(videoDeviceFD>=0) {
//        cPvrUsb2Plugin::LogDebug("Close video device: %s.", VideoDeviceName());

        gettimeofday(&deviceClosedTime, NULL);
        close(videoDeviceFD);
        videoDeviceFD=-1;

        ClearTSBuffer();
    }
    return true;
}

int cRMDevice::PutTSData(unsigned char* _data, int _count)
{
    cMutexLock mutexLock(&tsBufferMutex);
    if(!tsBuffer) {
        cPvrUsb2Plugin::LogError("Unable to put TS data into ringbuffer. No buffer.");
        return 0;
    }
    if(tsBuffer->Free()<_count) {
        if(dvrOpen) {
            cPvrUsb2Plugin::LogError("Unable to put TS data into ringbuffer. Buffer full.");
        }
        return 0;
    }
    return tsBuffer->Put(_data, _count);
}

void cRMDevice::StopReader()
{
    delete reader;
    reader=NULL;
}

bool cRMDevice::CheckReader()
{
    // is reader thread already/still running?
    if(reader && !reader->Active()) {
        StopReader();
    }
    if(!reader) {
        cPvrUsb2Plugin::LogDebug("Create reader.");
        tsBuffer->Clear();
        reader=new cRMReader(this);
        logCounter=0;
    }
    return true;
}

bool cRMDevice::Wait4TSBuffer2Fill(int _fillSize)
{
    for(int retries=0;
            tsBuffer->Available()<_fillSize; retries++)
    {
        if(retries>100) {
            return false;
        }
        cCondWait::SleepMs(20);
    }
    return true;
}

void cRMDevice::LogTSBufferState()
{
    logCounter++;
    int available=tsBuffer->Available();
    if((logCounter%5000)==0 && 
            (available<TS_BUFFER_DEFAULT_FILL_SIZE/2 ||
            available>TS_BUFFER_DEFAULT_FILL_SIZE*2))
    {
        cPvrUsb2Plugin::LogDebug("TS available: %d limit: %d",
                available, tsPacketLimit);
    }
}

void cRMDevice::ClearTSBuffer()
{
    tsPacketCounter=0;
    tsBufferMutex.Lock();
    tsBuffer->Clear();
    tsBufferMutex.Unlock();
}

/* make sure you have the lock for tsBuffer before calling this method */
void cRMDevice::CleanupTSBuffer()
{
    // clean up data delivered in last call to GetTSPacket()
    if(delivered) {
        tsBuffer->Del(TS_SIZE); 
        delivered=false;
    }
}

bool cRMDevice::CheckTSBuffer(uchar* _data, int _count)
{
    if(_data[0]!=TS_SYNC_BYTE) {
        int count=_count;
        for (int i=1; i<count; i++) {
            if(_data[i]==TS_SYNC_BYTE) {
                count=i;
                break;
            }
        }
        tsBufferMutex.Lock();
        tsBuffer->Del(count);
        tsBufferMutex.Unlock();

        cPvrUsb2Plugin::LogError("Skipped %d bytes to sync to TS stream.");
        return false;
    }
    return true;
}

/* GetTSPacket seems to be called as fast as possible - this is faster
 * then we get data. We can reduce stuttering problems by including
 * occasional waits to keep the TS packet output in sync with the MPEG reader.
 * Check if we should wait now.
 */
bool cRMDevice::Have2Wait()
{
    tsPacketLimit=tsBuffer->Available()*200/TS_BUFFER_SIZE;
    if(tsPacketCounter>tsPacketLimit) {
        tsPacketCounter=0;
        return true;
    }
    return false;
}

bool cRMDevice::GetTSPacket(uchar*& _data)
{
    if(!tsBuffer) {
        cPvrUsb2Plugin::LogError("Unable to get TS packet. No buffer.");
        return false;
    }

#ifdef MANAGE_BUFFER
    LogTSBufferState();
#endif

    int count=0;
    int retries=0;
    uchar* data=0;

    for(retries=0; count==0; retries++) {
        if(retries>10) {
            cPvrUsb2Plugin::LogError("Unable to get TS packet. Giving up after %d retries.", retries);
            return false;
        }

        // keep lock small but don't lock too often
        tsBufferMutex.Lock();
        if(dvrJustOpened) {
            dvrJustOpened=false;
            int dropCount=0;
            while(tsBuffer->Available()>TS_BUFFER_DEFAULT_FILL_SIZE) {
                int dummy=TS_SIZE;
                tsBuffer->Get(dummy); 
                tsBuffer->Del(TS_SIZE); 
                dropCount++;
            }
            lastAvailable=tsBuffer->Available();
            cPvrUsb2Plugin::LogDebug(
                    "Dropped %d TS packets to sync buffer(%d).",
                    dropCount, tsBuffer->Available());
        } else {
            CleanupTSBuffer();
        }

#ifdef MANAGE_BUFFER
        bool waitNow=Have2Wait();
#endif

        data=tsBuffer->Get(count);
        tsBufferMutex.Unlock();
   

#ifdef MANAGE_BUFFER
        if(waitNow) {
            cCondWait::SleepMs(10);
        }
#endif

        if(!data || (count<TS_SIZE)) {
            CheckReader();

            if(!retries) {
                tsPacketCounter=0;
            }

#ifdef MANAGE_BUFFER
            // wait for buffer to fill
            cPvrUsb2Plugin::LogDebug("TS buffer empty. Waiting ... (%d, %d)", retries, tsPacketLimit);
            if(!Wait4TSBuffer2Fill(TS_BUFFER_DATA_LOW_FILL_SIZE)) {
                cPvrUsb2Plugin::LogDebug("TS buffer still empty");
            }
#endif

            count=0;

#ifndef MANAGE_BUFFER
            return true;
#endif
        }
    }

    if(!data) {
        cPvrUsb2Plugin::LogError("No TS data.");
        return false;
    } else if(!CheckTSBuffer(data, count)) {
        return GetTSPacket(_data);
    }

    tsPacketCounter++;

    _data=data;
    delivered=true;
    return true;
}

bool cRMDevice::Tune(int _frequency)
{
    return SetValue(eSetupFrequency, _frequency);
}
 
int cRMDevice::Frequency() const
{
    return CurrentValue(eSetupFrequency);
}
 
bool cRMDevice::SetPid(cPidHandle* _handle, int _type, bool _on)
{
    return true;
}
 
int cRMDevice::ProvidesCa(const cChannel* _channel) const
{
//   cPvrUsb2Plugin::LogDebug("ProvidesCa: %02x", _channel->Ca());
    return IsPvrUsb2Channel(_channel);
}

bool cRMDevice::ProvidesSource(int _source) const
{
//    cPvrUsb2Plugin::LogDebug("ProvidesSource: %d %d", _source, cSource::FromString(kPvrSource));
    return _source==cSource::FromString(kPvrSource);
}

bool cRMDevice::ProvidesTransponder(const cChannel* _channel) const
{
//    cPvrUsb2Plugin::LogDebug("ProvidesTransponder");
    return false;
}

bool cRMDevice::ProvidesChannel(const cChannel* _channel,
                int _priority, bool* _needsDetachReceivers) const
{
//    cPvrUsb2Plugin::LogDebug("ProvidesChannel: %d", _channel->Frequency());

    bool result=false;
    bool hasPriority=_priority<0 || _priority>Priority();
    bool needsDetachReceivers=false;

    if(ProvidesSource(_channel->Source()) && ProvidesCa(_channel)) {
        result=hasPriority;
        if(_priority>=0 && Receiving(true)) {
            if(Frequency()==(_channel->Frequency()+_channel->Srate())) {
                result=true;
            } else {
                needsDetachReceivers=true;
            }
        }
    }
    if(_needsDetachReceivers) {
        *_needsDetachReceivers=needsDetachReceivers;
    }
    return result;
}

bool cRMDevice::SetChannelDevice(const cChannel* _channel,
                bool _liveView)
{
    cPvrUsb2Plugin::LogDebug("SetChannelDevice: frequency: %d coderate: %d offset: %d",
            _channel->Frequency(), _channel->CoderateH(), _channel->Srate());

    if(_channel->Frequency()>10000) {
        SetValue(eSetupVideoInput, "television");
        return Tune(_channel->Frequency()+_channel->Srate()); 
    } else if(VideoDevice()==_channel->Frequency()) {
        SetValue(eSetupVideoInput, "s-video");
    }
    return false;
}

bool cRMDevice::IsPvrUsb2Channel(const cChannel* _channel)
{
    for(int i=0; i<ENTRIES(kPvrCaAlternates, int); i++) {
        if(kPvrCaAlternates[i]==_channel->Ca()) {
            return true;
        }
    }
    return _channel->Ca()==kPvrCa;
}

cRMChannel* cRMDevice::Vdr2PvrChannel(cChannel* _channel)
{
    cRMChannel* retVal=new cRMChannel(_channel->Frequency());
    retVal->SetName(_channel->Name());
    if(_channel->Ca()==kPvrCa) {
        retVal->SetOffset(_channel->Srate());
    }
    return retVal;
}

cChannel* cRMDevice::Pvr2VdrChannel(cRMChannel* _channel)
{
    cChannel* retVal=new cChannel();
    retVal->SetCableTransponderData(cSource::stCable,
            _channel->Frequency(), 0, _channel->Offset(), 0);
    retVal->SetId(0, 0, 1, 0);
    int cas[]={ kPvrCa, 0 };
    retVal->SetCaIds(cas);
    int aPids[]={ kAudioPid, 0 };
    int dPids[]={ 0 };
    char aLangs[][MAXLANGCODE2]={ "" };
    char dLangs[][MAXLANGCODE2]={ "" };
    retVal->SetPids(kVideoPid, 0, aPids, aLangs, dPids, dLangs, 0);
    retVal->SetName(_channel->Name(), "", "");
    return retVal;
}

void cRMDevice::Pvr2VdrChannel(cRMChannel* _channel, cChannel* _vdrChannel)
{
    _vdrChannel->SetCableTransponderData(cSource::stCable,
            _channel->Frequency(), 0, _channel->Offset(), 0);
    int cas[]={ kPvrCa, 0 };
    _vdrChannel->SetCaIds(cas);
    int aPids[]={ kAudioPid, 0 };
    int dPids[]={0};
    char aLangs[][MAXLANGCODE2]={ "" };
    char dLangs[][MAXLANGCODE2]={ "" };
    _vdrChannel->SetPids(kVideoPid, 0, aPids, aLangs, dPids, dLangs, 0);
    _vdrChannel->SetName(_channel->Name(), "", "");
}

bool cRMDevice::StartChannelScan(cRMScanChannelListener* _listener)
{
    scannerMutex.Lock();
    if(scanner) {
        delete scanner;
    }
    scanner=new cRMScanner(this, _listener);
    scannerMutex.Unlock();

    return true;
}

bool cRMDevice::StopChannelScan()
{
    scannerMutex.Lock();
    if(scanner) {
        delete scanner;
        scanner=NULL;
    }
    scannerMutex.Unlock();

    return true;
}

