#include <cstring>
#include <unistd.h>
#include "utils.h"
#include "device.h"
#include "json/parser.h"

// declare of scoped vars and funcs
namespace hm {
    const int gWaitTime = 3000;
    std::map<int, std::string> gOpenTypeMap = {  // NOLINT
            {0,  "未知方式"},
            {1,  "密码开锁"},
            {2,  "刷卡开锁"},
            {3,  "先刷卡后密码开锁"},
            {4,  "先密码后刷卡开锁"},
            {5,  "远程开锁,如通过室内机或者平台对门口机开锁"},
            {6,  "开锁按钮进行开锁"},
            {7,  "开锁"},
            {8,  "密码+刷卡+组合开锁"},
            {10, "密码+组合开锁"},
            {11, "刷卡+组合开锁"},
            {12, "多人开锁"},
            {13, "钥匙开门"},
            {14, "胁迫密码开门"},
            {15, "二维码开门"},
            {16, "目标识别开门"},
            {18, "人证对比"},
            {19, "证件+人证比对"},
            {20, "蓝牙开门"},
            {21, "个性化密码开门"},
            {22, "UserID+密码"},
            {23, "人脸+密码开锁"},
            {24, "+密码开锁"},
            {25, "+人脸开锁"},
            {26, "刷卡+人脸开锁"},
            {27, "人脸或密码开锁"},
            {28, "或密码开锁"},
            {29, "或人脸开锁"},
            {30, "刷卡或人脸开锁"},
            {31, "刷卡或开锁"},
            {32, "+人脸+密码开锁"},
            {33, "刷卡+人脸+密码开锁"},
            {34, "刷卡++密码开锁"},
            {35, "卡++人脸组合开锁"},
            {36, "或人脸或密码"},
            {37, "卡或人脸或密码开锁"},
            {38, "卡或人脸开锁"},
            {39, "卡++人脸+密码组合开锁"},
            {40, "卡或人脸或密码开锁"},
            {41, "(证件+人证比对)或刷卡或人脸"},
            {42, "人证比对或刷卡(二维码)或人脸"},
            {43, "DTMF开锁(包括SIPINFO,RFC2833,INBAND)"},
            {44, "远程二维码开门"},
            {45, "远程人脸开门"},
            {46, "人证比对开门()"},
            {47, "临时密码开门"},
            {48, "健康码开门"},
            {49, "目标识别开锁"},
            {50, "目标+密码组合开锁"},
            {51, "人脸+目标组合开锁"},
            {52, "卡+目标组合开锁"},
            {53, "目标或密码开锁"},
            {54, "人脸或目标开锁"},
            {55, "卡或目标开锁"},
            {56, "人脸+目标+密码组合开锁"},
            {57, "卡+人脸+目标组合开锁"},
            {58, "卡+目标+密码组合开锁"},
            {59, "人脸或目标或密码开锁"},
            {60, "卡或人脸或目标开锁"},
            {61, "卡或目标或密码开锁"},
            {62, "卡+人脸+目标+密码组合开锁"},
            {63, "卡或人脸或目标或密码开锁"}
    };

    int onSubscribeData(
            LLONG handler, DWORD type, void *info, BYTE *imgBuf, DWORD bufSize,
            LDWORD user, int seq, void *reserved
    );

    Device *getDeviceBySubId(const LLONG &subId);
}

// implement of scoped funcs
namespace hm {
    int onSubscribeData(
            LLONG handler, DWORD type, void *info, BYTE *imgBuf, DWORD bufSize,
            LDWORD user, int seq, void *reserved
    ) {
        Device *device = getDeviceBySubId(handler);
        if (device == nullptr) {
            Log(Warn, "unrecognized subscribe uid: %ld", handler);
            return 0;
        }
        switch (type) {
            case EVENT_IVS_ACCESS_CTL:  // 人脸
                if (device->isFaceDevice)
                    device->onAccessCtlEvent((DEV_EVENT_ACCESS_CTL_INFO *) info, imgBuf, bufSize);
                else Log(Warn, "seq[%s] is car device, but got a face event", device->seq.c_str());
                break;
            case EVENT_IVS_TRAFFICJUNCTION:  // 车辆闸机
                if (!device->isFaceDevice)
                    device->onTrafficJunctionEvent((DEV_EVENT_TRAFFICJUNCTION_INFO *) info, imgBuf, bufSize);
                else Log(Warn, "seq[%s] is face device, but got a car event", device->seq.c_str());
                break;
            default:
                Log(Warn, "unrecognized event: %u", type);
                break;
        }
        return 1;
    }

