import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
/**
 * \brief Trida, ktera obsahuje staticke funkce pro krizeni permutacnich chromozomu
 * @author nemec
 *
 */
public class Crossover {
	/**
	 * Fukce reprezentujici krizeni typu Edge Recombination Crossover
	 * @param parent1 Rodic1
	 * @param parent2 Rodic2
	 * @param mutationProbability Pravdepodobnost mutace
	 * @param mutationType Typ mutace 1-Insert 2-Swap 3-Inverse 4-Scramble Default-Insert
	 * @return Potomek
	 */
	public static Chromozome edgeRecombinationCrossOver(Chromozome parent1, Chromozome parent2, double mutationProbability, int mutationType){
		
		int size = parent1.getChromozomeSize();
		Chromozome child = new Chromozome(size, 0);
		ArrayList<ArrayList<Integer>> storage = new ArrayList<ArrayList<Integer>>();
		
		ArrayList<Integer> neighbors;
		
		for(int i = 0; i < size; i++){
			neighbors = new ArrayList<Integer>();
			int current = parent1.getValueOfGene(i);
			int position = parent2.get().indexOf(current);
			int second = 0;
			int second1 = 0;
			
			if(i==0){
				neighbors.add(parent1.getValueOfGene(size-1));
				neighbors.add(parent1.getValueOfGene(i+1));
				
				if(position == 0){
					second = parent2.getValueOfGene(size-1);
					second1 = parent2.getValueOfGene(position+1);
					
					if(!neighbors.contains(second)){
						neighbors.add(second);
					}
					
					if(!neighbors.contains(second1)){
						neighbors.add(second1);
					}	
				}
				
				if(position > 0 && position < size-1){
					second = parent2.getValueOfGene(position-1);
					second1 = parent2.getValueOfGene(position+1);
					
					if(!neighbors.contains(second)){
						neighbors.add(second);
					}
					
					if(!neighbors.contains(second1)){
						neighbors.add(second1);
					}	
				}
				
				if(position == size-1){
					second = parent2.getValueOfGene(0);
					second1 = parent2.getValueOfGene(position-1);
					
					if(!neighbors.contains(second)){
						neighbors.add(second);
					}
					
					if(!neighbors.contains(second1)){
						neighbors.add(second1);
					}
				}
				storage.add(neighbors);
			}
			
			if(i > 0 && i < size-1){
				neighbors.add(parent1.getValueOfGene(i-1));
				neighbors.add(parent1.getValueOfGene(i+1));
				
				if(position == 0){
					second = parent2.getValueOfGene(size-1);
					second1 = parent2.getValueOfGene(position+1);
					
					if(!neighbors.contains(second)){
						neighbors.add(second);
					}
					
					if(!neighbors.contains(second1)){
						neighbors.add(second1);
					}	
				}
				
				if(position > 0 && position < size-1){
					second = parent2.getValueOfGene(position-1);
					second1 = parent2.getValueOfGene(position+1);
					
					if(!neighbors.contains(second)){
						neighbors.add(second);
					}
					
					if(!neighbors.contains(second1)){
						neighbors.add(second1);
					}	
				}
				
				if(position == size-1){
					second = parent2.getValueOfGene(0);
					second1 = parent2.getValueOfGene(position-1);
					
					if(!neighbors.contains(second)){
						neighbors.add(second);
					}
					
					if(!neighbors.contains(second1)){
						neighbors.add(second1);
					}
				}
				storage.add(neighbors);
			}
			
			if(i == size-1){
				neighbors.add(parent1.getValueOfGene(0));
				neighbors.add(parent1.getValueOfGene(i-1));
				
				if(position == 0){
					second = parent2.getValueOfGene(size-1);
					second1 = parent2.getValueOfGene(position+1);
					
					if(!neighbors.contains(second)){
						neighbors.add(second);
					}
					
					if(!neighbors.contains(second1)){
						neighbors.add(second1);
					}	
				}
				
				if(position > 0 && position < size-1){
					second = parent2.getValueOfGene(position-1);
					second1 = parent2.getValueOfGene(position+1);
					
					if(!neighbors.contains(second)){
						neighbors.add(second);
					}
					
					if(!neighbors.contains(second1)){
						neighbors.add(second1);
					}	
				}
				
				if(position == size-1){
					second = parent2.getValueOfGene(0);
					second1 = parent2.getValueOfGene(position-1);
					
					if(!neighbors.contains(second)){
						neighbors.add(second);
					}
					
					if(!neighbors.contains(second1)){
						neighbors.add(second1);
					}
				}
				storage.add(neighbors);
			}
		}
		
		/*for(int o=0; o<size; o++){
			System.out.println(storage.get(o));
		}*/
		
		int remove = parent1.getValueOfGene(0);
		child.setGene(remove, 0);
		//System.out.println(storage.size());
		//storage.remove(0);
		//System.out.println(storage.size());
		ArrayList<Integer> positions = new ArrayList<Integer>();
		ArrayList<Integer> ignore = new ArrayList<Integer>();
		
		ignore.add(0);
		
		for(int l=1; l<size; l++){
			for(int m=0; m<storage.size(); m++){
				if(storage.get(m).contains(remove) && !ignore.contains(m)){
					int index = storage.get(m).indexOf(remove);
					storage.get(m).remove(index);
					positions.add(m);
				}
			}
			//System.out.println(ignore);
			//System.out.println(positions);
			//System.out.println("Size je: " +positions.size());
			if(positions.size()==0){
				//System.out.println("HERE!");
				for(int q=0; q<storage.size(); q++){
					//System.out.println("Q je :"+q);
					if(!ignore.contains(q)){
						remove = parent1.getValueOfGene(q);
						//System.out.println("q je: "+q+" a remove je: "+remove);
						ignore.add(q);
						break;
					}
				}
			}
			else {
				int sizeofremove = storage.get(positions.get(0)).size();
				//System.out.println("Size of remove je: " +sizeofremove);
				int indexofremove = positions.get(0);
		
				for(int p=0; p<positions.size(); p++){
					if(storage.get(positions.get(p)).size() < sizeofremove){
						sizeofremove = storage.get(positions.get(p)).size();
						indexofremove = positions.get(p);
						//System.out.println("Index of remove je:"+indexofremove);
					}
				}
				remove = parent1.getValueOfGene(indexofremove);
				ignore.add(indexofremove);
			}
			//System.out.println("Remove je: "+remove);
			child.setGene(remove, l);
			positions.clear();
			//child.printChromozome();
			//System.out.println(ignore);
		}
		
		//child.printChromozome();
		
		Random random = new Random();
		double mutationchance;
		
		mutationchance = random.nextDouble();
		if(mutationchance > 0 && mutationchance <= mutationProbability){
			switch(mutationType){
			
			case 1:
				child = insertMutation(child);
				break;
				
			case 2:
				child = swapMutation(child);
				break;
				
			case 3:
				child = inverseMutation(child);
				break;
				
			case 4:
				child = scrambleMutation(child);
				break;
				
			default:
				child = insertMutation(child);
				break;
			}
		}
		return child;
		
	}
	/**
	 * Funkce reprezentujici krizeni typu Order One
	 * @param parent1 Rodic1
	 * @param parent2 Rodic2
	 * @param mutationProbability Pravdepodobnost mutace
	 * @param mutationType Typ mutace 1-Insert 2-Swap 3-Inverse 4-Scramble Default-Insert
	 * @return Potomek
	 */
	public static Chromozome orderOneCrossOver(Chromozome parent1, Chromozome parent2, double mutationProbability, int mutationType){
		
		int size = parent1.getChromozomeSize();
		Chromozome child = new Chromozome(size, 0);
		ArrayList<Integer> storage = new ArrayList<Integer>();
		
		Random random = new Random();
		Random random2 = new Random();
		
		
		int breakpoint = random.nextInt(size);
		int breakpoint2 = random2.nextInt(size - breakpoint) + breakpoint;
		
		//System.out.println(breakpoint);
		//System.out.println(breakpoint2);
		
		for(int i = breakpoint; i<breakpoint2+1; i++){
			child.setGene(parent1.getValueOfGene(i), i);
			storage.add(parent1.getValueOfGene(i));
		}
		
		//child.printChromozome();
		
		int pointer = breakpoint2 + 1;
		int pointer2 = breakpoint2 + 1;
		
		while(child.get().contains(0)){
			
			if(pointer == size){
				pointer=0;
			}
			
			if(pointer2 == size){
				pointer2=0;
			}
			//System.out.println(pointer);
			
			if(!storage.contains(parent2.getValueOfGene(pointer))){
				child.setGene(parent2.getValueOfGene(pointer), pointer2);
				pointer++;
				pointer2++;
			}
			else{
				pointer++;
			}
		}
		//child.printChromozome();
		
		Random random3 = new Random();
		double mutationchance;
		
		mutationchance = random3.nextDouble();
		
		mutationchance = random.nextDouble();
		if(mutationchance > 0 && mutationchance <= mutationProbability){
			switch(mutationType){
			
			case 1:
				child = insertMutation(child);
				break;
				
			case 2:
				child = swapMutation(child);
				break;
				
			case 3:
				child = inverseMutation(child);
				break;
				
			case 4:
				child = scrambleMutation(child);
				break;
				
			default:
				child = insertMutation(child);
				break;
			}
		}
		return child;
	}
	/**
	 * Funkce, ktera reprezentuje krizeni typu PMX
	 * @param parent1 Rodic1
	 * @param parent2 Rodic2
	 * @param mutationProbability Sance na mutaci
	 * @param mutationType Typ mutace 1-Insert 2-Swap 3-Inverse 4-Scramble Default-Insert
	 * @return
	 */
	public static Chromozome pmxCrossOver(Chromozome parent1, Chromozome parent2, double mutationProbability, int mutationType){
		
		int size = parent1.getChromozomeSize();
		Chromozome child = new Chromozome(size, 0);
		ArrayList<Integer> storage = new ArrayList<Integer>();
		
		Random random = new Random();
		Random random2 = new Random();
		
		
		int breakpoint = random.nextInt(size);
		int breakpoint2 = random2.nextInt(size - breakpoint) + breakpoint;
		
		//System.out.println(breakpoint);
		//System.out.println(breakpoint2);
		
		for(int i = breakpoint; i<breakpoint2+1; i++){
			child.setGene(parent1.getValueOfGene(i), i);
			storage.add(parent1.getValueOfGene(i));
		}
		
		//child.printChromozome();
		//System.out.println(storage);
		int pointer = breakpoint;
		int value = 0;
		int value2 = 0;
		int index = 0;
		boolean found = false;
		
		while(pointer < breakpoint2+1){
			
			if(!storage.contains(parent2.getValueOfGene(pointer))){
				found = false;
				value = parent2.getValueOfGene(pointer);
				value2 = parent1.getValueOfGene(pointer);
				index = parent2.get().indexOf(value2);
				//System.out.println("Value je " + value);
				//tem.out.println("Index je " + index);
				while(found == false){
					if(index < breakpoint || index > breakpoint2){
						child.setGene(value, index);
						//System.out.println("Boo");
						found = true;
						
					}
					else{
						value2 = parent1.getValueOfGene(index);
						index = parent2.get().indexOf(value2);
						//System.out.println("Value2 je " + value);
						//System.out.println("Index2 je " + index);
						//System.out.println("Bee");
						//break;
					}
				}
				pointer++;
			}
			else{
				pointer++;
				//System.out.println("bla");
			}
			
		}
		
		
		for(int k=0; k<size; k++){
			if(child.getValueOfGene(k)==0){
				child.setGene(parent2.getValueOfGene(k), k);
			}
				
		}
		//child.printChromozome();
		
		Random random3 = new Random();
		double mutationchance;
		
		mutationchance = random3.nextDouble();
		
		mutationchance = random.nextDouble();
		if(mutationchance > 0 && mutationchance <= mutationProbability){
			switch(mutationType){
			
			case 1:
				child = insertMutation(child);
				break;
				
			case 2:
				child = swapMutation(child);
				break;
				
			case 3:
				child = inverseMutation(child);
				break;
				
			case 4:
				child = scrambleMutation(child);
				break;
				
			default:
				child = insertMutation(child);
				break;
			}
		}
		return child;
	}
	
