#pragma once
#include <iostream>
#include <sstream>
#include <vector>
#include <math.h>
#include <omp.h>
#include <list>
#include <hdf5.h>
#include "Star.h"
#include "Point.h"
#include "taos.h"

void StarTrace(std::vector<Star>& Stars, double* cleandata, int xs, int ys, int frames, int radius_pct);
Point TraceStarCenter(Point last_center, int search_limit, int separation_cap, double* cleandata, int xs, int ys, int frame);
int PointsDistance(Point p1, Point p2);
void CropStarMark(Point p1, Point p2, double* cleandata, int frame, int xs, int ys, std::vector<double>& cropped_data);

using namespace std;

void StarTrace(std::vector<Star>& Stars, double* cleandata, int xs, int ys, int frames, int radius_pct) {

	Point current_center, last_center;
	Point p1, p2;
	std::vector<double> cropped_data;
	int search_limit, separation_cap;
	int radius;
	int diff;
	int size = Stars.size();

//#pragma omp parallel for shared(Stars, cleandata, xs, ys, frames, size) private(current_center, last_center, p1, p2, cropped_data, search_limit, separation_cap, radius, diff) num_threads(4)
	for (int i = 0; i < Stars.size(); i++) {

		last_center = Stars[i].getCenter();

		separation_cap = Stars[i].getRadius() + 0.5;
		search_limit = separation_cap * radius_pct;

		radius = (Stars[i].getRadius() / 2) + 0.5;

		if (radius < (MIN_MARK_SIDE - 1) / 2) {
			radius = (MIN_MARK_SIDE - 1) / 2;
		}

		Stars[i].setSquareSide((radius * 2) + 1);
		Stars[i].setMemory(frames);

		for (int frame = 0; frame < frames; frame++) {

			current_center = TraceStarCenter(last_center, search_limit, separation_cap, cleandata, xs, ys, frame);
			Stars[i].addCenterInTime(current_center);

			p1.setX(current_center.getX() - radius);
			p1.setY(current_center.getY() - radius);
			p2.setX(current_center.getX() + radius);
			p2.setY(current_center.getY() + radius);

			if (p1.getX() < 0) {
				diff = abs(p1.getX());
				p1.setX(0);
				p2.setX(p2.getX() + diff);
			}
			else if (p2.getX() > (xs - 1)) {
				diff = p2.getX() - (xs - 1);
				p2.setX(xs - 1);
				p1.setX(p1.getX() - diff);
			}

			if (p2.getY() > (ys - 1)) {
				diff = p2.getY() - (ys - 1);
				p2.setY(ys - 1);
				p1.setY(p1.getY() - diff);
			}
			else if (p1.getY() < 0) {
				diff = abs(p1.getY());
				p1.setY(0);
				p2.setY(p2.getY() + diff);
			}

			CropStarMark(p1, p2, cleandata, frame, xs, ys, cropped_data);

			Stars[i].addData(cropped_data, frame);

			cropped_data.clear();
			last_center = current_center;

		}
	}

}

Point TraceStarCenter(Point last_center, int search_limit, int separation_cap, double* cleandata, int xs, int ys, int frame) {

	int maxval = std::numeric_limits<int>::min();
	//int minval = std::numeric_limits<int>::max();
	int indx;
	Point p;

	int current_frame;
	int current_row;

	current_frame = (frame * (xs * ys));

	//Debemos verificar que al obtener estos puntos de rango de busqueda no nos pasemos de los limites 
	//del frame
	int init_y = last_center.getY() - search_limit;
	int fin_y = last_center.getY() + search_limit;

	if (init_y < 0) {
		init_y = 0;
	}
	if (fin_y > ys) {
		fin_y = ys;
	}

	int init_x = last_center.getX() - search_limit;
	int fin_x = last_center.getX() + search_limit;

	if (init_x < 0) {
		init_x = 0;
	}
	if (fin_x > xs) {
		fin_x = xs;
	}

	for (int y = init_y; y < fin_y; y++) {

		current_row = y * xs;

		for (int x = init_x; x < fin_x; x++) {

			indx = x + current_row + current_frame;

			if (cleandata[indx] > maxval) {
				maxval = cleandata[indx];
				p.setX(x);
				p.setY(y);
			}
		}
	}

	//Checamos la separacion entre el centro anterior y el nuevo centro encontrado
	//Si la distancia entre ambos puntos es mayor al limite proporcionado, regresamos el centro anterior
	//De otra manera regresamos el nuevo centro

	if (PointsDistance(last_center, p) <= separation_cap) {
		return p;
	}
	else {
		return last_center;
	}
}

//Compara 2 puntos y devuelve la distancia entre ellos
int PointsDistance(Point p1, Point p2) {
	return sqrt(pow(p2.getX() - p1.getX(), 2) + pow(p2.getY() - p1.getY(), 2));
}

void CropStarMark(Point p1, Point p2, double* cleandata, int frame, int xs, int ys, std::vector<double>& cropped_data) {

	int offset = p1.getX() + (p1.getY() * xs) + (frame * xs * ys);
	int y_indx;
	std::list<double> templist;

	for (int y = 0; y + p1.getY() <= p2.getY(); y++) {

		y_indx = y * xs;

		for (int x = 0; x + p1.getX() <= p2.getX(); x++) {
			templist.push_back(cleandata[offset + x + y_indx]);
		}

		templist.reverse();

		for (std::list<double>::iterator litr = templist.begin(); litr != templist.end(); litr++) {
			cropped_data.push_back(*litr);
		}

		templist.clear();

	}

}