# XXX clean up this mess!

import winlean

const
  INVALID_HANDLE_VALUE = int(- 1) # GetStockObject

type
  TMEMORYSTATUSEX {.final, pure.} = object
    dwLength: int32
    dwMemoryLoad: int32
    ullTotalPhys: int64
    ullAvailPhys: int64
    ullTotalPageFile: int64
    ullAvailPageFile: int64
    ullTotalVirtual: int64
    ullAvailVirtual: int64
    ullAvailExtendedVirtual: int64
    
  SYSTEM_INFO* {.final, pure.} = object
    wProcessorArchitecture*: int16
    wReserved*: int16
    dwPageSize*: int32
    lpMinimumApplicationAddress*: pointer
    lpMaximumApplicationAddress*: pointer
    dwActiveProcessorMask*: int32
    dwNumberOfProcessors*: int32
    dwProcessorType*: int32
    dwAllocationGranularity*: int32
    wProcessorLevel*: int16
    wProcessorRevision*: int16

  LPSYSTEM_INFO* = ptr SYSTEM_INFO
  TSYSTEMINFO* = SYSTEM_INFO

  TMemoryInfo* = object
    MemoryLoad*: int ## occupied memory, in percent
    TotalPhysMem*: int64 ## Total Physical memory, in bytes
    AvailablePhysMem*: int64 ## Available physical memory, in bytes
    TotalPageFile*: int64 ## The current committed memory limit 
                          ## for the system or the current process, whichever is smaller, in bytes.
    AvailablePageFile*: int64 ## The maximum amount of memory the current process can commit, in bytes.
    TotalVirtualMem*: int64 ## Total virtual memory, in bytes
    AvailableVirtualMem*: int64 ## Available virtual memory, in bytes
    
  TOSVERSIONINFOEX {.final, pure.} = object
    dwOSVersionInfoSize: int32
    dwMajorVersion: int32
    dwMinorVersion: int32
    dwBuildNumber: int32
    dwPlatformId: int32
    szCSDVersion: array[0..127, char]
    wServicePackMajor: int16
    wServicePackMinor: int16
    wSuiteMask: int16
    wProductType: int8
    wReserved: char
    
  TVersionInfo* = object
    majorVersion*: int
    minorVersion*: int
    buildNumber*: int
    platformID*: int
    SPVersion*: string ## Full Service pack version string
    SPMajor*: int ## Major service pack version
    SPMinor*: int ## Minor service pack version
    SuiteMask*: int
    ProductType*: int
    
  TPartitionInfo* = tuple[FreeSpace, TotalSpace: Tfiletime]
  
