// Windows/SystemInfo.cpp

#include "StdAfx.h"

#include "../../C/CpuArch.h"

#include "../Common/IntToString.h"

#ifdef _WIN32

#include "Registry.h"

#else

#include <unistd.h>
#include <sys/utsname.h>
#ifdef __APPLE__
#include <sys/sysctl.h>
#elif !defined(_AIX)

#include <sys/auxv.h>

#ifdef MY_CPU_ARM_OR_ARM64
#include <asm/hwcap.h>
#endif
#endif

#ifdef __linux__
#include "../Windows/FileIO.h"
#endif

#endif // WIN32

#include "SystemInfo.h"
#include "System.h"

using namespace NWindows;

#ifdef __linux__

static bool ReadFile_to_Buffer(CFSTR fileName, CByteBuffer &buf)
{
  NWindows::NFile::NIO::CInFile file;
  if (!file.Open(fileName))
    return false;
  /*
  UInt64 size;
  if (!file.GetLength(size))
  {
    // GetLength() doesn't work "/proc/cpuinfo"
    return false;
  }
  if (size >= ((UInt32)1 << 29))
    return false;
  */
  size_t size = 0;
  size_t addSize = ((size_t)1 << 12);
  for (;;)
  {
    // printf("\nsize = %d\n", (unsigned)size);
    buf.ChangeSize_KeepData(size + addSize, size);
    size_t processed;
    if (!file.ReadFull(buf + size, addSize, processed))
      return false;
    if (processed == 0)
    {
      buf.ChangeSize_KeepData(size, size);
      return true;
    }
    size += processed;
    addSize *= 2;
  }
}

#endif


#ifndef __APPLE__
static void PrintHex(AString &s, UInt64 v)
{
  char temp[32];
  ConvertUInt64ToHex(v, temp);
  s += temp;
}
#endif

#ifdef MY_CPU_X86_OR_AMD64

static void PrintCpuChars(AString &s, UInt32 v)
{
  for (int j = 0; j < 4; j++)
  {
    Byte b = (Byte)(v & 0xFF);
    v >>= 8;
    if (b == 0)
      break;
    s += (char)b;
  }
}


static void x86cpuid_to_String(const Cx86cpuid &c, AString &s, AString &ver)
{
  s.Empty();

  UInt32 maxFunc2 = 0;
  UInt32 t[3];

  MyCPUID(0x80000000, &maxFunc2, &t[0], &t[1], &t[2]);

  bool fullNameIsAvail = (maxFunc2 >= 0x80000004);

  if (fullNameIsAvail)
  {
    for (unsigned i = 0; i < 3; i++)
    {
      UInt32 d[4] = { 0 };
      MyCPUID(0x80000002 + i, &d[0], &d[1], &d[2], &d[3]);
      for (unsigned j = 0; j < 4; j++)
        PrintCpuChars(s, d[j]);
    }
  }

  s.Trim();
  
  if (s.IsEmpty())
  {
    for (int i = 0; i < 3; i++)
      PrintCpuChars(s, c.vendor[i]);
    s.Trim();
  }

  {
    char temp[32];
    ConvertUInt32ToHex(c.ver, temp);
    ver += temp;
  }
}

/*
static void x86cpuid_all_to_String(AString &s)
{
  Cx86cpuid p;
  if (!x86cpuid_CheckAndRead(&p))
    return;
  s += "x86cpuid maxFunc = ";
  s.Add_UInt32(p.maxFunc);
  for (unsigned j = 0; j <= p.maxFunc; j++)
  {
    s.Add_LF();
    // s.Add_UInt32(j); // align
    {
      char temp[32];
      ConvertUInt32ToString(j, temp);
      unsigned len = (unsigned)strlen(temp);
      while (len < 8)
      {
        len++;
        s.Add_Space();
      }
      s += temp;
    }

    s += ":";
    UInt32 d[4] = { 0 };
    MyCPUID(j, &d[0], &d[1], &d[2], &d[3]);
    for (unsigned i = 0; i < 4; i++)
    {
      char temp[32];
      ConvertUInt32ToHex8Digits(d[i], temp);
      s += " ";
      s += temp;
    }
  }
}
*/

#endif



#ifdef _WIN32

