package cz.vutbr.feec.session.gnp;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
import java.util.TimeZone;

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 pokus.sd.ValueChecker;

import cz.vutbr.feec.iodata.ReadConfig;
import cz.vutbr.feec.iodata.WriteResults;
import cz.vutbr.feec.math.DistanceAndConversion;
import cz.vutbr.feec.math.HostsPositionCostFunction;

public class SequentialEstimation {
	private static LandmarkInfo[] lmList, lmListActive, lmListComputeSort, lmListMeassureSort;
	private static Host host;
	private static int numOfLmToPing;
	private static double[] resultsRtt;
	private static int sizeOfRndArrayFromLmList;
	//numOfEchoRequestAll obsahuje počet všech provedených dotazů Echo Request
	////numOfEchoRequest obsahuje počet dotazů zaslaných pouze pro odhad pozice hosta
	private static int numOfEchoRequest = 0, numOfEchoRequestAll = 0;
	private static int mainCounter = 0;
	private static int numRepeatEstimation = 0;
	private static long startTime, stopTime;
	private static int dispersion = 0;
	private static int maxRtt = 0;
	private static final String LANDMARKS_LIST = "landmarks_list.conf";
	private static final int NEW_MEASSURE_INTERVAL = 4;
	private static int newMeassureCounter = 0;
	//aby se po posledním měření ještě provedl dostatečný počet odhadů-bez měření
	//aby bylo poslední měření dostatečně využito k odhadu
	private static final int MAX_ESTIMATIONS = 5*NEW_MEASSURE_INTERVAL + NEW_MEASSURE_INTERVAL-1;
	private static final int MAX_ESTIMATION_CORE_CALL = 3;
	private static int estCoreCount = 0;
	//určuje procentuální odchylku, možno nastavit od 0% do 100%, čím nižší, tím je pravděpodobnější vyšší 
	//chyba výpočtu
//	private static float secondCondition = 80;
	
	public SequentialEstimation(int numOfLandmarks, int dispersion, int maxRtt, float hostLatitude, float hostLongitude) {
		setTestParameters(numOfLandmarks, dispersion, maxRtt, hostLatitude, hostLongitude);
		try {
			lmList = ReadConfig.readLmList(LANDMARKS_LIST);
		} catch (IOException e) {
			e.printStackTrace();
		}	
	}
	
	public SequentialEstimation() {
		try {
			lmList = ReadConfig.readLmList(LANDMARKS_LIST);
		} catch (IOException e) {
			e.printStackTrace();
		}	
	}
	
	public void setTestParameters(int numOfLandmarks, int dispersion, int maxRtt, float hostLatitude, float hostLongitude) {
		try {
			//host = new Host(true, InetAddress.getLocalHost(), 49.19106f, 16.611419f);  //Brno
			//host = new Landmark(true, InetAddress.getLocalHost(), 50.120346, 17.382648);	//Vrbno pod Pradědem	
			host = new Host(true, InetAddress.getLocalHost(), hostLatitude, hostLongitude);
			
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}			
		if(numOfLandmarks < 2 ) numOfLandmarks = 4;
		if(maxRtt < 100 ) maxRtt = 100;
	
		SequentialEstimation.dispersion = dispersion;
		SequentialEstimation.numOfLmToPing = numOfLandmarks;
		SequentialEstimation.sizeOfRndArrayFromLmList = numOfLandmarks + dispersion;
		SequentialEstimation.maxRtt = maxRtt;	
		resultsRtt = new double[numOfLandmarks];
	}
	
