#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <fstream>
#include <unistd.h>
#include "dhnetsdk.h"

#define Log(x...) printf("[HmGate|%3d] %14s : ",__LINE__,__FUNCTION__),printf(x),printf("\n")

int imageRead(const std::string &path, char *&buffer) {
    std::ifstream img(path, std::ios::binary);
    img.seekg(0, std::ios::end);
    int size = (int) img.tellg();
    img.seekg(0, std::ios::beg);
    buffer = new char[size];
    img.read(buffer, size);
    return size;
}

void imageSave(const std::string &path, char *&start, const int &size) {
    if (size > 0 && start != nullptr) {
        std::ofstream out(path, std::ios::binary);
        if (!out.is_open()) return;
        out.write(start, size);
        out.close();
    }
}

class Device {
private:
    LLONG loginId;
    const char *uid = "TempUserId", *name = "临时用户", *path = "./face.jpg";

    // 用户信息查询：CLIENT_StartFindUserInfo
    void userFind() {
        NET_IN_USERINFO_START_FIND stin;
        stin.dwSize = sizeof stin;
        strcpy(stin.szUserID, "");
        NET_OUT_USERINFO_START_FIND stout;
        stout.dwSize = sizeof stout;

        LLONG handler = CLIENT_StartFindUserInfo(loginId, &stin, &stout, 1000);
        if (handler) {
            Log("start: cap: %d, total: %d", stout.nCapNum, stout.nTotalCount);
            NET_IN_USERINFO_DO_FIND din;
            din.dwSize = sizeof din;
            din.nStartNo = 0;
            din.nCount = 3;
            NET_OUT_USERINFO_DO_FIND dout;
            dout.dwSize = sizeof dout;
            dout.nMaxNum = 3;
            dout.pstuInfo = new NET_ACCESS_USER_INFO[3];

            bool res = CLIENT_DoFindUserInfo(handler, &din, &dout, 1000);
            if (res) {
                Log("find success: count: %d", dout.nRetNum);
                for (int i = 0; i < dout.nRetNum; ++i) Log("user[%d].name: %s", i + 1, dout.pstuInfo[i].szName);
            } else {
                Log("find fail: %x", CLIENT_GetLastError());
            }
            delete[] dout.pstuInfo;
        } else {
            Log("can't start");
        }
        CLIENT_StopFindUserInfo(handler);
        sleep(1);
    }

    void faceAdd() {
        Log("-");
        NET_IN_ADD_FACE_INFO in;
        in.dwSize = sizeof in;
        strcpy(in.szUserID, uid);
        in.stuFaceInfo.nFacePhoto = 1;
        char *img;
        in.stuFaceInfo.nFacePhotoLen[0] = imageRead(path, img);
        in.stuFaceInfo.pszFacePhoto[0] = (char *) img;

        NET_OUT_ADD_FACE_INFO out;
        out.dwSize = sizeof out;
        bool res = CLIENT_FaceInfoOpreate(loginId, EM_FACEINFO_OPREATE_ADD, &in, &out, 1000);
        if (res) {
            Log("add face success");
        } else {
            Log("add face fail: %x", CLIENT_GetLastError());
        }
        delete[] img;
    }

