/** 
  @file System.h
 
  @maintainer Morgan McGuire, matrix@graphics3d.com
 
  @cite Rob Wyatt http://www.gamasutra.com/features/wyatts_world/19990709/processor_detection_01.htm
  @cite Benjamin Jurke http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-ProcessorDetectionClass&forum=cotd&id=-1
  @cite Michael Herf http://www.stereopsis.com/memcpy.html

  @created 2003-01-25
  @edited  2005-09-06
 */

#ifndef G3D_SYSTEM_H
#define G3D_SYSTEM_H

#include "G3D/platform.h"
#include "G3D/g3dmath.h"
#include "G3D/G3DGameUnits.h"
#include "G3D/BinaryFormat.h"
#include <string>

#ifdef G3D_OSX
#include <CoreServices/CoreServices.h>
#endif

namespace G3D {

/**
 Routine used by the demos to find the data.  Searches in
 ../data, ../../data, etc. up to 5 levels back.  Checks
 common locations like c:\libraries\g3d-<version>\data
 and some hard-coded paths on the Brown University file
 system.
 */
std::string demoFindData(bool errorIfNotFound = true);

/** G3D, SDL, and IJG libraries require license documentation
    to be distributed with your program.  This generates the
    string that must appear in your documentation. 
    <B>Your program can be commercial, closed-source</B> under
    any license you want.*/
std::string license();


/**
 Computes the CRC32 value of a byte array.  CRC32 is designed to be a hash
 function that produces different values for similar strings.

 This implementation is compatible with PKZIP and GZIP.

 Based on http://www.gamedev.net/reference/programming/features/crc32/
*/
uint32 crc32(const void* bytes, size_t numBytes);

/**
 OS and processor abstraction.  The first time any method is called the processor
 will be analyzed.  Future calls are then fast.

 Timing function overview:
    System::getCycleCount
      - actual cycle count

    System::getTick
      - High-resolution time in seconds since program started

    System::getLocalTime
      - High-resolution time in seconds since Jan 1, 1970
        (because it is stored in a double, this may be less
         accurate than getTick)

 */
class System {
public:

	/** Called automatically by the other System routines.*/
	static void init();

	/** */
	static bool hasMMX();

	/** */
	static bool hasCPUID();
	
	/** */
	static bool hasSSE();
	
	/** */
	static bool hasSSE2();
	
	/** */
	static bool has3DNow();

	
	/** */
    static bool hasRDTSC();

	static const std::string& cpuVendor();
	
	/** e.g. "Windows", "GNU/Linux" */
    static const std::string& operatingSystem();

	/** */
    static const std::string& cpuArchitecture();

    /**
     Returns the endianness of this machine.
     */
    static G3DEndian machineEndian();

    /**
     Guarantees that the start of the array is aligned to the 
     specified number of bytes.
     */
    static void* alignedMalloc(size_t bytes, size_t alignment);

    /**
     Uses pooled storage to optimize small allocations (1 byte to 5 kilobytes).  
     Can be 10x to 100x faster than calling ::malloc or new.

     The result must be freed with free.

     Threadsafe on Win32.

     @sa calloc realloc OutOfMemoryCallback free
     */
    static void* malloc(size_t bytes);

    static void* calloc(size_t n, size_t x);

    /**
     @param size Size of memory that the system was trying to allocate
     @param recoverable If true, the system will attempt to allocate again
            if the callback returns true.  If false, malloc is going to return 
            NULL and this invocation is just to notify the application.
     @return Return true to force malloc to attempt allocation again if the
            error was recoverable.
     */
    typedef bool (*OutOfMemoryCallback)(size_t size, bool recoverable);

    /**
     When System::malloc fails to allocate memory because the system is
     out of memory, it invokes this handler (if it is not NULL).
     The argument to the callback is the amount of memory that malloc
     was trying to allocate when it ran out.  If the callback returns
     true, System::malloc will attempt to allocate the memory again.
     If the callback returns false, then System::malloc will return NULL.

     You can use outOfMemoryCallback to free data structures or to 
     register the failure.
     */
    static OutOfMemoryCallback outOfMemoryCallback;

    /**
     Version of realloc that works with System::malloc.
     */
    static void* realloc(void* block, size_t bytes);

    /** Returns a string describing how well System::malloc is using its internal pooled storage.
        "heap" memory was slow to allocate; the other data sizes are comparatively fast.*/
    static std::string mallocPerformance();
    static void resetMallocPerformanceCounters();

    /**
     Free data allocated with System::malloc.

     Threadsafe on Win32.
     */
    static void free(void* p);

    /**
     Frees memory allocated with alignedMalloc.
     */
    static void alignedFree(void* ptr);

	/** An implementation of memcpy that may be up to 2x as fast as the C library
	    one on some processors.  Guaranteed to have the same behavior as memcpy
		in all cases. */
	static void memcpy(void* dst, const void* src, size_t numBytes);