	/**
	 * Tato metoda provádí odhad pozice hosta, která je uvedena při vytvoření instance této třídy, 
	 * díky známým bodům, tzv. landmarkům. Počet těchto landmarků je taktéž znám při vytvoření instance 
	 * této třídy. Jádro metody tvoří algoritmus Simplex Downhill (veřejná metoda estimationCore), 
	 * který je stochastický a snaží se tedy náhodným přístupem minimalizovat hodnotu funkce, 
	 * jež je implementována ve třídě cz.vutbr.feec.math.HostPositionCostFunction.
	 */
	public void estimation() {
		int continent = -1;
		numOfEchoRequestAll = 0;
		mainCounter++;
		estCoreCount = 0;
		//průběžné výsledky se ukládají do proměnné hostPom, která je v podstatě kopií proměnné host
		Host hostPom = new Host(true,host.getAddress(),host.getLatitude(),host.getLongitude());
		MeasureRttDistancesToHosts meassure = new MeasureRttDistancesToHosts();
		LandmarkInfo[] lmPom = new LandmarkInfo[3];
		boolean newEstCore = false;
		/*
		 * 2 nová pole, která obsahují odkazy na stejné objekty jako lmList, 
		 * ale budou seřazena podle rozdílných kritérií
		 */
		lmListMeassureSort = new LandmarkInfo[lmList.length];
		lmListComputeSort = new LandmarkInfo[lmList.length];
		System.arraycopy(lmList, 0, lmListMeassureSort, 0, lmList.length);
		System.arraycopy(lmList, 0, lmListComputeSort, 0, lmList.length);
		
		//uložení startovacího času nejdůležitější části algoritmu
		startTime = System.currentTimeMillis();
		
		
		//určení kontinentu, na kterém se host nachází
		continent = meassureWorld();
		//určení části kontinentu, ve které se host pravděpodobně nachází
		LandmarkInfo nearestPartOfContinent = getNearestPartOfContinent(continent);
		//hlavní landmark určené nejbližší části kontinentu zapiš do výsledků
		WriteResults.writeComputedPosition("landmarks.log", mainCounter + "; " + 
				nearestPartOfContinent.getHostName() + "; " + 
				nearestPartOfContinent.getLatitude() + "; " + 
				nearestPartOfContinent.getLongitude() + "; " +
				nearestPartOfContinent.getDistanceToHostMeassured());
		
		//první odhad pozice
		hostPom = estimationCore(nearestPartOfContinent);
		do {
			//srovnej podle vypoctene hodnoty RTT
			sortLmListComputed(lmListComputeSort, hostPom.getComputedLatitude(), hostPom.getComputedLongitude());
			//zkopírování prvních několika lm z lmListComputedSort do lmPom
			System.arraycopy(lmListComputeSort, 0, lmPom, 0, lmPom.length);
			
			//změření RTT pro pole landmarků lmPom
			double[] results = meassure.measureRtt(lmPom);
			newEstCore = false; 
			/*
			 * kontrola celého pole lmPom, zda-li není seřazení chybné, tudíž i odhad by pak byl chybný,
			 * tolerance je 1 ms
			 */
			cycle:for(int i=0; i<results.length-1; i++) {
				if((results[i]-1) > results[i+1]) {
					newEstCore = true;
					break cycle;
				}
			}
			//čítač spuštění jádra algoritmu
			estCoreCount++;
			//pokud bylo pole lmPom chybně seřazené, dojde ke spuštění jádra algoritmu
			if(newEstCore) hostPom = estimationCore(hostPom);
		} while(estCoreCount < MAX_ESTIMATION_CORE_CALL && newEstCore);
		
		//uložení vypočtené pozice do originálního objektu host
		host.setComputedLatitude(hostPom.getComputedLatitude());
		host.setComputedLongitude(hostPom.getComputedLongitude());
		//srovnej podle vypoctene hodnoty RTT
		sortLmListComputed(lmListComputeSort, host.getComputedLatitude(), host.getComputedLongitude());
		
		/*
		 * kontrola, zda-li není landmark požitý k odhadu pozice bližší z pohledu RTT 
		 * než landmark označený za nejbližšího.
		 * Provede se ping na nejbližší odhadnutý LM a RTT se porovná s RTT landmarku, 
		 * který měl z pole použitých LM nejnižší RTT. 
		 * Pokud je RTT odhadnutého vyšší, provede se výměna s nejbližším LM použitým k odhadu 
		 * (s nejnižším RTT).
		 */
		double nearestLmRtt = meassure.measureRtt(lmListComputeSort[0]);
		int indexLmChange = 0;
		if(nearestLmRtt > lmListActive[0].getDistanceToHostMeassured()) {
			circle:for(int i=0; i<lmListComputeSort.length; i++) {
				if(lmListComputeSort[i].getHostAddress().equalsIgnoreCase(lmListActive[0].getHostAddress())) {
					indexLmChange = i;
					break circle;
				}
			}
			if(indexLmChange > 0) swap(lmListComputeSort, 0, indexLmChange);
		}
		
		//uložení času konce algoritmu
		stopTime = System.currentTimeMillis();
		//uložení počtu odeslaných dotazů Echo Request nutných pro odhad
		numOfEchoRequest = numOfEchoRequestAll;
				
		//zapíše(zachová) hodnoty RTT použité k odhadu konečné pozice
		for(int i=0; i<resultsRtt.length; i++) {
			resultsRtt[i] = lmListActive[i].getDistanceToHostMeassured();
		}
		
		//srovnej podle zmerene hodnoty RTT		
		sortLmListMeassured(lmListMeassureSort);	
		
		//aby ve výsledcích byly vypsány původní hodnoty použité při odhadu pozice
/*		for(int i=0; i<resultsRtt.length; i++) {
			lmListFixedRnd[i].setDistanceToHostMeassured((float) resultsRtt[i]);
		}
*/		
		writeResults();	//zapis vysledky

	//	WriteResults.writeComputedPosition("landmarks.log", mainCounter + "; " 
	//			+ averageNormalizedError(lmListActive, host.getComputedLatitude(), host.getComputedLongitude()));

	}
	