    Device *getDeviceBySubId(const LLONG &subId) {
        for (auto &itr : gDevicesVec) if (itr->subId == subId) return itr.get();
        return nullptr;
    }

    void Device::init() {
        NET_IN_INIT_DEVICE_ACCOUNT in;
        in.dwSize = sizeof in;
        strcpy(in.szMac, this->mac.c_str());
        strcpy(in.szUserName, gConfLoginUser.c_str());
        strcpy(in.szPwd, gConfLoginPass.c_str());
        in.byPwdResetWay = this->reset;

        NET_OUT_INIT_DEVICE_ACCOUNT out;
        out.dwSize = sizeof out;

        if (CLIENT_InitDevAccount(&in, &out, gWaitTime, this->local.data())) {
            Log(Info, "device: seq[%s] init success", this->seq.c_str());
            this->inited = true;
            return;
        }
        Log(
                Error, "device(ip[%s], mac[%s], seq[%s]) init fail",
                this->ip.c_str(), this->mac.c_str(), this->seq.c_str()
        );
        this->running = false;
    }

    bool Device::login() {
        NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY in = {0};
        in.dwSize = sizeof in;
        strcpy(in.szIP, this->ip.c_str());
        in.nPort = this->port;
        strcpy(in.szUserName, gConfLoginUser.c_str());
        strcpy(in.szPassword, gConfLoginPass.c_str());
        in.emSpecCap = EM_LOGIN_SPEC_CAP_TCP;

        NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY out;
        out.dwSize = sizeof out;

        for (int i = 0; i < 3; ++i) {
            this->loginId = CLIENT_LoginWithHighLevelSecurity(&in, &out);
            if (this->loginId) {
                Log(Trace, "device seq[%s] login success", this->seq.c_str());
                usleep(1000000);
                return true;
            }
            sleep(1);
        }
        Log(
                Error, "device(ip[%s], mac[%s], seq[%s]) login fail",
                this->ip.c_str(), this->mac.c_str(), this->seq.c_str()
        );
        this->running = false;
        return false;
    }

    void Device::logout() {
        if (this->loginId) CLIENT_Logout(this->loginId);
        this->loginId = 0, this->running = false;
    }

    bool Device::subscribe() {
        if (isFaceDevice) {
            this->subId = CLIENT_RealLoadPictureEx(
                    this->loginId, 0, EVENT_IVS_ACCESS_CTL, TRUE,
                    onSubscribeData, 0, nullptr
            );
        } else {
            this->subId = CLIENT_RealLoadPictureEx(
                    this->loginId, 0, EVENT_IVS_TRAFFICJUNCTION, TRUE,
                    onSubscribeData, 0, nullptr
            );
        }
        if (this->subId) {
            Log(Trace, "device seq[%s] subscribe success", this->seq.c_str());
            return true;
        }
        Log(
                Error, "device(ip[%s], mac[%s], seq[%s]) face subscribe fail",
                this->ip.c_str(), this->mac.c_str(), this->seq.c_str()
        );
        this->running = false;
        return false;
    }

    void Device::unsubscribe() {
        if (this->subId) CLIENT_StopLoadPic(this->subId);
        this->subId = 0, this->running = false;
    }

    void Device::varUserInsert(const User &user) {
        bool have = false;
        std::lock_guard<std::mutex> lock(uMtx);
        for (const User &itr : faceUserList) {
            if (itr.uid == user.uid) {
                have = true;
                break;
            }
        }
        if (!have) faceUserList.push_back(user);
    }

