#pragma once
#include <iostream>
#include <map>
#include <chrono>
#include <thread>
#include <mutex>
#include <opencv2/opencv.hpp>

#include "proto/def.grpc.pb.h"

template <class Type>
class lockFunc {
public:
    template<class Func, class ... Args>
    int lock_exe(Func func, Args&&... args) {
        lock.lock();
        bool ret = ((Type* )this->*func)(args...);
        lock.unlock();
        return ret;
    }

    template<class Func, class ... Args>
    int lock_exe(Func func, Type* p, Args&&... args) {
        lock.lock();
        bool ret = (p->*func)(args...);
        lock.unlock();
        return ret;
    }

private:
    std::mutex lock;
};

class MatDataBuffer : public lockFunc<MatDataBuffer>{
public:
    static MatDataBuffer *iter() {
        static MatDataBuffer *instance = nullptr;
        if (instance == nullptr) {
            instance = new MatDataBuffer();
        }
        return instance;
    }

    bool set(int azimuth, int lable, cv::Mat &mat) {
        if (azimuth >= DeviceAzimuth::DEVICE_AZIMUTH_MAX) {
            return false;
        }
        if (data[azimuth].size() > 10) {
            data[azimuth].erase(data[azimuth].begin());
        }
        data[azimuth].emplace_back(lable, mat);
        return true;
    }

    bool get(int azimuth, int lable, cv::Mat &mat, double timeout = 10) {
        if (azimuth >= DeviceAzimuth::DEVICE_AZIMUTH_MAX) {
            LOG(INFO) << "azimuth: " << azimuth << " < " << DeviceAzimuth::DEVICE_AZIMUTH_MAX;
            return false;
        }
         for (auto & iter : data[azimuth]) {
             if (iter.lable != lable) {
                 //LOG(INFO) << lable << " != " << iter.lable;
                 continue;
             } else {
                 std::chrono::duration<double> cost = std::chrono::steady_clock::now() - iter.t;
                 if (cost.count() > timeout) {
                     //LOG(INFO) << "data is too old : " << cost.count() << "s";
                     return false;
                 } else {
                     mat = iter.mat.clone();
                     return true;
                 }
             }
         }
        //(INFO) << "Not found data: " << lable << " in device " << azimuth;
        return false;
    }

    bool setIr(int lable, cv::Mat &mat) {
        if (ir.size() > 10) {
            ir.erase(ir.begin());
        }
        ir.emplace_back(lable, mat);
        return true;
    }

    bool getIr(int lable, cv::Mat &mat, double timeout = 10) {
        for (auto & iter : ir) {
            if (iter.lable != lable) {
                //LOG(INFO) << lable << " != " << iter.lable;
                continue;
            } else {
                std::chrono::duration<double> cost = std::chrono::steady_clock::now() - iter.t;
                if (cost.count() > timeout) {
                    //LOG(INFO) << "data is too old : " << cost.count() << "s";
                    return false;
                } else {
                    mat = iter.mat.clone();
                    return true;
                }
            }
        }
        //(INFO) << "Not found data: " << lable << " in device " << azimuth;
        return false;
    }

private:
    struct DataBufferInfo {
        std::chrono::steady_clock::time_point t;
        int lable;
        cv::Mat mat;

        DataBufferInfo(int lable_, cv::Mat &mat_) : lable(lable_), mat(mat_.clone()) {
            t = std::chrono::steady_clock::now();
        }
    };
private:
    std::vector<DataBufferInfo> data[DeviceAzimuth::DEVICE_AZIMUTH_MAX];
    std::vector<DataBufferInfo> ir;
};

class LabelYoloDataBuffer : public lockFunc<LabelYoloDataBuffer> {
public:
    static LabelYoloDataBuffer *iter() {
        static LabelYoloDataBuffer *instance = nullptr;
        if (instance == nullptr) {
            instance = new LabelYoloDataBuffer();
        }
        return instance;
    }

    bool set(JetStream::LabelYolo &yolo) {
        if (data.size() > 10) {
            data.pop();
        }
        data.push(yolo);
        return true;
    }

    bool get(JetStream::LabelYolo &yolo, double timeout = 10) {
        if (data.empty()) {
            return false;
        }
        yolo = data.front();
        data.pop();
        return true;
    }

private:
    std::queue<JetStream::LabelYolo> data;
};