#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"

#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <tchar.h>
#include <string.h>

#include "detect.h"

#include "include/cpu.h"

MODULE = Sys::Info::Driver::Windows  PACKAGE = Sys::Info::Driver::Windows

int
GetSystemMetrics(index)
    int index
CODE:
    RETVAL = GetSystemMetrics(index);
OUTPUT:
    RETVAL

void
GetSystemInfo()
PREINIT:
    OSVERSIONINFOEX osvi;
    SYSTEM_INFO     si;
    SYSTEM_INFO     si2;
    PGNSI           pGNSI;
    LPFN_ISWOW64PROCESS fnIsWow64Process;
    //PGPI            pGPI;
    BOOL            bOsVersionInfoEx;
    BOOL            bIsWow;
    //DWORD           dwType;
    TCHAR           wProcessorModel         [10];
    TCHAR           wProcessorStepping      [10];
    TCHAR           wProcessorArchitecture2 [64];
    unsigned int    wProcessBitness;
    unsigned int    wProcessorBitness;
PPCODE:
    /*
        See:
        - http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx
        - http://blogs.msdn.com/junfeng/archive/2005/07/01/434574.aspx
    */

    ZeroMemory(&si,   sizeof(SYSTEM_INFO));
    ZeroMemory(&si2,  sizeof(SYSTEM_INFO));
    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
        XSRETURN(1);
 
    // Copy the hardware information to the SYSTEM_INFO structure.
    pGNSI = (PGNSI) GetProcAddress(
                        GetModuleHandle( TEXT("kernel32.dll") ), 
                        "GetNativeSystemInfo"
                    );

    wProcessBitness   = 0;
    wProcessorBitness = 0;
    bIsWow = FALSE;

    (NULL != pGNSI) ? pGNSI(&si) : GetSystemInfo(&si);

    if ( VER_PLATFORM_WIN32_NT == osvi.dwPlatformId && osvi.dwMajorVersion > 4 ) {
        // We have Win2k or later
        EXTEND(SP, 26);

        switch (si.wProcessorArchitecture) {
            case PROCESSOR_ARCHITECTURE_ALPHA: 
                lstrcpy(  wProcessorArchitecture2, TEXT("Alpha"));
                wsprintf( wProcessorModel        , TEXT("%d"), HIBYTE(si.wProcessorRevision) );
                wsprintf( wProcessorStepping     , TEXT("%d"), LOBYTE(si.wProcessorRevision) );
                wProcessBitness   = 64;
                wProcessorBitness = 64;
                break;

            case PROCESSOR_ARCHITECTURE_IA64:
                lstrcpy(  wProcessorArchitecture2, TEXT("IA-64"));
                wsprintf( wProcessorModel        , TEXT("%d"), HIBYTE(si.wProcessorRevision) );
                wsprintf( wProcessorStepping     , TEXT("%d"), LOBYTE(si.wProcessorRevision) );
                wProcessBitness   = 64;
                wProcessorBitness = 64;
                break;

            case PROCESSOR_ARCHITECTURE_ALPHA64:
                lstrcpy(wProcessorArchitecture2  , TEXT("Alpha64"));
                wsprintf( wProcessorModel        , TEXT("%d"), HIBYTE(si.wProcessorRevision) );
                wsprintf( wProcessorStepping     , TEXT("%d"), LOBYTE(si.wProcessorRevision) );
                wProcessBitness   = 64;
                wProcessorBitness = 64;
                break;

            case PROCESSOR_ARCHITECTURE_INTEL:
                lstrcpy(  wProcessorArchitecture2, TEXT("x86") );
                wsprintf( wProcessorModel        , TEXT("%d"), HIBYTE(si.wProcessorRevision) );
                wsprintf( wProcessorStepping     , TEXT("%d"), LOBYTE(si.wProcessorRevision) );

                fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
                    GetModuleHandle( TEXT("kernel32.dll") ), 
                    "IsWow64Process"
                );

                if ( NULL != fnIsWow64Process ) {
                    if ( ! fnIsWow64Process(GetCurrentProcess(), &bIsWow) ){
                        croak("IsWow64Process failed with last error %d.", GetLastError());
                    } else {
                        if (bIsWow) {
                            pGNSI(&si2);
                            if (si2.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) {
                                wProcessBitness   = 32;
                                wProcessorBitness = 64;
                                lstrcpy( wProcessorArchitecture2, TEXT("IA-64") );
                            } else if (si2.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
                                wProcessBitness   = 32;
                                wProcessorBitness = 64;
                                lstrcpy( wProcessorArchitecture2, TEXT("x64") );
                            } else {
                                croak("I am running in the future!");
                            }
                        } else {
                            /* wProcessorBitness = (si.wProcessorLevel == 6 && si.wProcessorRevision >= 14)
                                              ? 64 // Core2
                                              : 32; */
                            /*
                            This is tricky. Only way to get a correct value seems to be
                               (1) either using "intrin.h" -> No good with MinGW
                               (2) or using a WMI call -> too complex under XS
                            So, I set this to -1 instead and then try to correct
                            it in the Perl layer with a WMI call.
                            Any patches regarding this are welcome.
                            */
                            lstrcpy( wProcessorArchitecture2, TEXT("x86 or x86-64") );
                            wProcessorBitness = -1;
                            wProcessBitness   = 32;
                        }
                    }
                }

                break;

            case PROCESSOR_ARCHITECTURE_UNKNOWN:
            default:
                lstrcpy(  wProcessorArchitecture2, TEXT("") );
                lstrcpy(  wProcessorModel        , TEXT("") );
                lstrcpy(  wProcessorStepping     , TEXT("") );
                break;
        }

        // build the info hash
        // Processor
        // TODO: dwAllocationGranularity
        PUSHs( sv_2mortal( newSVpv( "dwNumberOfProcessors"         , 0 ) ) );
        PUSHs( sv_2mortal( newSVuv( si.dwNumberOfProcessors            ) ) );

        PUSHs( sv_2mortal( newSVpv( "dwProcessorType"              , 0 ) ) );
        PUSHs( sv_2mortal( newSVuv( si.dwProcessorType                 ) ) );

        PUSHs( sv_2mortal( newSVpv( "wProcessorArchitecture"       , 0 ) ) );
        PUSHs( sv_2mortal( newSVuv( si.wProcessorArchitecture          ) ) );

        PUSHs( sv_2mortal( newSVpv( "wProcessorLevel"              , 0 ) ) );
        PUSHs( sv_2mortal( newSVuv( si.wProcessorLevel                 ) ) );

        PUSHs( sv_2mortal( newSVpv( "dwActiveProcessorMask"        , 0 ) ) );
        PUSHs( sv_2mortal( newSVuv( si.dwActiveProcessorMask           ) ) );

        PUSHs( sv_2mortal( newSVpv( "wProcessorRevision"           , 0 ) ) );
        PUSHs( sv_2mortal( newSVuv( si.wProcessorRevision              ) ) );

        PUSHs( sv_2mortal( newSVpv( "wProcessorModel"              , 0 ) ) );
        PUSHs( sv_2mortal( newSVpv( wProcessorModel                , 0 ) ) );

        PUSHs( sv_2mortal( newSVpv( "wProcessorStepping"           , 0 ) ) );
        PUSHs( sv_2mortal( newSVpv( wProcessorStepping             , 0 ) ) );

        PUSHs( sv_2mortal( newSVpv( "wProcessorArchitecture2"      , 0 ) ) );
        PUSHs( sv_2mortal( newSVpv( wProcessorArchitecture2        , 0 ) ) );

        // other
        PUSHs( sv_2mortal( newSVpv( "dwOemId"                      , 0 ) ) );
        PUSHs( sv_2mortal( newSVuv( si.dwOemId                         ) ) );

        PUSHs( sv_2mortal( newSVpv( "dwPageSize"                   , 0 ) ) );
        PUSHs( sv_2mortal( newSViv( si.dwPageSize                      ) ) );

        PUSHs( sv_2mortal( newSVpv( "lpMinimumApplicationAddress"  , 0 ) ) );
        PUSHs( sv_2mortal( newSVuv( si.lpMinimumApplicationAddress     ) ) );

        PUSHs( sv_2mortal( newSVpv( "lpMaximumApplicationAddress"  , 0 ) ) );
        PUSHs( sv_2mortal( newSVuv( si.lpMaximumApplicationAddress     ) ) );

        PUSHs( sv_2mortal( newSVpv( "wProcessBitness"              , 0 ) ) );
        PUSHs( sv_2mortal( newSViv(  wProcessBitness                   ) ) );

        PUSHs( sv_2mortal( newSVpv( "wProcessorBitness"            , 0 ) ) );
        PUSHs( sv_2mortal( newSViv(  wProcessorBitness                 ) ) );

    }
    else {
        croak( "GetSystemInfo() can not be run on this version of Windows.");
    }

