//
//  opencv_benchmark.cpp
//  MNN
//
//  Created by MNN on 2022/06/21.
//  Copyright © 2018, Alibaba Group Holding Limited
//

#include <iostream>
#include <iomanip>
#include <opencv2/core/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/calib3d.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include "cv/imgproc/imgproc.hpp"
#include "cv/calib3d.hpp"
#ifdef MNN_IMGCODECS
#include "cv/imgcodecs.hpp"
#endif

using namespace MNN;
using namespace Express;
using namespace CV;

constexpr int LOOP = 10;

static std::vector<std::string> functions;
static std::vector<double> times;
constexpr const char* path = "./imgs/cat.jpg";

template <typename T>
VARP cv2mnn(const cv::Mat& src) {
    VARP dst = _Input({ src.rows, src.cols, src.channels() }, NHWC, halide_type_of<T>());
    auto inputPtr = dst->writeMap<T>();
    memcpy(inputPtr, src.ptr(0), dst->getInfo()->size * sizeof(T));
    return dst;
}

#define arg_concat_impl(x, y) x ## y
#define arg_concat(x, y) arg_concat_impl(x, y)
#define arg_switch_0(CASE0, CASE1, CASE2, CASE3) CASE0
#define arg_switch_1(CASE0, CASE1, CASE2, CASE3) CASE1
#define arg_switch_2(CASE0, CASE1, CASE2, CASE3) CASE2
#define arg_switch_3(CASE0, CASE1, CASE2, CASE3) CASE3
// just support COND = [0, 1, 2, 3]
#define arg_switch(COND, CASE0, CASE1, CASE2, CASE3) arg_concat(arg_switch_, COND)(CASE0, CASE1, CASE2, CASE3)

#define BENCH_IMPL(mode, func, ...)\
    auto t1 =  std::chrono::high_resolution_clock::now();\
    for (int i = 0; i < LOOP; i++) {\
arg_switch(mode, cv::func(__VA_ARGS__);, auto dst = func(__VA_ARGS__);dst->readMap<void>();, auto dst = func(__VA_ARGS__);dst[0]->readMap<void>();, func(__VA_ARGS__);)\
    }\
    auto t2 =  std::chrono::high_resolution_clock::now();\
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count() / (1000. * LOOP);\
    times.push_back(duration); \