    void Device::varUserDelete(const std::string &uid) {
        std::lock_guard<std::mutex> lock(uMtx);
        for (auto it = faceUserList.begin(); it != faceUserList.end(); ++it) {
            if (it->uid == uid) {
                faceUserList.erase(it);
                break;
            }
        }
    }

    bool Device::varUserExists(const std::string &uid) {
        std::lock_guard<std::mutex> lock(uMtx);
        for (User &user : faceUserList) if (user.uid == uid) return true;
        return false;
    }

    bool Device::addUserFace(const std::string &uid, const std::string &face, std::string &msg) {
        NET_IN_ADD_FACE_INFO in;
        in.dwSize = sizeof in;
        strcpy(in.szUserID, uid.c_str());
        in.stuFaceInfo.nFacePhoto = 1;
        in.stuFaceInfo.nFacePhotoLen[0] = imgcpy(in.stuFaceInfo.pszFacePhoto[0], face);
        // Log(Info, "face-size: %ld, pho-len:%d", face.size(), in.stuFaceInfo.nFacePhotoLen[0]);
        // imageSave("./java.jpg", in.stuFaceInfo.pszFacePhoto[0], in.stuFaceInfo.nFacePhotoLen[0]);

        NET_OUT_ADD_FACE_INFO out;
        out.dwSize = sizeof out;
        bool res = CLIENT_FaceInfoOpreate(loginId, EM_FACEINFO_OPREATE_ADD, &in, &out, gWaitTime);
        if (!res) msg = "user face add failed";
        delete[] in.stuFaceInfo.pszFacePhoto[0];
        return res;
    }

    bool Device::removeUserOnly(const std::string &uid, std::string &msg) {
        NET_IN_ACCESS_USER_SERVICE_REMOVE in;
        in.dwSize = sizeof in;
        in.nUserNum = 1;
        std::strcpy(in.szUserID[0], uid.c_str());
        in.bUserIDEx = 0;
        NET_OUT_ACCESS_USER_SERVICE_REMOVE out;
        out.dwSize = sizeof out;
        out.nMaxRetNum = 1;
        out.pFailCode = new NET_EM_FAILCODE;

        bool res = CLIENT_OperateAccessUserService(
                loginId, NET_EM_ACCESS_CTL_USER_SERVICE_REMOVE, &in, &out, gWaitTime
        );
        if (res) varUserDelete(uid);
        else msg = "user info delete failed";
        delete out.pFailCode;
        return res;
    }

    bool Device::removeUserFace(const std::string &uid, std::string &msg) {
        NET_IN_REMOVE_FACE_INFO in;
        in.dwSize = sizeof in;
        strcpy(in.szUserID, uid.c_str());

        NET_OUT_REMOVE_FACE_INFO out;
        out.dwSize = sizeof out;
        bool res = CLIENT_FaceInfoOpreate(loginId, EM_FACEINFO_OPREATE_REMOVE, &in, &out, gWaitTime);
        if (!res) msg = "user face remove failed";
        return res;
    }

    void Device::varCarWoBInsert(const bool &isBlack, const Plate &plate) {
        bool have = false;
        std::lock_guard<std::mutex> lock(isBlack ? cbMtx : cwMtx);
        for (const Plate &itr : isBlack ? carBlackList : carWhiteList) {
            if (itr.cid == plate.cid) {
                have = true;
                break;
            }
        }
        if (!have) isBlack ? carBlackList.push_back(plate) : carWhiteList.push_back(plate);
    }

    void Device::varCarWoBDelete(const bool &isBlack, const int &cid) {
        std::lock_guard<std::mutex> lock(isBlack ? cbMtx : cwMtx);
        for (auto it = (isBlack ? carBlackList : carWhiteList).begin();
             it != (isBlack ? carBlackList : carWhiteList).end(); ++it) {
            if (it->cid == cid) {
                (isBlack ? carBlackList : carWhiteList).erase(it);
                break;
            }
        }
    }

