package pokus.sd;

import java.net.InetAddress;
import java.net.UnknownHostException;
import org.spaceroots.mantissa.optimization.CostException;
import org.spaceroots.mantissa.optimization.NelderMead;
import org.spaceroots.mantissa.optimization.NoConvergenceException;
import org.spaceroots.mantissa.optimization.PointCostPair;

import java.util.Random;
import java.io.*;

import pokus.sd.ValueChecker;

//trida slouzi k odhadnuti pozice hosta a vyberu nejblizsiho landmarku
public class HostPositionEstimation {
	private static double[] resultsRtt;
	private static Landmark[] lmList, fixedLm, dynamicLm, fixedLmRnd;
	private static final int LM_TO_PING = 5;
	private static final String NOTE = "#";
	private static final String SEPARATOR = ":";
	private static final String FIXED_LM = "F";
	private static final String DYNAMIC_LM = "D";
	private static Host[] host = new Host[1];
	private static int numOfFixed = 0, numOfDynamic = 0;
	
	
	public static void main(String[] args) throws UnknownHostException, CostException {
				
/*		Conversion conv = new Conversion();
		Landmark A = new Landmark(true);
		Landmark B = new Landmark(true);
		A.setPosition(conv.convert(50, 5, 16, 'N'), conv.convert(14, 25, 13, 'E'));	//Praha
		B.setPosition(conv.convert(38, 53, 31, 'N'), conv.convert(77, 1, 26, 'W'));	//Washington
		
		System.out.println(conv.getDistance(A, B));
		System.out.println(conv.getDistance2(A, B));		
*/		
		
		host[0] = new Host(true, InetAddress.getLocalHost(), 49.19106, 16.611419);	//Brno
		//host[0] = new Landmark(true, InetAddress.getLocalHost(), 50.120346, 17.382648);	//Vrbno pod Pradědem				
		
		readLmList();
		generateRndLmList();
		meassure();
		
		//odhad pozice pomoci Simplex Downhill
		HostsPositionCostFunction hostPositionFunc = new HostsPositionCostFunction();
		
		Random rnd = new Random();
		PointCostPair optimum = null;
		boolean finished = false;
		int cycles = 0;
		
		while (!finished){
			finished = true;
			try {
				// vytvorim vstupni argumenty simplex downhill optimalizace
					double vertexA[] = new double[2];
					double vertexB[] = new double[2];
					//rnd.nextDouble() nelze použít, protože nevrací záporné hodnoty
					//náhodné hodnoty by měly být z rozsahu (-90, 90>, resp. (-180, 180>
					// osa X
					vertexA[0] = (((double) rnd.nextInt())/Integer.MAX_VALUE) * 90.0d; 
					// osa Y
					vertexA[1] = (((double) rnd.nextInt())/Integer.MAX_VALUE) * 180.0d;

					// osa X
					vertexB[0] = (((double) rnd.nextInt())/Integer.MAX_VALUE) * 90.0d;
					// osa Y
					vertexB[1] = (((double) rnd.nextInt())/Integer.MAX_VALUE) * 180.0d;
					
					
				// pocitam simplex downhill
				optimum = new NelderMead().minimizes(hostPositionFunc,
						10000, new ValueChecker(1.0e-3), vertexA, vertexB);
			} catch (NoConvergenceException e) {
				System.out.println(e);
				cycles++;
				if (cycles < 100) {
					finished = false;
				}
			}
		}
	
		System.out.println(optimum.point[0]);
		System.out.println(optimum.point[1]);
		System.out.println((optimum.point[0] % 90));
		System.out.println((optimum.point[1] % 180));
		
		host[0].setComputedLatitude(optimum.point[0] % 90);
		host[0].setComputedLongitude(optimum.point[1] % 180);
		
		System.out.println("Odchylka: " + new Conversion().getDistance2((optimum.point[0] % 90), 
				(optimum.point[1] % 180), host[0].getLatitude(), host[0].getLongitude())/1000 + " km");
		
		System.out.println("Prumerna normovana chyba: " + new HostPositionEstimation().
				averageNormalizedError(fixedLmRnd, host[0].getComputedLatitude(), host[0].getComputedLongitude()));

		Landmark[] sortLm = new Landmark[lmList.length];
		sortLmList(lmList, host[0].getComputedLatitude(), host[0].getComputedLongitude());

		for(int i=0; i<sortLm.length; i++) {
			System.out.println(lmList[i].getAddress().getHostName() + "; " + lmList[i].getDistanceToHost());
		}
	}

	public double[] getPingValue() {
		return resultsRtt;
	}
	
	public Landmark[] getLandmarks() {
		return fixedLmRnd;
	}
	