static const char * const k_PROCESSOR_ARCHITECTURE[] =
{
    "x86" // "INTEL"
  , "MIPS"
  , "ALPHA"
  , "PPC"
  , "SHX"
  , "ARM"
  , "IA64"
  , "ALPHA64"
  , "MSIL"
  , "x64" // "AMD64"
  , "IA32_ON_WIN64"
  , "NEUTRAL"
  , "ARM64"
  , "ARM32_ON_WIN64"
};

#define MY__PROCESSOR_ARCHITECTURE_INTEL 0
#define MY__PROCESSOR_ARCHITECTURE_AMD64 9


#define MY__PROCESSOR_INTEL_PENTIUM  586
#define MY__PROCESSOR_AMD_X8664      8664

/*
static const CUInt32PCharPair k_PROCESSOR[] =
{
  { 2200, "IA64" },
  { 8664, "x64" }
};

#define PROCESSOR_INTEL_386      386
#define PROCESSOR_INTEL_486      486
#define PROCESSOR_INTEL_PENTIUM  586
#define PROCESSOR_INTEL_860      860
#define PROCESSOR_INTEL_IA64     2200
#define PROCESSOR_AMD_X8664      8664
#define PROCESSOR_MIPS_R2000     2000
#define PROCESSOR_MIPS_R3000     3000
#define PROCESSOR_MIPS_R4000     4000
#define PROCESSOR_ALPHA_21064    21064
#define PROCESSOR_PPC_601        601
#define PROCESSOR_PPC_603        603
#define PROCESSOR_PPC_604        604
#define PROCESSOR_PPC_620        620
#define PROCESSOR_HITACHI_SH3    10003
#define PROCESSOR_HITACHI_SH3E   10004
#define PROCESSOR_HITACHI_SH4    10005
#define PROCESSOR_MOTOROLA_821   821
#define PROCESSOR_SHx_SH3        103
#define PROCESSOR_SHx_SH4        104
#define PROCESSOR_STRONGARM      2577    // 0xA11
#define PROCESSOR_ARM720         1824    // 0x720
#define PROCESSOR_ARM820         2080    // 0x820
#define PROCESSOR_ARM920         2336    // 0x920
#define PROCESSOR_ARM_7TDMI      70001
#define PROCESSOR_OPTIL          18767   // 0x494f
*/


/*
static const char * const k_PF[] =
{
    "FP_ERRATA"
  , "FP_EMU"
  , "CMPXCHG"
  , "MMX"
  , "PPC_MOVEMEM_64BIT"
  , "ALPHA_BYTE"
  , "SSE"
  , "3DNOW"
  , "RDTSC"
  , "PAE"
  , "SSE2"
  , "SSE_DAZ"
  , "NX"
  , "SSE3"
  , "CMPXCHG16B"
  , "CMP8XCHG16"
  , "CHANNELS"
  , "XSAVE"
  , "ARM_VFP_32"
  , "ARM_NEON"
  , "L2AT"
  , "VIRT_FIRMWARE"
  , "RDWRFSGSBASE"
  , "FASTFAIL"
  , "ARM_DIVIDE"
  , "ARM_64BIT_LOADSTORE_ATOMIC"
  , "ARM_EXTERNAL_CACHE"
  , "ARM_FMAC"
  , "RDRAND"
  , "ARM_V8"
  , "ARM_V8_CRYPTO"
  , "ARM_V8_CRC32"
  , "RDTSCP"
  , "RDPID"
  , "ARM_V81_ATOMIC"
  , "MONITORX"
};
*/

#endif


#ifdef _WIN32

static void PrintPage(AString &s, UInt32 v)
{
  if ((v & 0x3FF) == 0)
  {
    s.Add_UInt32(v >> 10);
    s += "K";
  }
  else
    s.Add_UInt32(v >> 10);
}

static AString TypeToString2(const char * const table[], unsigned num, UInt32 value)
{
  char sz[16];
  const char *p = NULL;
  if (value < num)
    p = table[value];
  if (!p)
  {
    ConvertUInt32ToString(value, sz);
    p = sz;
  }
  return (AString)p;
}

