/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 Robert Osfield
 *
 * 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.
*/
//#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <osg/Timer>

using namespace osg;

// follows are the constructors of the Timer class, once version
// for each OS combination.  The order is WIN32, FreeBSD, Linux, IRIX,
// and the rest of the world.
//
// all the rest of the timer methods are implemented within the header.


const Timer* Timer::instance()
{
    static Timer s_timer;
    return &s_timer;
}

#ifdef WIN32

    #include <sys/types.h>
    #include <fcntl.h>
    #include <windows.h>
    #include <winbase.h>
    Timer::Timer()
    {
        //_useStandardClock = false;
        _useStandardClock = true;

        if (_useStandardClock)
        {
        #if defined(__CYGWIN__)
            _secsPerTick = 1e-6; // gettimeofday()'s precision.
        #else
            _secsPerTick = (1.0 / (double) CLOCKS_PER_SEC);
        #endif
        }
        else
        {
            // use a static here to ensure that the time to determine
            // the cpu frequency is not incurred more than once
            // per app execution.
            static double _tempSecsPerClick=0.0;
            if (_tempSecsPerClick==0.0)
            {
                // QueryPerformanceCounter under Windows 2000 Service Pack 3
                // two 2.4 GHz cpus was timed to take about 70 times longer
                // than the RDTSC assembly instruction sequence, but if
                // that counter is available we use it to quickly determine
                // the Time Stamp Counter rate, quickly as in 240 microseconds
                LARGE_INTEGER frequency;
                if(QueryPerformanceFrequency(&frequency))
                {
                    //#define DEBUG_TIME_OUTPUT 1
                    LARGE_INTEGER QPCstart, QPCstop,
                        QPCstartAfter, QPCstopAfter;
                    Timer_t TSCstart, TSCstop;
                    double QPCSecsPerClock = 1.0/frequency.QuadPart;
                    double elapsed, last, current, bound;

                    QueryPerformanceCounter(&QPCstart);
                    TSCstart=tick();
                    QueryPerformanceCounter(&QPCstartAfter);

                    current = 0;
                    do
                    {
                        // store the seconds per clock
                        last = current;
                        // read the clocks
                        QueryPerformanceCounter(&QPCstop);
                        TSCstop=tick();
                        QueryPerformanceCounter(&QPCstopAfter);
                        // average before and after to approximate reading
                        // both clocks at the same time
                        elapsed = ((QPCstop.QuadPart+QPCstopAfter.QuadPart)
                            -(QPCstart.QuadPart+QPCstartAfter.QuadPart))/2.0
                            *QPCSecsPerClock;
                        // TSC seconds per clock
                        current = elapsed / (TSCstop-TSCstart);
                        // calculate a bound to check against
                        bound = current/1000000;
                        // break if current-bound<last && current+bound>last
                    }while(current-bound>last || current+bound<last);
                    _tempSecsPerClick = current;
                    #ifdef DEBUG_TIME_OUTPUT
                    fprintf(stderr, "current %e, last %e\n",
                        1.0/current, 1.0/last);
                    fprintf(stderr, "Total Time %e\n",
                        (QPCstop.QuadPart-QPCstart.QuadPart)*QPCSecsPerClock);
                    #endif
                }
                #ifndef DEBUG_TIME_OUTPUT
                else
                #endif
                {
                    Timer_t start_time = tick();
                    Sleep (1000);
                    Timer_t end_time = tick();

                    _tempSecsPerClick = 1.0/(double)(end_time-start_time);
                    #ifdef DEBUG_TIME_OUTPUT
                    fprintf(stderr, "Sec delay rate %e\n",
                        1.0/_tempSecsPerClick);
                    #endif
                }
            }
            _secsPerTick = _tempSecsPerClick;
        }
    }

#elif defined(__FreeBSD__)

    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/sysctl.h>
    #include <sys/types.h>
    Timer::Timer()
    {
        _useStandardClock = false;

        if (_useStandardClock)
        {
            _secsPerTick = 1e-6; // gettimeofday()'s precision.
        }
        else
        {
            int cpuspeed;
            size_t len;

            len = sizeof(cpuspeed);
            if (sysctlbyname("machdep.tsc_freq", &cpuspeed, &len, NULL, NULL) == -1)
            {
                _useStandardClock = true;
            perror("sysctlbyname(machdep.tsc_freq)");
                return;
            }

            _secsPerTick = 1.0/cpuspeed;
        }
    }

