/*
 * @Author  : Simon Fojtu (simon.fojtu@gmail.com)
 * @Date    : 10.07.2014
 */

#include <vector>
#include <math.h>
#include <stdexcept>
#include "FireLog.h"
#include "FireSight.hpp"
#include "jansson.h"

using namespace std;
using namespace firesight;

bool Pt2Res::compare_XY_by_x(XY a, XY b) {
    return a.x < b.x;
}

bool Pt2Res::compare_XY_by_y(XY a, XY b) {
    return a.y < b.y;
}

int Pt2Res::nsamples_RANSAC(size_t ninl, size_t xlen, unsigned int NSAMPL, double confidence) {
    // q = \prod_{i=0}^{NSAMPL-1} (ninl-i)/(xlen-i)
    double q = 1;
    int nsamples;
    for (unsigned int i = 0; i < NSAMPL - 1; i++)
        q *= (double) (ninl-i)/(xlen-i);

    if (q < 1e-10)
        return INT_MAX;

     nsamples = (int) log(1-confidence) / log(1-q);
     if (nsamples < 1)
         nsamples = 1;

     return nsamples;
}

double Pt2Res::_RANSAC_line(XY * x, size_t nx, XY C) {
    assert(nx == 2);

    XY u(x[1].x - x[0].x, x[1].y - x[0].y);
    double norm_u = sqrt(u.x*u.x + u.y*u.y);
    XY p(x[1].x - C.x, x[1].y - C.y);
    double e = abs(u.x*p.y - u.y*p.x) / norm_u;

    return e;
}

double Pt2Res::_RANSAC_pattern(XY * x, size_t nx, XY C) {
    assert(nx == 2);

    XY u(x[1].x - x[0].x, x[1].y - x[0].y);
    // squared distance from x[0] to x[1]
    double usq = u.x*u.x + u.y*u.y;
    // vector from x[0] to C determines the unit vector
    XY Ap(C.x - x[0].x, C.y - x[0].y);
    // projection of C onto the line parametrized as x[0] + t (x[1] - x[0])
    double t = (Ap.x * u.x + Ap.y * u.y) / usq;

    return abs(t-round(t));
}

vector<XY> Pt2Res::RANSAC_2D(unsigned int NSAMPL, vector<XY> coords, double thr, double confidence, double(*err_fun)(XY *, size_t, XY)) {

    double bsupp = -1;
    vector<double> berr;
    vector<XY> binl;
    unsigned int max_iterations = 1000;
    if (coords.size() < NSAMPL)
        return binl; //errMsg = "Not enough detected circles, at least 2 needed";

    for (unsigned int it = 0; it < max_iterations; it++) {
        int idx0, idx1;
        vector<double> err;
        vector<XY> inl;
        double supp = 0;

        XY * sampl = (XY *) malloc(sizeof(XY) * NSAMPL);
        int * sampl_id = (int *) malloc(sizeof(int) * NSAMPL);
        for (unsigned int ni = 0; ni < NSAMPL; ni++) {
            bool rerun;
            do {
                rerun = false;
                sampl_id[ni] = rand() % coords.size();
                for (unsigned int oi = 0; oi < ni; oi++) {
                    if (sampl_id[ni] == sampl_id[oi]) {
                        rerun = true;
                        break;
                    }
                }
            } while (rerun);
            sampl[ni] = coords[sampl_id[ni]];
        }


        for (size_t cid = 0; cid < coords.size(); cid++) {
            double e;

            XY C = coords[cid];

            e = err_fun(sampl, NSAMPL, C);

            err.push_back(e);

            if (e < thr) {
                inl.push_back(C);
                supp += (1-e*e);
            }
        }
        free(sampl);
        free(sampl_id);

        // support - approximation of ML estimator
        supp /= thr * thr * coords.size();

        if (supp > bsupp) {
            bsupp = supp;
            binl = inl;
            berr = err;

            // update max_iterations
            max_iterations = nsamples_RANSAC(inl.size(), coords.size(), NSAMPL, confidence);
        }
    }
}

void Pt2Res::least_squares(vector<XY> xy, double * a, double * b) {
    double SUMx = 0, SUMy = 0, SUMxy = 0, SUMxx = 0;

    for (size_t i = 0; i < xy.size(); i++) {
        SUMx = SUMx + xy[i].x;
        SUMy = SUMy + xy[i].y;
        SUMxy = SUMxy + xy[i].x*xy[i].y;
        SUMxx = SUMxx + xy[i].x*xy[i].x;
    }
    *a = ( SUMx*SUMy - xy.size()*SUMxy ) / ( SUMx*SUMx - xy.size()*SUMxx );
    *b = ( SUMy - (*a)*SUMx ) / xy.size();
}

double Pt2Res::getResolution(double thr1, double thr2, double confidence, double separation, vector<XY> coords) {
    double resolution = NAN;

    try {
        if (coords.size() < 2) {
            throw runtime_error("Not enough points given");
        }

        // fit line through circle centers
        vector<XY> binl = RANSAC_2D(2, coords, thr1, confidence, _RANSAC_line);

        if (binl.size() < 2) {
            throw runtime_error("Not enough points after RANSAC line");
        }

        // fit a line through the inliers
        double a, b;
        least_squares(binl, &a, &b);

        // run another RANSAC to get the pattern in the circle centers forming the line
        binl = RANSAC_2D(2, binl, thr2, confidence, _RANSAC_pattern);

        if (binl.size() < 2) {
            throw runtime_error("Not enough points after RANSAC pattern");
        }

        // sort the inliers
        if (abs(a) > 1)
            sort(binl.begin(), binl.end(), compare_XY_by_y);
        else
            sort(binl.begin(), binl.end(), compare_XY_by_x);

        // compute distance of neighbours (inter_d)
        vector<double> inter_d;
        for (size_t i = 1; i < binl.size(); i++)
            inter_d.push_back(sqrt(
                        (binl[i].x - binl[i-1].x)*(binl[i].x - binl[i-1].x) + 
                        (binl[i].y - binl[i-1].y)*(binl[i].y - binl[i-1].y)));

        // get the median (d0) of the distances
        sort(inter_d.begin(), inter_d.end());
        double d0;
        if (inter_d.size() % 2 == 0)
            d0 = (inter_d[inter_d.size() / 2] + inter_d[inter_d.size() / 2 - 1])/2;
        else
            d0 = inter_d[inter_d.size() / 2];

        resolution = d0 / separation;

    } catch (exception &e) {
        LOGERROR(e.what());
    }

    return resolution;
}