#include <unistd.h>
#include <cstring>
#include <thread>

#include "utils.h"
#include "manager.h"

// declare of scoped vars and funcs
namespace hm {
    std::mutex gDeviceMtx;
    std::vector<std::unique_ptr<Device>> gDevicesVec;
    bool gSdkOk = false;
    std::vector<LLONG> gSearchHandlers;

    void disconnectFunc(LLONG loginId, char *dIp, LONG dPort, LDWORD userInfo);

    void reconnectFunc(LLONG loginId, char *dIp, LONG dPort, LDWORD userInfo);

    [[noreturn]] void ManagerLoop();

    void startSearch();

    void stopSearch();

    void onDeviceFound(LLONG handler, DEVICE_NET_INFO_EX2 *deviceInfo, void *userData);

    bool deviceInclude(const std::string &seq);


    bool checkDevice(const Device *device, const bool &isFace, std::string &msg);

    Device *getDeviceBySeq(const std::string &seq);

    Device *getDeviceByLoginId(const LLONG &loginId);
}

// implement of scoped funcs
namespace hm {
    void disconnectFunc(LLONG loginId, char *dIp, LONG dPort, LDWORD userInfo) {
        Log(Info, "loginId: 0x%lx, device[ip: %s, port: %d], user: %ld", loginId, dIp, dPort, userInfo);
        Device *device = getDeviceByLoginId(loginId);
        if (device != nullptr) device->online = false;
    }

    void reconnectFunc(LLONG loginId, char *dIp, LONG dPort, LDWORD userInfo) {
        Log(Info, "loginId: 0x%lx, device[ip: %s, port: %d], user: %ld", loginId, dIp, dPort, userInfo);
        Device *device = getDeviceByLoginId(loginId);
        if (device != nullptr) device->online = true;
    }

    [[noreturn]] void ManagerLoop() {
        bool first = true;
        while (true) {
            startSearch();
            stopSearch();
            for (const auto &itr: gDevicesVec) {
                itr->Start();
                if (itr->online) {
                    itr->isFaceDevice ?
                    itr->queryFaceUsers() :
                    (itr->queryCarWoBList(true), itr->queryCarWoBList(false));
                }
            }
            if (first) {
                first = false;
                Log(Info, "All device managed");
            }
            sleep(gConfSearchGap);
        }
    }

    void startSearch() {
        NET_IN_STARTSERACH_DEVICE pInBuf = {0};
        NET_OUT_STARTSERACH_DEVICE pOutBuf = {0};
        pInBuf.dwSize = sizeof(NET_IN_STARTSERACH_DEVICE);
        pInBuf.cbSearchDevices = onDeviceFound;
        pInBuf.pUserData = nullptr;
        pOutBuf.dwSize = sizeof(NET_OUT_STARTSERACH_DEVICE);

        std::vector<std::string> localIPs = getIpList();
        for (const std::string &ip : localIPs) {
            memset(pInBuf.szLocalIp, 0, MAX_LOCAL_IP_LEN);
            strncpy(pInBuf.szLocalIp, ip.c_str(), MAX_LOCAL_IP_LEN - 1);
            LLONG handler = CLIENT_StartSearchDevicesEx(&pInBuf, &pOutBuf);
            gSearchHandlers.push_back(handler);
        }
        Log(Trace, "search local area network device for %ds ...", gConfSearchDuration);
        sleep(gConfSearchDuration);
    }

    void stopSearch() {
        for (const LLONG handler: gSearchHandlers) CLIENT_StopSearchDevices(handler);
        gSearchHandlers.clear();
        Log(Trace, "search stopped ~");
    }

    void onDeviceFound(LLONG handler, DEVICE_NET_INFO_EX2 *deviceInfo, void *userData) {
        std::lock_guard<std::mutex> lock(gDeviceMtx);
        if (deviceInfo->stuDevInfo.iIPVersion == 4 && !deviceInclude(deviceInfo->stuDevInfo.szSerialNo)) {
            gDevicesVec.push_back(std::make_unique<Device>(deviceInfo));
            Log(
                    Info, "new device found, {ip: %s, mac: %s, seq: %s, type: %s}",
                    deviceInfo->stuDevInfo.szIP, deviceInfo->stuDevInfo.szMac, deviceInfo->stuDevInfo.szSerialNo,
                    deviceInfo->stuDevInfo.szDetailType
            );
        }
    }

