package main;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;

import association.HungarianAlgorithm;
import association.Probabilities;
import parameters.detection_arguments;
import parameters.io_arguments;
import preprocessing.Parser;
import preprocessing.Util;
import tracking.Atributes;
import tracking.Pairs;
import classification.SVM;
import classification.SVM_DATA;

public class Strategy1 { 
	
	public static void Strategy ( 
		ArrayList<SVM> svm_descriptors, 
		ArrayList<SVM_DATA> svm_classifiers,
		io_arguments io_parameters, 
		detection_arguments detection_parameters
	) 
	throws FileNotFoundException, ClassNotFoundException, IOException  {
		
		
		ArrayList<ArrayList<Atributes>> T = new ArrayList<ArrayList<Atributes>>();
		
		ArrayList<ArrayList<Pairs>> Pi = new ArrayList<ArrayList<Pairs>>();
		
		/*Performing Text Detection at frame 0*/
		BufferedImage frame0 = Get_Frame (io_parameters.get_bframe(), io_parameters);
		detection_parameters = new detection_arguments(io_parameters.get_detection_parameters_file());
		T.add(TextDetection.Detection (frame0, svm_descriptors, svm_classifiers, io_parameters, detection_parameters));
	
		for (int iframe = io_parameters.get_bframe()+1; iframe <= io_parameters.get_eframe(); iframe++) {
			
			/*Performing Text Detection at frame i*/
			BufferedImage Vi = Get_Frame (iframe, io_parameters);
			BufferedImage Vj = Get_Frame (iframe-1, io_parameters);
			
			detection_parameters = new detection_arguments(io_parameters.get_detection_parameters_file());
			
			ArrayList<Atributes> D = new ArrayList<Atributes>();
			
			D = TextDetection.Detection (Vi, svm_descriptors, svm_classifiers, io_parameters, detection_parameters);			
			
			ArrayList<Atributes> E = null;
			if (iframe > (io_parameters.get_bframe()+1)) {
				E = Extrapolate (T.get(iframe-1), Pi.get(iframe-2));
			}
			else {
				E = T.get(iframe-1);
			}			

			Merge (T, E, Vj, D, Vi, Pi, "./inputs/hog_fingerprint.txt", iframe);
		}
		
		Write_Detections (T, Pi, io_parameters);
		
	}
	
	static ArrayList<Atributes> Extrapolate (
			ArrayList<Atributes> Tj, 
			ArrayList<Pairs> Pj) {
		
		ArrayList<Atributes> E = new ArrayList<Atributes>();
				
		for (int j = 0; j < Tj.size(); j++) {

			Atributes Rj = Tj.get (j);
			
			Atributes Rk = Get_Equal (Rj, Pj);
			
			if (Rk != null) {
				Atributes Re = Get_Extrapolate_Region (Rj, Rk);
				E.add(Re);	
			}
			else {
				Atributes Re = Get_Extrapolate_Region (Rj, Rj);
				E.add(Re);	
			}					
		}
		return E;			
	}
	
	static Atributes Get_Extrapolate_Region (Atributes Rj, Atributes Rk) {
	
		Atributes E = new Atributes();
		
		double[] Cj = Rj.get_C();		
		double[] Uj = Rj.get_U();
		double[] Vj = Rj.get_V();
		
		double[] Ck = Rk.get_C();		
		double[] Uk = Rk.get_U();
		double[] Vk = Rk.get_V();
		
		E.set_C(Extrapolate_Position (Ck, Cj));
		E.set_U(Extrapolate_Position (Uk, Uj));
		E.set_V(Extrapolate_Position (Vk, Vj));
		
		E.set_label(Rj.get_label());
		
		return E;
	}
	
	static double[] Extrapolate_Position (double[] p_2, double[] p_1)
	{
		assert ( (p_1.length ==  p_2.length) && (p_1.length == 2) );	
		double[] p = new double[2];
		p[0] = 2*p_1[0] - p_2[0];
		p[1] = 2*p_1[1] - p_2[1];
		return p;
	}
	