    void userAdd() {
        Log("-");
        NET_IN_ACCESS_USER_SERVICE_INSERT in;
        in.dwSize = sizeof in;
        in.nInfoNum = 1;
        auto *user = new NET_ACCESS_USER_INFO;
        user->bUserIDEx = 0;
        strcpy(user->szUserID, uid);
        user->bUseNameEx = 0;
        strcpy(user->szName, name);
        user->emUserType = NET_ENUM_USER_TYPE_NORMAL;
        user->nUserStatus = 0;
        user->nDoorNum = 1, user->nDoors[0] = 0;
        user->nTimeSectionNum = 1, user->nTimeSectionNo[0] = 255;
        user->stuValidEndTime.dwYear = 2100, user->stuValidEndTime.dwMonth = 1, user->stuValidEndTime.dwDay = 1,
        user->stuValidEndTime.dwHour = 0, user->stuValidEndTime.dwMinute = 0, user->stuValidEndTime.dwSecond = 0;
        user->emAuthority = NET_ATTENDANCE_AUTHORITY_CUSTOMER;
        in.pUserInfo = user;

        NET_OUT_ACCESS_USER_SERVICE_INSERT 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_INSERT, &in, &out, 3000);
        if (res) {
            Log("add success: %d", out.nMaxRetNum);
            faceAdd();
        } else {
            Log("query fail: %x, code: %d", CLIENT_GetLastError(), *out.pFailCode);
        }
        delete user;
        delete out.pFailCode;
    }

    void faceRemove() {
        Log("-");
        NET_IN_REMOVE_FACE_INFO in;
        in.dwSize = sizeof in;
        strcpy(in.szUserID, uid);

        NET_OUT_REMOVE_FACE_INFO out;
        out.dwSize = sizeof out;
        bool res = CLIENT_FaceInfoOpreate(loginId, EM_FACEINFO_OPREATE_REMOVE, &in, &out, 3000);
        if (res) {
            Log("delete face success");
        } else {
            Log("delete face fail: %x", CLIENT_GetLastError());
        }
    }

    void userRemove() {
        faceRemove();
        Log("-");
        NET_IN_ACCESS_USER_SERVICE_REMOVE in;
        in.dwSize = sizeof in;
        in.nUserNum = 1;
        std::strcpy(in.szUserID[0], uid);
        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, 1000);
        if (res) {
            Log("delete user success");
        } else {
            Log("delete user fail: %x", CLIENT_GetLastError());
        }
        delete out.pFailCode;
    }

    void carQuery(const bool &isBlack) {
        Log("-");
        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) {
            Log("find success: handler = %ld", find_out.lFindeHandle);
            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) {
                Log("count success: total = %d", query_out.nRecordCount);
                int found = 0;
                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 = 5;

                    NET_OUT_FIND_NEXT_RECORD_PARAM do_out;
                    do_out.dwSize = sizeof do_out;
                    do_out.nMaxRecordNum = 5;
                    auto *records = new NET_TRAFFIC_LIST_RECORD[5];
                    do_out.pRecordList = records;
                    for (int i = 0; i < 5; ++i) records[i].dwSize = sizeof(NET_TRAFFIC_LIST_RECORD);

                    res = CLIENT_FindNextRecord(&do_in, &do_out);
                    if (res) {
                        // Log("query success: return = %d, max = %d", do_out.nRetRecordNum, do_out.nMaxRecordNum);
                        found += do_out.nRetRecordNum;
                        for (int i = 0; i < do_out.nRetRecordNum; ++i) {
                            Log(
                                    "id: %d, name: %s, plate: %s, auth-num: %d, control-num: %d, start: %d-%d-%d %d:%d:%d, end: %d-%d-%d %d:%d:%d",
                                    records[i].nRecordNo, records[i].szMasterOfCar, records[i].szPlateNumber,
                                    records[i].nAuthrityNum, records[i].emControlType,
                                    records[i].stBeginTime.dwYear, records[i].stBeginTime.dwMonth,
                                    records[i].stBeginTime.dwDay, records[i].stBeginTime.dwHour,
                                    records[i].stBeginTime.dwMinute, records[i].stBeginTime.dwSecond,
                                    records[i].stCancelTime.dwYear, records[i].stCancelTime.dwMonth,
                                    records[i].stCancelTime.dwDay, records[i].stCancelTime.dwHour,
                                    records[i].stCancelTime.dwMinute, records[i].stCancelTime.dwSecond
                            );
                        }
                    } else {
                        Log("query failed: %u", CLIENT_GetLastError());
                    }
                    delete[] records;
                }
            } else {
                Log("count failed: %u", CLIENT_GetLastError());
            }
            CLIENT_FindRecordClose(find_out.lFindeHandle);
        } else {
            Log("find failed: %u", CLIENT_GetLastError());
        }
        delete find_cond;
    }

    void carAdd(const bool &isBlack) {
        Log("-");
        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, name);
        strcpy(record->szPlateNumber, plate);
        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->stCancelTime.dwYear = 2077, record->stCancelTime.dwMonth = 1, record->stCancelTime.dwDay = 1,
        record->stCancelTime.dwHour = 0, record->stCancelTime.dwMinute = 0, record->stCancelTime.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) {
            Log("add success: record-number = %d", out.nRecordNo);
        } else {
            Log("add failed: %u", CLIENT_GetLastError());
        }
        delete record;
        delete info;
    }

    void carDel(const bool &isBlack, const int &id) {
        Log("-");
        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 = id;
        in.pstOpreateInfo = info;

        NET_OUT_OPERATE_TRAFFIC_LIST_RECORD out;
        out.dwSize = sizeof out;

        bool res = CLIENT_OperateTrafficList(loginId, &in, &out);
        if (res) {
            Log("delete success: id = %d", out.nRecordNo);
        } else {
            Log("delete failed: %u", CLIENT_GetLastError());
        }
        delete info;
    }

public:
    Device() {
        bool ok = CLIENT_Init(nullptr, 0);
        CLIENT_SetAutoReconnect(nullptr, 0);
        CLIENT_SetConnectTime(3000, 3);
        if (!ok) {
            Log("SDK init failed.");
            exit(-1);
        }
        Log("SDK init success");

        NET_IN_LOGIN_WITH_HIGHLEVEL_SECURITY in = {0};
        in.dwSize = sizeof in;
        strcpy(in.szIP, "192.168.1.108");
        in.nPort = 37777;
        strcpy(in.szUserName, "admin");
        strcpy(in.szPassword, "hmkj6688");

        NET_OUT_LOGIN_WITH_HIGHLEVEL_SECURITY out;
        out.dwSize = sizeof out;

        this->loginId = CLIENT_LoginWithHighLevelSecurity(&in, &out);
        if (!this->loginId) {
            Log("login failed.");
            exit(-1);
        }
        Log("login success");
    }

    void Run() {
        userFind();
//        userAdd();
//        userFind();
//        userRemove();
    }

    ~Device() {
        Log("clean, Bye ~");
        CLIENT_Cleanup();
    }
};

int main(int count, char **args) {
    Device device;
    device.Run();
    return 0;
}


/// json test
#include "json/parser.h"
#include <iostream>

using namespace std;
int main() {
    string str1 = R"({
"name": "Tinger",
"age": 18,
"words": ["this", "is", 123, null, false],
})", str2 = R"([
{
    "name": "jack",
    "age": 26
}, {
    "name": "jay",
    "age": 32
}, {
    "name": "john",
    "age": 62
}
])", str3 = R"({
"int": 2,
"double": 96.26,
"string": "casef"
})";
    json::Parser parser;
    json::Value value = parser.parse(str3);
    cout << value.str() << " " << value["465"].type_name() << endl;
    return 0;
}