void
CPUFeatures()
PREINIT:
    int      CPUInfo[4] = {-1};
    unsigned infoext[4];
    CPUDATA  cpu;
    ULONG    FeatureBits;
    unsigned i;
    unsigned ebx = 0;
    unsigned ecx = 0;
    unsigned CpuFeatures = 0;
    unsigned Flags;
    unsigned KFBits;
    unsigned FeatureFlags;
PPCODE:
    /*
        Resources:
        - http://msdn.microsoft.com/en-us/library/hskdteyh(VS.80).aspx
        - http://stackoverflow.com/questions/794632/programmatically-get-the-cache-line-size
        - http://en.wikipedia.org/wiki/CPUID

        __cpuid with an InfoType argument of 0 returns the number of
        valid Ids in CPUInfo[0] and the CPU identification string in
        the other three array elements. The CPU identification string is
        not in linear order. The code below arranges the information 
        in a human readable form.
    */
    __cpuid(CPUInfo, 0);
    cpu.Ids = CPUInfo[0];
    memset(cpu.String, 0, sizeof(cpu.String));
    *((int*)cpu.String)     = CPUInfo[1];
    *((int*)(cpu.String+4)) = CPUInfo[3];
    *((int*)(cpu.String+8)) = CPUInfo[2];

    // Get the information associated with each valid Id
    for ( i = 0; i <= cpu.Ids; ++i ) {
        __cpuid(CPUInfo, i);
        /*
        warn("\nFor InfoType %d\n", i);
        warn("CPUInfo[0] = 0x%x\n", CPUInfo[0]);
        warn("CPUInfo[1] = 0x%x\n", CPUInfo[1]);
        warn("CPUInfo[2] = 0x%x\n", CPUInfo[2]);
        warn("CPUInfo[3] = 0x%x\n", CPUInfo[3]);
        */
        // Interpret CPU feature information.
        if  ( i == 1 ) {
            ebx                        = CPUInfo[1];
            ecx                        = CPUInfo[2];
            CpuFeatures                = CPUInfo[3];
            cpu.SteppingID             =   CPUInfo[0]        & 0xf;
            cpu.Model                  =  (CPUInfo[0] >>  4) & 0xf;
            cpu.Family                 =  (CPUInfo[0] >>  8) & 0xf;
            cpu.ProcessorType          =  (CPUInfo[0] >> 12) & 0x3;
            cpu.Extendedmodel          =  (CPUInfo[0] >> 16) & 0xf;
            cpu.Extendedfamily         =  (CPUInfo[0] >> 20) & 0xff;
            cpu.BrandIndex             =   CPUInfo[1]        & 0xff;
            cpu.CLFLUSHcachelinesize   = ((CPUInfo[1] >>  8) & 0xff) * 8;
            cpu.APICPhysicalID         =  (CPUInfo[1] >> 24) & 0xff;
            cpu.SSE3NewInstructions    =  (CPUInfo[2] & 0x1  ) || 0;
            cpu.MONITOR_MWAIT          =  (CPUInfo[2] & 0x8  ) || 0;
            cpu.CPLQualifiedDebugStore =  (CPUInfo[2] & 0x10 ) || 0;
            cpu.ThermalMonitor2        =  (CPUInfo[2] & 0x100) || 0;
            cpu.FeatureInfo            =   CPUInfo[3];
        }
    }

    // Calling __cpuid with 0x80000000 as the InfoType argument
    // gets the number of valid extended IDs.
    __cpuid( CPUInfo, 0x80000000 );
    cpu.ExIds = CPUInfo[0];
    memset(cpu.BrandString, 0, sizeof(cpu.BrandString));

    if( cpu.ExIds >= 0x80000001 ) {
        __cpuid(infoext, 0x80000001);
        if( CF_MMX       & CpuFeatures ) Flags |= CF_MMX;
        if( CF_SSE       & CpuFeatures ) Flags |= CF_SSE;
        if( CF_SSE2      & CpuFeatures ) Flags |= CF_SSE2;
        if( CF_SSE3      & ecx         ) Flags |= CF_SSE3;
        if( CF_SSSE3     & ecx         ) Flags |= CF_SSSE3;
        if( CF_SSE41     & ecx         ) Flags |= CF_SSE41;
        if( CF_SSE42     & ecx         ) Flags |= CF_SSE42;
        if( CF_SSE5      & infoext[2]  ) Flags |= CF_SSE5;
        if( CF_SSE4A     & infoext[2]  ) Flags |= CF_SSE4A;
        if( CF_A3DNOW    & infoext[3]  ) Flags |= CF_A3DNOW;
        if( CF_MMXPLUS   & infoext[3]  ) Flags |= CF_MMXPLUS;
        if( CF_A3DNOWEXT & infoext[3]  ) Flags |= CF_A3DNOWEXT;
    }

    // Get the information associated with each extended ID.
    for ( i = 0x80000000; i <= cpu.ExIds; ++i ) {
        __cpuid(CPUInfo, i);
        /*
        warn("\nFor InfoType %x\n", i);
        warn("CPUInfo[0] = 0x%x\n", CPUInfo[0]);
        warn("CPUInfo[1] = 0x%x\n", CPUInfo[1]);
        warn("CPUInfo[2] = 0x%x\n", CPUInfo[2]);
        warn("CPUInfo[3] = 0x%x\n", CPUInfo[3]);
        */
        // Interpret CPU brand string and cache information.
        if  (i == 0x80000002)
            memcpy(cpu.BrandString, CPUInfo, sizeof(CPUInfo));
        else if  (i == 0x80000003)
            memcpy(cpu.BrandString + 16, CPUInfo, sizeof(CPUInfo));
        else if  (i == 0x80000004)
            memcpy(cpu.BrandString + 32, CPUInfo, sizeof(CPUInfo));
        else if  (i == 0x80000006) {
            cpu.L2CacheLineSize =  CPUInfo[2] & 0xff;
            cpu.L2Associativity = (CPUInfo[2] >> 12) & 0xf;
            cpu.L2CacheSizeK    = (CPUInfo[2] >> 16) & 0xffff;
        }
    }

    //warn("\n\nCPU String: %s\n", cpu.String);

    if  (cpu.Ids >= 1) {
        /*
        if (cpu.SteppingID)           warn("Stepping ID             = %d\n", cpu.SteppingID);
        if (cpu.Model)                warn("Model                   = %d\n", cpu.Model);
        if (cpu.Family)               warn("Family                  = %d\n", cpu.Family);
        if (cpu.ProcessorType)        warn("Processor Type          = %d\n", cpu.ProcessorType);
        if (cpu.Extendedmodel)        warn("Extended model          = %d\n", cpu.Extendedmodel);
        if (cpu.Extendedfamily)       warn("Extended family         = %d\n", cpu.Extendedfamily);
        if (cpu.BrandIndex)           warn("Brand Index             = %d\n", cpu.BrandIndex);
        if (cpu.CLFLUSHcachelinesize) warn("CLFLUSH cache line size = %d\n", cpu.CLFLUSHcachelinesize);
        if (cpu.APICPhysicalID)       warn("APIC Physical ID        = %d\n", cpu.APICPhysicalID);
        */

        if  (
            cpu.FeatureInfo            ||
            cpu.SSE3NewInstructions    ||
            cpu.MONITOR_MWAIT          ||
            cpu.CPLQualifiedDebugStore ||
            cpu.ThermalMonitor2
        ) {
            /*
            warn("\nThe following features are supported:\n");

            if (cpu.SSE3NewInstructions)    warn("      SSE3 New Instructions\n");
            if (cpu.MONITOR_MWAIT)          warn("      MONITOR/MWAIT\n");
            if (cpu.CPLQualifiedDebugStore) warn("      CPL Qualified Debug Store\n");
            if (cpu.ThermalMonitor2)        warn("      Thermal Monitor 2\n");
            */

            i       = 0;
            cpu.Ids = 1;
            while (i < (sizeof(szFeatures)/sizeof(const int*))) {
                if  (cpu.FeatureInfo & cpu.Ids) {
                    FeatureFlags |= szFeatures[i];
                }
                cpu.Ids <<= 1;
                ++i;
            }
        }
    }

    /*
    if  (cpu.ExIds >= 0x80000004)
        warn("\nCPU Brand String = %s\n", cpu.BrandString);

    if  (cpu.ExIds >= 0x80000006) {
        warn("L2 Cache Line Size = %d\n",  cpu.L2CacheLineSize);
        warn("L2 Associativity   = %d\n",  cpu.L2Associativity);
        warn("L2 Cache Size      = %dK\n", cpu.L2CacheSizeK);
    }
    */

    /*
    HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor
    */
    if (CpuFeatures & 0x00000002) KFBits |= KF_V86_VIS | KF_CR4;
    if (CpuFeatures & 0x00000008) KFBits |= KF_LARGE_PAGE | KF_CR4;
    if (CpuFeatures & 0x00000010) KFBits |= KF_RDTSC;
    if (CpuFeatures & 0x00000100) KFBits |= KF_CMPXCHG8B;
    if (CpuFeatures & 0x00000800) KFBits |= KF_FAST_SYSCALL;
    if (CpuFeatures & 0x00001000) KFBits |= KF_MTRR;
    if (CpuFeatures & 0x00002000) KFBits |= KF_GLOBAL_PAGE | KF_CR4;
    if (CpuFeatures & 0x00008000) KFBits |= KF_CMOV;
    if (CpuFeatures & 0x00010000) KFBits |= KF_PAT;
    if (CpuFeatures & 0x00200000) KFBits |= KF_DTS;
    if (CpuFeatures & 0x00800000) KFBits |= KF_MMX;
    if (CpuFeatures & 0x01000000) KFBits |= KF_FXSR;
    if (CpuFeatures & 0x02000000) KFBits |= KF_XMMI;
    if (CpuFeatures & 0x04000000) KFBits |= KF_XMMI64;

    if (CpuFeatures & 0x10000000) {
        cpu.Count = (UCHAR)(ebx >> 16);
        //warn("System has %d CPUs\n", cpu.Count);
    }

    PUSHs( sv_2mortal( newSVpv( "____ebx"               , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( ebx                         ) ) );

    PUSHs( sv_2mortal( newSVpv( "____ecx"               , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( ecx                         ) ) );

    PUSHs( sv_2mortal( newSVpv( "CpuFeatures"           , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( CpuFeatures                 ) ) );

    PUSHs( sv_2mortal( newSVpv( "SteppingID"            , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.SteppingID              ) ) );

    PUSHs( sv_2mortal( newSVpv( "Model"                 , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.Model                   ) ) );

    PUSHs( sv_2mortal( newSVpv( "Family"                , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.Family                  ) ) );

    PUSHs( sv_2mortal( newSVpv( "ProcessorType"         , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.ProcessorType           ) ) );

    PUSHs( sv_2mortal( newSVpv( "Extendedmodel"         , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.Extendedmodel           ) ) );

    PUSHs( sv_2mortal( newSVpv( "Extendedfamily"        , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.Extendedfamily          ) ) );

    PUSHs( sv_2mortal( newSVpv( "BrandIndex"            , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.BrandIndex              ) ) );

    PUSHs( sv_2mortal( newSVpv( "CLFLUSHcachelinesize"  , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.CLFLUSHcachelinesize    ) ) );

    PUSHs( sv_2mortal( newSVpv( "APICPhysicalID"        , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.APICPhysicalID          ) ) );

    PUSHs( sv_2mortal( newSVpv( "SSE3NewInstructions"   , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.SSE3NewInstructions     ) ) );

    PUSHs( sv_2mortal( newSVpv( "MONITOR_MWAIT"         , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.MONITOR_MWAIT           ) ) );

    PUSHs( sv_2mortal( newSVpv( "CPLQualifiedDebugStore", 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.CPLQualifiedDebugStore  ) ) );

    PUSHs( sv_2mortal( newSVpv( "ThermalMonitor2"       , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.ThermalMonitor2         ) ) );

    PUSHs( sv_2mortal( newSVpv( "FeatureInfo"           , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.FeatureInfo             ) ) );

    PUSHs( sv_2mortal( newSVpv( "ExIds"                 , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.ExIds                   ) ) );

    PUSHs( sv_2mortal( newSVpv( "Ids"                   , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.Ids                     ) ) );

    PUSHs( sv_2mortal( newSVpv( "Count"                 , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.Count                   ) ) );

    PUSHs( sv_2mortal( newSVpv( "BrandString"           , 0 ) ) );
    PUSHs( sv_2mortal( newSVpv( cpu.BrandString         , 0 ) ) );

    PUSHs( sv_2mortal( newSVpv( "String"                , 0 ) ) );
    PUSHs( sv_2mortal( newSVpv( cpu.String              , 0 ) ) );

    PUSHs( sv_2mortal( newSVpv( "L2CacheLineSize"       , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.L2CacheLineSize         ) ) );

    PUSHs( sv_2mortal( newSVpv( "L2Associativity"       , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.L2Associativity         ) ) );

    PUSHs( sv_2mortal( newSVpv( "L2CacheSizeK"          , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv( cpu.L2CacheSizeK            ) ) );

    PUSHs( sv_2mortal( newSVpv( "Flags"                 , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv(  Flags                      ) ) );

    PUSHs( sv_2mortal( newSVpv( "KFBits"                , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv(  KFBits                     ) ) );

    PUSHs( sv_2mortal( newSVpv( "FeatureFlags"           , 0 ) ) );
    PUSHs( sv_2mortal( newSVuv(  FeatureFlags                ) ) );