// ianpr2test.cpp : Этот файл содержит функцию "main". Здесь начинается и заканчивается выполнение программы.
//
#include <iostream>
#include <opencv2/opencv.hpp>
#include <chrono>
#include "../../include/iANPR2.h"
#include "../../include/iANPR2Errors.h"
#include <fstream>
#include <future>

using namespace std;
using namespace cv;
#ifdef _WINDOWS
#include <filesystem>
namespace fs = std::filesystem;
#else
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#endif

string lic;
char* license;
bool turn = true;

cv::Scalar colorsD[6] = {
    cv::Scalar(0, 0, 255),
    cv::Scalar(0, 255, 255),
    cv::Scalar(0, 255, 0),
    cv::Scalar(255, 0, 0),
    cv::Scalar(255, 255, 0),
    cv::Scalar(255, 0, 255)
};

string objectNames[6] =
{
    "number","person","car","moto","bus","truck"
};

void drawPlate(cv::Mat img, vector<NumberResult> numbers, string outName,
    vector<ObjectResult> objects = vector<ObjectResult>())
{    
    for (auto& r : numbers) {
        if (r.strings.empty())
            continue;
        string s = to_string(r.score).erase(to_string(r.score).size() - 4) + "|" + r.strings[0] + ":" +
            to_string(r.confidence[0]).erase(to_string(r.confidence[0]).size() - 4) + r.templates[0];
        cv::rectangle(img, r.rect, cv::Scalar(0, 0, 255), 2);
        cv::Point p = cv::Point(r.rect.x, r.rect.y - 20);
        if (p.y < 50)
            p.y = r.rect.y + r.rect.height + 20;
        int baseline = 0;
        cv::Size textSize = cv::getTextSize(s, cv::FONT_HERSHEY_SIMPLEX, 1, 1, &baseline);
        if (p.x + textSize.width > img.cols)
            p.x = img.cols - textSize.width;
        cv::putText(img, s, p, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
    }

    for (auto& r : objects) {
        cv::rectangle(img, r.rect, colorsD[r.classId % 6], 2);
        if (r.indexNum > -1)
        {
            cv::line(img, Point(r.rect.x, r.rect.y), Point(numbers[r.indexNum].rect.x, numbers[r.indexNum].rect.y), colorsD[r.classId % 6]);
        }
        cv::Point p = cv::Point(r.rect.x, r.rect.y - 20);
        if (p.y < 50)
            p.y = r.rect.y + r.rect.height + 20;
        int baseline = 0;
        string s = objectNames[r.classId % 6] + ":" + to_string(r.score).erase(to_string(r.score).size() - 4);
        cv::Size textSize = cv::getTextSize(s, cv::FONT_HERSHEY_SIMPLEX, 1, 1, &baseline);
        if (p.x + textSize.width > img.cols)
            p.x = img.cols - textSize.width;
        cv::putText(img, s, p, cv::FONT_HERSHEY_SIMPLEX, 1, colorsD[r.classId % 6], 2);
    }
    

    if (outName!="")
        cv::imwrite(outName, img);    
}

void test1Image(const char* cfgname, string name)
{
    // Инициализация iANPR2
    int error;    
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license,&error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru"},turn };

    iANPR2Settings(ia2, settings);

    std::vector <cv::Mat> imgs;
    imgs.push_back(cv::imread(name));
    int result = anpr2PLate(ia2, imgs);    
    if (result != int(IANPR2::iANPR2Errors::IA_OK))
        std::cout << "Inference problem! Error = " << result << " \n";


    vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
    if (!numbers.empty())
        for (auto number : numbers[0])
            if (!number.strings.empty())
                std::cout << number.strings[0] << "\n";

    for (int k = 0; k < numbers.size(); k++)
    {
        drawPlate(imgs[k], numbers[k], name+ "_out.png");
    }

    // Удаление iANPR2 
    iANPR2Release(&ia2);
}

void testFSImage(const char* cfgname, string name)
{
    // Инициализация iANPR2
    int error;
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru"},turn };

    iANPR2Settings(ia2, settings);

    std::vector <cv::Mat> imgs;
    imgs.push_back(cv::imread(name));
    int result = anpr2FirstStep(ia2, imgs);
    if (result != int(IANPR2::iANPR2Errors::IA_OK))
        std::cout << "Inference problem! Error = " << result << " \n";

    vector<vector<ObjectResult>> objects = anpr2GetAddResult(ia2);
    
    if (!objects.empty())
        for (auto obj : objects[0])
            if (obj.classId == 0)
            {
                std::cout << "First step found class number\n";
                // Это номер - надо распознать
                std::vector <cv::Mat> imgs2;
                imgs2.push_back(imgs[0](obj.rect));
                cv::imwrite("number.jpg", imgs2[0]);

                int result = anpr2SecondStep(ia2, imgs2);
                if (result != int(IANPR2::iANPR2Errors::IA_OK))
                    std::cout << "Inference problem! Error = " << result << " \n";

                vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
                if (!numbers.empty())
                    for (auto number : numbers[0])
                        if (!number.strings.empty())
                            std::cout << number.strings[0] << "\n";
            }


    // Удаление iANPR2 
    iANPR2Release(&ia2);
}


void test1ImageFull(const char* cfgname, string name)
{
    // Инициализация iANPR2
    int error;
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru"},turn };
    settings.expandedResult = true;

    iANPR2Settings(ia2, settings);

    std::vector <cv::Mat> imgs;
    imgs.push_back(cv::imread(name));
    int result = anpr2PLate(ia2, imgs);
    //int result = anpr2FirstStep(ia2, imgs);
    if (result != int(IANPR2::iANPR2Errors::IA_OK))
        std::cout << "Inference problem! Error = " << result << " \n";


    vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
    if (!numbers.empty())
        for (auto number : numbers[0])
            if (!number.strings.empty())
                std::cout << number.strings[0] << "\n";

    vector<vector<ObjectResult>> objects = anpr2GetAddResult(ia2);

    if (numbers.empty())
        numbers.push_back(vector<NumberResult>());
    drawPlate(imgs[0], numbers[0], name + "_out.png", objects[0]);    

    // Удаление iANPR2
    iANPR2Release(&ia2);
}

void performanceTest1Image(const char* cfgname, int countTests, int countRepeat)
{
    // Инициализация iANPR2
    int error;    
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru"},turn };

    iANPR2Settings(ia2, settings);
    
    std::vector <cv::Mat> imgs;    
    imgs.push_back(cv::imread("image2.jpg"));          
    //std::vector<uchar> buffer;
    //cv::imencode(".bmp", imgs[0], buffer);
    for (int i = 0; i < countTests; i++)
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int j = 0; j < countRepeat; j++)
        {
           // int result = anpr2AddImage(ia2, reinterpret_cast<char*>(buffer.data()), buffer.size());
            //result = anpr2inference(ia2);
            int result = anpr2PLate(ia2, imgs);
            //std::this_thread::sleep_for(std::chrono::milliseconds(30));
            if (result != int(IANPR2::iANPR2Errors::IA_OK))
                std::cout << "Inference problem! Error = " << result << " \n";
        }


        auto t2 = std::chrono::high_resolution_clock::now();
        unsigned long dt = (unsigned long)((std::chrono::nanoseconds)(t2 - t1) / countRepeat).count();
        double r = (double)dt / 1000000000;
        printf("[Test%d=%.5f]\n", i, r);
        //printf("[Test%d=%.5f;%.6f]\n", i, r, r / imgs.size());
    }

    vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
    if (!numbers.empty())
        for (auto number : numbers[0])
            if (!number.strings.empty())
                std::cout << number.strings[0] << "\n";

    for (int k = 0; k < numbers.size(); k++)
    {
        drawPlate(imgs[k], numbers[k], "image2_out.jpg");
    }

    // Удаление iANPR2
    iANPR2Release(&ia2);
}

