
#include <assert.h>
#include <stdlib.h>
#if defined(_WIN32) || defined(_WIN64)
#include <windows.h>
#else
#include <sys/times.h>
#include <sys/time.h>
#include <unistd.h>
#endif
#include <time.h>

#include "lc_clock.h"

/** converts CPU clock ticks into seconds */
static
double cputime2sec(
   clock_t               cputime             /**< clock ticks for CPU time */
   )
{
   clock_t clocks_per_second;

#if defined(_WIN32) || defined(_WIN64)
   clocks_per_second = 100;
#else
#ifndef CLK_TCK
   clocks_per_second = sysconf(_SC_CLK_TCK);
#else
   clocks_per_second = CLK_TCK;
#endif
#endif

   return (double)cputime / (double)clocks_per_second;
}

/** converts wall clock time into seconds */
static
double walltime2sec(
   long                  sec,                /**< seconds counter */
   long                  usec                /**< microseconds counter */
   )
{
   return (double)sec + 0.000001 * (double)usec;
}

/** creates a clock and initializes it */
LC_CLOCK* LC_clockCreate(void)
{
   LC_CLOCK* clck;

   clck = (LC_CLOCK*) malloc(sizeof(LC_CLOCK));

   clck->clocktype = LC_CLOCKTYPE_WALL;
   LC_clockReset(clck);

   return clck;
}

/** frees a clock */
void LC_clockFree(
   LC_CLOCK**            clck                /**< pointer to clock timer */
   )
{
   assert( clck != NULL );

   free(*clck);
   *clck = NULL;
}

/** sets the type of the clock */
void LC_clockSetType(
   LC_CLOCK*             clck,               /**< clock timer */
   LC_CLOCKTYPE          clocktype           /**< type of clock */
   )
{
   assert( clck != NULL );

   clck->clocktype = clocktype;
   LC_clockReset(clck);
}


/** completely stop the clock and reset the clock's counter to zero */
void LC_clockReset(
   LC_CLOCK*             clck                /**< clock timer */
   )
{
   assert( clck != NULL );

   switch( clck->clocktype )
   {
   case LC_CLOCKTYPE_CPU:
      clck->data.cpuclock.user = 0;
      break;
   case LC_CLOCKTYPE_WALL:
      clck->data.wallclock.sec = 0;
      clck->data.wallclock.usec = 0;
      break;
   default:
      abort();
   }
   clck->nruns = 0;
}

/** starts measurement of time in the given clock */
void LC_clockStart(
   LC_CLOCK*             clck                /**< clock timer */
   )
{
#if defined(_WIN32) || defined(_WIN64)
   FILETIME creationtime;
   FILETIME exittime;
   FILETIME kerneltime;
   FILETIME usertime;
#else
   struct timeval tp; /*lint !e86*/
   struct tms now;
#endif

   assert( clck != NULL );
   assert( clck->nruns >= 0 );

   if ( clck->nruns == 0 )
   {
      switch ( clck->clocktype )
      {
      case LC_CLOCKTYPE_CPU:
#if defined(_WIN32) || defined(_WIN64)
         GetProcessTimes(GetCurrentProcess(), &creationtime, &exittime, &kerneltime, &usertime);
         clck->data.cpuclock.user -= usertime.dwHighDateTime * 42950 + usertime.dwLowDateTime / 100000L;
#else
         (void)times(&now);
         clck->data.cpuclock.user -= now.tms_utime;
#endif
         break;

      case LC_CLOCKTYPE_WALL:
#if defined(_WIN32) || defined(_WIN64)
         clck->data.wallclock.sec -= time(NULL);
#else
         gettimeofday(&tp, NULL);
         if( tp.tv_usec > 0 )
         {
            clck->data.wallclock.sec -=  (tp.tv_sec + 1);
            clck->data.wallclock.usec += (1000000 - tp.tv_usec);
         }
         else
         {
            clck->data.wallclock.sec -= tp.tv_sec;
            clck->data.wallclock.usec -= tp.tv_usec;
         }
#endif
         break;

      default:
         abort();
      }
   }

   ++clck->nruns;
}