// #if defined(_7ZIP_LARGE_PAGES) || defined(_WIN32)
// #ifdef _WIN32
void PrintSize_KMGT_Or_Hex(AString &s, UInt64 v)
{
  char c = 0;
  if ((v & 0x3FF) == 0) { v >>= 10; c = 'K';
  if ((v & 0x3FF) == 0) { v >>= 10; c = 'M';
  if ((v & 0x3FF) == 0) { v >>= 10; c = 'G';
  if ((v & 0x3FF) == 0) { v >>= 10; c = 'T';
  }}}}
  else
  {
    PrintHex(s, v);
    return;
  }
  char temp[32];
  ConvertUInt64ToString(v, temp);
  s += temp;
  if (c)
    s += c;
}
// #endif
// #endif

static void SysInfo_To_String(AString &s, const SYSTEM_INFO &si)
{
  s += TypeToString2(k_PROCESSOR_ARCHITECTURE, ARRAY_SIZE(k_PROCESSOR_ARCHITECTURE), si.wProcessorArchitecture);

  if (!( (si.wProcessorArchitecture == MY__PROCESSOR_ARCHITECTURE_INTEL && si.dwProcessorType == MY__PROCESSOR_INTEL_PENTIUM)
      || (si.wProcessorArchitecture == MY__PROCESSOR_ARCHITECTURE_AMD64 && si.dwProcessorType == MY__PROCESSOR_AMD_X8664)))
  {
    s += " ";
    // s += TypePairToString(k_PROCESSOR, ARRAY_SIZE(k_PROCESSOR), si.dwProcessorType);
    s.Add_UInt32(si.dwProcessorType);
  }
  s += " ";
  PrintHex(s, si.wProcessorLevel);
  s += ".";
  PrintHex(s, si.wProcessorRevision);
  if ((UInt64)si.dwActiveProcessorMask + 1 != ((UInt64)1 << si.dwNumberOfProcessors))
  if ((UInt64)si.dwActiveProcessorMask + 1 != 0 || si.dwNumberOfProcessors != sizeof(UInt64) * 8)
  {
    s += " act:";
    PrintHex(s, si.dwActiveProcessorMask);
  }
  s += " cpus:";
  s.Add_UInt32(si.dwNumberOfProcessors);
  if (si.dwPageSize != 1 << 12)
  {
    s += " page:";
    PrintPage(s, si.dwPageSize);
  }
  if (si.dwAllocationGranularity != 1 << 16)
  {
    s += " gran:";
    PrintPage(s, si.dwAllocationGranularity);
  }
  s += " ";

  DWORD_PTR minAdd = (DWORD_PTR)si.lpMinimumApplicationAddress;
  UInt64 maxSize = (UInt64)(DWORD_PTR)si.lpMaximumApplicationAddress + 1;
  const UInt32 kReserveSize = ((UInt32)1 << 16);
  if (minAdd != kReserveSize)
  {
    PrintSize_KMGT_Or_Hex(s, minAdd);
    s += "-";
  }
  else
  {
    if ((maxSize & (kReserveSize - 1)) == 0)
      maxSize += kReserveSize;
  }
  PrintSize_KMGT_Or_Hex(s, maxSize);
}

#ifndef _WIN64
EXTERN_C_BEGIN
typedef VOID (WINAPI *Func_GetNativeSystemInfo)(LPSYSTEM_INFO lpSystemInfo);
EXTERN_C_END
#endif

#endif

#ifdef __APPLE__
#ifndef MY_CPU_X86_OR_AMD64
static void Add_sysctlbyname_to_String(const char *name, AString &s)
{
  size_t bufSize = 256;
  char buf[256];
  if (My_sysctlbyname_Get(name, &buf, &bufSize) == 0)
    s += buf;
}
#endif
#endif

void GetSysInfo(AString &s1, AString &s2);
void GetSysInfo(AString &s1, AString &s2)
{
  s1.Empty();
  s2.Empty();

  #ifdef _WIN32
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    {
      SysInfo_To_String(s1, si);
      // s += " : ";
    }
    
    #if !defined(_WIN64) && !defined(UNDER_CE)
    Func_GetNativeSystemInfo fn_GetNativeSystemInfo = (Func_GetNativeSystemInfo)(void *)GetProcAddress(
        GetModuleHandleA("kernel32.dll"), "GetNativeSystemInfo");
    if (fn_GetNativeSystemInfo)
    {
      SYSTEM_INFO si2;
      fn_GetNativeSystemInfo(&si2);
      // if (memcmp(&si, &si2, sizeof(si)) != 0)
      {
        // s += " - ";
        SysInfo_To_String(s2, si2);
      }
    }
    #endif
  #endif
}