	public double averageNormalizedError(Landmark[] fix, double hostLat, double hostLng) {
		double sum=0;
		double pom=0;
		
		for(int i=0; i<fix.length; i++) {
			double meassured = resultsRtt[i];
			double computed = new HostsPositionCostFunction().getDistanceRtt(fix[i].getLatitude(), 
					fix[i].getLongitude(), hostLat, hostLng);
			System.out.println("Normovana chyba pro spoj " + (i+1) + ": " + 
					(pom = ((meassured - computed)/meassured)*((meassured - computed)/meassured)));
			sum += pom;
		}
		
		return sum/fixedLmRnd.length;
	}
	
	private static int[] numOfUnavailableLm(double[] res) {
		//hodnota je ve tvaru [pocet nedostupnych,cislo nedostupneho]
		//pokud je 1 nedostupny, je cislo nedostupneho vyplneno indexem nedostupneho
		//v jinych pripadech je cislo nedostupneho -1
		int[] sum = new int[2];
		sum[0] = 0;
		for(int i=0; i<res.length; i++) {
			if(Double.isInfinite(res[i])) {
				sum[0]++;
				sum[1] = i;
			}
		}
		if(sum[0] != 1) sum[1] = -1; 
		return sum;
	}
	
	private static void meassure() {
		MeasureRttDistancesToHosts meassure = new MeasureRttDistancesToHosts();
		resultsRtt = meassure.measureRtt(fixedLmRnd);
		int[] numUnav = new int[2];
		int counter = 0;
		
		//kontrola, zda byly vsechny landmarky dostupne
		numUnav = numOfUnavailableLm(resultsRtt);
		
		while(numUnav[0] > 0) {	
			counter++;
			//pro více než 1 nedostupný lm provede celé měření znovu
			if(numUnav[0] > 1) {
				resultsRtt = meassure.measureRtt(fixedLmRnd);
			}
			//pro 1 nedostupný provede měření pouze pro daný lm
			else if(numUnav[0] == 1) {
				int indexUnav = numUnav[1];
				Landmark[] unavailableLm = new Landmark[1];
				unavailableLm[0] = fixedLmRnd[indexUnav];
				double[] res = new double[1];
				res = meassure.measureRtt(unavailableLm);
				resultsRtt[indexUnav] = res[0];
			}
			//znovu se provede kontrola dostupnosti všech lm
			numUnav = numOfUnavailableLm(resultsRtt);
			
			//pro více než jeden nedostupný lm se provede vygenerování nového listu lm
			//a nové měření
			if(numUnav[0] > 1) {
				generateRndLmList();
				resultsRtt = meassure.measureRtt(fixedLmRnd);
			}
			//jeden nedostupný lm se nahradí jiným náhodně vygenerovaným lm, 
			//pro který se provede měření 
			else if(numUnav[0] == 1) {
				int indexUnav = numUnav[1];
				Random rnd = new Random();
				int rndLmIndex = 0;
				boolean same;
				
				//hledá lm, který není obsažený mezi již dříve vybranými lm
				do{
					same = false;
					do{
						//generovaný index nesmí být indexem dynamického landmarku
						rndLmIndex = rnd.nextInt(lmList.length);
					}while(!lmList[rndLmIndex].isFixed());
					
					for(int i=0; i<fixedLmRnd.length; i++) {
						if(fixedLmRnd[i].getAddress().getHostName().equalsIgnoreCase
								(lmList[rndLmIndex].getAddress().getHostName())) {
							same = true;
						}
					}
				}while(same);
				
				fixedLmRnd[indexUnav] = lmList[rndLmIndex];
				
				Landmark[] unavailableLm = new Landmark[1];
				unavailableLm[0] = fixedLmRnd[indexUnav];
				double[] res = new double[1];
				res = meassure.measureRtt(unavailableLm);
				resultsRtt[indexUnav] = res[0];
			}
			numUnav = numOfUnavailableLm(resultsRtt);
			if(counter > 4) {
				System.err.println("Error: Too many unavailable Landmarks!");
				break;
			}
		}					
	}
	