	public Host estimationCore(LandmarkInfo nearestPartOfContinent) {
		boolean isFirstErr = false;
	//	boolean isSecondErr = false;
		double[] computedPosition = new double[2];
	//	int secondErr = 0, secondErrLowest = 0;
		Host hostInCore = new Host(true,nearestPartOfContinent.getAddress(),nearestPartOfContinent.getLatitude(), 
					nearestPartOfContinent.getLongitude());

		//funkce pro ohodnocení řešení získané pomocí simplex downhill
		HostsPositionCostFunction hostPositionFunc = new HostsPositionCostFunction();
		
		//seřaď všechny landmarky podle vzdálenosti od této části kontinentu
		sortLmListComputed(lmList, nearestPartOfContinent.getLatitude(), nearestPartOfContinent.getLongitude());		
		//pro odhad získá skupinu nejbližších LM z dané oblasti a současně změří RTT
		generateLmListActive();
		
		//zapiš výsledky měření do pole výsledků RTT
		for(int i=0; i<lmListActive.length; i++) {
			resultsRtt[i] = lmListActive[i].getDistanceToHostMeassured();
		}
		/*
		 * Vnější cyklus bude probíhat do té doby, dokud bude existovat jedna ze 2 velkých chyb - 
		 * hodnota vypočtené vzdálenosti je větší, či rovna změřené vzdálenost RTT
		 * (z praktického hlediska se toto nidky nemůže stát, proto je to velká chyba). 
		 * Druhá chyba není tak úplně jednoznačná:  v poli landmarků seřazených podle vypočtené vzdálenosti 
		 * je změřená vzdálenost pro landmark s indexem i větší, než pro landmark s indexem i+1 
		 * teoreticky není tato chyba tak úplně chybou-pakety jdou různými cestami-různé zpoždění-nejdou přímo! 
		 * Ukončení nastane při překročení maximálního počtu opakování.
		 */
		numRepeatEstimation = 0;
		do {
			//čítač počtu opakování jednoho odhadu
			numRepeatEstimation++;
			/*
			 * pokud byl určitý počet předchozích odhadů neúspěšný, tak se jeden 
			 * z použitých LM vymění za jiný z dané oblasti a provede se na 
			 * něj měření a s obmněněným polem vektorů vzdálenosti se provede další odhad
			 */
/*			if(numRepeatEstimation % 3 == 0) {
				if(newMeassureCounter < sizeOfRndArrayFromLmList) {
					secondCondition -= 5;
				}
				else if(newMeassureCounter < 2*sizeOfRndArrayFromLmList) {
					secondCondition -= 10;
				}
				else if(newMeassureCounter < 3*sizeOfRndArrayFromLmList) {
					secondCondition -= 15;				
				}
			}
*/			if(numRepeatEstimation % NEW_MEASSURE_INTERVAL == 0) {
	//			generateRndLmList();
				Random rndLm = new Random();
				//náhodně se zvolí index LM, který se má vyměnit
				int indexLmForChange = rndLm.nextInt(lmListActive.length);
				//vrátí hodnotu RTT nového LM
				double res = meassureOneNotInLmListActive(indexLmForChange);
				//uloží novou hodnotu do pole použitých landmarků
				lmListActive[indexLmForChange].setDistanceToHostMeassured((float) res);
				//aktualizuje i pole RTT, aby odpovídalo pořadí v poli použitých LM
				for(int i=0; i<lmListActive.length; i++) {
					resultsRtt[i] = lmListActive[i].getDistanceToHostMeassured();
				}
				newMeassureCounter++;
				//secondCondition = 100;
			}
			
			boolean finished = false;
			Random rnd = new Random();
			PointCostPair optimum = null;		
			int cycles = 0;	
			isFirstErr = false;
		//	isSecondErr = false;
		//	secondErr = 0;
	
			//cyklus samotného algoritmu simplex downhill
			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
					try {
						optimum = new NelderMead().minimizes(hostPositionFunc,
								10000, new ValueChecker(1.0e-3), vertexA, vertexB);
					} catch (CostException e) {
						e.printStackTrace();
					}
				} 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.setComputedLatitude((float) optimum.point[0] % 90);
			//host.setComputedLongitude((float) optimum.point[1] % 180);
			
			/*
			 * modulo 90 a 180 je nutné, protože výsledky občas bývají posunuté o
			 * 90, resp. 180 a jejich násobky-což není špatně, z pohledu kružnice 
			 * je to pořád ta stejná pozice
			 */
			computedPosition[0] = optimum.point[0] % 90;
			computedPosition[1] = optimum.point[1] % 180;
			
			//odchylka od skutečné pozice v kilometrech
			System.out.println("Odchylka: " + DistanceAndConversion.getDistance2(computedPosition[0], 
					computedPosition[1], hostInCore.getLatitude(), hostInCore.getLongitude())/1000 + " km");
			//průměrná normovaná chyba
			System.out.println("Prumerna normovana chyba: " + averageNormalizedError(lmListActive, 
					computedPosition[0], computedPosition[1]));
			
			/*
			 * výpočet skutečné vzdálenosti mezi hostem a použitými landmarky, zápis hodnot do pole použitých 
			 * landmarků
			 */
			computeDistanceToLm(lmListActive, computedPosition[0], computedPosition[1]);

			//srovnání použitých landmarků podle vypočtené vzdálenosti
			quickSortComputed(lmListActive, 0, lmListActive.length-1);
			
			//kontrola, zda nenastala chybná situace, kdy je hodnota vypočtené vzdálenosti větší, či rovna 
			//změřené vzdálenost RTT-z praktického hlediska se toto nidky nemůže stát
			a:for(int i=0; i<lmListActive.length; i++) {
				if(lmListActive[i].getDistanceToHostComputed()>=lmListActive[i].getDistanceToHostMeassured()){
					isFirstErr = true;
					break a;
				}
			}
			//další kontrola chyb se provede, pouze pokud předchozí základní kontrola byla bezchybná
/*			if(!isFirstErr) {
				//kontroluje se, zda-li nenastala situace, kdy v poli landmarků seřazených podle vypočtené vzdálenosti 
				//je změřená vzdálenost pro landmark s indexem i větší, než pro landmark s indexem i+1
				//teoreticky není tato chyba tak úplně chybou-pakety jdou různými cestami-různé zpoždění-nejdou přímo!
				for(int i=0; i<lmListActive.length-1; i++) {
					if(((lmListActive[i+1].getDistanceToHostMeassured() 
							/ lmListActive[i].getDistanceToHostMeassured())*100) > secondCondition){
						isSecondErr = true;
						secondErr++;
					}
				}	
			}	
*/			//pouze pokud se předchozí řešení zdá být lepší se aktualizuje vypočtená pozice uložená v objektu host
/*			if((secondErr < secondErrLowest && !isFirstErr) || secondErrLowest == 0) {
				secondErrLowest = secondErr;
				hostInCore.setComputedLatitude((float) computedPosition[0]);
				hostInCore.setComputedLongitude((float) computedPosition[1]);
			}
*/			
			if(!isFirstErr) {
				hostInCore.setComputedLatitude((float) computedPosition[0]);
				hostInCore.setComputedLongitude((float) computedPosition[1]);				
			}
			/*
			 * odhad probíhá, dokud existují v řešení 2 typy chyb (viz. výše), 
			 * nebo dokud nebyl překročen maximální počet odhadů MAX_ESTIMATIONS
			 */
//		}while((isFirstErr || isSecondErr) && numRepeatEstimation < MAX_ESTIMATIONS);
		}while(isFirstErr && numRepeatEstimation < MAX_ESTIMATIONS);
		
