#pragma once

#include <fstream>
#include <opencv2/opencv.hpp>
#include <cstdlib>

//
// Main Interface
//

static void extract_iris_from_texture(const cv::Mat& texture, const cv::Point eye_region_center, const int eye_region_radius, cv::Mat& iris);
static void extract_iris_from_scan_image(const cv::Mat& scan_image, const std::vector<cv::Point>& eye_contour, cv::Mat& iris);


//
// Helper for copying back
//
static void mask_circle(const cv::Size mask_size, const cv::Point center, const int radius, cv::Mat& result);


//
// Implementation
//

static void extract_eye(const cv::Mat& image, const cv::Point center, const int radius, cv::Mat& result)
{
    cv::Point r(radius, radius);
    cv::Rect region_of_interest(center - r, center + r);
    image(region_of_interest).copyTo(result);
}

static void extract_iris(const cv::Mat& image, cv::Point& iris_center, int& iris_radius)
{
    int min_radius = image.rows / 5;
    int max_radius = image.rows / 2;
    int max_dist_iris_to_image_center = image.rows / 5;
    cv::Point image_center(image.rows / 2, image.cols / 2);

    cv::Mat gray;
    cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);

    //
    // NOTE(swenninger):
    //  Blur seems to help circle detection
    //
    cv::medianBlur(gray, gray, 9);

    //
    // Hough Circles transform
    //
    // NOTE(swenninger):
    //   see https://docs.opencv.org/2.4/modules/imgproc/doc/feature_detection.html?highlight=houghcircles#houghcircles
    //
    double inv_resize_factor = 1.f;         // Resize factor for "accumulator"
    double min_circle_dist = gray.rows / 2; // Dist between two detected circles
    double magic_hough_param_1 = 100;       // Higher of the two Canny thresholds
    double magic_hough_param_2 = 10;        // "Accumulator" threshold, the smaller the more circles are detected

    std::vector<cv::Vec3f> circles;
    cv::HoughCircles(gray, circles, cv::HOUGH_GRADIENT, inv_resize_factor, min_circle_dist,
                     magic_hough_param_1, magic_hough_param_2, min_radius, max_radius);

    //
    // Loop through detected circles and filter
    //
    bool found_iris = false;
    for( size_t i = 0; i < circles.size(); i++ )
    {
        cv::Vec3f c = circles[i];
        cv::Point center = cv::Point((int)c[0], (int)c[1]);
        int radius = (int)c[2];

        //
        // Detected circle should roughly be in image center
        //
        if(norm(center - image_center) > max_dist_iris_to_image_center)
            continue;

        iris_center = center;
        iris_radius = radius;
        found_iris  = true;

        // Visualize
        // cv::circle(image, iris_center, iris_radius, cv::Scalar(255,0,255), 1, cv::LINE_AA);
        // cv::circle(image, iris_center, 3, cv::Scalar(255,0,255), -1, cv::LINE_AA);
    }

    //
    // NOTE(swenninger):
    //  Fallback for when HoughCircles() does not find a suitable circle
    //  Iris should roughly be in image center
    //
    if (!found_iris)
    {
        printf("[WARNING] Iris_detection::extract_iris: Could not find suitable iris circle. Setting to default\n");
        iris_center = image_center;
        iris_radius = image.rows / 5; // TIMO PREVIOUSLY 4

       // Visualize
       // cv::circle(image, iris_center, iris_radius, cv::Scalar(255,0,255), 1, cv::LINE_AA);
       // cv::circle(image, iris_center, 3, cv::Scalar(255,0,255), -1, cv::LINE_AA);
    }
}

static void mask_circle(const cv::Size mask_size, const cv::Point center, const int radius, cv::Mat& mask)
{
    mask = cv::Mat::zeros(mask_size, CV_8UC1);
    cv::circle(mask, center, radius, cv::Scalar(255), -1, cv::LINE_AA);
}

static inline void apply_mask(const cv::Mat& src, const cv::Mat& mask, cv::Mat& result)
{
    src.copyTo(result, mask);
}

//
// Returns center and enclosing circle radius
//
static void center_and_radius_from_contour(const std::vector<cv::Point>& contour, cv::Point& center, int& radius)
{
    center = cv::Point(0, 0);

    for (const auto& p : contour)
    {
        center += p;
    }

    center /= (int)contour.size();

    radius = 0;
    for (const auto& p : contour)
    {
        double dist = cv::norm(p - center);
        if (radius < dist)
        {
            radius = dist;
        }
    }
}

//
//
//
static void intersect_contour_with_circle(const std::vector<cv::Point>& contour, const cv::Point contour_offset,
                                          const cv::Point& center, const int radius,
                                          const cv::Size mask_size,
                                          cv::Mat& mask)
{
    //
    // Draw filled polygon of the area enclosed by the eye contour
    //
    cv::Mat contour_mask = cv::Mat::zeros(mask_size, CV_8UC1);
    const cv::Point* contour_ptr = contour.data();
    int num_points = (int)contour.size();
    cv::fillPoly(contour_mask, &contour_ptr, &num_points, 1, cv::Scalar(255), cv::LINE_AA, 0, contour_offset);

    //
    // Draw circle that is defined by center and radius
    //
    cv::Mat iris_mask = cv::Mat::zeros(mask_size, CV_8UC1);
    cv::circle(iris_mask, center, radius, cv::Scalar(255), -1, cv::LINE_AA);

    //
    // Intersection of contour and iris defines the new mask
    //
    mask = iris_mask & contour_mask;
}