	static void Merge (ArrayList<ArrayList<Atributes>> T, 
			ArrayList<Atributes> E, 
			BufferedImage Vj,
			ArrayList<Atributes> D, 
			BufferedImage Vi,
			ArrayList<ArrayList<Pairs>> Pi,
			String hog_fingerprint_parameters_filename,
			int iframe) {

		int num_of_elem = 0;
		
		ArrayList<Atributes> Ti = new ArrayList<Atributes>();

		ArrayList<Pairs> Pi_i_j = new ArrayList<Pairs>();

		boolean transpose = false; 

		double sigma_s = 1.0;
		double sigma_p = 16.0;
		double sigma_a = 20.0;

		double assignment_threshold = 0.05;

		Parser parser = new Parser();	

		Probabilities Pr = new Probabilities(); 

		ArrayList<String> hog_fingerprint_parameters = parser.Get_Array_List (hog_fingerprint_parameters_filename);

		System.out.printf("Aqui0\n");
		if (E.size() != 0) {

			/*Computing the likelihood probability matrix between sets {Ti} and {Tj}*/
			if (D.size() != 0) {

				double[][] P = new double[E.size()][D.size()];

				for (int j = 0; j < E.size(); j++) {

					Atributes Re = E.get (j);					
					BufferedImage Ve = Get_Image_Region (Vj, Re);

					for (int k = 0; k < D.size(); k++) {

						Atributes Rd = D.get (k);
						BufferedImage Vd = Get_Image_Region (Vi, Rd);						

						P[j][k] = Pr.prob_s (Re, Rd, sigma_s) * Pr.prob_p (Re, Rd, sigma_p) * Pr.prob_a (Ve, Vd, sigma_a, hog_fingerprint_parameters);
					}
				}

				/*Using Hungarian algorithm, to create the list with the best assignments*/
				HungarianAlgorithm HA = new HungarianAlgorithm();

				if (P.length > P[0].length) {
					P = HA.transpose(P);
					transpose = true;
				}

				int[][] A = new int[P.length][2];

				/*Getting the list of assignments*/
				A = HA.hgAlgorithm (P, "max"); 

				/*-------------------------------------------*/
				/*Connect those elements in {E} that have an association in {D}*/
				for (int i = 0; i < A.length; i++) {
					if (P[A[i][0]][A[i][1]] > assignment_threshold) {
						if (transpose) {
							Atributes Re = E.get(A[i][1]);
							Atributes Rd = D.get(A[i][0]);
							Pairs p_i_j = new Pairs();
							p_i_j.i = Re;
							p_i_j.j = Rd;
							Pi_i_j.add(p_i_j);
							Rd.set_label(Re.get_label()); /*Adicionar Re, ou média de Rd e Re?*/
							Ti.add(Rd); /*Adicionar Re, ou média de Rd e Re?*/
							num_of_elem++;
						}
						else {
							Atributes Re = E.get(A[i][0]);
							Atributes Rd = D.get(A[i][1]);
							Pairs p_i_j = new Pairs();
							p_i_j.i = Re;
							p_i_j.j = Rd;
							Pi_i_j.add(p_i_j);
							Rd.set_label(Re.get_label()); /*Adicionar Re, ou média de Rd e Re?*/
							Ti.add(Rd); /*Adicionar Re, ou média de Rd e Re?*/
							num_of_elem++;
						}
					}
				}
			} 
		}
		System.out.printf("Fim do Aqui0\n");

		System.out.printf("Aqui1\n");
		/*For those elements in {D} that do not have an association in {E} insert them*/
		for (int i = 0; i < D.size(); i++) {
			Atributes Rd = D.get (i);
			if (!Belong(Rd, Pi_i_j)) {
				Rd.set_label(String.format("N:%02d", num_of_elem));
				Ti.add(Rd);	
				num_of_elem++;
			}
		}
		System.out.printf("Fim do Aqui1\n");
		T.add(Ti);
		Pi.add(Pi_i_j);
	}

	
	static boolean Belong (Atributes R, ArrayList<Pairs> Pi_i_j) {
		
		for (int i = 0; i < Pi_i_j.size(); i++) {
			Atributes tmp = Pi_i_j.get(i).j;
			if (tmp == R) {
				return true;
			}
		}	
		return false;
	}
	