	/** An implementation of memset that may be up to 2x as fast as the C library
	    one on some processors.  Guaranteed to have the same behavior as memset
		in all cases. */
	static void memset(void* dst, uint8 value, size_t numBytes);

    /**
     Returns the fully qualified filename for the currently running executable.

     This is more reliable than arg[0], which may be intentionally set
     to an incorrect value by a calling program, relative to a now
     non-current directory, or obfuscated by sym-links.

     @cite Linux version written by Nicolai Haehnle <prefect_@gmx.net>, http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-getexename&forum=cotd&id=-1
     */
    static std::string currentProgramFilename();

    /** G3D Version string */
    static const std::string& version();

    /** 
      Either Debug or Release, depending on whether _DEBUG was defined at compile-time for the library.
      */
    static const std::string& build();

    /**
     Causes the current thread to yield for the specified duration
     and consume almost no CPU.
     The sleep will be extremely precise; it uses System::time() 
     to calibrate the exact yeild time.
     */
    static void sleep(RealTime t);

    /**
     Clears the console.
     Console programs only.
     */
    static void consoleClearScreen();

    /**
     Returns true if a key is waiting.
     Console programs only.
     */
    static bool consoleKeyPressed();
    
    /**
     Blocks until a key is read (use consoleKeyPressed to determine if
     a key is waiting to be read) then returns the character code for
     that key.
     */
    static int consoleReadKey();

    /**
     Returns a highly accurate time in milliseconds that
     is relative to an arbitrary per-platform baseline
     (e.g. the time the program started)
     
     Use differences in two tick times to measure
     events to a high degree of precision (e.g. for profiling,
     frame rate counting).

     This is as accurate as System::getCycleCount, but returns a time
     in seconds instead of cycles.
     @deprecated Call time();
     */
    static RealTime getTick();

    /**
     @deprecated Call time();
     */
    static RealTime getLocalTime();

    /**
     The actual time (measured in seconds since
     Jan 1 1970 midnight).
     
     Adjusted for local timezone and daylight savings
     time.   This is as accurate and fast as getCycleCount().
    */
    static RealTime time() {
        return getLocalTime();
    }

    /**
     To count the number of cycles a given operation takes:

     <PRE>
     unsigned long count;
     System::beginCycleCount(count);
     ...
     System::endCycleCount(count);
     // count now contains the cycle count for the intervening operation.

     */
    static void beginCycleCount(uint64& cycleCount);
    static void endCycleCount(uint64& cycleCount);

    static uint64 getCycleCount();

    /** Set an environment variable for the current process */
    static void setEnv(const std::string& name, const std::string& value);
	
    /**
     Prints a human-readable description of this machine
     to the text output stream.  Either argument may be NULL.
     */
    static void describeSystem(
        class TextOutput& t);

    static void describeSystem(
        std::string&        s);

private:
    /**
	 (CKO) Note: Not sure why these are specifically needed
	 for OS X. I made them private though.
	*/
#   ifdef G3D_OSX
		static long m_OSXCPUSpeed; //In Cycles/Second
		static double m_secondsPerNS;
#   endif
};


#ifdef _MSC_VER
    inline uint64 System::getCycleCount() {
        uint32 timehi, timelo;

        // Use the assembly instruction rdtsc, which gets the current
        // cycle count (since the process started) and puts it in edx:eax.
        __asm
            {
                rdtsc;
                mov timehi, edx;
                mov timelo, eax;
            }

        return ((uint64)timehi << 32) + (uint64)timelo;
    }

#elif (defined(G3D_LINUX) || defined(G3D_MINGW32))

    inline uint64 System::getCycleCount() {
        uint32 timehi, timelo;

        __asm__ __volatile__ (
            "rdtsc            "
            : "=a" (timelo),
              "=d" (timehi)
            : );

        return ((uint64)timehi << 32) + (uint64)timelo;
    }

#elif defined(G3D_OSX)

    inline uint64 System::getCycleCount() {
		//Note:  To put off extra processing until the end, this does not 
		//return the actual clock cycle count.  It is a bus cycle count.
		//When endCycleCount() is called, it converts the two into a difference
		//of clock cycles
		
        return (uint64) UnsignedWideToUInt64(UpTime());
		//return (uint64) mach_absolute_time();
    }

#endif

inline void System::beginCycleCount(uint64& cycleCount) {
    cycleCount = getCycleCount();
}


inline void System::endCycleCount(uint64& cycleCount) {
	#ifndef G3D_OSX
		cycleCount = getCycleCount() - cycleCount;
	#else
		AbsoluteTime end = UpTime();
		init();
		Nanoseconds diffNS = 
            AbsoluteDeltaToNanoseconds(end, UInt64ToUnsignedWide(cycleCount));
		cycleCount = 
            (uint64) ((double) (System::m_OSXCPUSpeed) * 
                      (double) UnsignedWideToUInt64(diffNS) * m_secondsPerNS);
	#endif
}


} // namespace

#endif