    bool Device::varCarWoBExists(const bool &isBlack, const int &cid) {
        std::lock_guard<std::mutex> lock(isBlack ? cbMtx : cwMtx);
        for (const Plate &plate : isBlack ? carBlackList : carWhiteList) if (plate.cid == cid) return true;
        return false;
    }

    bool Device::varCarWoBExists(const bool &isBlack, const std::string &plate) {
        std::lock_guard<std::mutex> lock(isBlack ? cbMtx : cwMtx);
        for (const Plate &itr : isBlack ? carBlackList : carWhiteList) if (itr.plate == plate) return true;
        return false;
    }

    bool Device::insertOneCarWoBList(const bool &isBlack, Plate &plateInfo, std::string &msg) {
        if (varCarWoBExists(isBlack, plateInfo.plate)) {
            msg = "exist already";
            return false;
        }
        NET_IN_OPERATE_TRAFFIC_LIST_RECORD in;
        in.dwSize = sizeof in;
        in.emOperateType = NET_TRAFFIC_LIST_INSERT;
        in.emRecordType = isBlack ? NET_RECORD_TRAFFICBLACKLIST : NET_RECORD_TRAFFICREDLIST;
        auto *info = new NET_INSERT_RECORD_INFO;
        info->dwSize = sizeof(NET_INSERT_RECORD_INFO);
        auto *record = new NET_TRAFFIC_LIST_RECORD;
        strcpy(record->szMasterOfCar, plateInfo.name.c_str());
        strcpy(record->szPlateNumber, plateInfo.plate.c_str());
        time_t tst = plateInfo.timestamp / 1000;
        struct tm *timeInfo = localtime(&tst);
        record->stCancelTime.dwYear = timeInfo->tm_year + 1900, record->stCancelTime.dwMonth = timeInfo->tm_mon + 1;
        record->stCancelTime.dwDay = timeInfo->tm_mday, record->stCancelTime.dwHour = timeInfo->tm_hour;
        record->stCancelTime.dwMinute = timeInfo->tm_min, record->stCancelTime.dwSecond = timeInfo->tm_sec;
        record->stBeginTime.dwYear = 2000, record->stBeginTime.dwMonth = 1, record->stBeginTime.dwDay = 1;
        record->stBeginTime.dwHour = 0, record->stBeginTime.dwMinute = 0, record->stBeginTime.dwSecond = 0;
        record->nAuthrityNum = 0;
        info->pRecordInfo = record;
        in.pstOpreateInfo = info;

        NET_OUT_OPERATE_TRAFFIC_LIST_RECORD out;
        out.dwSize = sizeof out;
        bool res = CLIENT_OperateTrafficList(loginId, &in, &out);
        if (res) {
            plateInfo.cid = out.nRecordNo;
            plateInfo.cid_s = std::to_string(out.nRecordNo);
            varCarWoBInsert(isBlack, plateInfo);
        } else {
            msg = "insert failed";
            plateInfo.cid = 0;
        }
        delete record;
        delete info;
        return res;
    }

    bool Device::removeOneCarWoBList(const bool &isBlack, const int &cid, std::string &msg) {
        if (!varCarWoBExists(isBlack, cid)) {
            msg = "no such plate";
            return false;
        }
        NET_IN_OPERATE_TRAFFIC_LIST_RECORD in;
        in.dwSize = sizeof in;
        in.emOperateType = NET_TRAFFIC_LIST_REMOVE;
        in.emRecordType = isBlack ? NET_RECORD_TRAFFICBLACKLIST : NET_RECORD_TRAFFICREDLIST;
        auto *info = new NET_REMOVE_RECORD_INFO;
        info->dwSize = sizeof(NET_REMOVE_RECORD_INFO);
        info->nRecordNo = cid;
        in.pstOpreateInfo = info;

        NET_OUT_OPERATE_TRAFFIC_LIST_RECORD out;
        out.dwSize = sizeof out;

        bool res = CLIENT_OperateTrafficList(loginId, &in, &out);
        if (res) varCarWoBDelete(isBlack, cid);
        else msg = "remove failed";
        delete info;
        return res;
    }

