/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   JUCE 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
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#include "win32_headers.h"
#include "../../../src/juce_core/basics/juce_StandardHeader.h"

//==============================================================================
#ifdef __BORLANDC__
void juce_StopInDebugger()
{
    asm int 3;
}
#endif


//==============================================================================
// Auto-link the other win32 libs that are needed by library calls..
#if defined (JUCE_DLL) && defined (_MSC_VER)

  #pragma comment(lib, "kernel32.lib")
  #pragma comment(lib, "user32.lib")
  #pragma comment(lib, "shell32.lib")
  #pragma comment(lib, "gdi32.lib")
  #pragma comment(lib, "vfw32.lib")
  #pragma comment(lib, "comdlg32.lib")
  #pragma comment(lib, "winmm.lib")
  #pragma comment(lib, "wininet.lib")
  #pragma comment(lib, "rpcrt4.lib")
  #pragma comment(lib, "ole32.lib")
  #pragma comment(lib, "advapi32.lib")
  #pragma comment(lib, "ws2_32.lib")

  #if JUCE_QUICKTIME
    #pragma comment(lib, "qtmlclient.lib")
  #endif

  #if JUCE_OPENGL
    #pragma comment(lib, "OpenGL32.Lib")
    #pragma comment(lib, "GlU32.Lib")
  #endif
#endif


//==============================================================================
BEGIN_JUCE_NAMESPACE

#include "../../../src/juce_core/io/files/juce_File.h"
#include "../../../src/juce_core/basics/juce_SystemStats.h"
#include "../../../src/juce_core/basics/juce_Logger.h"
#include "../../../src/juce_core/misc/juce_Uuid.h"
#include "juce_win32_DynamicLibraryLoader.h"

extern void juce_updateMultiMonitorInfo(); // from WindowDriver

//==============================================================================
void Logger::outputDebugString (const String& text)
{
    OutputDebugString (text + T("\n"));
}

void Logger::outputDebugPrintf (const tchar* format, ...)
{
    String text;
    va_list args;
    va_start (args, format);
    text.vprintf(format, args);
    outputDebugString (text);
}

//==============================================================================
static int64 hiResTicksPerSecond;
static double hiResTicksScaleFactor;
static SYSTEM_INFO systemInfo;

static struct _LogicalCpuInfo
{
    bool htSupported;
    bool htAvailable;
    int numPackages;
    int numLogicalPerPackage;
    unsigned long physicalAffinityMask;
} logicalCpuInfo;


//==============================================================================
static juce_noinline unsigned int getCPUIDWord (int* familyModel = 0, int* extFeatures = 0)
{
    unsigned int cpu = 0;
    unsigned int ext = 0;
    unsigned int family = 0;
#ifdef __GNUC__
    unsigned int dummy = 0;
#endif

#ifndef __MINGW32__
    __try
#endif
    {
#ifdef __GNUC__
        __asm__ ("cpuid" : "=a" (family), "=b" (ext), "=c" (dummy),"=d" (cpu) : "a" (1));
#else
        __asm
        {
            mov eax, 1
            cpuid
            mov cpu, edx
            mov family, eax
            mov ext, ebx
        }

#endif
    }
#ifndef __MINGW32__
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        return 0;
    }
#endif

    if (familyModel != 0)
        *familyModel = family;

    if (extFeatures != 0)
        *extFeatures = ext;

    return cpu;
}

static void juce_getCpuVendor (char* const v)
{
    int vendor[4];
    zeromem (vendor, 16);

#ifndef __MINGW32__
    __try
#endif
    {
#ifdef __GNUC__
        __asm__ __volatile__ (
            "movl 0, %%eax             \n\
             cpuid                     \n\
             movl %%ebx, %[vendor1]    \n\
             movl %%edx, %[vendor2]    \n\
             movl %%ecx, %[vendor3]"
             :
             : [vendor1] "m" (vendor[0]),
               [vendor2] "m" (vendor[1]),
               [vendor3] "m" (vendor[2])
             : "cc", "eax", "ebx", "ecx", "edx", "memory");
#else
        __asm
        {
            mov eax, 0
            cpuid
            mov [vendor], ebx
            mov [vendor + 4], edx
            mov [vendor + 8], ecx
        }
#endif
    }
#ifndef __MINGW32__
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        *v = 0;
    }
#endif

    memcpy (v, vendor, 16);
}

const String SystemStats::getCpuVendor()
{
    char v [16];
    juce_getCpuVendor (v);

    return String (v, 16);
}

#ifdef __BORLANDC__
 #pragma warn -8004
#endif