void performanceTest1BImage(const char* cfgname, int countTests, int countRepeat)
{
    // Инициализация iANPR2
    int error;
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{""},turn };
    settings.typeResultNumberStr = iANPR2_RESULT_NUMBER_FULL;
    settings.bigSymbols = true;

    iANPR2Settings(ia2, settings);

    std::vector <cv::Mat> imgs;
    imgs.push_back(cv::imread("image3.jpg"));
    //std::vector<uchar> buffer;
    //cv::imencode(".bmp", imgs[0], buffer);
    for (int i = 0; i < countTests; i++)
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int j = 0; j < countRepeat; j++)
        {
            // int result = anpr2AddImage(ia2, reinterpret_cast<char*>(buffer.data()), buffer.size());
             //result = anpr2inference(ia2);
            int result = anpr2PLate(ia2, imgs);
            //std::this_thread::sleep_for(std::chrono::milliseconds(30));
            if (result != int(IANPR2::iANPR2Errors::IA_OK))
                std::cout << "Inference problem! Error = " << result << " \n";
        }


        auto t2 = std::chrono::high_resolution_clock::now();
        unsigned long dt = (unsigned long)((std::chrono::nanoseconds)(t2 - t1) / countRepeat).count();
        double r = (double)dt / 1000000000;
        printf("[Test%d=%.5f]\n", i, r);
        //printf("[Test%d=%.5f;%.6f]\n", i, r, r / imgs.size());
    }

    vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
    if (!numbers.empty())
        for (auto number : numbers[0])
            if (!number.strings.empty())
                std::cout << number.strings[0] << "\n";

    for (int k = 0; k < numbers.size(); k++)
    {
        drawPlate(imgs[k], numbers[k], "image3_out.jpg");
    }

    // Удаление iANPR2
    iANPR2Release(&ia2);
}