    Plate *Device::getPlateByCNo(const std::string &cno) {
        for (auto &itr : carWhiteList) if (itr.plate == cno) return &itr;
        for (auto &itr : carBlackList) if (itr.plate == cno) return &itr;
        return nullptr;
    }
}

namespace hm {
    Device::Device(DEVICE_NET_INFO_EX2 *info) {
        this->inited = (info->stuDevInfo.byInitStatus & 3) != 1;
        this->port = info->stuDevInfo.nPort;
        this->reset = info->stuDevInfo.byPwdResetWay;
        this->seq = info->stuDevInfo.szSerialNo;
        this->mac = info->stuDevInfo.szMac;
        this->ip = info->stuDevInfo.szIP;
        this->local = info->szLocalIP;
        // 108: {szDeviceType: BSC, szDetailType: DH-ASI31A-MW, szNewDetailType: DH-ASI31A-MW}
        // 109: {szDeviceType: ITC, szDetailType: ITC313-PW2A-LF, szNewDetailType: ITC313-PW2A-LF}
        this->isFaceDevice = strcmp("DH-ASI31A-MW", info->stuDevInfo.szDetailType) == 0;

        this->online = this->running = false;
        this->loginId = this->subId = 0;
    }

    Device::~Device() {
        this->unsubscribe();
        this->logout();
    }

    void Device::queryFaceUsers() {
        NET_IN_USERINFO_START_FIND start_in;
        start_in.dwSize = sizeof start_in;
        strcpy(start_in.szUserID, "");
        NET_OUT_USERINFO_START_FIND start_out;
        start_out.dwSize = sizeof start_out;

        LLONG handler = CLIENT_StartFindUserInfo(loginId, &start_in, &start_out, gWaitTime);
        if (handler) {
            int offset = 0, count = 10;
            faceUserList.clear();
            while (offset < start_out.nTotalCount) {
                NET_IN_USERINFO_DO_FIND find_in;
                find_in.dwSize = sizeof find_in;
                find_in.nStartNo = offset;
                find_in.nCount = count;
                NET_OUT_USERINFO_DO_FIND find_out;
                find_out.dwSize = sizeof find_out;
                find_out.nMaxNum = count;
                find_out.pstuInfo = new NET_ACCESS_USER_INFO[count];
                bool res = CLIENT_DoFindUserInfo(handler, &find_in, &find_out, gWaitTime);
                if (res) {
                    for (int i = 0; i < find_out.nRetNum; ++i) {
                        User one = {
                                .uid=find_out.pstuInfo[i].szUserID, .name=find_out.pstuInfo[i].szName,
                                .password = find_out.pstuInfo[i].szPsw,
                                .timestamp=1000 * toTimestamp(
                                        find_out.pstuInfo[i].stuValidEndTime.dwYear,
                                        find_out.pstuInfo[i].stuValidEndTime.dwMonth,
                                        find_out.pstuInfo[i].stuValidEndTime.dwDay,
                                        find_out.pstuInfo[i].stuValidEndTime.dwHour,
                                        find_out.pstuInfo[i].stuValidEndTime.dwMinute,
                                        find_out.pstuInfo[i].stuValidEndTime.dwSecond
                                )
                        };
                        varUserInsert(one);
                    }
                    offset += find_out.nRetNum;
                    delete[] find_out.pstuInfo;
                } else break;
            }
            CLIENT_StopFindUserInfo(handler);
        }
    }