static void initLogicalCpuInfo()
{
    int familyModelWord, extFeaturesWord;
    int featuresWord = getCPUIDWord (&familyModelWord, &extFeaturesWord);
    HANDLE hCurrentProcessHandle = GetCurrentProcess();

    logicalCpuInfo.htSupported = false;
    logicalCpuInfo.htAvailable = false;
    logicalCpuInfo.numLogicalPerPackage = 1;
    logicalCpuInfo.numPackages = 0;
    logicalCpuInfo.physicalAffinityMask = 0;

    unsigned long processAffinity;
    unsigned long systemAffinity;

    if (! GetProcessAffinityMask (hCurrentProcessHandle, &processAffinity, &systemAffinity))
        return;

    // Checks: CPUID supported, model >= Pentium 4, Hyperthreading bit set, logical CPUs per package > 1
    if (featuresWord == 0
        || ((familyModelWord >> 8) & 0xf) < 15
        || (featuresWord & (1 << 28)) == 0
        || ((extFeaturesWord >> 16) & 0xff) < 2)
    {
        logicalCpuInfo.physicalAffinityMask = processAffinity;
        return;
    }

    logicalCpuInfo.htSupported = true;
    logicalCpuInfo.numLogicalPerPackage = (extFeaturesWord >> 16) & 0xff;

    unsigned int affinityMask;
    unsigned int physAff = 0;

    unsigned char i = 1;
    unsigned char physIdMask = 0xFF;
    unsigned char physIdShift = 0;

    unsigned char apicId;
    unsigned char logId;
    unsigned char physId;

    while (i < logicalCpuInfo.numLogicalPerPackage)
    {
        i *= 2;
        physIdMask <<= 1;
        physIdShift++;
    }

    affinityMask = 1;
    logicalCpuInfo.numPackages = 0;

    while ((affinityMask != 0) && (affinityMask <= processAffinity))
    {
        if (SetProcessAffinityMask (hCurrentProcessHandle, affinityMask))
        {
            Sleep(0); // schedule onto correct CPU

            featuresWord = getCPUIDWord (&familyModelWord, &extFeaturesWord);
            apicId = (unsigned char) (extFeaturesWord >> 24);
            logId = (unsigned char) (apicId & ~physIdMask);
            physId = (unsigned char) (apicId >> physIdShift);

            if (logId != 0)
                logicalCpuInfo.htAvailable = true;

            if ((((int) logId) % logicalCpuInfo.numLogicalPerPackage) == 0)
            {
                // This is a physical CPU
                physAff |= affinityMask;
                logicalCpuInfo.numPackages++;
            }
        }

        affinityMask = affinityMask << 1;
    }

    logicalCpuInfo.physicalAffinityMask = physAff;

    SetProcessAffinityMask(hCurrentProcessHandle, processAffinity);
}

//==============================================================================
void juce_initialiseThreadEvents();
void juce_initialiseUnicodeFileFunctions();

static struct JuceCpuProps
{
    bool hasMMX : 1, hasSSE : 1, hasSSE2 : 1, has3DNow : 1;
} juce_CpuProps;

bool SystemStats::hasMMX()
{
    return juce_CpuProps.hasMMX;
}

bool SystemStats::hasSSE()
{
    return juce_CpuProps.hasSSE;
}

bool SystemStats::hasSSE2()
{
    return juce_CpuProps.hasSSE2;
}

bool SystemStats::has3DNow()
{
    return juce_CpuProps.has3DNow;
}

void SystemStats::initialiseStats()
{
    juce_initialiseUnicodeFileFunctions();
    juce_initialiseThreadEvents();

    juce_CpuProps.hasMMX   = IsProcessorFeaturePresent (PF_MMX_INSTRUCTIONS_AVAILABLE) != 0;
    juce_CpuProps.hasSSE   = IsProcessorFeaturePresent (PF_XMMI_INSTRUCTIONS_AVAILABLE) != 0;
    juce_CpuProps.hasSSE2  = IsProcessorFeaturePresent (PF_XMMI64_INSTRUCTIONS_AVAILABLE) != 0;
#ifdef PF_AMD3D_INSTRUCTIONS_AVAILABLE
    juce_CpuProps.has3DNow = IsProcessorFeaturePresent (PF_AMD3D_INSTRUCTIONS_AVAILABLE) != 0;
#else
    juce_CpuProps.has3DNow = IsProcessorFeaturePresent (PF_3DNOW_INSTRUCTIONS_AVAILABLE) != 0;
#endif

    LARGE_INTEGER f;
    QueryPerformanceFrequency (&f);
    hiResTicksPerSecond = f.QuadPart;
    hiResTicksScaleFactor = 1000.0 / hiResTicksPerSecond;

    String s (SystemStats::getJUCEVersion());

    GetSystemInfo (&systemInfo);
    initLogicalCpuInfo();

#ifdef JUCE_DEBUG
    const MMRESULT res = timeBeginPeriod (1);
    jassert (res == TIMERR_NOERROR);
#else
    timeBeginPeriod (1);
#endif

#if JUCE_DEBUG && defined (_MSC_VER) && JUCE_CHECK_MEMORY_LEAKS
    _CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
}