	static Atributes Get_Equal (Atributes R, ArrayList<Pairs> Pi_i_j) {
		
		for (int i = 0; i < Pi_i_j.size(); i++) {
			Atributes tmp = Pi_i_j.get(i).j;
			if (tmp == R) {
				return Pi_i_j.get(i).i;
			}
		}	
		return null;
	}
	
	static BufferedImage Get_Image_Region (BufferedImage image, Atributes R) {
		
		double[] C = R.get_C();		
		double[] U = R.get_U();
		double[] V = R.get_V();
		
		int xmin = (int)(C[0] - V[0]);
		int ymin = (int)(C[1] - U[1]);
		int xmax = (int)(C[0] + V[0]);
		int ymax = (int)(C[1] + U[1]);
		
		System.out.printf("C : %f %f\n", C[0], C[1]);
		System.out.printf("U : %f %f\n", U[0], U[1]);
		System.out.printf("V : %f %f\n", V[0], V[1]);
		System.out.printf("xmin : %d, ymin : %d, xmax : %d, ymax : %d, width : %d, height : %d\n", xmin, ymin, xmax, ymax, image.getWidth(), image.getHeight());
		BufferedImage region = Util.getSubImage(image, xmin, ymin, xmax, ymax);

        return region;
	}
	
	
	static void Write_Detections (ArrayList<ArrayList<Atributes>> T, ArrayList<ArrayList<Pairs>> Pi, io_arguments io_parameters) {
		
		
		for (int iframe = io_parameters.get_bframe(); iframe <= io_parameters.get_eframe(); iframe++) {

			/*Creating sub-directories (for each frame) to store the detection and tracking results*/
			File directory = new File (io_parameters.get_out_path() + String.format("%06d", iframe));
			directory.mkdir();
			
			/*Writing detection results*/
			BufferedImage Vi = Get_Frame (iframe, io_parameters);

			if (iframe == io_parameters.get_bframe()) {
				write_set_regions (Vi, T.get(iframe), null, io_parameters, false, iframe);
			}
			else {
				write_set_regions (Vi, T.get(iframe), Pi.get(iframe-1), io_parameters, false, iframe);
			}
		}
	}
		
	
	static BufferedImage Get_Frame (int iframe, io_arguments io_parameters) {
		/*Getting the image frame name*/
		String frame_name = io_parameters.get_frames_path() + String.format("%06d", iframe) + ".png";

		/*Reading the image frame*/
		try {
			BufferedImage frame = ImageIO.read(new File(frame_name));
			return frame;
		}
		catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
			return null;
		}
	}
	
	static void write_set_regions (BufferedImage frame, ArrayList<Atributes> set, ArrayList<Pairs> Pi_i_j, io_arguments io_parameters, boolean option, int iframe) {

		BufferedImage image = Util.imageScale (frame, 1.0);
		
		Graphics2D g = image.createGraphics();	
		g.setColor(Color.black);		
	   	g.setStroke(new BasicStroke(2.0f));
	    Font f = new Font("SansSerif", Font.BOLD, 12);
   	    g.setFont(f);
	   	
		for (int box = 0; box < set.size(); box++) {
			
			String label = "";
		    			
			Atributes A = set.get (box);		
			
			if (Pi_i_j != null) {
				Atributes tmp = Get_Equal (A, Pi_i_j); 
				if (tmp != null) {
					label = tmp.get_label()+"M";
				}
				else {
					label = A.get_label();
				}
			}	
			else {
				label = A.get_label();
			}
			
			double[] C = A.get_C();		
			double[] U = A.get_U();
			double[] V = A.get_V();
			int x = (int)(C[0] - V[0]);
			int y = (int)(C[1] - U[1]);
			int w = (int)(V[0]*2);
			int h = (int)(U[1]*2);
	    
		    g.drawRect(x, y, w, h);	 
	  		g.drawString(label, x, y);
		}
		g.dispose();
		try {
			ImageIO.write(image, "png", new File(io_parameters.get_out_path() + String.format("%06d", iframe) + "/detection.png"));
		}
		catch (Exception e) {
			System.err.printf("cannot write image %s\n", io_parameters.get_out_path() + String.format("%06d", iframe) + "/detection.png");
		} 
	}
}
