#include "Version.h"
#include "Util.h"
#include <stdio.h>
#include <QtWidgets>
#include <QScreen>


#ifdef __APPLE__
#  include "TargetConditionals.h"
#elif defined(_WIN32)
#  include <windows.h>
#else
#  include <unistd.h>
#  include <sys/utsname.h>
#  include <sys/sysctl.h>
#  include <sys/types.h>
#endif


void getSystemInfo(SystemInfo& a_info)
{
    a_info.m_cpuType = "unknown";
    a_info.m_osType = "unknown";
    a_info.m_osVersion = "unknown";

#ifdef _WIN32
    a_info.m_osType = "Windows";

    // Get RAM size
    MEMORYSTATUSEX status;
    status.dwLength = sizeof(status);
    GlobalMemoryStatusEx(&status);
    a_info.m_ramSize = status.ullTotalPhys;

    // Get OS version
    const OSVERSIONINFO osver = winOsVersion();
    a_info.m_osVersion.reserve(512);
    snprintf(a_info.m_osVersion.data(), "%i.%i.%i", osver.dwMajorVersion, osver.dwMinorVersion, osver.dwBuildNumber);

    // Get OS arch
    SYSTEM_INFO info;
    GetNativeSystemInfo(&info);
# ifdef _WIN64
    //if (info.wProcessorArchitecture == ???)
    a_info.m_cpuType = "x86_64";
# else
    a_info.m_cpuType = "x86";
# endif
#else
    long pages = sysconf(_SC_PHYS_PAGES);
    long page_size = sysconf(_SC_PAGE_SIZE);
    a_info.m_ramSize = pages * page_size;
    struct utsname u;
    long ret = uname(&u);
    if (ret != -1)
    {
        a_info.m_cpuType = u.machine;
        a_info.m_osVersion = u.sysname + std::string(" ") + u.release;
    }
# ifdef __APPLE__
    // Get RAM Size
    int mib[2] = { CTL_HW, HW_MEMSIZE };
    size_t len = sizeof(uint64_t);
    sysctl(mib, sizeof(mib) / sizeof(mib[0]), &u.ramSize, &len, NULL, 0);
#  if TARGET_OS_IPHONE
    a_info.m_osType = "iOS";
#  elif TARGET_OS_MAC
    const char *macCodeName = "";
    switch (int(MacintoshVersion)) {
        case MV_CHEETAH:     macCodeName = " Cheetah"; break;
        case MV_PUMA:        macCodeName = " Puma"; break;
        case MV_JAGUAR:      macCodeName = " Jaguar"; break;
        case MV_PANTHER:     macCodeName = " Panther"; break;
        case MV_TIGER:       macCodeName = " Tiger"; break;
        case MV_LEOPARD:     macCodeName = " Leopard"; break;
        case MV_SNOWLEOPARD: macCodeName = " Snow Leopard"; break;
        case MV_LION:        macCodeName = " Lion"; break;
        case MV_MOUNTAINLION:macCodeName = " Mountain Lion"; break;
        case MV_MAVERICKS:   macCodeName = " Mavericks"; break;
        case MV_YOSEMITE:    macCodeName = " Yosemite"; break;
    }
    a_info.m_osType = "Mac OS X" + std::string(macCodeName);
#  endif
# elif __ANDROID__
    a_info.m_osType = "Android";
# elif __linux
    // system("lsb_release -sd");
    FILE* f = fopen("/etc/lsb-release", "rt");
    char dist[64] = { 0 };
    if (f) {
        char buf[128];
        while (fgets(buf, 128, f) != 0)
            if (sscanf(buf, "DISTRIB_DESCRIPTION=%64c", dist) == 1)
                break;
        if (dist[0]) {
            std::vector<std::string> strBits = split(dist, '"');
            a_info.m_osType = strBits[(strBits.size()==3)?1:0];
        } else {
            a_info.m_osType = "non-specific LSB";
        }
        fclose(f);
    } else {
        a_info.m_osType = "non-LSB";
    }
# else
    a_info.m_osType = "Generic UNIX";
# endif
#endif

    // TODO: improve the CPU detection using libcpuid

    // Make human readable RAM size string
    const char* humanUnitStrings[] = { " bytes", " KB", " MB", " GB", " TB" };
    int ramUnit = 0;
    uint64_t ramSize = a_info.m_ramSize;
    while (ramSize > 1024 && ramUnit < (YQ_ARRAY_SIZE(humanUnitStrings)-1))
        ramUnit++, ramSize /= 1000;
    a_info.m_ramSizeStr = std::to_string(ramSize) + humanUnitStrings[ramUnit];

    // Obtain the screen properties
    QScreen *s = QApplication::screens()[0];
    int refreshRate = (s) ? s->refreshRate() : 0;
    int depth = (s) ? s->depth() : 0;
    a_info.m_resolutionWidth = (s) ? s->geometry().width() : 0;
    a_info.m_resolutionHeight = (s) ? s->geometry().height() : 0;
    a_info.m_resolutionStr = std::to_string(a_info.m_resolutionWidth) + "x"
        + std::to_string(a_info.m_resolutionHeight) + " "
        + std::to_string(depth) + "bit "
        + "@" + std::to_string(refreshRate) + "Hz";
        ;
}