const
  # SuiteMask - VersionInfo.SuiteMask
  VER_SUITE_BACKOFFICE* = 0x00000004
  VER_SUITE_BLADE* = 0x00000400
  VER_SUITE_COMPUTE_SERVER* = 0x00004000
  VER_SUITE_DATACENTER* = 0x00000080
  VER_SUITE_ENTERPRISE* = 0x00000002
  VER_SUITE_EMBEDDEDNT* = 0x00000040
  VER_SUITE_PERSONAL* = 0x00000200
  VER_SUITE_SINGLEUSERTS* = 0x00000100
  VER_SUITE_SMALLBUSINESS* = 0x00000001
  VER_SUITE_SMALLBUSINESS_RESTRICTED* = 0x00000020
  VER_SUITE_STORAGE_SERVER* = 0x00002000
  VER_SUITE_TERMINAL* = 0x00000010
  VER_SUITE_WH_SERVER* = 0x00008000

  # ProductType - VersionInfo.ProductType
  VER_NT_DOMAIN_CONTROLLER* = 0x0000002
  VER_NT_SERVER* = 0x0000003
  VER_NT_WORKSTATION* = 0x0000001
  
  VER_PLATFORM_WIN32_NT* = 2
  
  # Product Info - getProductInfo() - (Remove unused ones ?)
  PRODUCT_BUSINESS* = 0x00000006
  PRODUCT_BUSINESS_N* = 0x00000010
  PRODUCT_CLUSTER_SERVER* = 0x00000012
  PRODUCT_DATACENTER_SERVER* = 0x00000008
  PRODUCT_DATACENTER_SERVER_CORE* = 0x0000000C
  PRODUCT_DATACENTER_SERVER_CORE_V* = 0x00000027
  PRODUCT_DATACENTER_SERVER_V* = 0x00000025
  PRODUCT_ENTERPRISE* = 0x00000004
  PRODUCT_ENTERPRISE_E* = 0x00000046
  PRODUCT_ENTERPRISE_N* = 0x0000001B
  PRODUCT_ENTERPRISE_SERVER* = 0x0000000A
  PRODUCT_ENTERPRISE_SERVER_CORE* = 0x0000000E
  PRODUCT_ENTERPRISE_SERVER_CORE_V* = 0x00000029
  PRODUCT_ENTERPRISE_SERVER_IA64* = 0x0000000F
  PRODUCT_ENTERPRISE_SERVER_V* = 0x00000026
  PRODUCT_HOME_BASIC* = 0x00000002
  PRODUCT_HOME_BASIC_E* = 0x00000043
  PRODUCT_HOME_BASIC_N* = 0x00000005
  PRODUCT_HOME_PREMIUM* = 0x00000003
  PRODUCT_HOME_PREMIUM_E* = 0x00000044
  PRODUCT_HOME_PREMIUM_N* = 0x0000001A
  PRODUCT_HYPERV* = 0x0000002A
  PRODUCT_MEDIUMBUSINESS_SERVER_MANAGEMENT* = 0x0000001E
  PRODUCT_MEDIUMBUSINESS_SERVER_MESSAGING* = 0x00000020
  PRODUCT_MEDIUMBUSINESS_SERVER_SECURITY* = 0x0000001F
  PRODUCT_PROFESSIONAL* = 0x00000030
  PRODUCT_PROFESSIONAL_E* = 0x00000045
  PRODUCT_PROFESSIONAL_N* = 0x00000031
  PRODUCT_SERVER_FOR_SMALLBUSINESS* = 0x00000018
  PRODUCT_SERVER_FOR_SMALLBUSINESS_V* = 0x00000023
  PRODUCT_SERVER_FOUNDATION* = 0x00000021
  PRODUCT_SMALLBUSINESS_SERVER* = 0x00000009
  PRODUCT_STANDARD_SERVER* = 0x00000007
  PRODUCT_STANDARD_SERVER_CORE * = 0x0000000D
  PRODUCT_STANDARD_SERVER_CORE_V* = 0x00000028
  PRODUCT_STANDARD_SERVER_V* = 0x00000024
  PRODUCT_STARTER* = 0x0000000B
  PRODUCT_STARTER_E* = 0x00000042
  PRODUCT_STARTER_N* = 0x0000002F
  PRODUCT_STORAGE_ENTERPRISE_SERVER* = 0x00000017
  PRODUCT_STORAGE_EXPRESS_SERVER* = 0x00000014
  PRODUCT_STORAGE_STANDARD_SERVER* = 0x00000015
  PRODUCT_STORAGE_WORKGROUP_SERVER* = 0x00000016
  PRODUCT_UNDEFINED* = 0x00000000
  PRODUCT_ULTIMATE* = 0x00000001
  PRODUCT_ULTIMATE_E* = 0x00000047
  PRODUCT_ULTIMATE_N* = 0x0000001C
  PRODUCT_WEB_SERVER* = 0x00000011
  PRODUCT_WEB_SERVER_CORE* = 0x0000001D
  
  PROCESSOR_ARCHITECTURE_AMD64* = 9 ## x64 (AMD or Intel)
  PROCESSOR_ARCHITECTURE_IA64* = 6 ## Intel Itanium Processor Family (IPF)
  PROCESSOR_ARCHITECTURE_INTEL* = 0 ## x86
  PROCESSOR_ARCHITECTURE_UNKNOWN* = 0xffff ## Unknown architecture.
  
  # GetSystemMetrics
  SM_SERVERR2 = 89 
  
proc globalMemoryStatusEx*(lpBuffer: var TMEMORYSTATUSEX){.stdcall, dynlib: "kernel32",
    importc: "GlobalMemoryStatusEx".}
    
proc getMemoryInfo*(): TMemoryInfo =
  ## Retrieves memory info
  var statex: TMEMORYSTATUSEX
  statex.dwLength = sizeof(statex).int32

  globalMemoryStatusEx(statex)
  result.MemoryLoad = statex.dwMemoryLoad
  result.TotalPhysMem = statex.ullTotalPhys
  result.AvailablePhysMem = statex.ullAvailPhys
  result.TotalPageFile = statex.ullTotalPageFile
  result.AvailablePageFile = statex.ullAvailPageFile
  result.TotalVirtualMem = statex.ullTotalVirtual
  result.AvailableVirtualMem = statex.ullAvailExtendedVirtual

proc getVersionEx*(lpVersionInformation: var TOSVERSIONINFOEX): WINBOOL{.stdcall,
    dynlib: "kernel32", importc: "GetVersionExA".}

proc getProcAddress*(hModule: int, lpProcName: cstring): pointer{.stdcall,
    dynlib: "kernel32", importc: "GetProcAddress".}

proc getModuleHandleA*(lpModuleName: cstring): int{.stdcall,
     dynlib: "kernel32", importc: "GetModuleHandleA".}

