/* -*-c++-*- Producer - Copyright (C) 2001-2004  Don Burns
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * OpenSceneGraph Public License for more details.
 */


#ifndef PRODUCER_TIMER
#define PRODUCER_TIMER 1

#include <Producer/Export>

#if defined(_MSC_VER)
    namespace Producer {
        typedef __int64 Timer_t;
    }
#elif defined(__linux) || defined(__FreeBSD__) || defined(__CYGWIN__)|| defined(__MINGW32__)
    namespace Producer {
        typedef unsigned long long Timer_t;
    }
#elif defined(__sgi)
    namespace Producer {
        typedef unsigned long long Timer_t;
    }
#elif defined(unix)
    namespace Producer {
        typedef unsigned long long Timer_t;
    }
#elif defined __APPLE__ || defined macintosh
    namespace Producer {
        typedef double Timer_t;
    }
#else
    #include <ctime>
    namespace Producer {
        typedef std::clock_t Timer_t;
    }
#endif

namespace Producer {

/** A high resolution, low latency time stamper.*/
class PR_EXPORT Timer {
    public:
        Timer();
        ~Timer() {}

#if 0 // obsolete - requires Carbon
    #if defined __DARWIN_OSX__  || defined macintosh
        // PJA MAC OSX - inline Tick() pollutes namespace so badly 
        // we cant compile, due to Carbon.h ...
            Timer_t tick() const;
    #else
        inline Timer_t tick() const;
    #endif
#else
        inline Timer_t tick() const;
#endif
        
        inline double delta_s( Timer_t t1, Timer_t t2 ) const { return (double)(t2 - t1)*_secsPerClick; }
        inline double delta_m( Timer_t t1, Timer_t t2 ) const { return delta_s(t1,t2)*1e3; }
        inline double delta_u( Timer_t t1, Timer_t t2 ) const { return delta_s(t1,t2)*1e6; }
        inline double delta_n( Timer_t t1, Timer_t t2 ) const { return delta_s(t1,t2)*1e9; }

    private :

        double                          _secsPerClick;
        bool                            _useStandardClock;
       
#       ifdef __sgi
        static unsigned long*                  _clockAddress_32;
        static unsigned long long*             _clockAddress_64;
        static double                          s_secsPerClick;
    // for SGI machines with 32 bit clocks.
        static unsigned long           _lastClockValue;
        static unsigned long long      _rollOver;
#       endif
        
};

}

#if defined(_MSC_VER)

    #include <time.h>
    #pragma optimize("",off)

    namespace Producer{

        inline Timer_t Timer::tick( void ) const
        {
            if (_useStandardClock) return clock();

            volatile Timer_t ts;
            volatile unsigned int HighPart;
            volatile unsigned int LowPart;
            _asm
            {
                xor eax, eax        //  Used when QueryPerformanceCounter()
                xor edx, edx        //  not supported or minimal overhead
                _emit 0x0f          //  desired
                _emit 0x31          //
                mov HighPart,edx
                mov LowPart,eax
            }
            //ts = LowPart | HighPart >> 32;
            *((unsigned int*)&ts) = LowPart;
            *((unsigned int*)&ts+1) = HighPart;
            return ts;
        }

    }
    #pragma optimize("",on)

#elif defined(__MINGW32__)

    #include <sys/time.h>

    #define CLK(x)      __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x))
    namespace Producer{

    inline Timer_t Timer::tick() const
    {
        if (_useStandardClock)
            return clock();
        else
        {
            Timer_t x;CLK(x);return x;
        }
    }

    }

#elif defined(__linux) || defined(__FreeBSD__) || defined(__CYGWIN__)

    #include <sys/time.h>

#  if defined(__powerpc)
#    ifndef __HAVE_POWERPC_GET_TBL
#      define __HAVE_POWERPC_GET_TBL 1
static inline unsigned long get_tbl(void)
{
        unsigned long tbl;
        asm volatile ("mftb %0":"=r" (tbl));
        return tbl;
}
#    endif

#    define CLK(x)\
{ \
        unsigned long tb, tblu; \
        do { \
                tb = get_tbl(); \
               __asm__ __volatile__ ("mftbu %0":"=r" (tblu)); \
        } while (tb != get_tbl()); \
        x = (((Timer_t) tblu) << 32) | (Timer_t) tb; \
}

#  elif defined(__i386)
    #define CLK(x)      __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x))

#  endif

    namespace Producer{

        inline Timer_t Timer::tick() const
        {
#  ifdef CLK
            if (_useStandardClock)
            {
                struct timeval tv;
                gettimeofday(&tv, NULL);
                return ((Producer::Timer_t)tv.tv_sec)*1000000+(Producer::Timer_t)tv.tv_usec;
            }
            else
            {
                Timer_t x;CLK(x);return x;
            }
#  else // CLK
            struct timeval tv;
            gettimeofday(&tv, NULL);
            return ((Producer::Timer_t)tv.tv_sec)*1000000+(Producer::Timer_t)tv.tv_usec;
#  endif // CLK
        }

    }
  
#elif defined(__sgi)

    #include <sys/types.h>
    #include <sys/time.h>

    namespace Producer{

        inline  Timer_t Timer::tick() const
        {
            if (_useStandardClock)
            {
                struct timeval tv;
                gettimeofday(&tv, NULL);
                return ((Producer::Timer_t)tv.tv_sec)*1000000+(Producer::Timer_t)tv.tv_usec;
            }
            else
            {
                if ( _clockAddress_64 )
                    return *_clockAddress_64;
                else
        		{
                   unsigned long clockValue = *_clockAddress_32;
                   if( _lastClockValue > clockValue )
                   {
						# ifdef __GNUC__
						_rollOver += 0x100000000LL;
						#else
						_rollOver += 0x100000000L;
						#endif
                   }
                   _lastClockValue = clockValue;
                   return _rollOver + clockValue;
                }
            }
        }
    }

#elif defined (__APPLE__) || defined (macintosh)

    #include <sys/time.h>

    namespace Producer{
        inline Timer_t Timer::tick() const
        {
            // Always uses std::clock()
            struct timeval tv;
            gettimeofday(&tv, NULL);
            return ((Producer::Timer_t)tv.tv_sec)*1000000+(Producer::Timer_t)tv.tv_usec;
        }
    }
    
#elif defined(unix)

    #include <sys/time.h>

    namespace Producer{
        inline  Timer_t Timer::tick() const
        {
            struct timeval tv;
            gettimeofday(&tv, NULL);
            return ((Producer::Timer_t)tv.tv_sec)*1000000+(Producer::Timer_t)tv.tv_usec;
        }
    }

#else 

    // no choice, always use std::clock()
    namespace Producer{

        inline  Timer_t Timer::tick( void ) const { return std::clock(); }
    }

#endif


#endif
