/*
 * time_pts.c: Adjustable clock in PTS units
 *
 * See the main source file 'xineliboutput.c' for copyright information and
 * how to reach the author.
 *
 * $Id: time_pts.c,v 1.1 2007/01/06 03:42:17 phintuka Exp $
 *
 */

#define __STDC_FORMAT_MACROS
#define __STDC_CONSTANT_MACROS 
#include <inttypes.h>
#include <time.h>

#include <vdr/config.h>

#include "../logdefs.h"            // logging

#include "time_pts.h"


#define MAX_SCR ((int64_t)0x1ffffffffLL)

#if _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK)
#else
#  warning Posix monotonic clock not available
#endif


cTimePts::cTimePts(void)
{
  m_Paused     = false;
  m_Multiplier = 90000;
  m_Monotonic  = false;

#if _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK)
  struct timespec resolution;   

  if(clock_getres(CLOCK_MONOTONIC, &resolution)) {
    LOGERR("cTimePts: clock_getres(CLOCK_MONOTONIC) failed");
  } else {
    LOGDBG("cTimePts: clock_gettime(CLOCK_MONOTONIC): clock resolution %d us",
	   ((int)resolution.tv_nsec) / 1000);

    if( resolution.tv_sec == 0 && resolution.tv_nsec <= 1000000 ) {
      struct timespec tp;
      if(clock_gettime(CLOCK_MONOTONIC, &tp)) {
	LOGERR("cTimePts: clock_gettime(CLOCL_MONOTONIC) failed");
      } else {
	LOGDBG("cTimePts: using monotonic clock");
	m_Monotonic = true;
      }
    }
  }
#endif

  Set();
}

int64_t cTimePts::Now(void)
{
  if(m_Paused)
    return begin;

  struct timeval t;

#if _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK)
  if(m_Monotonic) {
    struct timespec tp;

    if(clock_gettime(CLOCK_MONOTONIC, &tp)) {
      LOGERR("cTimePts: clock_gettime(CLOCK_MONOTONIC) failed");
      return -1;
    }

    t.tv_sec  = tp.tv_sec;
    t.tv_usec = tp.tv_nsec/1000;

  } else if (gettimeofday(&t, NULL)) {
    LOGERR("cTimePts: gettimeofday() failed");
    return -1;
  }
#else
  if (gettimeofday(&t, NULL)) {
    LOGERR("cTimePts: gettimeofday() failed");
    return -1;
  }
#endif

  t.tv_sec -= tbegin.tv_sec;
  if(t.tv_usec < tbegin.tv_usec) {
    t.tv_sec--;
    t.tv_usec += 1000000;
  }
  t.tv_usec -= tbegin.tv_usec;

  int64_t pts = 0;
  pts += ((int64_t)t.tv_sec) * INT64_C(90000);
  pts += ((int64_t)t.tv_usec) * INT64_C(90) / INT64_C(1000);
  if(m_Multiplier != 90000)
    pts = pts * m_Multiplier / INT64_C(90000);

  return ( pts + begin ) & MAX_SCR;
}

void cTimePts::Set(int64_t Pts)
{
  begin = Pts;

#if _POSIX_TIMERS > 0 && defined(_POSIX_MONOTONIC_CLOCK)
  if(m_Monotonic) {
    struct timespec tp;

    if(!clock_gettime(CLOCK_MONOTONIC, &tp)) {
      tbegin.tv_sec  = tp.tv_sec;
      tbegin.tv_usec = tp.tv_nsec/1000;
      return;
    }

    LOGERR("cTimePts: clock_gettime(CLOCL_MONOTONIC) failed");
    m_Monotonic = false;
  }
#endif

  gettimeofday(&tbegin, NULL);
}

void cTimePts::Pause(void)
{
  Set(Now());
  m_Paused = true;
}

void cTimePts::Resume(void)
{
  if(m_Paused) {
    Set(begin);
    m_Paused = false;
  }
}

void cTimePts::TrickSpeed(int Multiplier)
{
  Set(Now());

  if(Multiplier < 0)
    m_Multiplier = 90000 * (-Multiplier);
  else if(Multiplier > 0)
    m_Multiplier = 90000 / Multiplier;
  else
    LOGERR("cTimePts::SetSpeed: Multiplier=%d", Multiplier);
}

