
#include "personal_calibrator.hpp"

using namespace cv;
using namespace std;

void CallBackFunc(int event, int x, int y, int flags, void* is_click)  {
    if (event == EVENT_LBUTTONDOWN){
        bool* temp = (bool*)is_click;
        *temp = true;
    }
}

PersonalCalibrator::PersonalCalibrator (int screen_width, int screen_height) {
    cv::namedWindow("calibration", CV_WINDOW_NORMAL);
    cv::setWindowProperty("calibration", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
    // set the mouse
    is_click_ = false;
    //set the callback function for any mouse event
    setMouseCallback("calibration", CallBackFunc, &is_click_); // wait for clicking

    screen_width_ = screen_width;
    screen_height_ = screen_height;

    center_radius_ = (int)((float)screen_width_ / 200.0f);
}

PersonalCalibrator::~PersonalCalibrator() {
    cv::setWindowProperty("calibration", CV_WND_PROP_FULLSCREEN, CV_WINDOW_NORMAL);
    cv::destroyWindow("calibration");
}

void PersonalCalibrator::generatePoints(int num_points) {
    index_point_ = -1;
    srand(time(NULL));
    Point2i current_point;

    for (int num = 0; num < num_points; ++num) {
        current_point.x = (rand() % screen_width_); // range is [0, 1]
        current_point.y = (rand() % screen_height_); // range is [0, 1]
        points_.emplace_back(current_point);
    }
}

void PersonalCalibrator::initialWindow() {
    // get the focus of the window
    namedWindow("GetFocus", CV_WINDOW_NORMAL);
    cv::Mat img = cv::Mat::zeros(100, 100, CV_8UC3);
    cv::imshow("GetFocus", img);
    cv::setWindowProperty("GetFocus", CV_WND_PROP_FULLSCREEN, CV_WINDOW_FULLSCREEN);
    waitKey(1);
    cv::setWindowProperty("GetFocus", CV_WND_PROP_FULLSCREEN, CV_WINDOW_NORMAL);
    cv::destroyWindow("GetFocus");

    // show instruction
    cv::Mat show_img = cv::Mat::zeros(screen_height_, screen_width_, CV_8UC3);
    string show_text = "Please click/touch when looking at the dots";
    cv::putText(show_img, show_text, cv::Point(400,600), FONT_HERSHEY_COMPLEX_SMALL, 2, cv::Scalar(255,255,255), 2);
    imshow("calibration", show_img);
    cv::waitKey(3000);
    for (int i=255; i > 0; i-=5) { //Gradient disappear, nice!
        show_img = cv::Mat::zeros(screen_height_, screen_width_, CV_8UC3);
        cv::putText(show_img, show_text, cv::Point(400,600), FONT_HERSHEY_COMPLEX_SMALL, 2, cv::Scalar(i,i,i), 2);
        imshow("calibration", show_img);
        cv::waitKey(1);
    }
}

bool PersonalCalibrator::showNextPoint() {
    cv::Mat show_img = cv::Mat::zeros(screen_height_, screen_width_, CV_8UC3);
    index_point_ ++;
    cv::circle(show_img, cv::Point(points_[index_point_].x, points_[index_point_].y), center_radius_, cv::Scalar(255, 255, 255), -1);
    is_click_ = false;

    while (true) {
        imshow("calibration", show_img);
        int key = cv::waitKey(10); // wait for interaction
        if (key == 27) // if press the ESC key
            return false;
        if (is_click_) {
            break;
        }
    }
    return true;
}

void PersonalCalibrator::confirmClicking() {
    cv::Mat show_img = cv::Mat::zeros(screen_height_, screen_width_, CV_8UC3);
    cv::circle(show_img, cv::Point(points_[index_point_].x, points_[index_point_].y), center_radius_, cv::Scalar(0, 200, 0), -1);
    imshow("calibration", show_img);
    cv::waitKey(500);
}

// this polyfit function is copied from  opencv/modules/contrib/src/polyfit.cpp
// This original code was written by
//  Onkar Raut
//  Graduate Student,
//  University of North Carolina at Charlotte
cv::Mat polyfit(const Mat& src_x, const Mat& src_y, int order)
{
    CV_Assert((src_x.rows>0)&&(src_y.rows>0)&&(src_x.cols>0)&&(src_y.cols>0)&&(order>=1));
    Mat matrix;
    Mat bias = Mat::ones((int)src_x.rows, 1, CV_32FC1);

    Mat input_x = Mat::zeros(src_x.rows, order*src_x.cols, CV_32FC1);

    Mat copy;
    for(int i=1; i<=order;i++){
        copy = src_x.clone();
        pow(copy,i,copy);
        copy.copyTo(input_x(Rect((i-1)*src_x.cols, 0, copy.cols, copy.rows)));
    }

    Mat new_mat;
    cv::hconcat(input_x, bias, new_mat);
    cout << "new_mat: " << new_mat << endl;
    cv::solve(new_mat, src_y, matrix, DECOMP_NORMAL);

    cout << "model_matrix: " << matrix << endl;
    Mat calibrated = new_mat * matrix;
    cout << "calibrated: " << calibrated << endl;
    double dist_original = norm(src_x, src_y, NORM_L2);
    cout << "dist_original: " << dist_original << endl;
    double dist_calibrated = norm(calibrated, src_y, NORM_L2);
    cout << "dist_calibrated: " << dist_calibrated << endl;

    return matrix;

}

void PersonalCalibrator::generateModel(vector<Point2f> prediction, vector<Point2f> ground_truth, int order) {

    cv::Mat input_x = cv::Mat((int)prediction.size(), 2, CV_32FC1, prediction.data());
    cv::Mat input_y = cv::Mat((int)ground_truth.size(), 2, CV_32FC1, ground_truth.data());
    cout << "input_x: " << input_x << endl;
    cout << "input_y: " << input_y << endl;
    cv::Mat model_matrix;
    model_matrix_ = polyfit(input_x, input_y, order);
}

void PersonalCalibrator::saveModel(std::string file_path) {
    cv::FileStorage storage(file_path, cv::FileStorage::WRITE);
    storage << model_matrix_;
    storage.release();  
}