#include "pch.h"
#include "misc.h"

#include "sccsdk.h"

#include <shlwapi.h>
#include <strsafe.h>
#include <spdlog/spdlog.h>
#include <magic_enum.hpp>

TCHAR *binToHexString(TCHAR *p, const unsigned char *cp, unsigned int count) {
    static const TCHAR hex_asc[] = TEXT("0123456789abcdef");
    while (count) {
        const unsigned char c = *cp++;
        /* put lowercase hex digits */
        *p++                  = static_cast<TCHAR>(0x20 | hex_asc[c >> 4]);
        *p++                  = static_cast<TCHAR>(0x20 | hex_asc[c & 0xf]);
        count--;
    }

    return p;
}

void RemoveTailLineBreak(TCHAR *pInputStr, int strSize) {
    size_t length;
    if (pInputStr) {
        if (StringCbLength(pInputStr, strSize, &length) == S_OK && length > 0) {
            if (pInputStr[length - 2] == '\r' && pInputStr[length - 1] == '\n') {
                pInputStr[length - 2] = pInputStr[length - 1] = 0;
            } else if (pInputStr[length - 1] == '\n') {
                pInputStr[length - 1] = 0;
            }
        }
    }
}

int RunCommand(TCHAR *pszCmd, TCHAR *pszResultBuffer, int dwResultBufferSize, unsigned long *pRetCode) {
    BOOL                bRet;
    HANDLE              hReadPipe  = nullptr;
    HANDLE              hWritePipe = nullptr;
    DWORD               retCode;
    STARTUPINFO         si;
    PROCESS_INFORMATION pi;
    SECURITY_ATTRIBUTES securityAttributes;

    if (pszCmd == nullptr) {
        SPDLOG_ERROR(TEXT("Input params Error: [{0}]"), pszCmd);
        return -ERR_INPUT_PARAMS;
    }

    if (pszResultBuffer && dwResultBufferSize > 0) {
        memset(pszResultBuffer, 0, dwResultBufferSize);
    }

    memset(&si, 0, sizeof(STARTUPINFO));
    memset(&pi, 0, sizeof(PROCESS_INFORMATION));

    // 设定管道的安全属性
    securityAttributes.bInheritHandle       = TRUE;
    securityAttributes.nLength              = sizeof(securityAttributes);
    securityAttributes.lpSecurityDescriptor = nullptr;

    // 创建匿名管道
    bRet = ::CreatePipe(&hReadPipe, &hWritePipe, &securityAttributes, 0);
    if (FALSE == bRet) {
        SPDLOG_ERROR(TEXT("CreatePipe Error"));
        return -ERR_SYS_CALL;
    }

    // 设置新进程参数
    si.cb          = sizeof(si);
    si.hStdError   = hWritePipe;
    si.hStdOutput  = hWritePipe;
    si.wShowWindow = SW_HIDE;
    si.dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

    // 创建新进程执行命令, 将执行结果写入匿名管道中
    bRet = ::CreateProcess(nullptr, (pszCmd), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi);
    if (FALSE == bRet) {
        SPDLOG_ERROR(TEXT("CreateProcess Error"));
        return -ERR_CREATE_PROCESS;
    }

    // 等待命令执行结束
    //::WaitForSingleObject(pi.hThread, INFINITE);
    ::WaitForSingleObject(pi.hThread, 3000);
    ::WaitForSingleObject(pi.hProcess, 3000);

    if (pszResultBuffer) {
        // 从匿名管道中读取结果到输出缓冲区
        ::RtlZeroMemory(pszResultBuffer, dwResultBufferSize);
        ::ReadFile(hReadPipe, pszResultBuffer, dwResultBufferSize, nullptr, nullptr);
    }

    // 获取调用程序返回值
    if (pRetCode) {
        if (GetExitCodeProcess(pi.hProcess, &retCode)) {
            *pRetCode = retCode;
        }
    }

    // 关闭句柄, 释放内存
    ::CloseHandle(pi.hThread);
    ::CloseHandle(pi.hProcess);
    ::CloseHandle(hWritePipe);
    ::CloseHandle(hReadPipe);

    RemoveTailLineBreak(pszResultBuffer, dwResultBufferSize);
    //pszResultBuffer[dwResultBufferSize - 1] = 0;
    return ERR_SUCCESS;
}

