// Copyright 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "aemu/base/EintrWrapper.h"
#include "aemu/base/StringFormat.h"
#include "aemu/base/system/System.h"
#include "aemu/base/threads/Thread.h"

#ifdef _WIN32
#include <windows.h>

#include "aemu/base/system/Win32UnicodeString.h"
#include "aemu/base/msvc.h"
#endif

#include <vector>

#ifdef __QNX__
#include <fcntl.h>
#include <devctl.h>
#include <sys/procfs.h>
#endif

#ifdef __APPLE__
#include <libproc.h>
#include <mach/clock.h>
#include <mach/mach.h>
#endif  // __APPLE__

#ifdef _MSC_VER
// #include "aemu/base/msvc.h"
// #include <dirent.h>
#else
#include <time.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/resource.h>
#include <unistd.h>
#endif

#include <string.h>

using FileSize = uint64_t;

#ifdef _WIN32

using android::base::Win32UnicodeString;

// Return |path| as a Unicode string, while discarding trailing separators.
Win32UnicodeString win32Path(const char* path) {
    Win32UnicodeString wpath(path);
    // Get rid of trailing directory separators, Windows doesn't like them.
    size_t size = wpath.size();
    while (size > 0U &&
           (wpath[size - 1U] == L'\\' || wpath[size - 1U] == L'/')) {
        size--;
    }
    if (size < wpath.size()) {
        wpath.resize(size);
    }
    return wpath;
}

using PathStat = struct _stat64;

#else  // _WIN32

using PathStat = struct stat;

#endif  // _WIN32

namespace {

struct TickCountImpl {
private:
    uint64_t mStartTimeUs;
#ifdef _WIN32
    long long mFreqPerSec = 0;    // 0 means 'high perf counter isn't available'
#elif defined(__APPLE__)
    clock_serv_t mClockServ;
#endif

public:
    TickCountImpl() {
#ifdef _WIN32
        LARGE_INTEGER freq;
        if (::QueryPerformanceFrequency(&freq)) {
            mFreqPerSec = freq.QuadPart;
        }
#elif defined(__APPLE__)
        host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &mClockServ);
#endif
        mStartTimeUs = getUs();
    }

#ifdef __APPLE__
    ~TickCountImpl() {
        mach_port_deallocate(mach_task_self(), mClockServ);
    }
#endif

    uint64_t getStartTimeUs() const {
        return mStartTimeUs;
    }

    uint64_t getUs() const {
#ifdef _WIN32
    if (!mFreqPerSec) {
        return ::GetTickCount() * 1000;
    }
    LARGE_INTEGER now;
    ::QueryPerformanceCounter(&now);
    return (now.QuadPart * 1000000ULL) / mFreqPerSec;
#elif defined __linux__ || defined __QNX__
    timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
#else // APPLE
    mach_timespec_t mts;
    clock_get_time(mClockServ, &mts);
    return mts.tv_sec * 1000000LL + mts.tv_nsec / 1000;
#endif
    }
};

// This is, maybe, the only static variable that may not be a LazyInstance:
// it holds the actual timestamp at startup, and has to be initialized as
// soon as possible after the application launch.
static const TickCountImpl kTickCount;

}  // namespace