void performanceTest2Image(const char* cfgname, int countTests, int countRepeat)
{
    // Инициализация iANPR2
    int error;
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru", "kz"},turn};

    iANPR2Settings(ia2, settings);

    std::vector <cv::Mat> imgs2, imgs1;
    imgs2.push_back(cv::imread("image2.jpg"));
    imgs1.push_back(cv::imread("image1.jpg"));
    //std::vector<uchar> buffer;
    //cv::imencode(".bmp", imgs[0], buffer);
    for (int i = 0; i < countTests; i++)
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int j = 0; j < countRepeat; j++)
        {
            // int result = anpr2AddImage(ia2, reinterpret_cast<char*>(buffer.data()), buffer.size());
             //result = anpr2inference(ia2);
            int result = 0;
            if (j %2 == 0)
                anpr2PLate(ia2, imgs2);
            else
                anpr2PLate(ia2, imgs1);
            //std::this_thread::sleep_for(std::chrono::milliseconds(30));
            if (result != int(IANPR2::iANPR2Errors::IA_OK))
                std::cout << "Inference problem! Error = " << result << " \n";
        }


        auto t2 = std::chrono::high_resolution_clock::now();
        unsigned long dt = (unsigned long)((std::chrono::nanoseconds)(t2 - t1) / countRepeat).count();
        double r = (double)dt / 1000000000;
        printf("[Test%d=%.5f]\n", i, r);
        //printf("[Test%d=%.5f;%.6f]\n", i, r, r / imgs.size());
    }

    vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
    if (!numbers.empty())
        for (auto number : numbers[0])
            if (!number.strings.empty())
                std::cout << number.strings[0] << "\n";

    // Удаление iANPR2
    iANPR2Release(&ia2);
}

