#ifndef __EV_UTILS_HPP__
#define __EV_UTILS_HPP__
#pragma once

#include <string>
#include <jsoncons/json.hpp>
#include <fstream>
#include <algorithm>
#include <sys/statvfs.h>
#include <mutex>
#include <set>
#include <zmq.h>
#include <string>
#include <spdlog/spdlog.h>
#include <fmt/format.h>
#include <jsoncons/json.hpp>

enum EV_MSG_ERROR_CODE {
    EV_MSG_ERROR_NONE,
    EV_MSG_ERROR_INVALID_PARAM,
    EV_MSG_ERROR_EXCEPTION,
    EV_MSG_ERROR_UNSUPPORTED_CMD,
    EV_MSG_ERROR_CONTENT_SYNTAX,
    EV_MSG_ERROR_NUM
};

struct StrException : public std::exception {
    std::string s;
    StrException(std::string ss) : s(ss) {}
    ~StrException() throw () {} // Updated
    const char* what() const throw()
    {
        return s.c_str();
    }
};

unsigned short crc16(const unsigned char* data_p, unsigned char length);

namespace evutils {
using namespace std;
using namespace jsoncons;
typedef struct consts{
    /// for mqtt proto: json payload
    static const char version[];
    static const string kMsgCmd;
    static const string kMsgCmdConfig;
    static const string kMsgConfigVgw;
    static const string kMsgConfigMqtt;
    static const string kMsgConfigUpload;
    static const string kMsgConfigFeatures;
    static const string kMsgConfigEnable;
    static const string kMsgConfigLevel;
    static const string kMsgConfigInterval;
    static const string kMsgConfigDuration;
    static const string kMsgConfigHumanThreash;
    static const string kMsgConfigFaceThreash;
    static const string kMsgConfigRegion;
    static const string kMsgConfigMotion;
    static const string kMsgConfigRecord;
    static const string kMsgConfigAi;
    static const string kMsgCmdUploadVideo;
    static const string kMsgCmdStatus;
    static const string kMsgCmdGetConfig;
    static const string kMsgCmdReboot;
    static const string kMsgCmdQR;
    static const string kMsgCmdPtz;
    static const string kMsgType;
    static const string kMsgTypeImage;
    static const string kMsgEncode;
    static const string kMsgEncodeBase64;
    static const string kMsgFormat;
    static const string kMsgFormatJpeg;
    static const string kMsgCata;
    static const string kMsgTypeReport;
    static const string kMsgTypeResponse; 
    static const string kMsgTime;
    static const string kMsgData;
    static const string kMsgRid;
    static const string kMsgCode;
    static const string kMsgMsg;
    static const string kMsgSn;

    static const string recFilePath;
    static const long long TS_2020;
    static const long long TS_MAX;
    /// topics
    static const string sub_topic;
    static const string pub_topic_response;
    static const string pub_topic_eventdata;
    static const string pub_topic_lastwill;
    static const string pub_topic_report;
    static const string mqtt_url;
    static const string vgw_addr;
    static const string upload_addr;
    static const string strPubUrl;
    static consts& self();
}consts;

extern string msg_field(const json &msg, const string &field);
extern bool is_sdcard_avail(char *path = nullptr);
extern json make_default_cloud_config();
extern json make_default_sys_extra_config();
extern json make_online_msg(string sn);
extern json make_lastwill_msg(string sn);
extern void get_mac_addr(char *buf, char *intf = nullptr);
extern bool get_sdcard_megabytes(uint64_t &total, uint64_t &free, char* path = nullptr);
extern int setupDealer(void **ctx, void **s, string ident, string addr, int timeoutMs = 0, int sndQS = 0);
extern int setupRouter(void **ctx, void **s, string addr, int rcvQS=0);
extern int z_recv_multiple(void *s, vector<uint8_t> &buf, int &frames);
extern vector<string> split(const string &s, char delim);

template <typename T>
using cb_remove_elem = void(*)(T elem);

template <typename TN>
class OrderedList {
private:
    set<TN> list_;
    size_t maxSize;
    mutex mut;
    TN oldestTs;
    int freeMB = 0;
    const int kReservedMB_ = 1000;
    cb_remove_elem<TN> fn_remove;
    unsigned long cntInsert = 0;
public:
    OrderedList() = delete;
    OrderedList(ssize_t maxSize, cb_remove_elem<TN> fn_remove):maxSize(maxSize), fn_remove(fn_remove) {}
    void insert(TN elem, cb_remove_elem<TN> fn=nullptr)
    {
        // list_.insert(lower_bound(list_.begin(), list_.end(), elem), elem);
        if(cntInsert % 100 == 0) {
            oldestTs = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count() - 60*2*1000*maxSize;
            spdlog::info("record: maxSlices {}, oldestTs {}", maxSize, oldestTs);
            uint64_t mt, mf;
            if(get_sdcard_megabytes(mt, mf)){
                freeMB = mf;
            }else{
                freeMB = -1;
            }
        }
        ++cntInsert;
        
        /// ATTENTION: we don't remove old file unless it can't be hold
        bool forceRemoveOldFiles = false;
        if(forceRemoveOldFiles){
            if(elem < oldestTs) {
                if(fn != nullptr) {
                    (*fn)(elem);
                }
                else if(fn_remove != nullptr) {
                    (*fn_remove)(elem);
                }
                return;
            }
        }

        if(list_.size() == 0) {
            list_.insert(list_.begin(),elem);
            return;
        }

        auto itr = list_.rbegin();

        for(; itr != list_.rend(); itr++) {
            if(*itr < elem) {
                break;
            }
        }

        if(itr == list_.rbegin() ) {
            list_.insert(list_.end(), elem);
        }
        else {
            list_.insert(itr.base(), elem);
        }

        //if(list_.size() > maxSize||(freeMB != -1 && freeMB <= kReservedMB_)) {
        if(freeMB <= kReservedMB_) {
            lock_guard<mutex> lg(mut);
            for(int i = 0; i < 10; i++){
                auto ts = *(list_.begin());
                list_.erase(list_.begin());
                if(fn != nullptr) {
                    (*fn)(ts);
                }
                else if(fn_remove != nullptr) {
                    (*fn_remove)(ts);
                }
                else {
                    // no callback available
                }
            }
        }
    }