namespace android {
namespace base {

std::string getEnvironmentVariable(const std::string& key) {
#ifdef _WIN32
    Win32UnicodeString varname_unicode(key);
    const wchar_t* value = _wgetenv(varname_unicode.c_str());
    if (!value) {
        return std::string();
    } else {
        return Win32UnicodeString::convertToUtf8(value);
    }
#else
    const char* value = getenv(key.c_str());
    if (!value) {
        value = "";
    }
    return std::string(value);
#endif
}

void setEnvironmentVariable(const std::string& key, const std::string& value) {
#ifdef _WIN32
    std::string envStr =
        StringFormat("%s=%s", key, value);
    // Note: this leaks the result of release().
    _wputenv(Win32UnicodeString(envStr).release());
#else
    if (value.empty()) {
        unsetenv(key.c_str());
    } else {
        setenv(key.c_str(), value.c_str(), 1);
    }
#endif
}

bool isVerboseLogging() {
    return false;
}

int fdStat(int fd, PathStat* st) {
#ifdef _WIN32
    return _fstat64(fd, st);
#else   // !_WIN32
    return HANDLE_EINTR(fstat(fd, st));
#endif  // !_WIN32
}

bool getFileSize(int fd, uint64_t* outFileSize) {
    if (fd < 0) {
        return false;
    }
    PathStat st;
    int ret = fdStat(fd, &st);
#ifdef _WIN32
    if (ret < 0 || !(st.st_mode & _S_IFREG)) {
        return false;
    }
#else
    if (ret < 0 || !S_ISREG(st.st_mode)) {
        return false;
    }
#endif
    // This is off_t on POSIX and a 32/64 bit integral type on windows based on
    // the host / compiler combination. We cast everything to 64 bit unsigned to
    // play safe.
    *outFileSize = static_cast<FileSize>(st.st_size);
    return true;
}

void sleepMs(uint64_t n) {
#ifdef _WIN32
    ::Sleep(n);
#else
    usleep(n * 1000);
#endif
}

void sleepUs(uint64_t n) {
#ifdef _WIN32
    ::Sleep(n / 1000);
#else
    usleep(n);
#endif
}

void sleepToUs(uint64_t absTimeUs) {
    // Approach will vary based on platform.
    //
    // Linux/QNX has clock_nanosleep with TIMER_ABSTIME which does
    // exactly what we want, a sleep to some absolute time.
    //
    // Mac only has relative nanosleep(), so we'll need to calculate a time
    // difference.
    //
    // Windows has waitable timers. Pre Windows 10 1803, 1 ms was the best resolution. Past that, we can use high resolution waitable timers.
#ifdef __APPLE__
    uint64_t current = getHighResTimeUs();

    // Already passed deadline, return.
    if (absTimeUs < current) return;
    uint64_t diff = absTimeUs - current;

    struct timespec ts;
    ts.tv_sec = diff / 1000000ULL;
    ts.tv_nsec = diff * 1000ULL - ts.tv_sec * 1000000000ULL;
    int ret;
    do {
        ret = nanosleep(&ts, nullptr);
    } while (ret == -1 && errno == EINTR);
#elif defined __linux__ || defined __QNX__
    struct timespec ts;
    ts.tv_sec = absTimeUs / 1000000ULL;
    ts.tv_nsec = absTimeUs * 1000ULL - ts.tv_sec * 1000000000ULL;
    int ret;
    do {
        ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, nullptr);
    } while (ret == -1 && errno == EINTR);
#else // _WIN32

    // Create a persistent thread local timer object
    struct ThreadLocalTimerState {
        ThreadLocalTimerState() {
            timerHandle = CreateWaitableTimerEx(
                nullptr /* no security attributes */,
                nullptr /* no timer name */,
                CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
                TIMER_ALL_ACCESS);

            if (!timerHandle) {
                // Use an older version of waitable timer as backup.
                timerHandle = CreateWaitableTimer(nullptr, FALSE, nullptr);
            }
        }

        ~ThreadLocalTimerState() {
            if (timerHandle) {
                CloseHandle(timerHandle);
            }
        }

        HANDLE timerHandle = 0;
    };

    static thread_local ThreadLocalTimerState tl_timerInfo;

    uint64_t current = getHighResTimeUs();
    // Already passed deadline, return.
    if (absTimeUs < current) return;
    uint64_t diff = absTimeUs - current;

    // Waitable Timer appraoch

    // We failed to create ANY usable timer. Sleep instead.
    if (!tl_timerInfo.timerHandle) {
        Thread::sleepUs(diff);
        return;
    }