/*
* Пример распараллеливания по объектам
*/
void performanceTest1ImageP(const char* cfgname, int countTests, int countRepeat)
{
    // Инициализация iANPR2
    int error;
    iANPR2Object ia2[4];
    const int objects = 4;
    for (int i = 0; i < objects; i++)
    {
        ia2[i] = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru"},turn };
    for (int i = 0; i < objects; i++)
        iANPR2Settings(ia2[i], settings);

    std::vector <cv::Mat> imgs;
    imgs.push_back(cv::imread("image2.jpg"));
    for (int i = 0; i < countTests; i++)
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int j = 0; j < countRepeat; j++)
        {
            
            std::future<int> a[objects];
            for (int k = 0; k < objects; k++)
                a[k] = std::async(std::launch::async, anpr2PLate, ia2[k], imgs);

            for (int k = 0; k < objects; k++)
                a[k].wait();

            for (int k = 0; k < objects; k++)
                if (a[k].get() != int(IANPR2::iANPR2Errors::IA_OK))
                    std::cout << "Inference problem! Error = " << a[k].get() << " \n";
        }


        auto t2 = std::chrono::high_resolution_clock::now();
        unsigned long dt = (unsigned long)((std::chrono::nanoseconds)(t2 - t1) / countRepeat).count();
        double r = (double)dt / 1000000000;
        printf("[run=%.5f]\n", r / (imgs.size() * objects));
    }

    vector<vector<NumberResult>> numbers = anpr2GetResult(ia2[0]);
    if (!numbers.empty())
        for (auto number : numbers[0])
            if (!number.strings.empty())
                std::cout << number.strings[0] << "\n";

    for (int k = 0; k < numbers.size(); k++)
    {
        drawPlate(imgs[k], numbers[k], "image2_out.jpg");
    }

    // Удаление iANPR2    
    for (int i = 0; i < objects; i++)
        iANPR2Release(&ia2[i]);
}

void performanceTest4Images(const char* cfgname, int batchs, int countTests, int countRepeat)
{
    // Инициализация iANPR2
    int error;
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru"},turn };

    iANPR2Settings(ia2, settings);

    std::vector <cv::Mat> imgs;
    for(int i = 0; i < batchs; i++)
        imgs.push_back(cv::imread("image2.jpg"));

    for (int i = 0; i < countTests; i++)
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int j = 0; j < countRepeat; j++)
        {
            int result = anpr2PLate(ia2, imgs);
            if (result != int(IANPR2::iANPR2Errors::IA_OK))
                std::cout << "Inference problem! Error = " << result << " \n";
        }


        auto t2 = std::chrono::high_resolution_clock::now();
        unsigned long dt = (unsigned long)((std::chrono::nanoseconds)(t2 - t1) / countRepeat).count();
        double r = (double)dt / 1000000000;       
        printf("[Test%d=%.5f;%.6f]\n", i, r, r / imgs.size());
    }

    vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
    if (!numbers.empty())
        for (auto number : numbers[0])
            if (!number.strings.empty())
                std::cout << number.strings[0] << "\n";

    for (int k = 0; k < numbers.size(); k++)
    {
        drawPlate(imgs[k], numbers[k], "image2_out.jpg");
    }

    // Удаление iANPR2
    iANPR2Release(&ia2);
}