#define BENCHMARK_NAME(mode, name, func, ...) \
do {\
    BENCH_IMPL(mode, func, __VA_ARGS__)\
    functions.emplace_back(#name);\
} while(0);

#define BENCHMARK(mode, func, ...) \
do {\
    BENCH_IMPL(mode, func, __VA_ARGS__)\
    functions.emplace_back(#func); \
} while(0);

#define BENCHMARK_CV(func, ...) BENCHMARK(0, func, __VA_ARGS__)
#define BENCHMARK_MNN(func, ...) BENCHMARK(1, func, __VA_ARGS__)

void color(cv::Mat cvimg, VARP mnnimg) {
    cv::Mat dst;
#define CVTCOLOR(code)\
    BENCHMARK_NAME(0, code, cvtColor, cvimg, dst, cv::COLOR_##code)\
    BENCHMARK_NAME(1, code, cvtColor, mnnimg, COLOR_##code)
    CVTCOLOR(RGB2BGR)
    CVTCOLOR(RGB2GRAY)
    CVTCOLOR(RGB2RGBA)
    CVTCOLOR(RGB2BGRA)
    CVTCOLOR(RGB2YUV)
    CVTCOLOR(RGB2XYZ)
    CVTCOLOR(RGB2HSV)
    CVTCOLOR(RGB2HSV_FULL)
    CVTCOLOR(RGB2BGR555)
    CVTCOLOR(RGB2BGR565)
}

void filter(cv::Mat cvimg, VARP mnnimg) {
    cv::Mat dst;
    // blur
    BENCHMARK_CV(blur, cvimg, dst, {3, 3});
    BENCHMARK_MNN(blur, mnnimg, {3, 3});
    // boxFilter
    BENCHMARK_CV(boxFilter, cvimg, dst, -1, {3, 3});
    BENCHMARK_MNN(boxFilter, mnnimg, -1, {3, 3});
    // dilate
    BENCHMARK_CV(dilate, cvimg, dst, cv::getStructuringElement(0, {3, 3}));
    BENCHMARK_MNN(dilate, mnnimg, getStructuringElement(0, {3, 3}));
    // filter2D
    std::vector<float> kernel { 0, -1, 0, -1, 5, -1, 0, -1, 0 };
    cv::Mat cvKernel = cv::Mat(3, 3, CV_32FC1);
    memcpy(cvKernel.data, kernel.data(), kernel.size() * sizeof(float));
    VARP mnnKernel = _Const(kernel.data(), {3, 3});
    BENCHMARK_CV(filter2D, cvimg, dst, -1, cvKernel);
    BENCHMARK_MNN(filter2D, mnnimg, -1, mnnKernel);
    // boxFilter
    BENCHMARK_CV(GaussianBlur, cvimg, dst, {3, 3}, 10);
    BENCHMARK_MNN(GaussianBlur, mnnimg, {3, 3}, 10);
    // getDerivKernels
    BENCHMARK_CV(getDerivKernels, dst, dst, 1, 2, 1);
    BENCHMARK(3, getDerivKernels, 1, 2, 1);
    // getGaborKernel
    BENCHMARK_CV(getGaborKernel, {3, 3}, 10, 5, 5, 5, CV_PI*0.5, CV_32F);
    BENCHMARK_MNN(getGaborKernel, {3, 3}, 10, 5, 5, 5);
    // getGaussianKernel
    BENCHMARK_CV(getGaussianKernel, 3, 5, CV_32F);
    BENCHMARK_MNN(getGaussianKernel, 3, 5);
    // getStructuringElement
    BENCHMARK_CV(getStructuringElement, 0, {3, 3});
    BENCHMARK_MNN(getStructuringElement, 0, {3, 3});
    // Laplacian
    BENCHMARK_CV(Laplacian, cvimg, dst, -1);
    BENCHMARK_MNN(Laplacian, mnnimg, -1);
    // pyrDown
    BENCHMARK_CV(pyrDown, cvimg, dst);
    BENCHMARK_MNN(pyrDown, mnnimg);
    // pyrUp
    BENCHMARK_CV(pyrUp, cvimg, dst);
    BENCHMARK_MNN(pyrUp, mnnimg);
    // Scharr
    BENCHMARK_CV(Scharr, cvimg, dst, -1, 1, 0);
    BENCHMARK_MNN(Scharr, mnnimg, -1, 1, 0);
    // sepFilter2D
    std::vector<float> kernelX { 0, -1, 0 }, kernelY { -1, 0, -1 };
    cv::Mat cvKernelX = cv::Mat(1, 3, CV_32FC1);
    cv::Mat cvKernelY = cv::Mat(1, 3, CV_32FC1);
    memcpy(cvKernelX.data, kernelX.data(), kernelX.size() * sizeof(float));
    memcpy(cvKernelY.data, kernelY.data(), kernelY.size() * sizeof(float));
    VARP mnnKernelX = _Const(kernelX.data(), {1, 3});
    VARP mnnKernelY = _Const(kernelY.data(), {1, 3});
    BENCHMARK_CV(sepFilter2D, cvimg, dst, -1, cvKernelX, cvKernelY);
    BENCHMARK_MNN(sepFilter2D, mnnimg, -1, mnnKernelX, mnnKernelY);
    // Sobel
    BENCHMARK_CV(Sobel, cvimg, dst, -1, 1, 0);
    BENCHMARK_MNN(Sobel, mnnimg, -1, 1, 0);
    // sqrBoxFilter
    BENCHMARK_CV(sqrBoxFilter, cvimg, dst, -1, {1, 1}, {-1, -1});
    BENCHMARK_MNN(sqrBoxFilter, mnnimg, -1, {1, 1});
}

void geometric(cv::Mat cvimg, VARP mnnimg) {
    cv::Mat dst;
    // getAffineTransform
    float points[] = { 50, 50, 200, 50, 50, 200, 10, 100, 200, 20, 100, 250 };
    cv::Point2f cvSrc[3], cvDst[3];
    memcpy(cvSrc, points, 6 * sizeof(float));
    memcpy(cvDst, points + 6, 6 * sizeof(float));
    Point mnnSrc[3], mnnDst[3];
    memcpy(mnnSrc, points, 6 * sizeof(float));
    memcpy(mnnDst, points + 6, 6 * sizeof(float));
    BENCHMARK_CV(getAffineTransform, cvSrc, cvDst);
    BENCHMARK(3, getAffineTransform, mnnSrc, mnnDst);
    // getPerspectiveTransform
    BENCHMARK_CV(getPerspectiveTransform, cvSrc, cvDst);
    BENCHMARK(3, getPerspectiveTransform, mnnSrc, mnnDst);
    // getRotationMatrix2D
    cv::Point2f cvCenter {10, 10};
    Point mnnCenter {10, 10};
    BENCHMARK_CV(getRotationMatrix2D, cvCenter, 50, 0.6);
    BENCHMARK(3, getRotationMatrix2D, mnnCenter, 50, 0.6);
    // getRectSubPix
    BENCHMARK_CV(getRectSubPix, cvimg, {11, 11}, cvCenter, dst);
    BENCHMARK_MNN(getRectSubPix, mnnimg, {11, 11}, mnnCenter);
    // invertAffineTransform
    std::vector<float> M { 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 };
    cv::Mat cvM = cv::Mat(2, 3, CV_32FC1), cvMDst = cv::Mat(2, 3, CV_32FC1);
    memcpy(cvM.data, M.data(), M.size() * sizeof(float));
    Matrix mnnM;
    for (int i = 0; i < M.size(); i++) mnnM.set(i, M[i]);
    BENCHMARK_CV(invertAffineTransform, cvM, cvMDst);
    BENCHMARK(3, invertAffineTransform, mnnM);
    // resize
    BENCHMARK_CV(resize, cvimg, dst, cv::Size(), 2, 2);
    BENCHMARK_MNN(resize, mnnimg, {}, 2, 2);
    // warpAffine
    BENCHMARK_CV(warpAffine, cvimg, dst, cvM, {480, 360});
    BENCHMARK_MNN(warpAffine, mnnimg, mnnM, {480, 360});
}

void miscellaneous(cv::Mat cvimg, VARP mnnimg) {
    cv::Mat dst;
    // blendLinear
    int weightSize = cvimg.rows * cvimg.cols;
    std::vector<float> weight1(weightSize, 0.6), weight2(weightSize, 0.7);
    cv::Mat cvWeight1 = cv::Mat(cvimg.rows, cvimg.cols, CV_32FC1);
    cv::Mat cvWeight2 = cv::Mat(cvimg.rows, cvimg.cols, CV_32FC1);
    memcpy(cvWeight1.data, weight1.data(), weight1.size() * sizeof(float));
    memcpy(cvWeight2.data, weight2.data(), weight2.size() * sizeof(float));
    auto mnnWeight1 = cv2mnn<float>(cvWeight1);
    auto mnnWeight2 = cv2mnn<float>(cvWeight2);
    BENCHMARK_CV(blendLinear, cvimg, cvimg, cvWeight1, cvWeight2, dst);
    BENCHMARK_MNN(blendLinear, mnnimg, mnnimg, mnnWeight1, mnnWeight2);
    // threshold
    BENCHMARK_CV(threshold, cvimg, dst, 50, 20, cv::THRESH_BINARY);
    BENCHMARK_MNN(threshold, mnnimg, 50, 20, cv::THRESH_BINARY);
}

void structral(cv::Mat cvimg, VARP mnnimg) {
    static std::vector<uint8_t> img = {
        0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,1,0,0,0,0,0,0,0,0,0,
        0,0,1,1,1,1,1,1,1,0,0,0,0,
        0,0,1,0,0,1,0,0,0,1,1,0,0,
        0,0,1,0,0,1,0,0,1,0,0,0,0,
        0,0,1,0,0,1,0,0,1,0,0,0,0,
        0,0,1,1,1,1,1,1,1,0,0,0,0,
        0,0,0,1,0,0,1,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0
    };
    // findContours
    std::vector<std::vector<cv::Point>> cv_contours;
    std::vector<cv::Vec4i> hierarchy;
    VARP x = _Const(img.data(), {1, 11, 13, 1}, NHWC);
    cv::Mat mask = cv::Mat(11, 13, CV_8UC1);
    ::memcpy(mask.data, img.data(), img.size() * sizeof(uchar));
    BENCHMARK_CV(findContours, mask, cv_contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
    BENCHMARK(2, findContours, x, RETR_EXTERNAL, CHAIN_APPROX_NONE);
    // contourArea
    std::vector<cv::Point2i> cv_contour = { {0, 0}, {10, 0}, {10, 10}, {5, 4}};
    VARP mnn_contour = _Const(cv_contour.data(), {4, 2}, NHWC, halide_type_of<int>());
    BENCHMARK_CV(contourArea, cv_contour);
    BENCHMARK(3, contourArea, mnn_contour);
    // convexHull
    std::vector<int> y;
    BENCHMARK_CV(convexHull, cv_contour, y, false, false);
    BENCHMARK(3, convexHull, mnn_contour, false, false);
    // minAreaRect
    BENCHMARK_CV(minAreaRect, cv_contour);
    BENCHMARK(3, minAreaRect, mnn_contour);
    // boundingRect
    BENCHMARK_CV(boundingRect, cv_contour);
    BENCHMARK(3, boundingRect, mnn_contour);
    // connectedComponentsWithStats
    cv::Mat cv_label, cv_statsv, cv_centroids;
    VARP mnn_label, mnn_statsv, mnn_centroids;
    BENCHMARK_CV(connectedComponentsWithStats, mask, cv_label, cv_statsv, cv_centroids);
    BENCHMARK(3, connectedComponentsWithStats, x, mnn_label, mnn_statsv, mnn_centroids);
    // boxPoints
    BENCHMARK_CV(boxPoints, cv::minAreaRect(cv_contour), cv_label);
    BENCHMARK(3, boxPoints, minAreaRect(mnn_contour));
}

void draw(cv::Mat cvimg, VARP mnnimg) {
#define DRAW(func, ...)\
    BENCHMARK_CV(func, cvimg, __VA_ARGS__)\
    BENCHMARK(3, func, mnnimg, __VA_ARGS__)
    // arrowedLine
    DRAW(arrowedLine, {10, 10}, {300, 200}, {0, 0, 255}, 1)
    // circle
    DRAW(circle, {50, 50}, 10, {0, 0, 255}, 1)
    // line
    DRAW(line, {10, 10}, {50, 50}, {0, 0, 255}, 5)
    // rectangle
    DRAW(rectangle, {10, 10}, {200, 300}, {0, 0, 255}, 1)
    // drawContours
    cv::Mat gray, binary;
    cv::cvtColor(cvimg, gray, cv::COLOR_BGR2GRAY);
    cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);
    std::vector<std::vector<cv::Point>> cv_contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(binary, cv_contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    std::vector<std::vector<Point>> mnn_contours(cv_contours.size());
    for (int i = 0; i < cv_contours.size(); i++) {
        for (int j = 0; j < cv_contours[i].size(); j++) {
            Point p;
            p.set(cv_contours[i][j].x, cv_contours[i][j].y);
            mnn_contours[i].push_back(p);
        }
    }
    BENCHMARK_CV(drawContours, cvimg, cv_contours, -1, {0, 0, 255}, -1)
    BENCHMARK(3, drawContours, mnnimg, mnn_contours, -1, {0, 0, 255}, -1)
    // fillPoly
    BENCHMARK_CV(fillPoly, cvimg, cv_contours, {0, 0, 255})
    BENCHMARK(3, fillPoly, mnnimg, mnn_contours, {0, 0, 255})
}

void histogram(cv::Mat cvimg, VARP mnnimg) {
    std::vector<cv::Mat> images {cvimg};
    std::vector<int> histSize {256};
    std::vector<int> channels {0};
    std::vector<float> ranges {0., 256.};
    cv::Mat cvDest;
    // solvePnP
    BENCHMARK_CV(calcHist, images, channels, cv::Mat(), cvDest, histSize, ranges)
    BENCHMARK_MNN(calcHist, {mnnimg}, channels, nullptr, histSize, ranges)
}


void codecs(cv::Mat cvimg, VARP mnnimg) {
#ifdef MNN_IMGCODECS
    // imread
    BENCHMARK_CV(imread, path)
    BENCHMARK_MNN(imread, path)
    // imwrite
    BENCHMARK_CV(imwrite, "cv.jpg", cvimg)
    BENCHMARK(3, imwrite, "mnn.jpg", mnnimg)
#endif
}

void calib3d(cv::Mat cvimg, VARP mnnimg) {
    float model_points[18] = {
        0.0, 0.0, 0.0, 0.0, -330.0, -65.0, -225.0, 170.0, -135.0,
        225.0, 170.0, -135.0, -150.0, -150.0, -125.0, 150.0, -150.0, -125.0
    };
    float image_points[12] = {
        359, 391, 399, 561, 337, 297, 513, 301, 345, 465, 453, 469
    };
    float camera_matrix[9] = {
        1200, 0, 600, 0, 1200, 337.5, 0, 0, 1
    };
    float dist_coeffs[4] = { 0, 0, 0, 0 };
    VARP mnnObj = _Const(model_points, {6, 3});
    VARP mnnImg = _Const(image_points, {6, 2});
    VARP mnnCam = _Const(camera_matrix, {3, 3});
    VARP mnnCoe = _Const(dist_coeffs, {4, 1});
    cv::Mat cvObj = cv::Mat(6, 3, CV_32F, model_points);
    cv::Mat cvImg = cv::Mat(6, 2, CV_32F, image_points);
    cv::Mat cvCam = cv::Mat(3, 3, CV_32F, camera_matrix);
    cv::Mat cvCoe = cv::Mat(4, 1, CV_32F, dist_coeffs);
    std::vector<float> rv(3), tv(3);
    cv::Mat rvecs(rv),tvecs(tv);
    // solvePnP
    BENCHMARK_CV(solvePnP, cvObj, cvImg, cvCam, cvCoe, rvecs, tvecs, false, cv::SOLVEPNP_SQPNP)
    BENCHMARK(3, solvePnP, mnnObj, mnnImg, mnnCam, mnnCoe)
}

void printLine() {
    std::cout << "+----------------------------+----------+----------+" << std::endl;
}
void printLine(const char* col0, const char* col1, const char* col2) {
    std::cout << std::setiosflags(std::ios::left) << "|" << std::setw(28) << col0 << "|" << std::setw(10) << col1 << "|" << std::setw(10) << col2 << "|" << std::endl;
}
void printLine(const std::string& func, double t1, double t2) {
    std::cout << std::setiosflags(std::ios::left) << "|" << std::setw(28) << func << "|" << std::setw(10) << t1 << "|" << std::setw(10) << t2 << "|" << std::endl;
}
void log() {
    int count = times.size() / 2;
    printLine();
    printLine("function", "opencv", "MNN.cv");
    printLine();
    double cv_sum = 0., mnn_sum = 0.;
    for (int i = 0; i < count; i++) {
        auto func = functions[i * 2];
        double cv = times[i * 2];
        double mnn = times[i * 2 + 1];
        cv_sum += cv;
        mnn_sum += mnn;
        printLine(func, cv, mnn);
    }
    printLine();
    printLine("avg", cv_sum/count, mnn_sum/count);
    printLine();
}

int main(int argc, char** argv) {
    printf("opencv benchmark\n");
    cv::setNumThreads(1);
    // uint8
    auto img_uchar = cv::imread(path);
    auto mnn_uchar = cv2mnn<uint8_t>(img_uchar);
    // fp32
    cv::Mat img_fp32;
    img_uchar.convertTo(img_fp32, CV_32FC3);
    auto mnn_fp32 = cv2mnn<float>(img_fp32);
    color(img_uchar, mnn_uchar);
    filter(img_fp32, mnn_fp32);
    geometric(img_uchar, mnn_uchar);
    miscellaneous(img_fp32, mnn_fp32);
    structral(img_uchar, mnn_uchar);
    draw(img_uchar, mnn_uchar);
    codecs(img_uchar, mnn_uchar);
    calib3d(img_uchar, mnn_uchar);
    histogram(img_uchar, mnn_uchar);
    log();
    return 0;
}