#elif defined(__linux)

    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/mman.h>
    #include <sys/types.h>

    Timer::Timer()
    {
#if defined(__ia64) || defined(__x86_64__) || defined(__powerpc)
        _useStandardClock = true; // set to true to avoid problems with variable CPU speeds
#else
        _useStandardClock = true; // set to true to avoid problems with variable CPU speeds
#endif

        if (_useStandardClock)
        {
            _secsPerTick = 1e-6; // gettimeofday()'s precision.
        }
        else
        {
            char buff[128];
            FILE *fp = fopen( "/proc/cpuinfo", "r" );

            double cpu_mhz=0.0f;

            while( fgets( buff, sizeof( buff ), fp ) > 0 )
            {
                if( !strncmp( buff, "cpu MHz", strlen( "cpu MHz" )))
            {
                char *ptr = buff;

                while( ptr && *ptr != ':' ) ptr++;
                if( ptr )
                {
                  ptr++;
                  sscanf( ptr, "%lf", &cpu_mhz );
                }
                break;
            }
            }
            fclose( fp );

            if (cpu_mhz==0.0f)
            {
                // error - no cpu_mhz found.
                Timer_t start_time = tick();
                sleep (1);
                Timer_t end_time = tick();
                _secsPerTick = 1.0/(double)(end_time-start_time);
            }
            else
            {
                _secsPerTick = 1e-6/cpu_mhz;
            }
        }        
    }

#elif defined(__sgi)

    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/syssgi.h>
    #include <sys/mman.h>

    Timer::Timer( void )
    {
        _useStandardClock = false; // default to false.

        if (!_useStandardClock)
        {
            __psunsigned_t phys_addr, raddr;
            unsigned int cycleval;
            volatile unsigned long long *iotimer_addr;
            int fd, poffmask;

            poffmask     = getpagesize() - 1;
            phys_addr    = syssgi( SGI_QUERY_CYCLECNTR, &cycleval );
            raddr        = phys_addr & ~poffmask;

            _clockAddress_32 = 0;
            _clockAddress_64 = 0;
            _rollOver = 0;
            _lastClockValue = 0;

            if( (fd = open( "/dev/mmem", O_RDONLY )) < 0 )
            {
                perror( "/dev/mmem" );
                _useStandardClock=true;
                return;
            }

            iotimer_addr = (volatile unsigned long long *)mmap(
                (void *)0L,
                (size_t)poffmask,
                (int)PROT_READ,
                (int)MAP_PRIVATE, fd, (off_t)raddr);

            iotimer_addr = (unsigned long long *)(
                (__psunsigned_t)iotimer_addr + (phys_addr & poffmask)
                );

            _cycleCntrSize = syssgi( SGI_CYCLECNTR_SIZE );

            // Warning:  this casts away the volatile; not good
            if( _cycleCntrSize > 32 )
                _clockAddress_32 = 0,
                _clockAddress_64 = (unsigned long long *) iotimer_addr;
            else
                _clockAddress_32 = (unsigned long *) iotimer_addr,
                _clockAddress_64 = 0;

            _secsPerTick = (double)(cycleval)* 1e-12;
            
#if 0 // Obsolete
            // this is to force the use of the standard clock in
            // instances which the realtime clock is of such a small
            // size that it will loop too rapidly for proper realtime work.
            // this happens on the O2 for instance.
            if (_cycleCntrSize<=32) _useStandardClock=true;
#endif // Obsolete
            
        }

        if (_useStandardClock)
        {
            _secsPerTick = 1e-6; // gettimeofday()'s precision.
        }

    }

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

    Timer::Timer()
    {
        _useStandardClock = true;
        _secsPerTick = 1e-6; // gettimeofday()'s precision.
    }

#elif defined(unix)

    Timer::Timer( void )
    {
        _useStandardClock = true;
        _secsPerTick = 1e-6; // gettimeofday()'s precision.
    }

#else 

    // handle the rest of the OS world by just using the std::clock,

    Timer::Timer( void )
    {
        _useStandardClock = true;
        _secsPerTick = (1.0 / (double) CLOCKS_PER_SEC);
    }

#endif