/*
* Пример распараллеливания по объектам
*/
void performanceTest4ImagesP(const char* cfgname, int batch, int countTests, int countRepeat)
{
    // Инициализация iANPR2
    int error;
    const int objects = 4;
    iANPR2Object ia2[objects];
    for (int i = 0; i < objects; i++)
    {
        ia2[i] = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru"},turn };
    for (int i = 0; i < objects; i++)
        iANPR2Settings(ia2[i], settings);

    std::vector <cv::Mat> imgs;
    for( int i = 0; i < batch; i++)
        imgs.push_back(cv::imread("image2.jpg"));
    
    for (int i = 0; i < countTests; i++)
    {
        auto t1 = std::chrono::high_resolution_clock::now();
        for (int j = 0; j < countRepeat; j++)
        {

            std::future<int> a[objects];
            for (int k = 0; k < objects; k++)
                a[k] = std::async(std::launch::async, anpr2PLate, ia2[k], imgs);

            for (int k = 0; k < objects; k++)
                a[k].wait();

            for (int k = 0; k < objects; k++)
                if (a[k].get() != int(IANPR2::iANPR2Errors::IA_OK))
                    std::cout << "Inference problem! Error = " << a[k].get() << " \n";
        }


        auto t2 = std::chrono::high_resolution_clock::now();
        unsigned long dt = (unsigned long)((std::chrono::nanoseconds)(t2 - t1) / countRepeat).count();
        double r = (double)dt / 1000000000;
        printf("[Test%d=%.5f;%.6f]\n", i, r, r / (imgs.size()*objects));
    }

    vector<vector<NumberResult>> numbers = anpr2GetResult(ia2[0]);
    if (!numbers.empty())
        for (auto number : numbers[0])
            if (!number.strings.empty())
                std::cout << number.strings[0] << "\n";

    for (int k = 0; k < numbers.size(); k++)
    {
        drawPlate(imgs[k], numbers[k], "image2_out.jpg");
    }

    // Удаление iANPR2    
    for (int i = 0; i < objects; i++)
        iANPR2Release(&ia2[i]);
}

int videoTest(const char* cfgname, const char* video)
{
    // Инициализация iANPR2
    int error;
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{},turn,4,2,6,15,true };

    iANPR2Settings(ia2, settings);

    Mat frame;
    VideoCapture cap(video);
    VideoWriter writer;
    bool start = 1;
    int count = 0;
    //cap.set(cv::CAP_PROP_POS_FRAMES, 367); // Установить желаемое начало внутри видео
    while (cap.isOpened())
    {        
        if (cap.read(frame))
        {
            Mat image;            
            frame.copyTo(image);            
            if (start)
            {
                start = 0;
                int codec = VideoWriter::fourcc('D', 'I', 'V', 'X');
                double fps = 25.0;
                string filename = "out.avi";
                writer.open(filename, codec, fps, image.size(), 1);
            }
            vector<Mat> imgs;
            imgs.push_back(image);

            int result = anpr2PLate(ia2, imgs);
            if (result != int(IANPR2::iANPR2Errors::IA_OK))
                std::cout << "Inference problem! Error = " << result << " \n";

            vector<vector<NumberResult>> numbers = anpr2GetResult(ia2); 
            std::cout << count << ":\n";
            if (!numbers.empty())
                for (auto number : numbers[0])
                    if (!number.strings.empty())
                        std::cout << number.strings[0] << "\n";

            for (int k = 0; k < numbers.size(); k++)
            {
                drawPlate(image, numbers[k], "");
            }            
            cv::putText(image, to_string(count), Point(0, image.rows), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2);

            // Результаты из памяти
            vector<vector<NumberResultMem>> mem = anpr2GetResultMem(ia2);
            if (!mem.empty())
                for (size_t i = 0; i < mem[0].size(); i++)
                {
                    string s = mem[0][i].numberString + ":" + mem[0][i].numberTemplate;                                        
                    cv::Point p = cv::Point(0, 30*(i+1));                    
                    int baseline = 0;
                    cv::Size textSize = cv::getTextSize(s, cv::FONT_HERSHEY_SIMPLEX, 1, 1, &baseline);                    
                    cv::rectangle(image, Rect(0, 30 * i, textSize.width,30), cv::Scalar(255, 255, 255), cv::FILLED);
                    cv::putText(image, s, p, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 0), 2);
                }


            writer.write(image);
            imshow("iANPR2", image);

            int c = waitKey(1);
            if (c == 27) //ESC
                break;
            if ( c== 32)
                c = waitKey(0);
        }
        else
            break;
        count++;
    }

    // Удаление iANPR2
    iANPR2Release(&ia2);
    return 0;
}

