#include "OpenCvFrameProcessor.h"

#include <string>
#include <cstring>
#include <opencv2/opencv.hpp>

const char* OpenCvProcessModeName(OpenCvProcessMode mode)
{
    switch (mode) {
    case OCV_MODE_EDGE:    return "Canny Edge";
    case OCV_MODE_SHARPEN: return "Sharpen";
    case OCV_MODE_GRAY:    return "Gray";
    case OCV_MODE_BLUR:    return "Gaussian Blur";
    default:               return "Unknown";
    }
}

void OpenCvFrameProcessor::SetError(const char* text)
{
    m_lastError = text ? text : "";
}

bool OpenCvFrameProcessor::ProcessRgb32(const HWS_VIDEO_FRAME* frame,
                                        OpenCvProcessMode mode,
                                        std::vector<unsigned char>& output,
                                        int* outputStride)
{
    if (outputStride) *outputStride = 0;
    if (!frame || !frame->data[0]) {
        SetError("empty frame");
        return false;
    }
    if (frame->width <= 0 || frame->height <= 0) {
        SetError("invalid frame size");
        return false;
    }
    if (frame->pixelFormat != HWS_PIXFMT_RGB32) {
        SetError("processor expects RGB32/BGRA frame");
        return false;
    }

    const int width = frame->width;
    const int height = frame->height;
    const int srcStride = (frame->stride[0] > 0) ? frame->stride[0] : width * 4;
    if (srcStride < width * 4) {
        SetError("invalid RGB32 stride");
        return false;
    }

    try {
        cv::Mat srcBgra(height, width, CV_8UC4, const_cast<unsigned char*>(frame->data[0]), (size_t)srcStride);
        cv::Mat dstBgra;

        if (mode == OCV_MODE_EDGE) {
            cv::Mat gray;
            cv::Mat edge;
            cv::cvtColor(srcBgra, gray, cv::COLOR_BGRA2GRAY);
            cv::GaussianBlur(gray, gray, cv::Size(3, 3), 0.0);
            cv::Canny(gray, edge, 70.0, 140.0);
            cv::cvtColor(edge, dstBgra, cv::COLOR_GRAY2BGRA);
        } else if (mode == OCV_MODE_SHARPEN) {
            cv::Mat kernel = (cv::Mat_<float>(3, 3) <<
                0.0f, -1.0f, 0.0f,
               -1.0f,  5.0f, -1.0f,
                0.0f, -1.0f, 0.0f);
            cv::filter2D(srcBgra, dstBgra, srcBgra.depth(), kernel);
        } else if (mode == OCV_MODE_GRAY) {
            cv::Mat gray;
            cv::cvtColor(srcBgra, gray, cv::COLOR_BGRA2GRAY);
            cv::cvtColor(gray, dstBgra, cv::COLOR_GRAY2BGRA);
        } else if (mode == OCV_MODE_BLUR) {
            cv::GaussianBlur(srcBgra, dstBgra, cv::Size(9, 9), 0.0);
        } else {
            srcBgra.copyTo(dstBgra);
        }

        cv::rectangle(dstBgra, cv::Rect(10, 10, 250, 44), cv::Scalar(0, 0, 0, 170), cv::FILLED);
        cv::putText(dstBgra,
                    std::string("OpenCV DLL: ") + OpenCvProcessModeName(mode),
                    cv::Point(22, 39),
                    cv::FONT_HERSHEY_SIMPLEX,
                    0.65,
                    cv::Scalar(0, 255, 255, 255),
                    2,
                    cv::LINE_AA);

        const int dstStride = width * 4;
        output.resize((size_t)dstStride * (size_t)height);
        if (dstBgra.isContinuous() && (int)dstBgra.step == dstStride) {
            memcpy(output.data(), dstBgra.data, output.size());
        } else {
            for (int y = 0; y < height; ++y) {
                memcpy(output.data() + (size_t)y * dstStride,
                       dstBgra.ptr(y),
                       (size_t)dstStride);
            }
        }
        if (outputStride) *outputStride = dstStride;
        SetError("");
        return true;
    } catch (const cv::Exception& e) {
        SetError(e.what());
        return false;
    } catch (...) {
        SetError("unknown OpenCV exception");
        return false;
    }
}