void GetCpuName(AString &s);

static void AddBracedString(AString &dest, AString &src)
{
  if (!src.IsEmpty())
  {
    AString s;
    s += '(';
    s += src;
    s += ')';
    dest.Add_OptSpaced(s);
  }
}

struct CCpuName
{
  AString CpuName;
  AString Revision;
  AString Microcode;
  AString LargePages;

  void Fill();

  void Get_Revision_Microcode_LargePages(AString &s)
  {
    s.Empty();
    AddBracedString(s, Revision);
    AddBracedString(s, Microcode);
    s.Add_OptSpaced(LargePages);
  }
};

void CCpuName::Fill()
{
  CpuName.Empty();
  Revision.Empty();
  Microcode.Empty();
  LargePages.Empty();

  AString &s = CpuName;

  #ifdef MY_CPU_X86_OR_AMD64
  {
    Cx86cpuid cpuid;
    if (x86cpuid_CheckAndRead(&cpuid))
    {
      x86cpuid_to_String(cpuid, s, Revision);
    }
    else
    {
    #ifdef MY_CPU_AMD64
    s += "x64";
    #else
    s += "x86";
    #endif
    }
  }
  #elif defined(__APPLE__)
  {
    Add_sysctlbyname_to_String("machdep.cpu.brand_string", s);
  }
  #endif


  if (s.IsEmpty())
  {
    #ifdef MY_CPU_LE
      s += "LE";
    #elif defined(MY_CPU_BE)
      s += "BE";
    #endif
  }
  
  #ifdef __APPLE__
  {
    AString s2;
    UInt32 v = 0;
    if (My_sysctlbyname_Get_UInt32("machdep.cpu.core_count", &v) == 0)
    {
      s2.Add_UInt32(v);
      s2 += 'C';
    }
    if (My_sysctlbyname_Get_UInt32("machdep.cpu.thread_count", &v) == 0)
    {
      s2.Add_UInt32(v);
      s2 += 'T';
    }
    if (!s2.IsEmpty())
    {
      s.Add_Space_if_NotEmpty();
      s += s2;
    }
  }
  #endif

  
  #ifdef _WIN32
  {
    NRegistry::CKey key;
    if (key.Open(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), KEY_READ) == ERROR_SUCCESS)
    {
      LONG res[2];
      CByteBuffer bufs[2];
      {
        for (int i = 0; i < 2; i++)
        {
          UInt32 size = 0;
          res[i] = key.QueryValue(i == 0 ?
            TEXT("Previous Update Revision") :
            TEXT("Update Revision"), bufs[i], size);
          if (res[i] == ERROR_SUCCESS)
            if (size != bufs[i].Size())
              res[i] = ERROR_SUCCESS + 1;
        }
      }
      if (res[0] == ERROR_SUCCESS || res[1] == ERROR_SUCCESS)
      {
        for (int i = 0; i < 2; i++)
        {
          if (i == 1)
            Microcode += "->";
          if (res[i] != ERROR_SUCCESS)
            continue;
          const CByteBuffer &buf = bufs[i];
          if (buf.Size() == 8)
          {
            UInt32 high = GetUi32(buf);
            if (high != 0)
            {
              PrintHex(Microcode, high);
              Microcode += ".";
            }
            PrintHex(Microcode, GetUi32(buf + 4));
          }
        }
      }
    }
  }
  #endif


  #ifdef _7ZIP_LARGE_PAGES
  Add_LargePages_String(LargePages);
  #endif
}