proc getVersionInfo*(): TVersionInfo =
  ## Retrieves operating system info
  var osvi: TOSVERSIONINFOEX
  osvi.dwOSVersionInfoSize = sizeof(osvi).int32
  discard getVersionEx(osvi)
  result.majorVersion = osvi.dwMajorVersion
  result.minorVersion = osvi.dwMinorVersion
  result.buildNumber = osvi.dwBuildNumber
  result.platformID = osvi.dwPlatformId
  result.SPVersion = $osvi.szCSDVersion
  result.SPMajor = osvi.wServicePackMajor
  result.SPMinor = osvi.wServicePackMinor
  result.SuiteMask = osvi.wSuiteMask
  result.ProductType = osvi.wProductType

proc getProductInfo*(majorVersion, minorVersion, SPMajorVersion, 
                     SPMinorVersion: int): int =
  ## Retrieves Windows' ProductInfo, this function only works in Vista and 7

  var pGPI = cast[proc (dwOSMajorVersion, dwOSMinorVersion, 
              dwSpMajorVersion, dwSpMinorVersion: int32, outValue: Pint32)](getProcAddress(
                getModuleHandleA("kernel32.dll"), "GetProductInfo"))
                
  if pGPI != nil:
    var dwType: int32
    pGPI(int32(majorVersion), int32(minorVersion), int32(SPMajorVersion), int32(SPMinorVersion), addr(dwType))
    result = int(dwType)
  else:
    return PRODUCT_UNDEFINED

proc getSystemInfo*(lpSystemInfo: LPSYSTEM_INFO){.stdcall, dynlib: "kernel32",
    importc: "GetSystemInfo".}
    
proc getSystemInfo*(): TSYSTEM_INFO =
  ## Returns the SystemInfo

  # Use GetNativeSystemInfo if it's available
  var pGNSI = cast[proc (lpSystemInfo: LPSYSTEM_INFO)](getProcAddress(
                getModuleHandleA("kernel32.dll"), "GetNativeSystemInfo"))
                
  var systemi: TSYSTEM_INFO              
  if pGNSI != nil:
    pGNSI(addr(systemi))
  else:
    getSystemInfo(addr(systemi))

  return systemi

proc getSystemMetrics*(nIndex: int32): int32{.stdcall, dynlib: "user32",
    importc: "GetSystemMetrics".}

