#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>
#include <vdr/device.h>
#include <vdr/thread.h>
#include "sync.h"
#include "configuration.h"


#define MAX_WAIT_IN_SECS 40

class cStcSynchronizer : public cSynchronizer
{
public:
    cStcSynchronizer(){};
    virtual ~cStcSynchronizer(){};
    virtual void sync(int64_t pts);
 private:
    int64_t GetSTC(bool fix);
};



int64_t cStcSynchronizer::GetSTC(bool fix)
{

  int64_t stc = -1;
  if (cDevice *pd = cDevice::PrimaryDevice())
    stc = pd->GetSTC();

  // add the 33rd bit if required
  if (fix)
  {
#if DEBUG
    fprintf(stderr, "cStcSynchronizer::sync - stc bugfix\n");
    fprintf(stderr, "cStcSynchronizer::sync - original stc=%lld\n",stc);
#endif
    stc |= (int64_t)1<<32;
  }
  return stc;
}

void cStcSynchronizer::sync(int64_t pts)
{
    active = true;

    
    bool fix = pts & (int64_t)1<<32;
    int64_t stc = GetSTC(fix);
    // no stc
    if (stc < 0)
	return;


    int64_t timeDiff = (pts-stc);
    if (pts<stc)
    {
	timeDiff += (int64_t)1<<33;
    }
    timeDiff /= 90;
 

#ifdef DEBUG
    fprintf(stderr, "cStcSynchronizer::sync\n");
    fprintf(stderr, "pts = %lld, stc = %lld\n",pts,stc);
    fprintf(stderr, "Wait time ~ %lld ms\n", timeDiff);
#endif

    

    // Sanity check so we don't wait too long 
    // sometimes FF cards alter PTS (and STC) so timeDiff can be huge
    if (timeDiff < MAX_WAIT_IN_SECS*1000)
	while (active && stc > 0 && stc < pts)
	{
	    usleep(100000);
	    int64_t newstc = GetSTC(fix);
	    //  backward jump in replay
	    if (newstc < stc)
		break;
	    stc = newstc;
	}
#ifdef DEBUG
    fprintf(stderr, "<-cStcSynchronizer::sync\n");
#endif
}

class cPtsSynchronizer : public cSynchronizer
{
public:
    cPtsSynchronizer():lastPts(0),lastTime(0),firstTime(0),firstPts(0){};
    virtual ~cPtsSynchronizer(){};
    virtual void sync(int64_t pts);
private:
    int64_t lastPts;
    int64_t lastTime;
    int64_t firstTime;
    int64_t firstPts;
};


void cPtsSynchronizer::sync(int64_t pts)
{
    int64_t now = msectime();

    if (firstPts == 0)
    {
	firstPts = pts;
	firstTime = now;
    }

    if (lastPts != 0)
    {
	// handle wrap
	if (pts<lastPts)
	{
	    firstPts = pts;
	    firstTime = now;
	}

	int64_t ptsDiff = pts-firstPts;
	int64_t showTime = firstTime + ptsDiff/90;
	int64_t diff = showTime-now;

#ifdef DEBUG
	fprintf(stderr, "->cPtsSynchronizer::sync\n");
	fprintf(stderr, "now-firstTime=%lld\n",(now-firstTime));
	fprintf(stderr, "pts-firstPts=%lld\n",(pts-firstPts));
	fprintf(stderr, "diff between time and pts = %lld\n",(pts-firstPts)/90-(now-firstTime));
	fprintf(stderr, "pts = %lld, lastPts = %lld\n",pts,lastPts);
        fprintf(stderr, "pts-lastPts in ms = %lld\n",(pts-lastPts)/90);
	fprintf(stderr, "now-lastTime = %lld\n", now-lastTime);
	fprintf(stderr, "configured delay = %d\n", gSubtitlesConfiguration.delay);
	fprintf(stderr, "old wait time=%lld\n", (pts-lastPts)/90-now+lastTime);
	fprintf(stderr, "Wait time ~ %lld ms\n", diff);
#endif
	showTime += gSubtitlesConfiguration.delay*1000;
	// sanity check
	if (diff < (MAX_WAIT_IN_SECS*1000))
	    while (active  && (now < showTime) )
	    {
		usleep(100000);
		now = msectime();
	    }
	else
	{
	    // probably a forward jump
	    firstPts = pts;
	    firstTime = now;
	}
    }
    lastPts = pts;
    lastTime = now;
#ifdef DEBUG
    fprintf(stderr, "<-cPtsSynchronizer::sync\n\n");
#endif
}

cSynchronizer* GetSynchronizer()
{
    if (gSubtitlesConfiguration.sync)
	return new cStcSynchronizer();
    else
	return new cPtsSynchronizer();
}


uint64_t msectime()
{

    struct timeval tv = {0,0};

    gettimeofday(&tv,0);
    
    uint64_t msec = ((int64_t)tv.tv_sec)*1000;
    msec += tv.tv_usec/1000;
    
    return msec;

}


