// iANPR2Server.cpp : Этот файл содержит функцию "main". Здесь начинается и заканчивается выполнение программы.
//

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <queue>
#include "../../include/iANPR2.h"
#include "../../include/iANPR2Errors.h"
#include "ConfigServer.h"
#include "AddFuncsHttp.h"
#include "ThreadSafeQueue.h"
#include <thread>
#include <chrono>

using namespace std;
using namespace cv;

string nameServer = "[iANPR2Server]";
iANPR2Object ia2[maxGPUObjects]; // iANPR2 объект
string lic;
char* license;
ConfigSetting config;

struct ELEM
{
    string image; // изображение в массиве байт
    size_t ID; // ID запроса
    int res; // статус распознавания
    string json; // результат распознавания в формате result
#ifdef WIN32
    chrono::steady_clock::time_point t1;
    chrono::steady_clock::time_point t2;
#else
    chrono::system_clock::time_point t1;
    chrono::system_clock::time_point t2;
#endif
};

TSQueue<ELEM> queueElems;
std::atomic<size_t> counter{ 0 };
char mapImages[maxImages] = { 0 };
ELEM resultsElem[maxImages];

void runQueue(int threadNum)
{
    // Бесконечный цикл очереди
    for (;;)
    {        
        // Ждем элемента в очереди
        ELEM e = queueElems.pop();
        e.res = anpr2AddImage(ia2[threadNum], e.image.data(), (long)e.image.length());
        if (e.res == int(IANPR2::iANPR2Errors::IA_OK))
        {
            e.res = anpr2inference(ia2[threadNum]);
            if (e.res == int(IANPR2::iANPR2Errors::IA_OK))
            {
                char buf[10000];
                int size = 10000;
                e.res = anpr2GetResultJSON(ia2[threadNum], buf, size);
                if (e.res == int(IANPR2::iANPR2Errors::IA_OK))
                {
                    e.json = buf;
                }
            }
        }
        e.image = "";
        e.t2 = std::chrono::high_resolution_clock::now();
        resultsElem[e.ID % maxImages] = e;
        mapImages[e.ID % maxImages] = 1;
    }
}

int pushQueue(string data, string &result, int &res, unsigned long &timeRes)
{
    if (queueElems.size() >= config.maxImagesInQueue)
        return 1;
    counter.fetch_add(1, std::memory_order_relaxed);
    size_t v = counter.load(std::memory_order_relaxed);
    if (mapImages[v % maxImages] != 0)
        return 2;

    ELEM e;
    e.ID = v;
    e.image = data;
    mapImages[v % maxImages] = 2;
    e.t1 = std::chrono::high_resolution_clock::now();
    queueElems.push(e);    

    //cout << "V=" << v << endl;
    // Теперь ожидать выполнения
    for (;;)
    {        
        if (mapImages[v % maxImages] == 1)
        {
            // Результат доступен
            result = resultsElem[v % maxImages].json;
            timeRes = (unsigned long)((std::chrono::nanoseconds)(resultsElem[v % maxImages].t2 - resultsElem[v % maxImages].t1)).count()
                / 1000000;
            res = resultsElem[v % maxImages].res;
            mapImages[v % maxImages] = 0;
            break;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    return 0;
}

void runHttpServer(int port, int mode)
{
    Server svr;

    if (!svr.is_valid()) {
        cout << nameServer << " has an error...\n";
        return;
    }

    svr.Post("/", [=](const Request& /*req*/, Response& res) {
        res.set_redirect("/CheckRun");
    });

    svr.Post("/CheckRun", [](const Request& /*req*/, Response& res) {
        res.set_content("OK", "text/plain");
    });

    svr.Post("/ImageInference", [](const Request& req, Response& res) {
 
        auto image = req.get_file_value("image");        
        string resStr = "";
        int r = 1000;
        unsigned long t = 0;
        if (image.name == "image")
        {
            //cout << "image file length: " << image.content.length() << endl
            int k = pushQueue(image.content, resStr, r, t);  
            if (k == 1) r = 1001;
            if (k == 2) r = 1002;
        }               
        string s = "{\"status\":" + to_string(r);
        if (resStr != "")
            s += ",\"result\":" + resStr;
        if ( t > 0 )
            s += ",\"timePeriod\":"+ to_string(t);
        s += "}";
        res.set_content(s, "application/json");
    });


    svr.Post("/StopServer",
        [&](const Request& /*req*/, Response& /*res*/) { svr.stop(); });

    svr.set_error_handler([](const Request& /*req*/, Response& res) {
        const char* fmt = "Error %d";
        char buf[BUFSIZ];
        snprintf(buf, sizeof(buf), fmt, res.status);
        res.set_content(buf, "text/html");
    });

    if (mode == 1)
        svr.set_logger([](const Request& req, const Response& res) {
        printf("%s", log(req, res).c_str());
    });

    svr.listen("localhost", port);
}

void runServer(string configPath)
{    
    // Чтение конфига    
    if (!loadConfig(configPath, config))
    {
        cout << nameServer << " config error ...\n";
        return;
    }

    // Инициализация
    for (int i = 0; i < maxImages; i++)
    {
        resultsElem[i].ID = 0;
        resultsElem[i].res = 0;
    }

    // Инициализация iANPR2
    int allThreads = 1;
#ifdef TRT_ON
    allThreads = config.gpuObjects;
#endif
    thread thr[maxGPUObjects];
    for (int i = 0; i < allThreads; i++)
    {
        int error;
        ia2[i] = iANPR2Init((char*)config.pathToConfigForLibrary.c_str(), license, &error);

        if (ia2[i] == NULL || error != int(IANPR2::iANPR2Errors::IA_OK))
        {
            cout << nameServer << "Cann't init iANPR2Object! Error = " << error << " \n";
            return;
        }

        iANPR2Setting settings;
        settings.detectConfThresh = config.detectConfThresh;
        settings.maxSymbolsPlate = config.maxSymbolsPlate;
        settings.minSymbolsPlate = config.minSymbolsPlate;
        settings.minPlateWidth = config.minPlateWidth;
        settings.minPlateHeight = config.minPlateHeight;
        settings.templateCountries = config.plateTemplates;
        settings.expandedResult = config.expandedResult == 1;

        iANPR2Settings(ia2[i], settings);

        // Сначала запустить очередь в параллельном потоке
        thr[i] = thread(runQueue,i);
    }
    // Запуск http сервера   
    runHttpServer(config.port, config.mode);
}

void printHelp()
{
    cout << "(C) 2024. IntBuSoft. iANPR2Server - iANPR2 Inference Server\nRun:\n";
    cout << " iANPR2Server.exe -s <port> <config json path>\n";    
    cout << " iANPR2Server.exe -sl <port> <config json path>\n";
}

int main(int argc, char* argv[])
{
    ifstream in("lic2.key");
    if (!in.is_open())
        return 0;
    getline(in, lic);
    in.close();
    license = (char*)lic.c_str();

    if (argc == 1)
    {
#ifdef WIN32
        runServer("C:\\WINDOWS\\configinserv.json");
#endif
        return 0;
    }

    vector<string> arg;
    for (int i = 0; i < argc - 1; i++)
        arg.push_back(argv[i + 1]);


    if (arg.size() == 1)
    {
        runServer(arg[0]);
        return 0;
    }

    printHelp();

    return 0;
}


