#include <Classifier.h>

#include <migraphx/onnx.hpp>
#include <migraphx/gpu/target.hpp>
#include <migraphx/quantization.hpp>
#include <hip/hip_runtime_api.h>
#include <Filesystem.h>
#include <SimpleLog.h>
#include <algorithm>
#include <CommonUtility.h>

namespace migraphxSamples
{

Classifier::Classifier()
{

}

Classifier::~Classifier()
{
    configurationFile.release();
}

cv::Mat Classifier::Preprocess(const std::vector<cv::Mat> &srcImages)
{
    // 数据预处理
    std::vector<cv::Mat> image;
    for(int i =0;i<srcImages.size();++i)
    {
        //BGR转换为RGB
        cv::Mat imgRGB;
        cv::cvtColor(srcImages[i], imgRGB, cv::COLOR_BGR2RGB);

        // 调整大小，使短边为256，保持长宽比
        cv::Mat shrink;
        float ratio = (float)256 / min(imgRGB.cols, imgRGB.rows);
        if(imgRGB.rows > imgRGB.cols)
        {
            cv::resize(imgRGB, shrink, cv::Size(256, int(ratio * imgRGB.rows)), 0, 0);
        }
        else
        {
            cv::resize(imgRGB, shrink, cv::Size(int(ratio * imgRGB.cols), 256), 0, 0);
        }

        // 裁剪中心窗口为224*224
        int start_x = shrink.cols/2 - 224/2;
        int start_y = shrink.rows/2 - 224/2;
        cv::Rect rect(start_x, start_y, 224, 224);
        cv::Mat images = shrink(rect);
        image.push_back(images);
    }

    // normalize并转换为NCHW
    cv::Mat inputBlob;
    Image2BlobParams image2BlobParams;
    image2BlobParams.scalefactor=cv::Scalar(1/58.395, 1/57.12, 1/57.375);
    image2BlobParams.mean=cv::Scalar(123.675, 116.28, 103.53);
    image2BlobParams.swapRB=false;
    blobFromImagesWithParams(image,inputBlob,image2BlobParams);
    return inputBlob;
}

ErrorCode Classifier::Initialize(InitializationParameterOfClassifier initializationParameterOfClassifier)
{
    // 读取配置文件
    std::string configFilePath=initializationParameterOfClassifier.configFilePath;
    if(!Exists(configFilePath))
    {
        LOG_ERROR(stdout, "no configuration file!\n");
        return CONFIG_FILE_NOT_EXIST;
    }
    if(!configurationFile.open(configFilePath, cv::FileStorage::READ))
    {
       LOG_ERROR(stdout, "fail to open configuration file\n");
       return FAIL_TO_OPEN_CONFIG_FILE;
    }
    LOG_INFO(stdout, "succeed to open configuration file\n");

    // 获取配置文件参数
    cv::FileNode netNode = configurationFile["Classifier"];
    std::string modelPath=(std::string)netNode["ModelPath"];
    useInt8=(bool)(int)netNode["UseInt8"];
    useFP16=(bool)(int)netNode["UseFP16"];
    useoffloadcopy=(bool)(int)netNode["Useoffloadcopy"];

    // 加载模型
    if(!Exists(modelPath))
    {
        LOG_ERROR(stdout,"%s not exist!\n",modelPath.c_str());
        return MODEL_NOT_EXIST;
    }
    net = migraphx::parse_onnx(modelPath);
    LOG_INFO(stdout,"succeed to load model: %s\n",GetFileName(modelPath).c_str());

    // 获取模型输入/输出节点信息
    std::unordered_map<std::string, migraphx::shape> inputs=net.get_inputs();
    std::unordered_map<std::string, migraphx::shape> outputs=net.get_outputs();
    inputName=inputs.begin()->first;
    inputShape=inputs.begin()->second;
    outputName=outputs.begin()->first;
    outputShape=outputs.begin()->second;
    int N=inputShape.lens()[0];
    int C=inputShape.lens()[1];
    int H=inputShape.lens()[2];
    int W=inputShape.lens()[3];
    inputSize=cv::Size(W,H);

    // 设置模型为GPU模式
    migraphx::target gpuTarget = migraphx::gpu::target{};

    // 量化
    if(useInt8)
    {
        // 创建量化校准数据,建议使用测试集中的多张典型图像
        cv::Mat srcImage=cv::imread("../Resource/Images/ImageNet_test.jpg",1);
        std::vector<cv::Mat> srcImages;
        for(int i=0;i<inputShape.lens()[0];++i)
        {
            srcImages.push_back(srcImage);
        }

        // 数据预处理
        cv::Mat inputBlob = Preprocess(srcImages);
        std::unordered_map<std::string, migraphx::argument> inputData;
        inputData[inputName]= migraphx::argument{inputShape, (float*)inputBlob.data};
        std::vector<std::unordered_map<std::string, migraphx::argument>> calibrationData = {inputData};

        // INT8量化
        migraphx::quantize_int8(net, gpuTarget, calibrationData);
    }
    else if(useFP16)
    {
        migraphx::quantize_fp16(net);
    }

    // 编译模型
    migraphx::compile_options options;
    options.device_id=0; // 设置GPU设备，默认为0号设备
    if(useoffloadcopy)
    {
        options.offload_copy=true;
    }
    else
    {
        options.offload_copy=false;
    }
    net.compile(gpuTarget,options);
    LOG_INFO(stdout,"succeed to compile model: %s\n",GetFileName(modelPath).c_str());

    // offloadcopy为false的时候，分配输入和输出内存
    if(!useoffloadcopy)
    {
        // 分配device输入内存
        inputBuffer_Device=nullptr;
        hipMalloc(&inputBuffer_Device, inputShape.bytes());
        programParameters[inputName] = migraphx::argument{inputShape, inputBuffer_Device};

        // 分配device和host输出内存
        outputBuffer_Device=nullptr;
        hipMalloc(&outputBuffer_Device, outputShape.bytes());
        programParameters[outputName] = migraphx::argument{outputShape, outputBuffer_Device};
        outputBuffer_Host=nullptr;                                                                // host内存
        outputBuffer_Host=malloc(outputShape.bytes());
    }
    
    // warm up
    if(useoffloadcopy)
    {
        std::unordered_map<std::string, migraphx::argument> inputData;
        inputData[inputName]=migraphx::argument{inputShape};
        net.eval(inputData);
    }
    else
    {
        migraphx::argument inputData= migraphx::argument{inputShape};
        hipMemcpy(inputBuffer_Device, inputData.data(), inputShape.bytes(), hipMemcpyHostToDevice);
        net.eval(programParameters);
    }

    // log
    LOG_INFO(stdout,"InputSize:%dx%d\n",inputSize.width,inputSize.height);
    LOG_INFO(stdout,"InputName:%s\n",inputName.c_str());
    LOG_INFO(stdout,"UseInt8:%d\n",(int)useInt8);
    LOG_INFO(stdout,"UseFP16:%d\n",(int)useFP16);
    LOG_INFO(stdout,"Useoffloadcopy:%d\n",(int)useoffloadcopy);

    return SUCCESS;
}

ErrorCode Classifier::Classify(const std::vector<cv::Mat> &srcImages,std::vector<std::vector<ResultOfPrediction>> &predictions)
{
    if(srcImages.size()==0||srcImages[0].empty()||srcImages[0].depth()!=CV_8U)
    {
        LOG_ERROR(stdout, "image error!\n");
        return IMAGE_ERROR;
    }
    
    // 数据预处理
    cv::Mat inputBlob = Preprocess(srcImages);

    // 当offload为true时，不需要内存拷贝
    if(useoffloadcopy)
    {
        std::unordered_map<std::string, migraphx::argument> inputData;
        inputData[inputName]= migraphx::argument{inputShape, (float*)inputBlob.data};

        // 推理
        std::vector<migraphx::argument> results = net.eval(inputData);

        // 获取输出节点的属性
        migraphx::argument result  = results[0];                 // 获取第一个输出节点的数据
        migraphx::shape outputShapes=result.get_shape();         // 输出节点的shape
        std::vector<std::size_t> outputSize=outputShapes.lens(); // 每一维大小，维度顺序为(N,C,H,W) 
        int numberOfOutput=outputShapes.elements();              // 输出节点元素的个数
        float *logits=(float *)result.data();                    // 输出节点数据指针

        // 获取每张图像的预测结果
        int numberOfClasses=numberOfOutput/srcImages.size();
        for(int i=0;i<srcImages.size();++i)
        {
            int startIndex=numberOfClasses*i;

            std::vector<float> logit;
            for(int j=0;j<numberOfClasses;++j)
            {
                logit.push_back(logits[startIndex+j]);
            }
            
            std::vector<ResultOfPrediction> resultOfPredictions;
            for(int j=0;j<numberOfClasses;++j)
            {
                ResultOfPrediction prediction;
                prediction.label=j;
                prediction.confidence=logit[j];

                resultOfPredictions.push_back(prediction);
            }

            predictions.push_back(resultOfPredictions);
        }
    }
    else   // 当offload为false时，需要内存拷贝
    {

        migraphx::argument inputData = migraphx::argument{inputShape, (float*)inputBlob.data};

        // 拷贝到device输入内存
        hipMemcpy(inputBuffer_Device, inputData.data(), inputShape.bytes(), hipMemcpyHostToDevice);

        // 推理
        std::vector<migraphx::argument> results = net.eval(programParameters);

        // 获取输出节点的属性
        migraphx::argument result   = results[0];                  // 获取第一个输出节点的数据
        migraphx::shape outputShapes=result.get_shape();           // 输出节点的shape
        std::vector<std::size_t> outputSize=outputShapes.lens();   // 每一维大小，维度顺序为(N,C,H,W) 
        int numberOfOutput=outputShapes.elements();                // 输出节点元素的个数

        // 将device输出数据拷贝到分配好的host输出内存
        hipMemcpy(outputBuffer_Host, outputBuffer_Device, outputShapes.bytes(), hipMemcpyDeviceToHost);  // 直接使用事先分配好的输出内存拷贝

        // 获取每张图像的预测结果
        int numberOfClasses=numberOfOutput/srcImages.size();
        std::vector<float> logit;
        for(int i=0;i<srcImages.size();++i)
        {
            int startIndex=numberOfClasses*i;
            for(int j=0;j<numberOfClasses;++j)
            {
                logit.push_back(((float *)outputBuffer_Host)[startIndex+j]);
            }
            
            std::vector<ResultOfPrediction> resultOfPredictions;
            for(int j=0;j<numberOfClasses;++j)
            {
                ResultOfPrediction prediction;
                prediction.label=j;
                prediction.confidence=logit[j];

                resultOfPredictions.push_back(prediction);
            }

            predictions.push_back(resultOfPredictions);
        }

        // 释放
        hipFree(inputBuffer_Device);
        hipFree(outputBuffer_Device);
        free(outputBuffer_Host);
    }

    return SUCCESS;

}

}