    void Device::queryCarWoBList(const bool &isBlack) {
        NET_IN_FIND_RECORD_PARAM find_in;
        find_in.dwSize = sizeof find_in;
        find_in.emType = isBlack ? NET_RECORD_TRAFFICBLACKLIST : NET_RECORD_TRAFFICREDLIST;
        auto *find_cond = new FIND_RECORD_TRAFFICREDLIST_CONDITION;
        find_cond->dwSize = sizeof(FIND_RECORD_TRAFFICREDLIST_CONDITION);
        strcpy(find_cond->szPlateNumber, "");
        strcpy(find_cond->szPlateNumberVague, "");
        find_cond->nQueryResultBegin = 0;
        find_cond->bRapidQuery = 0;
        find_in.pQueryCondition = find_cond;
        NET_OUT_FIND_RECORD_PARAM find_out;
        find_out.dwSize = sizeof find_out;
        bool res = CLIENT_FindRecord(loginId, &find_in, &find_out);
        if (res) {
            NET_IN_QUEYT_RECORD_COUNT_PARAM query_in;
            query_in.dwSize = sizeof query_in;
            query_in.lFindeHandle = find_out.lFindeHandle;
            NET_OUT_QUEYT_RECORD_COUNT_PARAM query_out;
            query_out.dwSize = sizeof query_out;
            res = CLIENT_QueryRecordCount(&query_in, &query_out);
            if (res) {
                int found = 0, batch = 5;
                while (found < query_out.nRecordCount) {
                    NET_IN_FIND_NEXT_RECORD_PARAM do_in;
                    do_in.dwSize = sizeof do_in;
                    do_in.lFindeHandle = find_out.lFindeHandle;
                    do_in.nFileCount = batch;
                    NET_OUT_FIND_NEXT_RECORD_PARAM do_out;
                    do_out.dwSize = sizeof do_out;
                    do_out.nMaxRecordNum = batch;
                    auto *records = new NET_TRAFFIC_LIST_RECORD[5];
                    do_out.pRecordList = records;
                    for (int i = 0; i < batch; ++i) records[i].dwSize = sizeof(NET_TRAFFIC_LIST_RECORD);
                    res = CLIENT_FindNextRecord(&do_in, &do_out);
                    if (res) {
                        found += do_out.nRetRecordNum;
                        for (int i = 0; i < do_out.nRetRecordNum; ++i) {
                            Plate one = {
                                    .name = records[i].szMasterOfCar, .plate = records[i].szPlateNumber,
                                    .cid_s = std::to_string(records[i].nRecordNo), .cid = records[i].nRecordNo,
                                    .timestamp = 1000 * toTimestamp(
                                            records[i].stCancelTime.dwYear, records[i].stCancelTime.dwMonth,
                                            records[i].stCancelTime.dwDay, records[i].stCancelTime.dwHour,
                                            records[i].stCancelTime.dwMinute, records[i].stCancelTime.dwSecond
                                    )
                            };
                            varCarWoBInsert(isBlack, one);
                        }
                    } else break;
                    delete[] records;
                }
            }
            CLIENT_FindRecordClose(find_out.lFindeHandle);
        }
        delete find_cond;
    }

    void Device::Start() {
        if (this->running) return;
        this->running = true;

        if (!this->inited) this->init();
        if (!this->login()) return;
        this->online = true;
        if (!this->subscribe()) return;
    }

    void Device::onAccessCtlEvent(DEV_EVENT_ACCESS_CTL_INFO *info, BYTE *imgBuf, DWORD bufSize) {
        char status[2] = {info->bStatus ? '1' : '0', 0}, method[64] = {0}, time[32] = {0};
        strcpy(method, gOpenTypeMap[0].c_str());
        if (gOpenTypeMap.find(info->emOpenMethod) != gOpenTypeMap.end())
            strcpy(method, gOpenTypeMap[info->emOpenMethod].c_str());
        sprintf(
                time, "%d-%02d-%02d %02d:%02d:%02d", info->UTC.dwYear, info->UTC.dwMonth,
                info->UTC.dwDay, info->UTC.dwHour + 8, info->UTC.dwMinute, info->UTC.dwSecond
        );
        Log(
                Info,
                R"({"mac":"%s","seq":"%s","time":"%s","result":%s,"userId":"%s","userName":"%s","method":"%s"})",
                this->mac.c_str(), this->seq.c_str(), time, status, info->szUserID, info->szCardName, method
        );
        // imageSave("./face.jpg", imgBuf, bufSize);
        std::map<std::string, std::string> data{
                {"mac",      this->mac},
                {"seq",      this->seq},
                {"time",     time},
                {"result",   status},
                {"userId",   info->szUserID},
                {"userName", info->szCardName},
                {"method",   method}
        };
        dataUpload(gConfFaceRemote, data, imgBuf, bufSize);
    }