//
// Filter the skin out of the image.
//
static void filter_skin(cv::Mat& image)
{
    cv::Vec3b current_color;
    int c_r, c_g, c_b;

    for(int i = 0; i < image.rows; ++i)
    {
        for(int j = 0; j < image.cols; ++j)
        {
            current_color = image.at<cv::Vec3b>(j, i);
            c_r = current_color[2];
            c_g = current_color[1];
            c_b = current_color[0];

            bool   cond1 = (c_r > 95 && c_g > 40 && c_b > 20);
            double delta = std::max(c_r, std::max(c_g, c_b)) - std::min(c_r, std::min(c_g, c_b));
            bool   cond2 = delta > 15;
            bool   cond3 = std::abs(c_r - c_g) > 15 && c_r > c_g && c_r > c_b;
            if ( cond1 && cond2 && cond3) // skin
            {
                 image.at<cv::Vec3b>(j, i) = cv::Vec3b(0, 0, 0);
            }
        }
    }
}

//
// Filter white out of the image.
// 
static void filter_white(cv::Mat& image)
{
    cv::Mat hsv_image;
    cv::cvtColor(image, hsv_image, cv::COLOR_BGR2HSV);
    
    cv::Vec3b current_color;
    int h, s, v;
    for (int row = 0; row < image.rows; ++row)
    {
        for (int col = 0; col < image.cols; ++col)
        {
            if (col == 120 && row == 170)
            {
                std::cout << "RGB: ";
                std::cout << image.at<cv::Vec3b>(col, row) << std::endl;

                std::cout << "HSV: ";
                std::cout << hsv_image.at<cv::Vec3b>(col, row) << std::endl;

            }

            current_color = hsv_image.at<cv::Vec3b>(col, row);
            h = current_color[0];
            s = current_color[1];
            v = current_color[2];

            bool cond1 = s < 48 && v > 22;
            bool cond2 = s < 150 && v > 150;
        
            if (cond1 || cond2) 
            {
                image.at<cv::Vec3b>(col, row) = cv::Vec3b(0, 0, 0);
            }

        }
    }
}

static void extract_iris_from_texture(const cv::Mat& texture, const cv::Point eye_region_center, const int eye_region_radius, cv::Mat& iris)
{
    cv::Mat eye, mask;
    cv::Point iris_center;
    int iris_radius;

    extract_eye(texture, eye_region_center, eye_region_radius, eye);
    int rand = std::rand();
    extract_iris(eye, iris_center, iris_radius);
    filter_white(eye);
    mask_circle(eye.size(), iris_center, iris_radius, mask);
    apply_mask(eye, mask, iris);
}

static void extract_iris_from_scan_image(const cv::Mat& scan_image, const std::vector<cv::Point>& eye_contour, cv::Mat& iris)
{
    cv::Mat eye, mask;
    cv::Point eye_center, iris_center;
    int eye_radius, iris_radius;

    center_and_radius_from_contour(eye_contour, eye_center, eye_radius);
    extract_eye(scan_image, eye_center, eye_radius, eye);
    extract_iris(eye, iris_center, iris_radius);
    cv::imwrite("before_masking.png", eye);
    filter_skin(eye);
    filter_white(eye);
    mask_circle(eye.size(), iris_center, iris_radius, mask);
    // cv::Point contour_offset = -(eye_center - cv::Point(eye_radius, eye_radius));
    // intersect_contour_with_circle(eye_contour, contour_offset, iris_center, iris_radius, eye.size(), mask);
    apply_mask(eye, mask, iris);
    cv::imwrite("resulting_iris.png", iris);

}

static bool load_contours(const std::string& filename, std::vector<cv::Point>& left_eye, std::vector<cv::Point>& right_eye)
{
    std::ifstream file(filename);

    if (!file)
    {
        printf("[ERROR] Cannot read contour file %s\n", filename.c_str());
        return false;
    }

    std::vector<cv::Point> all_contour_pixels;
    while(true)
    {
        cv::Point p;
        file >> p.x >> p.y;

        if (!file.good())
        {
            break;
        }

        all_contour_pixels.push_back(p);
    }

    if (all_contour_pixels.size() % 2 != 0)
    {
        printf("[ERROR] Uneven number of eye contour points. Cannot divide into left and right eye\n");
        return false;
    }

    size_t half_size = all_contour_pixels.size() / 2;
    right_eye  = std::vector<cv::Point>(all_contour_pixels.begin(), all_contour_pixels.begin() + half_size);
    left_eye = std::vector<cv::Point>(all_contour_pixels.begin() + half_size, all_contour_pixels.end());
    return true;
}