    set<TN> findByRange(TN tss, TN tse, TN & offsetS, TN &offsetE, int lagSecs, int *status = nullptr)
    {
        set<TN> ret;
        lock_guard<mutex> lg(mut);
        if(list_.size() == 0) {
            return ret;
        }

        TN first = *(list_.begin());
        auto _it = list_.end();
        TN end =  *(--_it);

        if(tse < first||tss > end || tse > end || end - tse < lagSecs*1000) {
            int s = 0;
            spdlog::info("range requested ({}, {}) is not in range existed ({}, {}).", tss, tse, first, end);
            if(tse < first) {
                s = -1;
            }else{
                s = 1;
            }

            if(status){
                *status = s;
            }
            return ret;
        }

        first = end = 0;

        int found = 0;
        TN last = 0;
        auto itr = list_.rbegin();
        for(; itr != list_.rend(); itr++) {
            if(*itr > tse) {
                continue;
            }

            if(*itr <= tse) {
                if(found != 1) {
                    spdlog::debug("\t matched : {}, s:{}, e:{}", *itr, tss, tse);
                    found = 1;
                    // check the end offset, not guaranteed
                    if( itr != list_.rbegin()) {
                        auto t = itr;
                        last = *(--t);
                    }
                }

                ret.insert(*itr);
                if(tss >= *itr) {
                    break;
                }
            }
        }

        if(found  == 1) {
            auto itr = ret.begin();
            offsetS = tss - *itr;
            if(last != 0) {
                offsetE = last - tse;
            }
            if(status){
                *status = 0;
            }
        }

        return ret;
    }
    set<TN> & items()
    {
        return list_;
    }
    size_t capacity(){
        return maxSize;
    }
};

typedef json(*fn_mk_default_json)();
extern string save_json(json &data, string path, bool isMerge = false, fn_mk_default_json mk = nullptr);
extern string load_json(json &config, string path, fn_mk_default_json mk = nullptr);

}

#endif