    void Device::onTrafficJunctionEvent(DEV_EVENT_TRAFFICJUNCTION_INFO *info, BYTE *imgBuf, DWORD bufSize) {
        char time[32] = {0}, speed[4] = {0}, status[2] = {info->byOpenStrobeState > 1 ? '1' : '0', 0};
        sprintf(speed, "%d", info->nSpeed);
        sprintf(
                time, "%d-%02d-%02d %02d:%02d:%02d", info->UTC.dwYear, info->UTC.dwMonth,
                info->UTC.dwDay, info->UTC.dwHour, info->UTC.dwMinute, info->UTC.dwSecond
        );
        std::string cno(info->stTrafficCar.szPlateNumber), username, cid;
        if (!cno.empty()) {
            Plate *plate = getPlateByCNo(cno);
            if (plate != nullptr) {
                username = plate->name;
                cid = plate->cid_s;
            }
        }
        Log(
                Info,
                R"({"mac":"%s","seq":"%s","time":"%s","plateNumber":"%s","username":"%s","cid":"%s","plateType":"%s","plateColor":"%s","carType":"%s","carSubType":"%s","carColor":"%s","speed":"%s","result":"%s"})",
                this->mac.c_str(), this->seq.c_str(), time, info->stTrafficCar.szPlateNumber,
                username.c_str(), cid.c_str(), info->stTrafficCar.szPlateType, info->stTrafficCar.szPlateColor,
                info->stuVehicle.szText, info->stuVehicle.szObjectSubType, info->stTrafficCar.szVehicleColor,
                speed, status
        );

        // imageSave("./car.jpg", imgBuf, bufSize);
        std::map<std::string, std::string> data{
                {"mac",         this->mac},
                {"seq",         this->seq},
                {"time",        time},
                {"plateNumber", info->stTrafficCar.szPlateNumber},
                {"username",    username},
                {"cid",         cid},
                {"plateType",   info->stTrafficCar.szPlateType},
                {"plateColor",  info->stTrafficCar.szPlateColor},
                {"carType",     info->stuVehicle.szText},
                {"carSubType",  info->stuVehicle.szObjectSubType},
                {"carColor",    info->stTrafficCar.szVehicleColor},
                {"speed",       speed},
                {"result",      status},
        };
        dataUpload(gConfCarRemote, data, imgBuf, bufSize);
    }

    std::string Device::getJsonStr() {
        /* 1: online: 1 -> true, 0 -> false
         * 37: {"mac":"","seq":"","ip":"","online":}
         * */
        size_t size = mac.size() + seq.size() + ip.size() + 38;
        std::string res(size, 0);
        sprintf(
                res.data(), R"({"mac":"%s","seq":"%s","ip":"%s","online":%d})",
                mac.c_str(), seq.c_str(), ip.c_str(), online
        );
        return res;
    }

    bool Device::getFaceUsers(std::vector<std::string> &users, std::string &msg) {
        for (User &user : faceUserList) users.push_back(user.getJsonStr());
        return true;
    }