/** stops measurement of time in the given clock */
void LC_clockStop(
   LC_CLOCK*             clck                /**< clock timer */
   )
{
#if defined(_WIN32) || defined(_WIN64)
   FILETIME creationtime;
   FILETIME exittime;
   FILETIME kerneltime;
   FILETIME usertime;
#else
   struct timeval tp; /*lint !e86*/
   struct tms now;
#endif

   assert( clck != NULL );
   assert( clck->nruns >= 1 );

   --clck->nruns;
   if ( clck->nruns == 0 )
   {
      switch ( clck->clocktype )
      {
      case LC_CLOCKTYPE_CPU:
#if defined(_WIN32) || defined(_WIN64)
         GetProcessTimes(GetCurrentProcess(), &creationtime, &exittime, &kerneltime, &usertime);
         clck->data.cpuclock.user += usertime.dwHighDateTime * 42950 + usertime.dwLowDateTime / 100000L;
#else
         (void)times(&now);
         clck->data.cpuclock.user += now.tms_utime;
#endif
         break;

      case LC_CLOCKTYPE_WALL:
#if defined(_WIN32) || defined(_WIN64)
         clck->data.wallclock.sec += time(NULL);
#else
         gettimeofday(&tp, NULL);
         if( tp.tv_usec + clck->data.wallclock.usec > 1000000 )
         {
            clck->data.wallclock.sec += (tp.tv_sec + 1);
            clck->data.wallclock.usec -= (1000000 - tp.tv_usec);
         }
         else
         {
            clck->data.wallclock.sec += tp.tv_sec;
            clck->data.wallclock.usec += tp.tv_usec;
         }
#endif
         break;

      default:
         abort();
      }
   }
}

/** gets the used time of this clock in seconds */
double LC_clockGetTime(
   LC_CLOCK*             clck                /**< clock timer */
   )
{
   double result = 0.0;

   if ( clck == NULL )
      return 0.0;

   if ( clck->nruns == 0 )
   {
      /* the clock is not running: convert the clocks timer into seconds */
      switch ( clck->clocktype )
      {
      case LC_CLOCKTYPE_CPU:
         result = cputime2sec(clck->data.cpuclock.user);
         break;
      case LC_CLOCKTYPE_WALL:
         result = walltime2sec(clck->data.wallclock.sec, clck->data.wallclock.usec);
         break;
      default:
         abort();
      }
   }
   else
   {
#if defined(_WIN32) || defined(_WIN64)
      FILETIME creationtime;
      FILETIME exittime;
      FILETIME kerneltime;
      FILETIME usertime;
#else
      struct timeval tp; /*lint !e86*/
      struct tms now;
#endif

      /* the clock is currently running: we have to add the current time to the clocks timer */
      switch ( clck->clocktype )
      {
      case LC_CLOCKTYPE_CPU:
#if defined(_WIN32) || defined(_WIN64)
         GetProcessTimes(GetCurrentProcess(), &creationtime, &exittime, &kerneltime, &usertime);
         result = cputime2sec(clck->data.cpuclock.user + usertime.dwHighDateTime * 42950 + usertime.dwLowDateTime / 100000L);
#else
         (void)times(&now);
         result = cputime2sec(clck->data.cpuclock.user + now.tms_utime);
#endif
         break;

      case LC_CLOCKTYPE_WALL:
#if defined(_WIN32) || defined(_WIN64)
         result = walltime2sec(clck->data.wallclock.sec + time(NULL), 0);
#else
         gettimeofday(&tp, NULL);
         if( tp.tv_usec + clck->data.wallclock.usec > 1000000 ) /*lint !e115 !e40*/
            result = walltime2sec(clck->data.wallclock.sec + tp.tv_sec + 1, (clck->data.wallclock.usec - 1000000) + tp.tv_usec); /*lint !e115 !e40*/
         else
            result = walltime2sec(clck->data.wallclock.sec + tp.tv_sec, clck->data.wallclock.usec + tp.tv_usec); /*lint !e115 !e40*/
#endif
         break;

      default:
         abort();
      }
   }

   return result;
}