void AddCpuFeatures(AString &s);
void AddCpuFeatures(AString &s)
{
  #ifdef _WIN32
  // const unsigned kNumFeatures_Extra = 32; // we check also for unknown features
  // const unsigned kNumFeatures = ARRAY_SIZE(k_PF) + kNumFeatures_Extra;
  const unsigned kNumFeatures = 64;
  UInt64 flags = 0;
  for (unsigned i = 0; i < kNumFeatures; i++)
  {
    if (IsProcessorFeaturePresent(i))
    {
      flags += (UInt64)1 << i;
      // s.Add_Space_if_NotEmpty();
      // s += TypeToString2(k_PF, ARRAY_SIZE(k_PF), i);
    }
  }
  s.Add_OptSpaced("f:");
  PrintHex(s, flags);
  
  #elif defined(__APPLE__)
  {
    UInt32 v = 0;
    if (My_sysctlbyname_Get_UInt32("hw.pagesize", &v) == 0)
    {
      s += "PageSize:";
      s.Add_UInt32(v >> 10);
      s += "KB";
    }
  }

  #else

  const long v = sysconf(_SC_PAGESIZE);
  if (v != -1)
  {
    s.Add_Space_if_NotEmpty();
    s += "PageSize:";
    s.Add_UInt32((UInt32)(v >> 10));
    s += "KB";
  }

  #if !defined(_AIX)

  #ifdef __linux__

  CByteBuffer buf;
  if (ReadFile_to_Buffer("/sys/kernel/mm/transparent_hugepage/enabled", buf))
  // if (ReadFile_to_Buffer("/proc/cpuinfo", buf))
  {
    s.Add_OptSpaced("THP:");
    AString s2;
    s2.SetFrom_CalcLen((const char *)(const void *)(const Byte *)buf, (unsigned)buf.Size());
    const int pos = s2.Find('[');
    if (pos >= 0)
    {
      const int pos2 = s2.Find(']', pos + 1);
      if (pos2 >= 0)
      {
        s2.DeleteFrom(pos2);
        s2.DeleteFrontal(pos + 1);
      }
    }
    s += s2;
  }
  // else throw CSystemException(MY_SRes_HRESULT_FROM_WRes(errno));

  #endif


  s.Add_OptSpaced("hwcap:");
  {
    unsigned long h = getauxval(AT_HWCAP);
    PrintHex(s, h);
    #ifdef MY_CPU_ARM64
    if (h & HWCAP_CRC32)  s += ":CRC32";
    if (h & HWCAP_SHA1)   s += ":SHA1";
    if (h & HWCAP_SHA2)   s += ":SHA2";
    if (h & HWCAP_AES)    s += ":AES";
    if (h & HWCAP_ASIMD)  s += ":ASIMD";
    #elif defined(MY_CPU_ARM)
    if (h & HWCAP_NEON)   s += ":NEON";
    #endif
  }

  {
    unsigned long h = getauxval(AT_HWCAP2);
    #ifndef MY_CPU_ARM
    if (h != 0)
    #endif
    {
      s += " hwcap2:";
      PrintHex(s, h);
      #ifdef MY_CPU_ARM
      if (h & HWCAP2_CRC32)  s += ":CRC32";
      if (h & HWCAP2_SHA1)   s += ":SHA1";
      if (h & HWCAP2_SHA2)   s += ":SHA2";
      if (h & HWCAP2_AES)    s += ":AES";
      #endif
    }
  }
  #endif // _AIX
  #endif // _WIN32
}


#ifdef _WIN32
#ifndef UNDER_CE

EXTERN_C_BEGIN
typedef void (WINAPI * Func_RtlGetVersion) (OSVERSIONINFOEXW *);
EXTERN_C_END

static BOOL My_RtlGetVersion(OSVERSIONINFOEXW *vi)
{
  HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
  if (!ntdll)
    return FALSE;
  Func_RtlGetVersion func = (Func_RtlGetVersion)(void *)GetProcAddress(ntdll, "RtlGetVersion");
  if (!func)
    return FALSE;
  func(vi);
  return TRUE;
}

#endif
#endif