proc `$`*(osvi: TVersionInfo): string =
  ## Turns a VersionInfo object, into a string

  if osvi.platformID == VER_PLATFORM_WIN32_NT and osvi.majorVersion > 4:
    result = "Microsoft "
    
    var si = getSystemInfo()
    # Test for the specific product
    if osvi.majorVersion == 6:
      if osvi.minorVersion == 0:
        if osvi.ProductType == VER_NT_WORKSTATION:
          result.add("Windows Vista ")
        else: result.add("Windows Server 2008 ")
      elif osvi.minorVersion == 1:
        if osvi.ProductType == VER_NT_WORKSTATION:
          result.add("Windows 7 ")
        else: result.add("Windows Server 2008 R2 ")
    
      var dwType = getProductInfo(osvi.majorVersion, osvi.minorVersion, 0, 0)
      case dwType
      of PRODUCT_ULTIMATE:
        result.add("Ultimate Edition")
      of PRODUCT_PROFESSIONAL:
        result.add("Professional")
      of PRODUCT_HOME_PREMIUM:
        result.add("Home Premium Edition")
      of PRODUCT_HOME_BASIC:
        result.add("Home Basic Edition")
      of PRODUCT_ENTERPRISE:
        result.add("Enterprise Edition")
      of PRODUCT_BUSINESS:
        result.add("Business Edition")
      of PRODUCT_STARTER:
        result.add("Starter Edition")
      of PRODUCT_CLUSTER_SERVER:
        result.add("Cluster Server Edition")
      of PRODUCT_DATACENTER_SERVER:
        result.add("Datacenter Edition")
      of PRODUCT_DATACENTER_SERVER_CORE:
        result.add("Datacenter Edition (core installation)")
      of PRODUCT_ENTERPRISE_SERVER:
        result.add("Enterprise Edition")
      of PRODUCT_ENTERPRISE_SERVER_CORE:
        result.add("Enterprise Edition (core installation)")
      of PRODUCT_ENTERPRISE_SERVER_IA64:
        result.add("Enterprise Edition for Itanium-based Systems")
      of PRODUCT_SMALLBUSINESS_SERVER:
        result.add("Small Business Server")
      of PRODUCT_STANDARD_SERVER:
        result.add("Standard Edition")
      of PRODUCT_STANDARD_SERVER_CORE:
        result.add("Standard Edition (core installation)")
      of PRODUCT_WEB_SERVER:
        result.add("Web Server Edition")
      else:
        discard
    # End of Windows 6.*

    if osvi.majorVersion == 5 and osvi.minorVersion == 2:
      if getSystemMetrics(SM_SERVERR2) != 0:
        result.add("Windows Server 2003 R2, ")
      elif (osvi.SuiteMask and VER_SUITE_PERSONAL) != 0: # Not sure if this will work
        result.add("Windows Storage Server 2003")
      elif (osvi.SuiteMask and VER_SUITE_WH_SERVER) != 0:
        result.add("Windows Home Server")
      elif osvi.ProductType == VER_NT_WORKSTATION and 
          si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64:
        result.add("Windows XP Professional x64 Edition")
      else:
        result.add("Windows Server 2003, ")
      
      # Test for the specific product
      if osvi.ProductType != VER_NT_WORKSTATION:
        if ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_IA64:
          if (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0:
            result.add("Datacenter Edition for Itanium-based Systems")
          elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0:
            result.add("Enterprise Edition for Itanium-based Systems")
        elif ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_AMD64:
          if (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0:
            result.add("Datacenter x64 Edition")
          elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0:
            result.add("Enterprise x64 Edition")
          else:
            result.add("Standard x64 Edition")
        else:
          if (osvi.SuiteMask and VER_SUITE_COMPUTE_SERVER) != 0:
            result.add("Compute Cluster Edition")
          elif (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0:
            result.add("Datacenter Edition")
          elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0:
            result.add("Enterprise Edition")
          elif (osvi.SuiteMask and VER_SUITE_BLADE) != 0:
            result.add("Web Edition")
          else:
            result.add("Standard Edition")
    # End of 5.2
    
    if osvi.majorVersion == 5 and osvi.minorVersion == 1:
      result.add("Windows XP ")
      if (osvi.SuiteMask and VER_SUITE_PERSONAL) != 0:
        result.add("Home Edition")
      else:
        result.add("Professional")
    # End of 5.1
    
    if osvi.majorVersion == 5 and osvi.minorVersion == 0:
      result.add("Windows 2000 ")
      if osvi.ProductType == VER_NT_WORKSTATION:
        result.add("Professional")
      else:
        if (osvi.SuiteMask and VER_SUITE_DATACENTER) != 0:
          result.add("Datacenter Server")
        elif (osvi.SuiteMask and VER_SUITE_ENTERPRISE) != 0:
          result.add("Advanced Server")
        else:
          result.add("Server")
    # End of 5.0
    
    # Include service pack (if any) and build number.
    if len(osvi.SPVersion) > 0:
      result.add(" ")
      result.add(osvi.SPVersion)
    
    result.add(" (build " & $osvi.buildNumber & ")")
    
    if osvi.majorVersion >= 6:
      if ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_AMD64:
        result.add(", 64-bit")
      elif ze(si.wProcessorArchitecture) == PROCESSOR_ARCHITECTURE_INTEL:
        result.add(", 32-bit")
    
  else:
    # Windows 98 etc...
    result = "Unknown version of windows[Kernel version <= 4]"
    

proc getFileSize*(file: string): BiggestInt =
  var fileData: TWIN32_FIND_DATA

  when useWinUnicode:
    var aa = newWideCString(file)
    var hFile = findFirstFileW(aa, fileData)
  else:
    var hFile = findFirstFileA(file, fileData)
  
  if hFile == INVALID_HANDLE_VALUE:
    raise newException(IOError, $getLastError())
  
  return fileData.nFileSizeLow

proc getDiskFreeSpaceEx*(lpDirectoryName: cstring, lpFreeBytesAvailableToCaller,
                         lpTotalNumberOfBytes,
                         lpTotalNumberOfFreeBytes: var TFiletime): WINBOOL{.
    stdcall, dynlib: "kernel32", importc: "GetDiskFreeSpaceExA".}

proc getPartitionInfo*(partition: string): TPartitionInfo =
  ## Retrieves partition info, for example ``partition`` may be ``"C:\"``
  var freeBytes, totalBytes, totalFreeBytes: TFiletime 
  discard getDiskFreeSpaceEx(r"C:\", freeBytes, totalBytes, 
                               totalFreeBytes)
  return (freeBytes, totalBytes)

when isMainModule:
  var r = getMemoryInfo()
  echo("Memory load: ", r.MemoryLoad, "%")
  
  var osvi = getVersionInfo()
  
  echo($osvi)

  echo(getFileSize(r"osinfo_win.nim") div 1024 div 1024)
  
  echo(rdFileTime(getPartitionInfo(r"C:\")[0]))