    bool deviceInclude(const std::string &seq) {
        for (const auto &itr : gDevicesVec) if (itr->seq == seq) return true;
        return false;
    }

    bool checkDevice(const Device *device, const bool &isFace, std::string &msg) {
        if (device == nullptr) {
            msg = "device seq error";
            return false;
        }
        if (!device->online) {
            msg = "device offline";
            return false;
        }
        if (device->isFaceDevice != isFace) {
            msg = "device type error";
            return false;
        }
        return true;
    }

    Device *getDeviceBySeq(const std::string &seq) {
        std::lock_guard<std::mutex> lock(gDeviceMtx);
        for (const auto &itr : gDevicesVec) if (itr->seq == seq) return itr.get();
        return nullptr;
    }

    Device *getDeviceByLoginId(const LLONG &loginId) {
        std::lock_guard<std::mutex> lock(gDeviceMtx);
        for (const auto &itr : gDevicesVec) if (itr->loginId == loginId) return itr.get();
        return nullptr;
    }
}

namespace hm {
    void StartManager() {
        gSdkOk = CLIENT_Init(disconnectFunc, 0);
        CLIENT_SetAutoReconnect(reconnectFunc, 0);
        CLIENT_SetConnectTime(1000, 3);
        if (!gSdkOk) {
            LogM(Error, "SDK init failed.");
            throw std::logic_error("SDK init failed.");
        }
        Log(Trace, "SDK init success");

        std::thread thread(ManagerLoop);
        thread.detach();
    }

    bool managerGetFaceDevices(std::vector<std::string> &devices, std::string &msg) {
        for (const auto &itr : gDevicesVec) {
            if (itr->isFaceDevice) devices.push_back(itr->getJsonStr());
        }
        return true;
    }

    bool managerGetFaceDeviceUsers(const std::string &seq, std::vector<std::string> &users, std::string &msg) {
        Device *device = getDeviceBySeq(seq);
        if (!checkDevice(device, true, msg)) return false;
        return device->getFaceUsers(users, msg);
    }

    bool managerInsertFaceDeviceUser(const std::string &seq, const User &user, std::string &msg) {
        Device *device = getDeviceBySeq(seq);
        if (!checkDevice(device, true, msg)) return false;
        return device->insertFaceUser(user, msg);
    }

    bool managerRemoveFaceDeviceUser(const std::string &seq, const std::string &uid, std::string &msg) {
        Device *device = getDeviceBySeq(seq);
        if (!checkDevice(device, true, msg)) return false;
        return device->removeFaceUser(uid, msg);
    }

    bool managerGetCarDevices(std::vector<std::string> &devices, std::string &msg) {
        for (const auto &itr : gDevicesVec) {
            if (!itr->isFaceDevice) devices.push_back(itr->getJsonStr());
        }
        return true;
    }

    bool managerGetCarDeviceWoBList(
            const std::string &seq, const bool &isBlack, std::vector<std::string> &list, std::string &msg
    ) {
        Device *device = getDeviceBySeq(seq);
        if (!checkDevice(device, false, msg)) return false;
        return device->getCarWoBList(isBlack, list, msg);
    }

    bool managerInsertCarDeviceWoBList(
            const std::string &seq, const bool &isBlack, std::vector<Plate> &list, std::string &msg
    ) {
        Device *device = getDeviceBySeq(seq);
        if (!checkDevice(device, false, msg)) return false;
        return device->insertCarWoBList(isBlack, list, msg);
    }

    bool managerRemoveCarDeviceWoBList(
            const std::string &seq, const bool &isBlack, std::vector<int> &cids, std::string &msg
    ) {
        Device *device = getDeviceBySeq(seq);
        if (!checkDevice(device, false, msg)) return false;
        return device->removeCarWoBList(isBlack, cids, msg);
    }

    void StopManager() {
        stopSearch();
        gDevicesVec.clear();
        if (gSdkOk) {
            CLIENT_Cleanup();
            gSdkOk = false;
        }
        Log(Info, "manager stopped ~");
    }
}