    LARGE_INTEGER dueTime;
    dueTime.QuadPart = -1LL * diff * 10LL; // 1 us = 1x 100ns
    SetWaitableTimer(
        tl_timerInfo.timerHandle,
        &dueTime,
        0 /* one shot timer */,
        0 /* no callback on finish */,
        NULL /* no arg to completion routine */,
        FALSE /* no suspend */);
    WaitForSingleObject(tl_timerInfo.timerHandle, INFINITE);
#endif
}

uint64_t getUnixTimeUs() {
    timeval tv;
    gettimeofday(&tv, nullptr);
    return tv.tv_sec * 1000000LL + tv.tv_usec;
}

uint64_t getHighResTimeUs() {
    return kTickCount.getUs();
}

uint64_t getUptimeMs() {
    return (kTickCount.getUs() - kTickCount.getStartTimeUs()) / 1000;
}

std::string getProgramDirectoryFromPlatform() {
    std::string res;
#if defined(__linux__)
    char path[1024];
    memset(path, 0, sizeof(path));  // happy valgrind!
    int len = readlink("/proc/self/exe", path, sizeof(path));
    if (len > 0 && len < (int)sizeof(path)) {
        char* x = ::strrchr(path, '/');
        if (x) {
            *x = '\0';
            res.assign(path);
        }
    }
#elif defined(__APPLE__)
    char s[PATH_MAX];
    auto pid = getpid();
    proc_pidpath(pid, s, sizeof(s));
    char* x = ::strrchr(s, '/');
    if (x) {
        // skip all slashes - there might be more than one
        while (x > s && x[-1] == '/') {
            --x;
        }
        *x = '\0';
        res.assign(s);
    } else {
        res.assign("<unknown-application-dir>");
    }
#elif defined(_WIN32)
    Win32UnicodeString appDir(PATH_MAX);
    int len = GetModuleFileNameW(0, appDir.data(), appDir.size());
    res.assign("<unknown-application-dir>");
    if (len > 0) {
        if (len > (int)appDir.size()) {
            appDir.resize(static_cast<size_t>(len));
            GetModuleFileNameW(0, appDir.data(), appDir.size());
        }
        std::string dir = appDir.toString();
        char* sep = ::strrchr(&dir[0], '\\');
        if (sep) {
            *sep = '\0';
            res.assign(dir.c_str());
        }
    }
#elif defined(__QNX__)
    char path[1024];
    memset(path, 0, sizeof(path));
    int fd = open ("/proc/self/exefile", O_RDONLY);
    if (fd != -1) {
        ssize_t len = read(fd, path, sizeof(path));
        if (len > 0 && len < sizeof(path)) {
            char* x = ::strrchr(path, '/');
            if (x) {
                *x = '\0';
                res.assign(path);
            }
        }
        close(fd);
    }
#else
#error "Unsupported platform!"
#endif
    return res;
}
std::string getProgramDirectory() {
    static std::string progDir;
    if (progDir.empty()) {
        progDir.assign(getProgramDirectoryFromPlatform());
    }
    return progDir;
}

std::string getLauncherDirectory() {
    return getProgramDirectory();
}

#ifdef __APPLE__
void cpuUsageCurrentThread_macImpl(uint64_t* user, uint64_t* sys);
#endif  // __APPLE__

CpuTime cpuTime() {
    CpuTime res;

    res.wall_time_us = kTickCount.getUs();

#ifdef __APPLE__
    cpuUsageCurrentThread_macImpl(
        &res.user_time_us,
        &res.system_time_us);
#elif __linux__
    struct rusage usage;
    getrusage(RUSAGE_THREAD, &usage);
    res.user_time_us =
        usage.ru_utime.tv_sec * 1000000ULL +
        usage.ru_utime.tv_usec;
    res.system_time_us =
        usage.ru_stime.tv_sec * 1000000ULL +
        usage.ru_stime.tv_usec;
#elif __QNX__
    int fd = open("/proc/self/as", O_RDONLY);
    if (fd != -1) {
        procfs_info info;
        if (devctl(fd, DCMD_PROC_INFO, &info, sizeof(info), NULL) == EOK) {
            res.user_time_us = info.utime / 1000; // time is in nanoseconds
            res.system_time_us = info.stime / 1000;
        }
        close(fd);
    }
#else // Windows
    FILETIME creation_time_struct;
    FILETIME exit_time_struct;
    FILETIME kernel_time_struct;
    FILETIME user_time_struct;
    GetThreadTimes(
        GetCurrentThread(),
        &creation_time_struct,
        &exit_time_struct,
        &kernel_time_struct,
        &user_time_struct);
    (void)creation_time_struct;
    (void)exit_time_struct;
    uint64_t user_time_100ns =
        user_time_struct.dwLowDateTime |
        ((uint64_t)user_time_struct.dwHighDateTime << 32);
    uint64_t system_time_100ns =
        kernel_time_struct.dwLowDateTime |
        ((uint64_t)kernel_time_struct.dwHighDateTime << 32);
    res.user_time_us = user_time_100ns / 10;
    res.system_time_us = system_time_100ns / 10;
#endif

    return res;
}


#ifdef _WIN32
// Based on chromium/src/base/file_version_info_win.cc's CreateFileVersionInfoWin
// Currently used to query Vulkan DLL's on the system and blacklist known
// problematic DLLs
// static
// Windows 10 funcs
typedef DWORD (*get_file_version_info_size_w_t)(LPCWSTR, LPDWORD);
typedef DWORD (*get_file_version_info_w_t)(LPCWSTR, DWORD, DWORD, LPVOID);
// Windows 8 funcs
typedef DWORD (*get_file_version_info_size_ex_w_t)(DWORD, LPCWSTR, LPDWORD);
typedef DWORD (*get_file_version_info_ex_w_t)(DWORD, LPCWSTR, DWORD, DWORD, LPVOID);
// common
typedef int (*ver_query_value_w_t)(LPCVOID, LPCWSTR, LPVOID, PUINT);
static get_file_version_info_size_w_t getFileVersionInfoSizeW_func = 0;
static get_file_version_info_w_t getFileVersionInfoW_func = 0;
static get_file_version_info_size_ex_w_t getFileVersionInfoSizeExW_func = 0;
static get_file_version_info_ex_w_t getFileVersionInfoExW_func = 0;
static ver_query_value_w_t verQueryValueW_func = 0;
static bool getFileVersionInfoFuncsAvailable = false;
static bool getFileVersionInfoExFuncsAvailable = false;
static bool canQueryFileVersion = false;

bool initFileVersionInfoFuncs() {
    // LOG(VERBOSE) << "querying file version info API...";
    if (canQueryFileVersion) return true;
    HMODULE kernelLib = GetModuleHandleA("kernelbase");
    if (!kernelLib) return false;
    // LOG(VERBOSE) << "found kernelbase.dll";
    getFileVersionInfoSizeW_func =
        (get_file_version_info_size_w_t)GetProcAddress(kernelLib, "GetFileVersionInfoSizeW");
    if (!getFileVersionInfoSizeW_func) {
        // LOG(VERBOSE) << "GetFileVersionInfoSizeW not found. Not on Windows 10?";
    } else {
        // LOG(VERBOSE) << "GetFileVersionInfoSizeW found. On Windows 10?";
    }
    getFileVersionInfoW_func =
        (get_file_version_info_w_t)GetProcAddress(kernelLib, "GetFileVersionInfoW");
    if (!getFileVersionInfoW_func) {
        // LOG(VERBOSE) << "GetFileVersionInfoW not found. Not on Windows 10?";
    } else {
        // LOG(VERBOSE) << "GetFileVersionInfoW found. On Windows 10?";
    }
    getFileVersionInfoFuncsAvailable =
        getFileVersionInfoSizeW_func && getFileVersionInfoW_func;
    if (!getFileVersionInfoFuncsAvailable) {
        getFileVersionInfoSizeExW_func =
            (get_file_version_info_size_ex_w_t)GetProcAddress(kernelLib, "GetFileVersionInfoSizeExW");
        getFileVersionInfoExW_func =
            (get_file_version_info_ex_w_t)GetProcAddress(kernelLib, "GetFileVersionInfoExW");
        getFileVersionInfoExFuncsAvailable =
            getFileVersionInfoSizeExW_func && getFileVersionInfoExW_func;
    }
    if (!getFileVersionInfoFuncsAvailable &&
        !getFileVersionInfoExFuncsAvailable) {
        // LOG(VERBOSE) << "Cannot get file version info funcs";
        return false;
    }
    verQueryValueW_func =
        (ver_query_value_w_t)GetProcAddress(kernelLib, "VerQueryValueW");
    if (!verQueryValueW_func) {
        // LOG(VERBOSE) << "VerQueryValueW not found";
        return false;
    }
    // LOG(VERBOSE) << "VerQueryValueW found. Can query file versions";
    canQueryFileVersion = true;
    return true;
}

bool queryFileVersionInfo(const char* path, int* major, int* minor, int* build_1, int* build_2) {
    if (!initFileVersionInfoFuncs()) return false;
    if (!canQueryFileVersion) return false;
    const Win32UnicodeString pathWide(path);
    DWORD dummy;
    DWORD length = 0;
    const DWORD fileVerGetNeutral = 0x02;
    if (getFileVersionInfoFuncsAvailable) {
        length = getFileVersionInfoSizeW_func(pathWide.c_str(), &dummy);
    } else if (getFileVersionInfoExFuncsAvailable) {
        length = getFileVersionInfoSizeExW_func(fileVerGetNeutral, pathWide.c_str(), &dummy);
    }
    if (length == 0) {
        // LOG(VERBOSE) << "queryFileVersionInfo: path not found: " << path.str().c_str();
        return false;
    }
    std::vector<uint8_t> data(length, 0);
    if (getFileVersionInfoFuncsAvailable) {
        if (!getFileVersionInfoW_func(pathWide.c_str(), dummy, length, data.data())) {
            // LOG(VERBOSE) << "GetFileVersionInfoW failed";
            return false;
        }
    } else if (getFileVersionInfoExFuncsAvailable) {
        if (!getFileVersionInfoExW_func(fileVerGetNeutral, pathWide.c_str(), dummy, length, data.data())) {
            // LOG(VERBOSE) << "GetFileVersionInfoExW failed";
            return false;
        }
    }
    VS_FIXEDFILEINFO* fixedFileInfo = nullptr;
    UINT fixedFileInfoLength;
    if (!verQueryValueW_func(
            data.data(),
            L"\\",
            reinterpret_cast<void**>(&fixedFileInfo),
            &fixedFileInfoLength)) {
        // LOG(VERBOSE) << "VerQueryValueW failed";
        return false;
    }
    if (major) *major = HIWORD(fixedFileInfo->dwFileVersionMS);
    if (minor) *minor = LOWORD(fixedFileInfo->dwFileVersionMS);
    if (build_1) *build_1 = HIWORD(fixedFileInfo->dwFileVersionLS);
    if (build_2) *build_2 = LOWORD(fixedFileInfo->dwFileVersionLS);
    return true;
}
#else
bool queryFileVersionInfo(const char*, int*, int*, int*, int*) {
    return false;
}
#endif // _WIN32

int getCpuCoreCount() {
#ifdef _WIN32
    SYSTEM_INFO si = {};
    ::GetSystemInfo(&si);
    return si.dwNumberOfProcessors < 1 ? 1 : si.dwNumberOfProcessors;
#else
    auto res = (int)sysconf(_SC_NPROCESSORS_ONLN);
    return res < 1 ? 1 : res;
#endif
}

} // namespace base
} // namespace android