		if(hostInCore.getComputedLatitude() == Float.NaN) {
			hostInCore.setComputedLatitude((float) computedPosition[0]);
			hostInCore.setComputedLongitude((float) computedPosition[1]);			
		}
		return hostInCore;
	}
	
	private double averageNormalizedError(LandmarkInfo[] fix, double hostLat, double hostLng) {
		double[] pom = new double[fix.length];
		double sum = 0;
		
		pom = normalizedError(fix, hostLat, hostLng);
		for(int i=0; i<fix.length; i++) {
			sum += pom[i];
		}
		
		return sum/lmListActive.length;
	}
	
	private double[] normalizedError(LandmarkInfo[] fix, double hostLat, double hostLng) {
		double[] normErr = new double[fix.length];;
		
		for(int i=0; i<fix.length; i++) {
			double meassured = resultsRtt[i];
			double computed = DistanceAndConversion.computeDistanceVector(fix[i].getLatitude(), 
					fix[i].getLongitude(), hostLat, hostLng);
			normErr[i] = ((meassured - computed)/meassured)*((meassured - computed)/meassured);
		}		
		return normErr;
	}
	
	private static int meassureWorld() {
		//nacist kontinentalni body z pole lmList
		//ping na tyto body
		//vybrat ten, s nejnizsim RTT
	
		LandmarkInfo[] mainLms = new LandmarkInfo[5];
		int numOfEuropeLms = 0, numOfNorthAmericaLms = 0, numOfSouthAmericaLms = 0, numOfAsiaLms = 0;
		int numOfAustraliaLms = 0;
		LandmarkInfo[] europe, northAmerica, southAmerica, asia, australia;
		int nearestIndex = 0;
		
		for(int i=0; i<lmList.length; i++) {
			int contIndex = lmList[i].getContinent();
			if(contIndex > 100) {
				switch(contIndex) {
					case 111: 
						numOfEuropeLms++;
						break;
					case 112:
						numOfNorthAmericaLms++;
						break;
					case 113: 
						numOfSouthAmericaLms++;
						break;
					case 114: 
						numOfAsiaLms++;
						break;
					case 115: 
						numOfAustraliaLms++;
						break;
					default: break;
				}	
			}
		}
		europe = new LandmarkInfo[numOfEuropeLms];
		northAmerica = new LandmarkInfo[numOfNorthAmericaLms];
		southAmerica = new LandmarkInfo[numOfSouthAmericaLms];
		asia = new LandmarkInfo[numOfAsiaLms];
		australia = new LandmarkInfo[numOfAustraliaLms];
		int e=0, na=0, sa=0, as=0, au=0;
		
		for(int i=0; i<lmList.length; i++) {
			int contIndex = lmList[i].getContinent();
			if(contIndex > 100) {
				switch(contIndex) {
					case 111: 
						europe[e++] = lmList[i];
						break;
					case 112:
						northAmerica[na++] = lmList[i];
						break;
					case 113: 
						southAmerica[sa++] = lmList[i];
						break;
					case 114: 
						asia[as++] = lmList[i];
						break;
					case 115: 
						australia[au++] = lmList[i];
						break;
					default: break;
				}	
			}
		}
		
		Random rnd = new Random();
		
		if(europe.length > 0) {
			mainLms[0] = europe[rnd.nextInt(europe.length)];
			rnd = new Random();
		}
		if(northAmerica.length > 0) {
			mainLms[1] = northAmerica[rnd.nextInt(northAmerica.length)];
			rnd = new Random();
		}
		if(southAmerica.length > 0) {
			mainLms[2] = southAmerica[rnd.nextInt(southAmerica.length)];
			rnd = new Random();		
		}
		if(asia.length > 0) {
			mainLms[3] = asia[rnd.nextInt(asia.length)];
			rnd = new Random();	
		}
		if(australia.length > 0) {
			mainLms[4] = australia[rnd.nextInt(australia.length)];
			rnd = new Random();	
		}
		
		MeasureRttDistancesToHosts meassure = new MeasureRttDistancesToHosts();
		double[] resultsRttWorld = new double[mainLms.length];
		resultsRttWorld = meassure.measureRtt(mainLms);
		
		for(int i=0; i< mainLms.length; i++) {
			if(resultsRttWorld[i] < resultsRttWorld[nearestIndex]) nearestIndex = i;
			mainLms[i].setDistanceToHostMeassured((float) resultsRttWorld[i]);
		}
		
		return nearestIndex+1;
	}
	
	private static LandmarkInfo getNearestPartOfContinent(int continent) {
		int nearestIndex = 0;
		int numOfLms = 0;
		LandmarkInfo[] secondLms;
		
		for(int i=0; i<lmList.length; i++) {
			if(lmList[i].getContinent() == continent+10) {
				numOfLms++;
			}
		}
		secondLms = new LandmarkInfo[numOfLms];
		int k=0;
		
		for(int i=0; i<lmList.length; i++) {
			if(lmList[i].getContinent() == continent+10) {
				secondLms[k++] = lmList[i];
			}
		}
		
		MeasureRttDistancesToHosts meassure = new MeasureRttDistancesToHosts();
		double[] resultsRttContinent = new double[secondLms.length];
		resultsRttContinent = meassure.measureRtt(secondLms);
		
		for(int i=0; i< secondLms.length; i++) {
			if(resultsRttContinent[i] < resultsRttContinent[nearestIndex]) nearestIndex = i;
			secondLms[i].setDistanceToHostMeassured((float) resultsRttContinent[i]);
		}

		System.out.println(secondLms[nearestIndex].getHostName());
	
		return secondLms[nearestIndex];
	}
	
	private static double meassureOneNotInLmListActive(int indexLmForChange) {
		Random rnd = new Random();
		int rndLmIndex = 0;
		boolean same=false;
		double res = Double.POSITIVE_INFINITY;
		MeasureRttDistancesToHosts meassure = new MeasureRttDistancesToHosts();
		
		do {
			//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(sizeOfRndArrayFromLmList);
				}while(!lmList[rndLmIndex].isFixed());
				
				for(int i=0; i<lmListActive.length; i++) {
					if(lmListActive[i].getAddress().getHostName().equalsIgnoreCase
							(lmList[rndLmIndex].getAddress().getHostName())) {
						same = true;
					}
				}
			}while(same);
			
			lmListActive[indexLmForChange] = lmList[rndLmIndex];
			
			LandmarkInfo unavailableLm = lmListActive[indexLmForChange];
			res = meassure.measureRtt(unavailableLm);
		}while(res == Double.POSITIVE_INFINITY);
		
		return res;

	}
	
	private static void generateRndLmList() {
		Random rnd = new Random();
		int[] rndLmIndex = new int[numOfLmToPing];
		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(sizeOfRndArrayFromLmList);
			}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;
		
		}

		lmListActive = new LandmarkInfo[numOfLmToPing];
		for(int i=0; i<lmListActive.length; i++) {
			lmListActive[i] = lmList[rndLmIndex[i]];
		}
		for(int i=0; i<lmListActive.length; i++) {
			System.out.println(lmListActive[i].getAddress().getHostName());
		}
	}
	
	private static void generateLmListActive() {
		LandmarkInfo[] lms = new LandmarkInfo[sizeOfRndArrayFromLmList];
		System.arraycopy(lmList, 0, lms, 0, lms.length);
		double[] res = new double[lms.length];
		
		MeasureRttDistancesToHosts meassure = new MeasureRttDistancesToHosts();
		
		for(int i=0; i<lms.length; i++) {
			res[i] = meassure.measureRtt(lms[i]);
			lms[i].setDistanceToHostMeassured((float) res[i]);
		}
		
		quickSortMeassured(lms, 0, lms.length-1);
		lmListActive = new LandmarkInfo[numOfLmToPing];
		
		System.arraycopy(lms, 0, lmListActive, 0, numOfLmToPing);

		for(int i=0; i<lmListActive.length; i++) {
			System.out.println(lmListActive[i].getAddress().getHostName());
		}
		
	}
	
	private static void sortLmListComputed(LandmarkInfo[] lmList, double lat, double lng) {
		computeDistanceToLm(lmList, lat, lng);
		quickSortComputed(lmList, 0, lmList.length-1);
	}
	
	private static void sortLmListMeassured(LandmarkInfo[] lmList) {
		meassureDistanceToLm(lmList);
		quickSortMeassured(lmList, 0, lmList.length-1);
	}
	
	private static void computeDistanceToLm(LandmarkInfo[] lmList, double lat, double lng) {
		
		for(int i=0; i<lmList.length; i++) {
			lmList[i].setDistanceToHostComputed((float) DistanceAndConversion.computeDistanceVector(lmList[i].getLatitude(), 
					lmList[i].getLongitude(), lat, lng));
			if(lmList[i].getLatitude() == lat && lmList[i].getLongitude() == lng) {
				lmList[i].setDistanceToHostComputed(0);
			}
		}
	}
	
	private static void meassureDistanceToLm(LandmarkInfo[] lmList) {
		MeasureRttDistancesToHosts meassure = new MeasureRttDistancesToHosts();
		for(int i=0; i<lmList.length; i++) {
			lmList[i].setDistanceToHostMeassured((float) meassure.measureRtt(lmList[i]));
		}
	}
	
	/**
	 * Provede seřazení pole landmarků pomocí algoritmu Quick Sort podle vypočtené hodnoty vzdálenost. 
	 * @param lmList pole landmarků
	 * @param left spodní index pole <b>lmList</b>
	 * @param right horní index pole <b>lmList</b>
	 * @return <b>lmList</b> pole seřazených landmarků
	 */
	private static LandmarkInfo[] quickSortComputed(LandmarkInfo[] lmList, int left, int right) {
		int pivotNewIndex = 0, pivotIndex = 0;
		
		if(right > left) {
			pivotIndex = left;
			pivotNewIndex = partitionComputed(lmList,left,right,pivotIndex);
			quickSortComputed(lmList, left, pivotNewIndex-1);
			quickSortComputed(lmList, pivotNewIndex+1, right);
		}		
		return lmList;
	}

	/**
	 * Součást algoritmu Quick Sort, který provede seřazení části pole landmarků podle 
	 * <b>vypočtené</b> hodnoty vzdálenosti.
	 * @param lmList
	 * @param left
	 * @param right
	 * @param pivotIndex
	 * @return
	 */
	private static int partitionComputed(LandmarkInfo[] lmList, int left, int right, int pivotIndex) {
		double pivotValue = lmList[pivotIndex].getDistanceToHostComputed();
		int storeIndex = 0;
		swap(lmList,pivotIndex, right);
		storeIndex = left;
		
		for(int i=left; i<=right-1; i++) {
			if(lmList[i].getDistanceToHostComputed() <= pivotValue) {
				swap(lmList,storeIndex, i);
				storeIndex++;	
			}
		}
		swap(lmList,right,storeIndex);
		return storeIndex;
	}
	
	/**
	 * Součást algoritmu Quick Sort. Provede se pouze přehození dvou prvků landmarků v poli lmList 
	 * s indexy <b>a</b> a <b>b</b>.
	 * @param lmList
	 * @param a
	 * @param b
	 */
	private static void swap(LandmarkInfo[] lmList, int a, int b) {
		LandmarkInfo pom = lmList[a];
		lmList[a] = lmList[b];
		lmList[b] = pom;
	}

	/**
	 * Provede seřazení pole landmarků pomocí algoritmu Quick Sort podle <b>změřené</b> hodnoty vzdálenost. 
	 * @param lmList pole landmarků
	 * @param left spodní index pole <b>lmList</b>
	 * @param right horní index pole <b>lmList</b>
	 * @return <b>lmList</b> pole seřazených landmarků
	 */
	private static LandmarkInfo[] quickSortMeassured(LandmarkInfo[] lmList, int left, int right) {
		int pivotNewIndex = 0, pivotIndex = 0;
		
		if(right > left) {
			pivotIndex = left;
			pivotNewIndex = partitionMeassured(lmList,left,right,pivotIndex);
			quickSortMeassured(lmList, left, pivotNewIndex-1);
			quickSortMeassured(lmList, pivotNewIndex+1, right);
		}		
		return lmList;
	}
	/**
	 * Součást algoritmu Quick Sort, který provede seřazení části pole landmarků podle změřené hodnoty vzdálenosti.
	 * @param lmList
	 * @param left
	 * @param right
	 * @param pivotIndex
	 * @return
	 */
	private static int partitionMeassured(LandmarkInfo[] lmList, int left, int right, int pivotIndex) {
		double pivotValue = lmList[pivotIndex].getDistanceToHostMeassured();
		int storeIndex = 0;
		swap(lmList,pivotIndex, right);
		storeIndex = left;
		
		for(int i=left; i<=right-1; i++) {
			if(lmList[i].getDistanceToHostMeassured() <= pivotValue) {
				swap(lmList,storeIndex, i);
				storeIndex++;	
			}
		}
		swap(lmList,right,storeIndex);
		return storeIndex;
	}
	
	/**
	 * Zapíše dosažené výsledky do souborů (results.log, landmarks_all.log, rtt_all.log) v aktuálním umístění 
	 * ve složce /results/.
	 */
	private static void writeResults() {
		String separator = "; ";
		double odchylka = DistanceAndConversion.getDistance2(host.getLatitude(), host.getLongitude(), 
				host.getComputedLatitude(), host.getComputedLongitude())/1000;
		String radka;
		
		//zjisteni skutecne pozice odhadnute trojice v poli landmarku serazenych podle zmerene hodnoty RTT
		int[] positionInArrayTrue = new int[3];
		for(int i=0; i<3; i++) {
			for(int j=0; j<lmListComputeSort.length; j++) {
				if(lmListComputeSort[i].getHostAddress().equalsIgnoreCase(lmListMeassureSort[j].getHostAddress())) {
					positionInArrayTrue[i] = j+1;				
					break;
				}
			}
		}
		
		/*kvalita odhadu
		8=nejhorsi
		1=nejlepsi
		1=prvni trojice LM zmerenych a LM vypoctenych jsou stejne
		2=prvni LM zmereny odpovide prvnimu LM vypoctenemu
		3=prvni tri pozice jsou obsazeny stejnymi LM, jen v jinem poradi
		4=2 vypoctene LM jsou obsazeny v prvni trojici vypoctenych v libovolnem poradi a je mezi nimi 1. zmereny LM
		5=2 vypoctene LM(neni mezi nimi ten, ktery je mezi zmerenymi prvni-nejmensi RTT, takze je to jen 2. a 3.)
		 jsou obsazeny v prvni trojici vypoctenych v libovolnem poradi
		6=1 vypocteny LM je v prvni trojici zmerenych a je to zrovna ten, ktery je prvni mezi zmerenymi
		7=1 vypocteny LM je v prvni trojici zmerenych
		8=prvni trojice LM vypoctenych a prvni trojice LM zmerenych si vubec neodpovidaji
		*/
		int estimationQuality = 7;	
		if(lmListComputeSort[0].getHostAddress().equalsIgnoreCase(lmListMeassureSort[0].getHostAddress()) 
				&& lmListComputeSort[1].getHostAddress().equalsIgnoreCase(lmListMeassureSort[1].getHostAddress())
				&& lmListComputeSort[2].getHostAddress().equalsIgnoreCase(lmListMeassureSort[2].getHostAddress())) {
			estimationQuality = 1;
		}
		else if(lmListComputeSort[0].getHostAddress().equalsIgnoreCase(lmListMeassureSort[0].getHostAddress())) {
			estimationQuality = 2;
		}
		else {
			int changeOver = 0;
			for(int i=0; i<3; i++) {
				for(int j=0; j<3; j++) {
					if(lmListComputeSort[i].getHostAddress().equalsIgnoreCase(lmListMeassureSort[j].getHostAddress())) {
						changeOver++;
						estimationQuality--;
						break;
					}
				}
				if(lmListMeassureSort[0].getHostAddress().equalsIgnoreCase(lmListComputeSort[i].getHostAddress())) {
					estimationQuality--;
				}
			}
			if(changeOver==1 && estimationQuality == 5) estimationQuality++;
			else if(changeOver == 1 && estimationQuality == 6) estimationQuality = 7;
			else if(estimationQuality == 7) estimationQuality++;
		}
		
		Date date = new Date(startTime);
		DateFormat df = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL, Locale.UK);
		df.setTimeZone(TimeZone.getTimeZone("GMT"));
		
		/*
		 * pořadí odhadu; počet landmarků; disperze; maximální RTT; jméno hosta; skutečná zeměpisná šířka hosta; 
		 * skutečná zeměpisná délka hosta; vypočtená zeměpisná šířka hosta; vypočtená zeměpisná délka hosta; 
		 * datum a čas; počet zaslaných dotazů Echo Request; doba měření a výpočtu; odchylka v km; počet opakování 
		 * samotného výpočtu, přičemž každé páté opakování je provedeno nové měření; skutečné pozice prvních 3 
		 * nejbližších odhadnutých landmarků v poli landmarků seřazeném podle skutečné hodnoty RTT; 
		 * kvalita odhadu-viz. výše; první 3 RTT podle odhadnutého pořadí; první 3 RTT podle skutečného pořadí  
		 * 
		 */
		radka = 
			mainCounter + separator + 
			numOfLmToPing + separator + 
			dispersion + separator +
			maxRtt + separator + 
			host.getHostName() + separator + 
			host.getLatitude() + separator + 
			host.getLongitude()	+ separator + 
			host.getComputedLatitude() + separator + 
			host.getComputedLongitude() + separator + 
			df.format(date) + separator + 
			numOfEchoRequest + separator + 
			(stopTime - startTime) + separator + 
		/*	odchylka + separator + */	
			//vyřazeno z finálních testů, aby zbytečně neodvádělo pozornost od důležitějších parametrů
			estCoreCount + separator + 
			positionInArrayTrue[0] + ";" + positionInArrayTrue[1] + ";" + positionInArrayTrue[2] + ";" + 
			estimationQuality + separator + 
			lmListMeassureSort[positionInArrayTrue[0]-1].getDistanceToHostMeassured() + separator + 
			lmListMeassureSort[positionInArrayTrue[1]-1].getDistanceToHostMeassured() + separator + 
			lmListMeassureSort[positionInArrayTrue[2]-1].getDistanceToHostMeassured() + separator + 
			lmListMeassureSort[0].getDistanceToHostMeassured() + separator + 
			lmListMeassureSort[1].getDistanceToHostMeassured() + separator + 
			lmListMeassureSort[2].getDistanceToHostMeassured() + separator + odchylka;
			
		WriteResults.writeComputedPosition("results.log", radka);
		
		/*
		 * cislo mereni,adresa,latitude,longitude,zmerena vzdalenost,vypoctena vzdalenost
		 * prvni 3 LM vypoctene, prvni 3 zmerene-souvisejici poradi vedle sebe
		 */
		for(int i=0; i<3; i++) {
			radka = 
				mainCounter + "; " + (i+1) + separator + 
				lmListComputeSort[i].getHostName() + separator + 
				lmListComputeSort[i].getLatitude() + separator + 
				lmListComputeSort[i].getLongitude() + separator + 
				lmListComputeSort[i].getDistanceToHostMeassured() + separator + 
				lmListComputeSort[i].getDistanceToHostComputed() + separator + 
				lmListMeassureSort[i].getHostName() + separator + 
				lmListMeassureSort[i].getLatitude() + separator + 
				lmListMeassureSort[i].getLongitude() + separator + 
				lmListMeassureSort[i].getDistanceToHostMeassured() + separator + 
				lmListMeassureSort[i].getDistanceToHostComputed() + separator + 
				df.format(date);

			WriteResults.writeComputedPosition("landmarks_best.log", radka);
		}
		
		radka = "";
		for(int i=0; i<lmListComputeSort.length; i++) {
			radka += separator + lmListComputeSort[i].getHostName();
		}
		WriteResults.writeComputedPosition("landmarks_all.log", mainCounter + " C" + radka);
		
		radka = "";
		for(int i=0; i<lmListMeassureSort.length; i++) {
			radka += separator + lmListMeassureSort[i].getHostName();
		}
		WriteResults.writeComputedPosition("landmarks_all.log", mainCounter + " M" + radka);
		
		radka = "";
		for(int i=0; i<lmListComputeSort.length; i++) {
			radka += separator + lmListComputeSort[i].getDistanceToHostComputed();
		}
		WriteResults.writeComputedPosition("rtt_all_Csort.log", mainCounter + " C" + radka);
		
		radka = "";
		for(int i=0; i<lmListComputeSort.length; i++) {
			radka += separator + lmListComputeSort[i].getDistanceToHostMeassured();
		}
		WriteResults.writeComputedPosition("rtt_all_Csort.log", mainCounter + " M" + radka);
		
		radka = "";
		for(int i=0; i<lmListMeassureSort.length; i++) {
			radka += separator + lmListMeassureSort[i].getDistanceToHostComputed();
		}
		WriteResults.writeComputedPosition("rtt_all_Msort.log", mainCounter + " C" + radka);
		
		radka = "";
		for(int i=0; i<lmListMeassureSort.length; i++) {
			radka += separator + lmListMeassureSort[i].getDistanceToHostMeassured();
		}
		WriteResults.writeComputedPosition("rtt_all_Msort.log", mainCounter + " M" + radka);
		
		//zapsani pouzitych landmarku spolu s jejich pozici a vektorem vzdalenosti(RTT)
		//ve trech verzich-1.zmereny-2.zmereny(přeměřený)-vypocteny
		for(int i=0; i<numOfLmToPing; i++) {
			radka = 
				mainCounter + separator + 
				lmListActive[i].getHostName() + separator + 
				lmListActive[i].getLatitude() + separator + 
				lmListActive[i].getLongitude() + separator + 
				resultsRtt[i] + separator + 
				lmListActive[i].getDistanceToHostMeassured() + separator + 
				lmListActive[i].getDistanceToHostComputed() + separator + 
				df.format(date);
				
			WriteResults.writeComputedPosition("landmarks.log", radka);					
		}
	}
	
	/**
	 * 
	 * @return hodnotu proměnné numOfEchoRequest, která obsahuje počet zaslaných dotazů 
	 * ICMP Echo Request při daném měření. Tyto dotazy byly odeslány pouze pro odhad pozice hosta.
	 */
	public static int getNumOfEchoRequest() {
		return numOfEchoRequest;
	}
	
	/**
	 * Určeno pro zvýšení počtu zaslaných dotazů protokolu ICMP Echo Request, 
	 * která je implementována ve třídě Ping, z níž jedinné je incrementNumOfPings() volána. 
	 * Při každém odeslání dotazu Echo Request je hodnota proměnné numOfEchoRequestAll 
	 * zvýšena o 1.
	 */
	public static void incrementNumOfEchoRequest() {
		numOfEchoRequestAll++;
	}
	
	/**
	 * 
	 * @return pole hodnot RTT změřených mezi hostem a vybranou skupinou landmarků.
	 */
	public static double[] getPingValue() {
		return resultsRtt;
	}
	
	/**
	 * 
	 * @return pole landmarků, které byly použity při měření RTT.
	 */
	public static LandmarkInfo[] getLandmarksActive() {
		return lmListActive;
	}
	
	/**
	 * 
	 * @return objekt host, který obsahuje informace o stanici, na které je spuštěna 
	 * tato aplikace a jejíž pozici se snažíme získat.
	 */
	public static Host getHost() {
		return host;
	}
	
	protected static int getNumOfEstimation() {
		return mainCounter;
	}
}