    bool Device::insertFaceUser(const User &user, std::string &msg) {
        if (varUserExists(user.uid)) {
            msg = "user existed already";
            return false;
        }
        NET_IN_ACCESS_USER_SERVICE_INSERT in;
        in.dwSize = sizeof in;
        in.nInfoNum = 1;
        auto *usr = new NET_ACCESS_USER_INFO;
        usr->bUserIDEx = 0;
        strcpy(usr->szUserID, user.uid.c_str());
        usr->bUseNameEx = 0;
        strcpy(usr->szName, user.name.c_str());
        strcpy(usr->szPsw, user.password.c_str());
        usr->emUserType = NET_ENUM_USER_TYPE_NORMAL;
        usr->nUserStatus = 0;
        usr->nDoorNum = 1, usr->nDoors[0] = 0;
        usr->nTimeSectionNum = 1, usr->nTimeSectionNo[0] = 255;
        usr->nSpecialDaysScheduleNum = 1, usr->nSpecialDaysSchedule[0] = 255;
        time_t tst = user.timestamp / 1000;
        struct tm *timeInfo = localtime(&tst);
        usr->stuValidEndTime.dwYear = timeInfo->tm_year + 1900;
        usr->stuValidEndTime.dwMonth = timeInfo->tm_mon + 1;
        usr->stuValidEndTime.dwDay = timeInfo->tm_mday;
        usr->stuValidEndTime.dwHour = timeInfo->tm_hour;
        usr->stuValidEndTime.dwMinute = timeInfo->tm_min;
        usr->stuValidEndTime.dwSecond = timeInfo->tm_sec;
        usr->stuValidBeginTime.dwYear = 2000;
        usr->stuValidBeginTime.dwMonth = 1;
        usr->stuValidBeginTime.dwDay = 1;
        usr->stuValidBeginTime.dwHour = 0;
        usr->stuValidBeginTime.dwMinute = 0;
        usr->stuValidBeginTime.dwSecond = 0;
        usr->nFirstEnterDoorsNum = 0;
        usr->emAuthority = NET_ATTENDANCE_AUTHORITY_CUSTOMER;
        usr->nFloorNum = 0;
        usr->nRoom = 0;
        usr->bFloorNoExValid = 0;
        usr->nFloorNumEx = 0;
        usr->bFloorNoEx2Valid = 0;
        usr->nUserTimeSectionsNum = 0;
        usr->bUserInfoExValid = 0;
        usr->bUserInfoEx2Valid = 0;
        in.pUserInfo = usr;

        NET_OUT_ACCESS_USER_SERVICE_INSERT out;
        out.dwSize = sizeof out;
        out.nMaxRetNum = 1;
        out.pFailCode = new NET_EM_FAILCODE;

        bool ok = true, res = CLIENT_OperateAccessUserService(
                loginId, NET_EM_ACCESS_CTL_USER_SERVICE_INSERT, &in, &out, gWaitTime
        );
        if (res) {
            res = addUserFace(user.uid, user.face, msg);
            if (res) varUserInsert(user);
            else {
                ok = false;
                removeUserOnly(user.uid, msg);
            }
        } else {
            ok = false;
            msg = "usr info insert failed";
        }
        delete usr;
        delete out.pFailCode;
        return ok;
    }

    bool Device::removeFaceUser(const std::string &uid, std::string &msg) {
        if (!varUserExists(uid)) {
            msg = "no such user";
            return false;
        }
        bool ok1 = removeUserFace(uid, msg), ok2 = removeUserOnly(uid, msg);
        return ok1 && ok2;
    }

    bool Device::getCarWoBList(const bool &isBlack, std::vector<std::string> &list, std::string &msg) {
        for (Plate &plate : isBlack ? carBlackList : carWhiteList) list.push_back(plate.getJsonStr());
        return true;
    }

    bool Device::insertCarWoBList(const bool &isBlack, std::vector<Plate> &list, std::string &msg) {
        json::Parser parser;
        json::Value result = parser.parse("[]");
        for (Plate &info : list) {
            json::Value one = parser.parse("{}");
            std::string reason;
            bool res = insertOneCarWoBList(isBlack, info, reason);
            one["cid"] = json::Value(info.cid);
            one["plate"] = json::Value(info.plate);
            one["success"] = json::Value(res);
            one["msg"] = json::Value(reason);
            result.push_back(one);
        }
        msg = result.str();
        return true;
    }

    bool Device::removeCarWoBList(const bool &isBlack, const std::vector<int> &cids, std::string &msg) {
        json::Parser parser;
        json::Value result = parser.parse("[]");
        for (const int &cid : cids) {
            json::Value one = parser.parse("{}");
            std::string reason;
            bool res = removeOneCarWoBList(isBlack, cid, reason);
            one["cid"] = json::Value(cid);
            one["success"] = json::Value(res);
            one["msg"] = json::Value(reason);
            result.push_back(one);
        }
        msg = result.str();
        return true;
    }
}