//==============================================================================
const String SystemStats::getOSType()
{
    OSVERSIONINFO info;
    info.dwOSVersionInfoSize = sizeof (info);
    GetVersionEx (&info);

    if (info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
    {
        return (info.dwMinorVersion == 0) ? SystemStats::Win95
                                          : SystemStats::Win98;
    }
    else if (info.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
        if (info.dwMajorVersion == 3)
        {
            return SystemStats::WinNT351;
        }
        else if (info.dwMajorVersion == 4)
        {
            return SystemStats::WinNT40;
        }
        else if (info.dwMajorVersion == 5)
        {
            return (info.dwMinorVersion == 0) ? SystemStats::Win2000
                                              : SystemStats::WinXP;
        }
    }

    return SystemStats::UnknownOS;
}

//==============================================================================
int SystemStats::getMemorySizeInMegabytes()
{
    MEMORYSTATUS mem;
    GlobalMemoryStatus (&mem);
    return (int) (mem.dwTotalPhys / (1024 * 1024)) + 1;
}

bool SystemStats::hasHyperThreading()
{
    return logicalCpuInfo.htAvailable;
}


int SystemStats::getNumPhysicalCpus()
{
    if (logicalCpuInfo.numPackages)
        return logicalCpuInfo.numPackages;

    return getNumLogicalCpus();
}

int SystemStats::getNumLogicalCpus()
{
    return systemInfo.dwNumberOfProcessors;
}

uint32 SystemStats::getPhysicalAffinityMask()
{
    return logicalCpuInfo.physicalAffinityMask;
}

//==============================================================================
uint32 juce_millisecondsSinceStartup()
{
    LARGE_INTEGER ticks;
    QueryPerformanceCounter (&ticks);
    return (uint32) ((ticks.QuadPart * 1000) / hiResTicksPerSecond);
}

double juce_millisecondsSinceStartupHiRes()
{
    LARGE_INTEGER ticks;
    QueryPerformanceCounter (&ticks);
    return ticks.QuadPart * hiResTicksScaleFactor;
}

int64 Time::getHighResolutionTicks()
{
    LARGE_INTEGER ticks;
    QueryPerformanceCounter (&ticks);
    return ticks.QuadPart;
}

int64 Time::getHighResolutionTicksPerSecond()
{
    return hiResTicksPerSecond;
}

int64 SystemStats::getClockCycleCounter()
{
    unsigned int hi = 0, lo = 0;

#ifdef __GNUC__
    __asm__ __volatile__ (
        "xor %%eax, %%eax               \n\
         xor %%edx, %%edx               \n\
         rdtsc                          \n\
         movl %%eax, %[lo]              \n\
         movl %%edx, %[hi]"
         :
         : [hi] "m" (hi),
           [lo] "m" (lo)
         : "cc", "eax", "ebx", "ecx", "edx", "memory");
#else
    __asm
    {
        xor eax, eax
        xor edx, edx
        rdtsc
        mov lo, eax
        mov hi, edx
    }
#endif

    return (int64) ((((uint64) hi) << 32) | lo);
}

int SystemStats::getCpuSpeedInMegaherz()
{
    const int64 cycles = SystemStats::getClockCycleCounter();
    const uint32 millis = Time::getMillisecondCounter();
    int lastResult = 0;

    for (;;)
    {
        int n = 1000000;
        while (--n > 0) {}

        const uint32 millisElapsed = Time::getMillisecondCounter() - millis;
        const int64 cyclesNow = SystemStats::getClockCycleCounter();

        if (millisElapsed > 80)
        {
            const int newResult = (int)(((cyclesNow - cycles) / millisElapsed) / 1000);

            if (millisElapsed > 500 || (lastResult == newResult && newResult > 100))
                return newResult;

            lastResult = newResult;
        }
    }
}


//==============================================================================
bool Time::setSystemTimeToThisTime() const
{
    SYSTEMTIME st;

    st.wDayOfWeek = 0;
    st.wYear           = (WORD) getYear();
    st.wMonth          = (WORD) (getMonth() + 1);
    st.wDay            = (WORD) getDayOfMonth();
    st.wHour           = (WORD) getHours();
    st.wMinute         = (WORD) getMinutes();
    st.wSecond         = (WORD) getSeconds();
    st.wMilliseconds   = (WORD) (millisSinceEpoch % 1000);

    // do this twice because of daylight saving conversion problems - the
    // first one sets it up, the second one kicks it in.
    return SetLocalTime (&st) != 0
            && SetLocalTime (&st) != 0;
}

const String Uuid::generateUuid()
{
    uuid_t uuid;
#ifndef UNICODE
    unsigned char* s = 0;
#else
    unsigned short* s = 0;
#endif
    String uuidStr;

    UuidCreate (&uuid);

    if (UuidToString (&uuid, &s) == RPC_S_OK)
    {
#ifndef UNICODE
        uuidStr = (const char*)s;
#else
        uuidStr = (const juce_wchar*)s;
#endif
        RpcStringFree (&s);
    }

    return uuidStr;
}

int SystemStats::getPageSize()
{
    return systemInfo.dwPageSize;
}

END_JUCE_NAMESPACE