void ShowWindowsErrorMessage(const TCHAR *pMsgHead) {
    LPVOID buf;

    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM |
                          FORMAT_MESSAGE_MAX_WIDTH_MASK,
                      nullptr,
                      GetLastError(),
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      (LPSTR)&buf,
                      0,
                      nullptr)) {
        SPDLOG_ERROR(TEXT("{0} Error({1}): {2}"), pMsgHead, GetLastError(), buf);
        LocalFree(buf);
    } else {
        SPDLOG_ERROR(TEXT("{0} Unknown Error{1}."), pMsgHead, GetLastError());
    }
}

void StringReplaceAll(TCHAR *pOrigin, const TCHAR *pOldStr, const TCHAR *pNewStr) {
    using namespace std;
    const int maxSize = lstrlen(pOrigin);
    auto      src     = string(pOrigin);
    for (string::size_type pos(0); pos != string::npos; pos += lstrlen(pNewStr)) {
        if ((pos = src.find(pOldStr, pos)) != string::npos) {
            src.replace(pos, lstrlen(pOldStr), pNewStr);
        } else {
            break;
        }
    }

    memset(pOrigin, 0, maxSize);
    StringCbCopyA(pOrigin, maxSize, src.c_str());
}

void StringRemoveAll(TCHAR *pOrigin, const TCHAR *pString) {
    StringReplaceAll(pOrigin, pString, "");
}

int FindFile(const TCHAR *pPath, PFILE_LIST pFileList, const bool exitWhenMatchOne) {
    std::vector<TCHAR *> pathList;
    TCHAR                rootPath[MAX_PATH];
    HANDLE               hFind;
    WIN32_FIND_DATA      ffd;

    hFind = FindFirstFile(pPath, &ffd);

    if (INVALID_HANDLE_VALUE == hFind) {
        return ERR_ITEM_UNEXISTS;
    }

    StringCbCopy(rootPath, MAX_PATH, pPath);
    PathRemoveFileSpec(rootPath);

    do {
        if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) {
                continue;
            }
        } else {
            TCHAR tmp[MAX_PATH];
            PathCombine(tmp, rootPath, ffd.cFileName);

            pathList.push_back(_strdup(tmp));
            if (exitWhenMatchOne) {
                break;
            }
        }
    } while (FindNextFile(hFind, &ffd) != 0);

    FindClose(hFind);

    if (GetLastError() != ERROR_NO_MORE_FILES) {
        return -ERR_FIND_FILE;
    }

    pFileList->pFilePath = static_cast<PFILE_PATH>(HeapAlloc(GetProcessHeap(), 0, pathList.size() * sizeof(FILE_PATH)));

    if (pFileList->pFilePath == nullptr) {
        SPDLOG_ERROR(TEXT("Malloc {0} bytes memory error"), pathList.size() * sizeof(FILE_PATH));
        return -ERR_MALLOC_MEMORY;
    }

    pFileList->nItems = static_cast<unsigned int>(pathList.size());

    memset(pFileList->pFilePath, 0, pathList.size() * sizeof(FILE_PATH));

    for (size_t i = 0; i < pathList.size(); i++) {
        StringCbCopy(pFileList->pFilePath[i].path, MAX_PATH, pathList.at(i));
    }

    for (auto iter = pathList.begin(); iter != pathList.end(); ++iter) {
        if (*iter != nullptr) {
            free(*iter);
            (*iter) = nullptr;
        }
    }

    pathList.clear();
    std::vector<TCHAR *> tmpSwapVector;
    tmpSwapVector.swap(pathList);

    return ERR_SUCCESS;
}

