// #include <android/asset_manager_jni.h>
// #include <android/bitmap.h>
// #include <android/log.h>

// #include <jni.h>

#include <string>
#include <vector>

// ncnn
#include <layer.h>
#include <net.h>
#include <benchmark.h>

#include <opencv2/opencv.hpp>

#include <Client/Device.h>

extern ncnn::UnlockedPoolAllocator g_blob_pool_allocator;
extern ncnn::PoolAllocator g_workspace_pool_allocator;

extern ncnn::Net yolov5;

class YoloV5Focus : public ncnn::Layer
{
public:
    YoloV5Focus()
    {
        one_blob_only = true;
    }

    virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option& opt) const
    {
        int w = bottom_blob.w;
        int h = bottom_blob.h;
        int channels = bottom_blob.c;

        int outw = w / 2;
        int outh = h / 2;
        int outc = channels * 4;

        top_blob.create(outw, outh, outc, 4u, 1, opt.blob_allocator);
        if (top_blob.empty())
            return -100;

        #pragma omp parallel for num_threads(opt.num_threads)
        for (int p = 0; p < outc; p++)
        {
            const float* ptr = bottom_blob.channel(p % channels).row((p / channels) % 2) + ((p / channels) / 2);
            float* outptr = top_blob.channel(p);

            for (int i = 0; i < outh; i++)
            {
                for (int j = 0; j < outw; j++)
                {
                    *outptr = *ptr;

                    outptr += 1;
                    ptr += 2;
                }

                ptr += w;
            }
        }

        return 0;
    }
};

static inline float intersection_area(const IDevice::RECOG_OBJECT& a, const IDevice::RECOG_OBJECT& b)
{
    if (a.x > b.x + b.w || a.x + a.w < b.x || a.y > b.y + b.h || a.y + a.h < b.y)
    {
        // no intersection
        return 0.f;
    }

    float inter_width = std::min(a.x + a.w, b.x + b.w) - std::max(a.x, b.x);
    float inter_height = std::min(a.y + a.h, b.y + b.h) - std::max(a.y, b.y);

    return inter_width * inter_height;
}

void qsort_descent_inplace(std::vector<IDevice::RECOG_OBJECT>& faceobjects, int left, int right);

void qsort_descent_inplace(std::vector<IDevice::RECOG_OBJECT>& faceobjects);

void nms_sorted_bboxes(const std::vector<IDevice::RECOG_OBJECT>& faceobjects, std::vector<int>& picked, float nms_threshold);

static inline float sigmoid(float x)
{
    return static_cast<float>(1.f / (1.f + exp(-x)));
}

void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<IDevice::RECOG_OBJECT>& objects);

inline void ncnn_init()
{
    ncnn::create_gpu_instance();
}

inline void ncnn_uninit()
{
    ncnn::destroy_gpu_instance();
}

// public native boolean Init(AssetManager mgr);
bool YoloV5Ncnn_Init(const std::string& paramFile, const std::string& binFile);

// public native Obj[] Detect(Bitmap bitmap, boolean use_gpu);
bool YoloV5NcnnDetect( ncnn::Mat& mat, bool use_gpu, std::vector<IDevice::RECOG_OBJECT>& objects);
bool YoloV5NcnnDetect( cv::Mat& mat, bool use_gpu, const std::string& blobName8, const std::string& blobName16, const std::string& blobName32, std::vector<IDevice::RECOG_OBJECT>& objects);