	private static Chromozome insertMutation(Chromozome mutate){
		
		Random random = new Random();
		Random random2 = new Random();
		
		int size = mutate.getChromozomeSize();
		int point1 = random.nextInt(size);
		int point2 = random2.nextInt(size - point1) + point1;
		int temp = 0;
		
		//System.out.println(point1);
		//System.out.println(point2);
		
		for(int i=point2; i > point1; i--){
			temp = mutate.getValueOfGene(i);
			mutate.setGene(mutate.getValueOfGene(i-1), i);
			mutate.setGene(temp, i-1);
		}
		
		return mutate;
	}
	
	private static Chromozome swapMutation(Chromozome mutate){
		
		Random random = new Random();
		Random random2 = new Random();
		
		int size = mutate.getChromozomeSize();
		int point1 = random.nextInt(size);
		int point2 = random2.nextInt(size - point1) + point1;
		
		//System.out.println(point1);
		//System.out.println(point2);
		
		int temp = mutate.getValueOfGene(point1);
		mutate.setGene(mutate.getValueOfGene(point2), point1);
		mutate.setGene(temp, point2);
		
		return mutate;
	}
	
	private static Chromozome inverseMutation(Chromozome mutate){
		
		Random random = new Random();
		Random random2 = new Random();
		
		int size = mutate.getChromozomeSize();
		int point1 = random.nextInt(size);
		int point2 = random2.nextInt(size - point1) + point1;
		ArrayList<Integer> temp = new ArrayList<Integer>();
		
		//System.out.println(point1);
		//System.out.println(point2);
		
		for(int i=point1;i<point2+1;i++){
			temp.add(mutate.getValueOfGene(i));
		}
		
		//System.out.println(temp);
		
		Collections.reverse(temp);
		
		//System.out.println(temp);
		
		for(int j=point1, k=0;j<point2+1;j++,k++){
			mutate.setGene(temp.get(k),j);
		}
		
		return mutate;
	}
	
	private static Chromozome scrambleMutation(Chromozome mutate){
		
		Random random = new Random();
		Random random2 = new Random();
		
		int size = mutate.getChromozomeSize();
		int point1 = random.nextInt(size);
		int point2 = random2.nextInt(size - point1) + point1;
		ArrayList<Integer> temp = new ArrayList<Integer>();
		
		//System.out.println(point1);
		//System.out.println(point2);
		
		for(int i=point1;i<point2+1;i++){
			temp.add(mutate.getValueOfGene(i));
		}
		
		//System.out.println(temp);
		
		Collections.shuffle(temp);
		
		//System.out.println(temp);
		
		for(int j=point1, k=0;j<point2+1;j++,k++){
			mutate.setGene(temp.get(k),j);
		}
		
		return mutate;
	}
	
	/*http://arxiv.org/ftp/arxiv/papers/1203/1203.3097.pdf*/
}