int videoTestInit2(const char* video)
{
    // Инициализация iANPR2
    int error;

    const char* detectionModelPath = "data\\models\\detectionsmall.model";
    const char* symbolsDetectModelPath = "data\\models\\symbolsmodel.model";
    const char* classificationModelPath = "data\\models\\classification.model";
    const char* classificationModelBPath = "data\\models\\classificationb.model";

    std::fstream s{ "data\\platetemplates.json", std::ios::in };
    std::stringstream buffer;
    buffer << s.rdbuf();

    iANPR2Object ia2 = iANPR2Init2(const_cast<char*>(detectionModelPath),
        1,
        const_cast<char*>(symbolsDetectModelPath),
        const_cast<char*>(classificationModelPath),
        const_cast<char*>(classificationModelBPath),
        const_cast<char*>(buffer.str().c_str()),
        license,
        &error);    

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru","kz","by","az","am","tj","uz","kg"},turn,4,2,6,15,true };
    settings.expandedResult = true;

    iANPR2Settings(ia2, settings);

    Mat frame;
    VideoCapture cap(video);
    VideoWriter writer;
    bool start = 1;
    int count = 0;
    //cap.set(cv::CAP_PROP_POS_FRAMES, 367); // Установить желаемое начало внутри видео
    while (cap.isOpened())
    {
        if (cap.read(frame))
        {
            Mat image;
            frame.copyTo(image);
            if (start)
            {
                start = 0;
                int codec = VideoWriter::fourcc('D', 'I', 'V', 'X');
                double fps = 25.0;
                string filename = "out.avi";
                writer.open(filename, codec, fps, image.size(), 1);
            }
            vector<Mat> imgs;
            imgs.push_back(image);

            int result = anpr2PLate(ia2, imgs);
            if (result != int(IANPR2::iANPR2Errors::IA_OK))
                std::cout << "Inference problem! Error = " << result << " \n";

            vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
            std::cout << count << ":\n";
            if (!numbers.empty())
                for (auto number : numbers[0])
                    if (!number.strings.empty())
                        std::cout << number.strings[0] << "\n";

            for (int k = 0; k < numbers.size(); k++)
            {
                drawPlate(image, numbers[k], "");
            }
            cv::putText(image, to_string(count), Point(0, image.rows), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2);

            // Результаты из памяти
            vector<vector<NumberResultMem>> mem = anpr2GetResultMem(ia2);
            if (!mem.empty())
                for (size_t i = 0; i < mem[0].size(); i++)
                {
                    string s = mem[0][i].numberString + ":" + mem[0][i].numberTemplate;
                    cv::Point p = cv::Point(0, 30 * (i + 1));
                    int baseline = 0;
                    cv::Size textSize = cv::getTextSize(s, cv::FONT_HERSHEY_SIMPLEX, 1, 1, &baseline);
                    cv::rectangle(image, Rect(0, 30 * i, textSize.width, 30), cv::Scalar(255, 255, 255), cv::FILLED);
                    cv::putText(image, s, p, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 0), 2);
                }


            writer.write(image);
            imshow("iANPR2", image);

            int c = waitKey(1);
            if (c == 27) //ESC
                break;
            if (c == 32)
                c = waitKey(0);
        }
        else
            break;
        count++;
    }

    // Удаление iANPR2
    iANPR2Release(&ia2);
    return 0;
}