	private static void readLmList() {
		//nacteni pozic Landmarků
		String radka, znak;
		int sepPozition = 0;
		
		numOfFixed = 0;
		numOfDynamic = 0;
		
		try {
			FileReader fr = new FileReader("gnp_test.conf");
			BufferedReader in = new BufferedReader(fr);
			
			while((radka = in.readLine()) != null) {
				znak = radka.substring(0, 1);
				if(!znak.equals(NOTE)) {
					sepPozition = radka.indexOf(SEPARATOR);
					if(sepPozition != -1) {
						if(radka.substring(sepPozition+1, sepPozition+2).equals(FIXED_LM)) {
							numOfFixed++;
						}
						else if(radka.substring(sepPozition+1, sepPozition+2).equals(DYNAMIC_LM)) {
							numOfDynamic++;
						}
					}
					else System.err.println("Chyba v souboru!");
				}
			}
			fr.close();
			fixedLm = new Landmark[numOfFixed];
			dynamicLm = new Landmark[numOfDynamic];
			lmList = new Landmark[numOfFixed+numOfDynamic];
			fr = new FileReader("gnp_test.conf");
			in = new BufferedReader(fr);
			int i=0;
			
			while((radka = in.readLine()) != null) {
				znak = radka.substring(0, 1);
				if(!znak.equals(NOTE)) {
					sepPozition = radka.indexOf(SEPARATOR);
					if(sepPozition != -1) {
						lmList[i++] = new Landmark(radka.substring(sepPozition+1, sepPozition+2).
								equals(FIXED_LM), InetAddress.getByName(radka.substring(0, sepPozition)), 
								Double.valueOf(radka.substring(sepPozition+3, 
										sepPozition = radka.indexOf(SEPARATOR, sepPozition+3))).doubleValue(), 
								Double.valueOf(radka.substring(sepPozition+1)).doubleValue());
					}
					else System.err.println("Chyba v souboru!");
				}
			}
			fr.close();

		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private static void generateRndLmList() {
		Random rnd = new Random();
		int[] rndLmIndex = new int[LM_TO_PING];
		int pom;
		boolean same;
		
		for(int i=0; i<rndLmIndex.length; i++) {
			same = false;
			do{
				//generovaný index nesmí být indexem dynamického landmarku
				pom = rnd.nextInt(lmList.length);
			}while(!lmList[pom].isFixed());
			
			a:for(int j=0; j<i; j++) {
				if(pom == rndLmIndex[j]) {
					i--;
					same = true;
					break a;
				}
			}
			if(!same) rndLmIndex[i] = pom;
		
			//stejny jako predchozi cyklus(i stejne narocny)
	/*		do{
				same = false;
				do{
					//generovaný index nesmí být indexem dynamického landmarku
					pom = rnd.nextInt(lmList.length);
				}while(!lmList[pom].isFixed());
				
				a:for(int j=0; j<i; j++) {
					if(pom == rndLmIndex[j]) {
						same = true;
						break a;
					}
				}
			}while(same);
			
			rndLmIndex[i] = pom;
		*/	
			
		}
		
		
		fixedLmRnd = new Landmark[LM_TO_PING];
		for(int i=0; i<fixedLmRnd.length; i++) {
			fixedLmRnd[i] = lmList[rndLmIndex[i]];
		}
		for(int i=0; i<fixedLmRnd.length; i++) {
			System.out.println(fixedLmRnd[i].getAddress().getHostName());
		}
	}
	
	private static void sortLmList(Landmark[] lmList, double lat, double lng) {
		computeDistanceToLm(lmList, lat, lng);
		quickSort(lmList, 0, lmList.length-1);
	}
	
	private static void computeDistanceToLm(Landmark[] lmList, double lat, double lng) {
		Conversion dist = new Conversion();
		
		for(int i=0; i<lmList.length; i++) {
			lmList[i].setDistanceToHost(dist.getDistance2(lmList[i].getLatitude(), lmList[i].getLongitude(),
					lat, lng));
		}
	}
	
	private static Landmark[] quickSort(Landmark[] lmList, int left, int right) {
		int pivotNewIndex = 0, pivotIndex = 0;
		
		if(right > left) {
			pivotIndex = left;
			pivotNewIndex = partition(lmList,left,right,pivotIndex);
			quickSort(lmList, left, pivotNewIndex-1);
			quickSort(lmList, pivotNewIndex+1, right);
		}		
		return lmList;
	}
	
	private static int partition(Landmark[] lmList, int left, int right, int pivotIndex) {
		double pivotValue = lmList[pivotIndex].getDistanceToHost();
		int storeIndex = 0;
		swap(lmList,pivotIndex, right);
		storeIndex = left;
		
		for(int i=left; i<=right-1; i++) {
			if(lmList[i].getDistanceToHost() <= pivotValue) {
				swap(lmList,storeIndex, i);
				storeIndex++;	
			}
		}
		swap(lmList,right,storeIndex);
		return storeIndex;
	}
	
	private static void swap(Landmark[] lmList,int a, int b) {
		Landmark pom = lmList[a];
		lmList[a] = lmList[b];
		lmList[b] = pom;
	}
}