#include "image_utilities.h"
#include <opencv2/imgproc.hpp>
#include <vector>
#include <cmath>
#include <iostream>
#include <exception>

using namespace std;

vector<cv::Point> mrect2box(const cv::RotatedRect& mrect) {
    cv::Point2f boxPoints[4];
    mrect.points(boxPoints);
    
    vector<cv::Point> result;
    for (int i = 0; i < 4; ++i) {
        result.emplace_back(cv::Point(static_cast<int>(boxPoints[i].x), static_cast<int>(boxPoints[i].y)));
    }
    
    return result;
}

cv::Mat block_threshold(const cv::Mat& gray) {
    try {
        int height = gray.rows;
        int width = gray.cols;
        
        vector<int> divisions = {8, 13};
        vector<int> block_sizes;
        for (int div : divisions) {
            int size = max(19, (height + width) / 2 / div);
            if (size % 2 == 0) size++;
            block_sizes.push_back(size);
        }
        
        vector<pair<int, int>> block_strategies;
        for (int size : block_sizes) {
            block_strategies.push_back({size, 0});
            block_strategies.push_back({size, size / 2});
        }
        
        cv::Mat otsu;
        double global_threshold = cv::threshold(gray, otsu, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
        
        cv::Mat adaptive_ref;
        cv::adaptiveThreshold(gray, adaptive_ref, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, block_sizes[0], 20);
        
        cv::Mat result = cv::Mat::zeros(gray.size(), CV_8UC1);
        
        for (auto& strategy : block_strategies) {
            int block_size = strategy.first;
            int offset = strategy.second;
            
            cv::Mat current_result = cv::Mat::zeros(gray.size(), CV_8UC1);
            
            for (int y = offset; y < height; y += block_size) {
                for (int x = offset; x < width; x += block_size) {
                    int end_y = min(y + block_size, height);
                    int end_x = min(x + block_size, width);
                    
                    cv::Mat block_roi = gray(cv::Rect(x, y, end_x - x, end_y - y));
                    
                    cv::Scalar mean, stddev;
                    cv::meanStdDev(block_roi, mean, stddev);
                    double variance = stddev[0] * stddev[0];
                    
                    cv::Mat block_result;
                    if (variance < 80) {
                        block_result = cv::Mat::zeros(block_roi.size(), CV_8UC1);
                        if (mean[0] < global_threshold) {
                            block_result = cv::Mat::ones(block_roi.size(), CV_8UC1) * 255;
                        }
                    } else {
                        cv::threshold(block_roi, block_result, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
                        
                        cv::Scalar ref_mean, block_mean;
                        cv::Mat adaptive_block = adaptive_ref(cv::Rect(x, y, end_x - x, end_y - y));
                        cv::meanStdDev(adaptive_block, ref_mean, cv::noArray());
                        cv::meanStdDev(block_result, block_mean, cv::noArray());
                        
                        if (abs(ref_mean[0] - block_mean[0]) > 200) {
                            block_result = 255 - block_result;
                        }
                    }
                    
                    block_result.copyTo(current_result(cv::Rect(x, y, end_x - x, end_y - y)));
                }
            }
            
            cv::bitwise_or(result, current_result, result);
        }
        
        return result;
    } catch (const exception& e) {
        cout << "Exception in block_threshold: " << e.what() << endl;
        return cv::Mat();
    } catch (...) {
        cout << "Unknown exception in block_threshold" << endl;
        return cv::Mat();
    }
}
