#pragma once
#include "ComKit.h"
#include "UtilHelper.h"
#include "serial.h"
#include "spdlog/spdlog.h"
#include <Windows.h>
#include <algorithm>
#include <chrono>
#include <devguid.h>
#include <format>
#include <optional>
#include <ranges>
#include <regex>
#include <regstr.h>
#include <setupapi.h>
#include <thread>
using namespace std::literals::chrono_literals;
#pragma comment(lib, "setupapi.lib")

std::vector<ComStruct> *ComKit::comPortNames = new std::vector<ComStruct>();
bool ComKit::isWorking = false;
serial::Serial ComKit::device = serial::Serial();
std::vector<std::string> ComKit::commands = std::vector<std::string>();
std::vector<std::string> ComKit::watchingComPort = std::vector<std::string>();
std::string ComKit::logContent = "";
std::mutex ComKit::m = std::mutex();
std::ostringstream ComKit::oss = std::ostringstream();

std::optional<std::string>
ComKit::GetUsbLocationPath(const std::string &comPortName) {
    HDEVINFO deviceinfoSet =
        SetupDiGetClassDevsA(&GUID_DEVCLASS_PORTS, NULL, NULL, DIGCF_PRESENT);
    if (deviceinfoSet == INVALID_HANDLE_VALUE)
        return std::nullopt;
    SP_DEVINFO_DATA deviceinfoData;
    ZeroMemory(&deviceinfoData, sizeof(SP_DEVINFO_DATA));
    deviceinfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    for (DWORD index = 0;
         SetupDiEnumDeviceInfo(deviceinfoSet, index, &deviceinfoData);
         index++) {
        wchar_t deviceName[256];
        if (!SetupDiGetDeviceRegistryPropertyW(
                deviceinfoSet, &deviceinfoData, SPDRP_FRIENDLYNAME, NULL,
                reinterpret_cast<PBYTE>(deviceName), sizeof(deviceName), NULL))
            continue;
        if (wcsstr(
                deviceName,
                std::wstring(comPortName.begin(), comPortName.end()).c_str())) {
            char locationPath[256];
            if (SetupDiGetDeviceRegistryPropertyA(
                    deviceinfoSet, &deviceinfoData, SPDRP_LOCATION_PATHS, NULL,
                    reinterpret_cast<PBYTE>(locationPath), sizeof(locationPath),
                    NULL)) {
                SetupDiDestroyDeviceInfoList(deviceinfoSet);
                return std::string(locationPath);
            } else {
                return std::nullopt;
            }
        }
    }
    SetupDiDestroyDeviceInfoList(deviceinfoSet);
    return std::nullopt;
}

std::optional<std::string>
ComKit::GetComPortInstancePath(const std::string &comPortName) {
    HDEVINFO deviceinfoSet =
        SetupDiGetClassDevsA(&GUID_DEVCLASS_PORTS, NULL, NULL, DIGCF_PRESENT);
    if (deviceinfoSet == INVALID_HANDLE_VALUE)
        return std::nullopt;
    SP_DEVINFO_DATA deviceinfoData;
    ZeroMemory(&deviceinfoData, sizeof(SP_DEVINFO_DATA));
    deviceinfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    for (DWORD index = 0;
         SetupDiEnumDeviceInfo(deviceinfoSet, index, &deviceinfoData);
         index++) {
        wchar_t deviceName[256];
        if (!SetupDiGetDeviceRegistryPropertyW(
                deviceinfoSet, &deviceinfoData, SPDRP_FRIENDLYNAME, NULL,
                reinterpret_cast<PBYTE>(deviceName), sizeof(deviceName), NULL))
            continue;
        if (wcsstr(
                deviceName,
                std::wstring(comPortName.begin(), comPortName.end()).c_str())) {
            char deviceInstancePath[256];
            if (SetupDiGetDeviceInstanceId(deviceinfoSet, &deviceinfoData,
                                           deviceInstancePath,
                                           sizeof(deviceInstancePath), NULL)) {
                SetupDiDestroyDeviceInfoList(deviceinfoSet);
                return std::string(deviceInstancePath);
            } else {
                return std::nullopt;
            }
        }
    }
    SetupDiDestroyDeviceInfoList(deviceinfoSet);
    return std::nullopt;
}

std::vector<ComStruct> *ComKit::GetAllComPortNames() {
    comPortNames->clear();
    HDEVINFO deviceinfoSet =
        SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, NULL, NULL,
                            DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    SP_DEVINFO_DATA deviceinfoData;
    deviceinfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    for (int deviceIndex = 0;
         SetupDiEnumDeviceInfo(deviceinfoSet, deviceIndex, &deviceinfoData);
         deviceIndex++) {
        wchar_t deviceName[256];
        if (!SetupDiGetDeviceRegistryPropertyW(
                deviceinfoSet, &deviceinfoData, SPDRP_FRIENDLYNAME, NULL,
                reinterpret_cast<PBYTE>(deviceName), sizeof(deviceName), NULL))
            continue;

        std::wstring wideDeviceName(deviceName);
        std::wregex comPortRegex(L"(COM[0-9]+)");
        std::wsmatch match;
        if (std::regex_search(wideDeviceName, match, comPortRegex) &&
            match.size() > 1) {
            std::wstring comPortNameW = match[1].str();
            std::string comPortName(comPortNameW.begin(), comPortNameW.end());
            comPortNames->push_back(
                {comPortName,
                 GetUsbLocationPath(comPortName).value_or("null")});
        }
    }
    SetupDiDestroyDeviceInfoList(deviceinfoSet);
    return comPortNames;
}

void ComKit::Start(std::string watchingPort, std::string input_commands) {
    if (isWorking)
        return;
    watchingComPort = std::move(Split(watchingPort));
    commands = std::move(Split(input_commands));
    isWorking = true;
}
void ComKit::Work() {
    if (isWorking) {
        auto currentPortStructures = GetAllComPortNames();
        std::vector<std::string> currentPorts;
        for (auto s : *currentPortStructures) {
            currentPorts.emplace_back(s.comPortName);
        }
        for (auto &port : watchingComPort) {
            if (std::find(currentPorts.begin(), currentPorts.end(), port) !=
                currentPorts.end()) {
                spdlog::info(std::format("Start Work With Port [{}]", port));
                device.OpenDevice(port);
                for (auto &command : commands) {
                    spdlog::info(std::format("Send Command: {}", command));
                    std::this_thread::sleep_for(50ms);
                    auto resp = device.GetAtUntilAndReturn(command, "OK");
                    spdlog::info(std::format("resp: {}", resp));
                    FlushLog();
                }
                device.CloseDevice();
            }
        }
    }
}

void ComKit::End() { isWorking = false; }

bool ComKit::ChangeComPortNumber(const std::string &oldPortName,
                                 const std::string &newPortName) {
    HKEY hKey;
    LONG lRes =
        RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\SERIALCOMM", 0,
                      KEY_ALL_ACCESS, &hKey);
    if (lRes != ERROR_SUCCESS) {
        auto error = GetLastError();
        return false;
    }

    lRes = RegSetValueExA(hKey, oldPortName.c_str(), 0, REG_SZ,
                          (BYTE *)newPortName.c_str(), newPortName.size() + 1);
    RegCloseKey(hKey);

    return lRes == ERROR_SUCCESS;
}

void ComKit::FlushLog() { logContent = oss.str(); }

std::vector<std::string> ComKit::Split(std::string str) {
    return ::Split<std::string>(str, ";");
}