int videoTestTraj(const char* cfgname, const char* video)
{
    // Инициализация iANPR2
    int error;
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru"},turn,4,2,6,15,true, true};

    iANPR2Settings(ia2, settings);

    Mat frame;
    VideoCapture cap(video);
    VideoWriter writer;
    bool start = 1;
    int count = 0;
    //cap.set(cv::CAP_PROP_POS_FRAMES, 367); // Установить желаемое начало внутри видео
    iANPR2TrajectoryObject traj = 0;
    cv::Point pointLines[4];
    while (cap.isOpened())
    {
        if (cap.read(frame))
        {
            Mat image;
            frame.copyTo(image);
            if (start)
            {
                start = 0;
                int codec = VideoWriter::fourcc('D', 'I', 'V', 'X');
                double fps = 25.0;
                string filename = "out.avi";
                writer.open(filename, codec, fps, image.size(), 1);
                traj = iANPR2Trajectory(image.rows, image.cols, 10000, 100, 1);
                pointLines[0] = Point(0, int(image.rows * 0.4));
                pointLines[1] = Point(image.cols, int(image.rows * 0.4));
                pointLines[2] = Point(0, int(image.rows * 0.45));
                pointLines[3] = Point(image.cols, int(image.rows * 0.45));
                iANPR2CreateLineIntersection(traj, pointLines[0].x, pointLines[0].y, pointLines[1].x, pointLines[1].y,
                    pointLines[2].x, pointLines[2].y, pointLines[3].x, pointLines[3].y);
            }
            vector<Mat> imgs;
            imgs.push_back(image);

            int result = anpr2PLate(ia2, imgs);
            if (result != int(IANPR2::iANPR2Errors::IA_OK))
                std::cout << "Inference problem! Error = " << result << " \n";

            vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
            vector<vector<ObjectResult>> objects = anpr2GetAddResult(ia2);
            vector<NumberResult> tn;
            vector<ObjectResult> on;
            if (!numbers.empty())
                tn = numbers[0];
            if (!objects.empty())
                on = objects[0];

            // Добавить в траекторию
            iANPR2TrajectoryProcess(traj, tn, on);

            std::cout << count << ":\n";
            if (!numbers.empty())
                for (auto number : numbers[0])
                    if (!number.strings.empty())
                        std::cout << number.strings[0] << "\n";

            for (int k = 0; k < numbers.size(); k++)
            {
                drawPlate(image, numbers[k], "", objects[k]);
            }
            cv::putText(image, to_string(count), Point(0, image.rows), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 0), 2);

            // Работа с траекторией
            vector<TrajectoryResult> tr = iANPR2TrajectoryGetResult(traj);

            int i = 0;
            for (auto t : tr)
            {                
                string s = t.strNumber + ":" + t.templateNumber + ":" + t.zone + ":" + to_string(t.weights) + "|ID:" + to_string(t.ID);
                cv::Point p = cv::Point(0, 30 * (i + 1));
                int baseline = 0;
                cv::Size textSize = cv::getTextSize(s, cv::FONT_HERSHEY_SIMPLEX, 1, 1, &baseline);
                cv::rectangle(image, Rect(0, 30 * i, textSize.width, 30), cv::Scalar(255, 255, 255), cv::FILLED);
                cv::putText(image, s, p, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 0), 2);
                // Рисование самой траектории
                if (!t.points.empty())
                {
                    cv::circle(image, t.points[0].point, 5, cv::Scalar(0, 0, 255), 3);
                    for (size_t j = 1; j < t.points.size(); j++)
                    {
                        cv::line(image, t.points[j - 1].point, t.points[j].point, cv::Scalar(0, 0, 255), 3);
                        cv::circle(image, t.points[j].point, 5, cv::Scalar(0, 0, 255), 3);
                    }
                }
                i++;
            }

            // Рисуем тестово линии
            line(image, pointLines[0], pointLines[1], Scalar(255, 0, 0), 3);
            line(image, pointLines[2], pointLines[3], Scalar(0, 255, 0), 3);

            // Пересечение траекторий
            std::string out = "";
            int index = 0;
            for (auto t : tr) // перебираем траектории
            {
                int res = iANPR2GetLineIntersection(traj, index);
                if (res == 1)
                    out += t.strNumber + ":up->down;";
                if (res == 2)
                    out += t.strNumber + ":down->up;";
                index++;                
            }
            if (out != "")
                cv::putText(image, out, Point(0, int(image.rows * 0.9)), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
         

            writer.write(image);
            imshow("iANPR2", image);

            int c = waitKey(1);
            if (c == 27) //ESC
                break;
            if (c == 32)
                c = waitKey(0);
        }
        else
            break;
        count++;
    }

    // Удаление iANPR2
    iANPR2Release(&ia2);
    return 0;
}

void testDir(const char* cfgname, string path, string pathout)
{
    // Инициализация iANPR2
    int error;
    iANPR2Object ia2 = iANPR2Init((char*)cfgname, license, &error);

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

    iANPR2Setting settings{ 0.25, 12, 60,{"ru"},turn };

    iANPR2Settings(ia2, settings);    


    double allTime = 0;
    int count = 0;    
    for (auto& p : fs::directory_iterator(path))
    {
        string name = p.path().filename().string();
        std::cout << name << endl;
        cv::Mat im = cv::imread(path + "/" + name);
        if (im.empty())
            continue;

        pair<string, vector<string>> res;
        res.first = name;
        std::vector <cv::Mat> imgs(1, im);
        

        int result = anpr2PLate(ia2, imgs);
        if (result != int(IANPR2::iANPR2Errors::IA_OK))
            std::cout << "Inference problem! Error = " << result << " \n";
        vector<vector<NumberResult>> numbers = anpr2GetResult(ia2);
        
        count++;        
        if (!numbers.empty())
            for (auto number : numbers[0])
                if (!number.strings.empty())
                    res.second.push_back(number.strings[0]);
        

        if (!numbers.empty())
            drawPlate(im, numbers[0], pathout + "/" + name);
    }        
}