void GetOsInfoText(AString &sRes)
{
  sRes.Empty();
    AString s;

    #ifdef _WIN32
    #ifndef UNDER_CE
      // OSVERSIONINFO vi;
      OSVERSIONINFOEXW vi;
      vi.dwOSVersionInfoSize = sizeof(vi);
      // if (::GetVersionEx(&vi))
      if (My_RtlGetVersion(&vi))
      {
        s += "Windows";
        if (vi.dwPlatformId != VER_PLATFORM_WIN32_NT)
          s.Add_UInt32(vi.dwPlatformId);
        s += " "; s.Add_UInt32(vi.dwMajorVersion);
        s += "."; s.Add_UInt32(vi.dwMinorVersion);
        s += " "; s.Add_UInt32(vi.dwBuildNumber);

        if (vi.wServicePackMajor != 0 || vi.wServicePackMinor != 0)
        {
          s += " SP:"; s.Add_UInt32(vi.wServicePackMajor);
          s += "."; s.Add_UInt32(vi.wServicePackMinor);
        }
        // s += " Suite:"; PrintHex(s, vi.wSuiteMask);
        // s += " Type:"; s.Add_UInt32(vi.wProductType);
        // s += " "; s += GetOemString(vi.szCSDVersion);
      }
      /*
      {
        s += " OEMCP:"; s.Add_UInt32(GetOEMCP());
        s += " ACP:";   s.Add_UInt32(GetACP());
      }
      */
    #endif
    #else // _WIN32

      if (!s.IsEmpty())
        s.Add_LF();
      struct utsname un;
      if (uname(&un) == 0)
      {
        s += un.sysname;
        // s += " : "; s += un.nodename; // we don't want to show name of computer
        s += " : "; s += un.release;
        s += " : "; s += un.version;
        s += " : "; s += un.machine;

        #ifdef __APPLE__
          // Add_sysctlbyname_to_String("kern.version", s);
          // it's same as "utsname.version"
        #endif
      }
    #endif  // _WIN32

    sRes += s;
}



void GetSystemInfoText(AString &sRes)
{
  GetOsInfoText(sRes);
  sRes.Add_LF();

    {
      AString s, s1, s2;
      GetSysInfo(s1, s2);
      if (!s1.IsEmpty() || !s2.IsEmpty())
      {
        s = s1;
        if (s1 != s2 && !s2.IsEmpty())
        {
          s += " - ";
          s += s2;
        }
      }
      {
        AddCpuFeatures(s);
        if (!s.IsEmpty())
        {
          sRes += s;
          sRes.Add_LF();
        }
      }
    }
    {
      AString s;
      GetCpuName(s);
      if (!s.IsEmpty())
      {
        sRes += s;
        sRes.Add_LF();
      }
    }
    /*
    #ifdef MY_CPU_X86_OR_AMD64
    {
      AString s;
      x86cpuid_all_to_String(s);
      if (!s.IsEmpty())
      {
        printCallback->Print(s);
        printCallback->NewLine();
      }
    }
    #endif
    */
}


void GetCpuName(AString &s);
void GetCpuName(AString &s)
{
  CCpuName cpuName;
  cpuName.Fill();
  s = cpuName.CpuName;
  AString s2;
  cpuName.Get_Revision_Microcode_LargePages(s2);
  s.Add_OptSpaced(s2);
}


void GetCpuName_MultiLine(AString &s);
void GetCpuName_MultiLine(AString &s)
{
  CCpuName cpuName;
  cpuName.Fill();
  s = cpuName.CpuName;
  AString s2;
  cpuName.Get_Revision_Microcode_LargePages(s2);
  if (!s2.IsEmpty())
  {
    s.Add_LF();
    s += s2;
  }
}

void GetCompiler(AString &s)
{
  #ifdef __VERSION__
    s += __VERSION__;
  #endif

  #ifdef __GNUC__
    s += " GCC ";
    s.Add_UInt32(__GNUC__);
    s += '.';
    s.Add_UInt32(__GNUC_MINOR__);
    s += '.';
    s.Add_UInt32(__GNUC_PATCHLEVEL__);
  #endif

  #ifdef __clang__
    s += " CLANG ";
    s.Add_UInt32(__clang_major__);
    s += '.';
    s.Add_UInt32(__clang_minor__);
  #endif

  #ifdef __xlC__
    s += " XLC ";
    s.Add_UInt32(__xlC__ >> 8);
    s += '.';
    s.Add_UInt32(__xlC__ & 0xFF);
    #ifdef __xlC_ver__
      s += '.';
      s.Add_UInt32(__xlC_ver__ >> 8);
      s += '.';
      s.Add_UInt32(__xlC_ver__ & 0xFF);
    #endif
  #endif

  #ifdef _MSC_VER
    s += " MSC ";
    s.Add_UInt32(_MSC_VER);
  #endif
}