int GetWindowsServiceStatus(const TCHAR *pSvrName, PDWORD pStatus) {
    SC_HANDLE              schSCManager;
    SC_HANDLE              schService;
    SERVICE_STATUS_PROCESS ssStatus;
    DWORD                  dwBytesNeeded = 0;

    if (pSvrName == nullptr || lstrlen(pSvrName) == 0) {
        SPDLOG_ERROR(TEXT("Input pSvrName params error"));
        return -ERR_INPUT_PARAMS;
    }

    if (pStatus == nullptr) {
        SPDLOG_ERROR(TEXT("Input pStatus params error"));
        return -ERR_INPUT_PARAMS;
    }

    // Get a handle to the SCM database.
    schSCManager = OpenSCManager(nullptr,                   // local computer
                                 nullptr,                   // ServicesActive database
                                 SC_MANAGER_ALL_ACCESS);    // full access rights

    if (nullptr == schSCManager) {
        SPDLOG_ERROR(TEXT("OpenSCManager failed {0}"), GetLastError());
        return -ERR_OPEN_SCM;
    }

    // Get a handle to the service.
    schService = OpenService(schSCManager,                                            // SCM database
                             pSvrName,                                                // name of service
                             SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS);    // full access

    if (schService == nullptr) {
        SPDLOG_ERROR(TEXT("OpenService failed {0}"), GetLastError());
        CloseServiceHandle(schSCManager);
        return -ERR_OPEN_SERVICE;
    }

    // Check the status in case the service is not stopped.
    if (!QueryServiceStatusEx(schService,                             // handle to service
                              SC_STATUS_PROCESS_INFO,                 // information level
                              reinterpret_cast<LPBYTE>(&ssStatus),    // address of structure
                              sizeof(SERVICE_STATUS_PROCESS),         // size of structure
                              &dwBytesNeeded))                        // size needed if buffer is too small
    {
        SPDLOG_ERROR(TEXT("QueryServiceStatusEx failed {0}"), GetLastError());
        CloseServiceHandle(schService);
        CloseServiceHandle(schSCManager);
        return -ERR_GET_SERVICESSTATUS;
    } else {
        *pStatus = ssStatus.dwCurrentState;
    }

    return ERR_SUCCESS;
}

int WideCharToTChar(const WCHAR *pWStr, TCHAR *pOutStr, int maxOutLen) {
    if constexpr (sizeof(TCHAR) == sizeof(WCHAR)) {
        if (wcslen(pWStr) * sizeof(WCHAR) >= maxOutLen) {
            SPDLOG_ERROR(TEXT("Output buffer is to short: {0} need {1}"), maxOutLen, wcslen(pWStr) * sizeof(WCHAR));
            return -ERR_INPUT_PARAMS;
        }
        memcpy(pOutStr, pWStr, wcslen(pWStr));
    } else {
        int len = WideCharToMultiByte(CP_ACP, 0, pWStr, static_cast<int>(wcslen(pWStr)), nullptr, 0, nullptr, nullptr);

        if (len >= maxOutLen) {
            SPDLOG_ERROR(TEXT("Output buffer is to short: {0} need {1}"), maxOutLen, len);
            return -ERR_INPUT_PARAMS;
        }

        WideCharToMultiByte(CP_ACP, 0, pWStr, static_cast<int>(wcslen(pWStr)), pOutStr, len, nullptr, nullptr);
        pOutStr[len] = 0;

        return ERR_SUCCESS;
    }
}

int TCharToWideChar(const TCHAR *pTStr, WCHAR *pOutStr, int maxOutLen) {
    if constexpr (sizeof(TCHAR) == sizeof(WCHAR)) {
        if (lstrlen(pTStr) * sizeof(WCHAR) >= maxOutLen) {
            SPDLOG_ERROR(TEXT("Output buffer is to short: {0} need {1}"), maxOutLen, lstrlen(pTStr) * sizeof(WCHAR));
            return -ERR_INPUT_PARAMS;
        }
        memcpy(pOutStr, pTStr, lstrlen(pTStr));
    } else {
        int len = MultiByteToWideChar(CP_ACP, 0, pTStr, lstrlen(pTStr), nullptr, 0);
        //int len = WideCharToMultiByte(CP_ACP, 0, pWStr, static_cast<int>(wcslen(pWStr)), nullptr, 0, nullptr, nullptr);

        if (len >= maxOutLen) {
            SPDLOG_ERROR(TEXT("Output buffer is to short: {0} need {1}"), maxOutLen, len);
            return -ERR_INPUT_PARAMS;
        }

        MultiByteToWideChar(CP_ACP, 0, pTStr, lstrlen(pTStr), pOutStr, len);
        pOutStr[len] = 0;

        return ERR_SUCCESS;
    }
}

static std::unordered_map<USER_ERRNO, std::string> g_UserErrorMap;

const CHAR *GetSDKErrorMessage(USER_ERRNO err) {
    std::unordered_map<USER_ERRNO, std::string>::iterator iter;

    if (g_UserErrorMap.empty()) {
        constexpr auto color_entries = magic_enum::enum_entries<USER_ERRNO>();

        for (auto colorEntry : color_entries) {
            g_UserErrorMap.emplace(colorEntry.first, std::string(colorEntry.second));
        }
    }

    if ((iter = g_UserErrorMap.find(err)) != g_UserErrorMap.end()) {
        return iter->second.c_str();
    } else {
        return "UNKNOWN";
    }
}