void printHelp()
{
    cout << "(C) 2025. IntBuSoft. ianpr2test - test util for iANPR2\nModes:\n";
    cout << " -i      - one image test: ianpr2test -i <path config> <path to image>\n";    
    cout << " -i2     - one image test with objects and extended result: ianpr2test -i2 <path config> <path to image>\n";
    cout << " -ifs    - one image first second test: ianpr2test -ifs <path config> <path to image>\n";
    cout << " -v      - video test: ianpr2test -v <path config> <path to video> \n";    
    cout << " -vI2    - video test: ianpr2test -vI2 <path config> <path to video> \n";
    cout << " -vt     - video trajectory test: ianpr2test -v <path config> <path to video> \n";
    cout << " -p1     - performance 1 batch test: ianpr2test -p1 <path config>\n";    
    cout << " -p1b    - performance 1 batch test with big classification: ianpr2test -p1b <path config>\n";
    cout << " -p1inf  - performance 1 batch test 100000 cicles: ianpr2test -p1inf <path config>\n";
    cout << " -p1inf2 - performance 1 batch test 100000 cicles (2 images): ianpr2test -p1inf2 <path config>\n";
    cout << " -p1o    - performance 1 batch test with object parallel: ianpr2test -p1o <path config>\n";    
    cout << " -p4     - performance 4 batch test: ianpr2test -p4 <path config>\n";    
    cout << " -p4o    - performance 4 batch test with object parallel: ianpr2test -p4o <path config>\n";    
    cout << " -d      - directory of images test: ianpr2test -d <path config> <path to directory> <path to output directory>\n";
}

int main(int argc, char* argv[])
{
    if (argc == 1)
    {
        printHelp();
        return 0;
    }

    ifstream in("lic2.key");
    if (!in.is_open())
    {
        cout << "Cann't find lic2.key file!:\n";
        return 0;
    }
    getline(in, lic);
    in.close();
    license = (char*)lic.c_str();

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

    if (arg[0] == "-i" && arg.size() == 3)
    {     
        test1Image(arg[1].c_str(), arg[2]);
        return 0;
    }
    if (arg[0] == "-ifs" && arg.size() == 3)
    {
        testFSImage(arg[1].c_str(), arg[2]);
        return 0;
    }
    if (arg[0] == "-i2" && arg.size() == 3)
    {
        test1ImageFull(arg[1].c_str(), arg[2]);
        return 0;
    }
    if (arg[0] == "-v" && arg.size() == 3)
    {
        videoTest(arg[1].c_str(), arg[2].c_str());
        return 0;
    }
    if (arg[0] == "-vI2" && arg.size() == 2)
    {
        videoTestInit2(arg[1].c_str());
        return 0;
    }    
    if (arg[0] == "-vt" && arg.size() == 3)
    {
        videoTestTraj(arg[1].c_str(), arg[2].c_str());
        return 0;
    }
    if (arg[0] == "-p1" && arg.size() == 2)
    {
        performanceTest1Image(arg[1].c_str(),10,100);
        return 0;
    }    
    if (arg[0] == "-p1b" && arg.size() == 2)
    {
        performanceTest1BImage(arg[1].c_str(), 10, 100);
        return 0;
    }
    if (arg[0] == "-p1inf" && arg.size() == 2)
    {
        performanceTest1Image(arg[1].c_str(), 100000, 100);
        return 0;
    }
    if (arg[0] == "-p1inf2" && arg.size() == 2)
    {
        performanceTest2Image(arg[1].c_str(), 100000, 100);
        return 0;
    }
    if (arg[0] == "-p1o" && arg.size() == 2)
    {
        performanceTest1ImageP(arg[1].c_str(), 10, 25);
        return 0;
    }

    if (arg[0] == "-p4" && arg.size() == 2)
    {
        performanceTest4Images(arg[1].c_str(), 4, 10, 100);
        return 0;
    }
    
    if (arg[0] == "-p4o" && arg.size() == 2)
    {        
        performanceTest4ImagesP(arg[1].c_str(), 4, 10, 25);
        return 0;
    }    

    if (arg[0] == "-d" && arg.size() == 4)
    {        
        testDir(arg[1].c_str(), arg[2].c_str(), arg[3].c_str());
        return 0;
    }


    cout << "Wrong arguments!!!\n\n";
    printHelp();
}
