#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <chrono>

using namespace cv;
using namespace std;

// Function to detect the red block and return its bounding box
bool detect_red_histogram(const Mat& frame, Rect& bounding_box) {
    // Convert to HSV color space
    Mat hsv;
    cvtColor(frame, hsv, COLOR_BGR2HSV);

    // Define red color range in HSV
    Mat mask1, mask2, mask;
    // Lower red range
    inRange(hsv, Scalar(0, 100, 100), Scalar(10, 255, 255), mask1);
    // Upper red range
    inRange(hsv, Scalar(160, 100, 100), Scalar(179, 255, 255), mask2);
    // Combine masks
    bitwise_or(mask1, mask2, mask);

    // Morphological operations to reduce noise
    Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
    morphologyEx(mask, mask, MORPH_OPEN, kernel);
    morphologyEx(mask, mask, MORPH_CLOSE, kernel);

    // Find contours
    vector<vector<Point>> contours;
    findContours(mask, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

    if (!contours.empty()) {
        // Assume the largest red object is the block
        size_t largest_idx = 0;
        double max_area = 0;
        for (size_t i = 0; i < contours.size(); ++i) {
            double area = contourArea(contours[i]);
            if (area > max_area) {
                max_area = area;
                largest_idx = i;
            }
        }

        if (max_area > 500) { // Minimum area threshold
            bounding_box = boundingRect(contours[largest_idx]);
            return true;
        }
    }

    return false;
}

int main() {
    // Parameters
    const int CALIBRATION_TIME = 4; // Seconds
    const int hue_bins = 180;
    const int sat_bins = 256;
    int histSize[] = { hue_bins, sat_bins };
    float hranges[] = { 0, 180 };
    float sranges[] = { 0, 256 };
    const float* ranges[] = { hranges, sranges };
    int channels[] = { 0, 1 };
    float ALPHA = 0.5f; // Weight for accumulating histogram

    // Initialize video capture
    VideoCapture cap(0);
    if (!cap.isOpened()) {
        cout << "Error: Unable to open the camera." << endl;
        return -1;
    }

    // Calibration Phase
    cout << "Starting Calibration Phase. Please move the red block across the frame." << endl;
    auto start_time = chrono::steady_clock::now();
    Mat accumulated_hist;
    bool hist_initialized = false;

    while (true) {
        Mat frame;
        if (!cap.read(frame)) {
            cout << "Error: Failed to capture frame." << endl;
            break;
        }

        flip(frame, frame, 1); // Mirror the frame

        // Detect red block
        Rect bbox;
        bool detected = detect_red_histogram(frame, bbox);
        if (detected) {
            // Draw bounding box in calibration phase (optional)
            rectangle(frame, bbox, Scalar(0, 255, 0), 2);

            // Extract ROI
            Mat roi = frame(bbox);
            Mat hsv_roi;
            cvtColor(roi, hsv_roi, COLOR_BGR2HSV);

            // Create mask for the ROI
            Mat mask1, mask2, mask_roi;
            inRange(hsv_roi, Scalar(0, 100, 100), Scalar(10, 255, 255), mask1);
            inRange(hsv_roi, Scalar(160, 100, 100), Scalar(179, 255, 255), mask2);
            bitwise_or(mask1, mask2, mask_roi);

            // Calculate histogram for the ROI
            Mat roi_hist;
            calcHist(&hsv_roi, 1, channels, mask_roi, roi_hist, 2, histSize, ranges, true, false);
            normalize(roi_hist, roi_hist, 0, 255, NORM_MINMAX);

            if (!hist_initialized) {
                roi_hist.copyTo(accumulated_hist);
                hist_initialized = true;
            }
            else {
                // Accumulate histogram using weighted average for robustness
                addWeighted(accumulated_hist, ALPHA, roi_hist, 1 - ALPHA, 0, accumulated_hist);
            }
        }

        // Display the frame
        imshow("Calibration Phase", frame);

        // Check if calibration time is over
        auto current_time = chrono::steady_clock::now();
        chrono::duration<double> elapsed = current_time - start_time;
        if (elapsed.count() > CALIBRATION_TIME)
            break;

        // Exit if 'q' is pressed during calibration
        char key = (char)waitKey(1);
        if (key == 'q' || key == 27) { // 'q' or 'ESC'
            cout << "Calibration aborted by user." << endl;
            cap.release();
            destroyAllWindows();
            return 0;
        }
    }

    if (accumulated_hist.empty()) {
        cout << "Error: No red block detected during calibration." << endl;
        cap.release();
        destroyAllWindows();
        return -1;
    }

    cout << "Calibration complete. Starting Detection Phase." << endl;

    // Detection Phase
    while (true) {
        Mat frame;
        if (!cap.read(frame)) {
            cout << "Error: Failed to capture frame." << endl;
            break;
        }

        flip(frame, frame, 1); // Mirror the frame
        Mat hsv;
        cvtColor(frame, hsv, COLOR_BGR2HSV);

        // Perform backprojection
        Mat back_proj;
        calcBackProject(&hsv, 1, channels, accumulated_hist, back_proj, ranges, 1, true);

        // Apply convolution with a disk kernel to smooth
        Mat disc = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
        filter2D(back_proj, back_proj, -1, disc);

        // Threshold the backprojection
        Mat thresh;
        threshold(back_proj, thresh, 50, 255, THRESH_BINARY);

        // Morphological operations
        morphologyEx(thresh, thresh, MORPH_OPEN, disc);
        morphologyEx(thresh, thresh, MORPH_CLOSE, disc);

        // Find contours
        vector<vector<Point>> contours;
        findContours(thresh, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

        if (!contours.empty()) {
            // Assume the largest contour is the red block
            double max_area = 0;
            size_t largest_idx = 0;
            for (size_t i = 0; i < contours.size(); ++i) {
                double area = contourArea(contours[i]);
                if (area > max_area) {
                    max_area = area;
                    largest_idx = i;
                }
            }

            if (max_area > 500) { // Minimum area threshold
                Rect detected_bbox = boundingRect(contours[largest_idx]);
                // Draw bounding box
                rectangle(frame, detected_bbox, Scalar(255, 0, 0), 2);
            }
        }

        // Optional: Display backprojection and threshold
        imshow("Back Projection", back_proj);
        imshow("Threshold", thresh);

        // Display the detection result
        imshow("Detection Phase", frame);

        // Exit if 'q' is pressed
        char key = (char)waitKey(1);
        if (key == 'q' || key == 27) { // 'q' or 'ESC'
            cout << "Detection terminated by user." << endl;
            break;
        }
    }

    // Release resources
    cap.release();
    destroyAllWindows();

    return 0;
}
based on this code, write only code for calibration and obtain the reference histogram in a .npy file that i can later upload to another program (tell how to also)