/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: GeneticPlacer.java
 * Written by Team 4: Benedikt Mueller, Richard Fallert
 * 
 * This code has been developed at the Karlsruhe Institute of Technology (KIT), Germany, 
 * as part of the course "Multicore Programming in Practice: Tools, Models, and Languages".
 * Contact instructor: Dr. Victor Pankratius (pankratius@ipd.uka.de)
 *
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.placement.genetic2;

import com.sun.electric.tool.placement.PlacementFrame.PlacementNetwork;
import com.sun.electric.tool.placement.PlacementFrame.PlacementNode;
import com.sun.electric.tool.placement.genetic2.metrics.DeltaBBMetric;

import java.util.List;
import java.util.Random;

/**
 * Genetic algorithm for placement.
 */
public class GeneticPlacer {
	// private static final int MAX_INDIVIDUALS = 200; // for islands
	// (deprecated)
	private static final int INDIVIDUALS = 1000; // population size for
													// UnifiedPopulation

	private static int EVOLUTION_STEPS;

	// maximum runtime of the placement algorithm in seconds
	public int maxRuntime;
	// number of threads
	public int numThreads;
	// if false: NO system.out.println statements
	public boolean printDebugInformation;
	long startTime;

	UnifiedPopulation population = null;

	Reference ref;

	public String getAlgorithmName() {
		return "team4Genetic";
	}

	public UnifiedPopulation getPopulation() {
		return population;
	}

	GeneticPlacer(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks, int runtime,
			int threads, boolean debug) {
		maxRuntime = runtime * 1000;
		EVOLUTION_STEPS = Math.max(maxRuntime / 10000, 1);
		numThreads = threads;
		printDebugInformation = debug;

		startTime = System.currentTimeMillis();

		Random rand = new Random(System.currentTimeMillis());

		DeltaBBMetric.init(nodesToPlace, allNetworks);
		ref = new Reference(nodesToPlace, allNetworks, rand);
		DeltaBBMetric.setRef(ref);
		ref.calculateRefNetLength();

		// initialize population
		DeltaIndividual[] indis = new DeltaIndividual[INDIVIDUALS];
		for (int i = 0; i < INDIVIDUALS; i++) {
			indis[i] = new DeltaIndividual(ref, rand);
		}

		population = new UnifiedPopulation(nodesToPlace, allNetworks, indis, maxRuntime / EVOLUTION_STEPS,
				numThreads, rand);

	}

	private void placeUnified(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks,
			long partTime) {
		long localStartTime = System.currentTimeMillis();

		if (printDebugInformation) {
			System.out.println("Starting genetic algorithm...\n" + "Total running time is " + maxRuntime
					/ 1000 + " seconds (~" + maxRuntime / 60000 + " minutes).\n" + "There will be "
					+ EVOLUTION_STEPS + " text messages while running,\n"
					+ "each will tell you the current round number and champion badness:");
		}

		int round = 0;
		long t = 0;
		long localT = 0;
		DeltaIndividual champion;
		ref.calculateFirstTime();

		while (t < partTime) {

			population.setProgress(((double) localT) / (double) (partTime - (localStartTime - startTime)));

			population.evolveLocalMT(numThreads); // this is where the evolution
													// happens!

			champion = (DeltaIndividual) population.getChampion();

			if (printDebugInformation) {
				System.out.println("Round " + round + " of " + EVOLUTION_STEPS + " | champion badness: "
						+ population.getChampion().getBadness());

				System.out.println("champion overlap: " + champion.getBadnessComponents()[1]);
				System.out.println("champion netlength: " + champion.getBadnessComponents()[0]);
			}

			ref.update(champion);

			if (round % 4 == 0) {
				// adapt the grid to the changed placement
				ref.createGrid();
			}

			if (printDebugInformation) {
				System.out.println("ref overlap: " + ref.getBadnessComponents()[1]);
				System.out.println("ref netlength: " + ref.getBadnessComponents()[0]);
				System.out.println();
			}

			population.reboot();

			round++;
			t = (System.currentTimeMillis() - startTime);
			localT = (System.currentTimeMillis() - localStartTime);
		}

		// find the champion
		champion = (DeltaIndividual) population.getChampion();

		// write champion placement to nodesToPlace
		ref.update(champion);

		ref.calculateFirstTime();

		if (printDebugInformation) {
			System.out.println("final ref net length: " + ref.getBadnessComponents()[0]);
			System.out.println("final ref overlap: " + ref.getBadnessComponents()[1]);
			System.out.println("final ref naiveOverlap: " + ref.getNaiveOverlap());
			System.out.println("final net length (bb):  " + DeltaBBMetric.compute());
			// System.out.println("final evolverSteps: " +
			// population.getEvolverSteps());
		}
	}

	private void placeAnnealing(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks,
			long partTime) {
		long localStartTime = System.currentTimeMillis();
		SimulatedAnnealing annealing = new SimulatedAnnealing(nodesToPlace, allNetworks, maxRuntime
				/ EVOLUTION_STEPS, numThreads, ref);

		if (printDebugInformation) {
			System.out.println("Starting simulated annealing...\n" + "Total running time is " + maxRuntime
					/ 1000 + " seconds (~" + maxRuntime / 60000 + " minutes).\n" + "There will be "
					+ EVOLUTION_STEPS + " text messages while running,\n"
					+ "each will tell you the current round number and champion badness:");
		}
		int round = 0;
		long t = 0;
		long localT = 0;

		ref.calculateFirstTime();
		while (t < partTime) {

			annealing.setProgress(((double) localT) / (double) (partTime - (localStartTime - startTime)));

			annealing.go();

			if (printDebugInformation) {
				System.out.println("Round " + round + " of " + EVOLUTION_STEPS);
				System.out.println("ref netlength: " + ref.getBadnessComponents()[0]);
				System.out.println("ref overlap: " + ref.getBadnessComponents()[1]);
			}

			if (round % 4 == 0) {
				// adapt the grid to the changed placement
				ref.createGrid();
			}

			// annealing.reboot();

			round++;
			t = (System.currentTimeMillis() - startTime);
			localT = (System.currentTimeMillis() - localStartTime);
		}

		if (printDebugInformation) {
			System.out.println("final ref net length: " + ref.getBadnessComponents()[0]);
			System.out.println("final ref overlap: " + ref.getBadnessComponents()[1]);
			System.out.println("final ref naiveOverlap: " + ref.getNaiveOverlap());
			System.out.println("final net length (bb):  " + DeltaBBMetric.compute());
			// System.out.println("final evolverSteps: " +
			// population.getEvolverSteps());
		}
	}

	// /**
	// * This function is deprecated, use placeUnified or placeAnnealing
	// instead.
	// * It is only provided for comparison and can be deleted at any time
	// */
	// private void placeIslands(List<PlacementNode> nodesToPlace,
	// List<PlacementNetwork> allNetworks)
	// {
	// BBMetric m_bb = new BBMetric(nodesToPlace, allNetworks);
	//		
	// // create root random number generator
	// Random rand = new Random(System.currentTimeMillis());
	//		
	// // create one random number generator per thread
	// int[] seeds = new int[numThreads];
	// Random[] rands = new Random[numThreads];
	//		
	// for(int i=0; i<numThreads; i++)
	// {
	// seeds[i] = rand.nextInt();
	// rands[i] = new Random(seeds[i]);
	// }
	//
	//		
	// // initialize populations (one per thread)
	// Population<ClassicIndividual>[] populations = new Population[numThreads];
	// Thread[] threads = new Thread[numThreads];
	//		
	//		
	// for(int i=0; i<numThreads; i++) {
	//			
	// ClassicIndividual[] indis = new
	// ClassicIndividual[MAX_INDIVIDUALS/numThreads];
	// for(int n = 0; n < MAX_INDIVIDUALS/numThreads; n++)
	// indis[n] = new ClassicIndividual(ref, rand);
	//			
	// populations[i] =
	// new Population<ClassicIndividual>(nodesToPlace,
	// allNetworks,
	// indis,
	// (long)(maxRuntime/EVOLUTION_STEPS), // Gesamtzeit geteilt durch
	// Evolutionsschritte
	// rands[i]);
	// }
	//		
	// if(printDebugInformation)
	// {
	// System.out.println("Starting genetic algorithm...\n"
	// + "Total running time is " + maxRuntime/1000 + " seconds (~"
	// +maxRuntime/60000+ " minutes).\n"
	// + "There will be " + EVOLUTION_STEPS + " text messages while running,\n"
	// + "each will tell you the current round number and champion badness:");
	// }
	//		
	// // periodically start threads and exchange champions between populations
	// int round = 0;
	// long t = 0;
	//		
	// while(t < maxRuntime)
	// {
	// for(int i=0; i<numThreads; i++)
	// {
	// populations[i].setProgress(((double)t)/((double)maxRuntime));
	// threads[i] = new Thread(populations[i]);
	// threads[i].start();
	// }
	//			
	// // wait until all populations have completed some rounds of evolution
	// for(int i=0; i<numThreads; i++)
	// {
	// try {
	// threads[i].join();
	// } catch (InterruptedException e) { e.printStackTrace(); }
	// }
	//			
	// if(printDebugInformation)
	// {
	// System.out.println("(" + round + ") : "
	// + populations[0].getChampion().getBadness());
	// }
	//			
	//			
	// // exchange champions so the populations can cooperate
	// for(int i=0; i<numThreads; i++)
	// {
	// //if(rand.nextDouble() > 0.5)
	// {
	// populations[i].insert(populations[(i+1)%numThreads].getChampion());
	// //populations[i].insert(populations[(i+1)%numThreads].getRandomOne());
	// }
	// }
	//
	// // write champion placement to nodesToPlace
	// population.getChampion().writeToPlacement(nodesToPlace);
	// population.reEvaluateAll();
	//			
	// round++;
	// t = (System.currentTimeMillis()-startTime);
	// }
	//		
	// // find the best champion of all populations
	// Individual champion = populations[0].getChampion();
	//		
	// for(int p=1; p<numThreads; p++)
	// {
	// Individual challenger = populations[p].getChampion();
	// if(challenger.getBadness() < champion.getBadness())
	// {
	// champion = challenger;
	// }
	// }
	//		
	// // write champion placement to nodesToPlace
	// population.getChampion().writeToPlacement(nodesToPlace);
	// if(printDebugInformation)
	// {
	// System.out.println("final champion badness: " + champion.getBadness());
	// System.out.println("final champion overlap: " +
	// champion.calculateOverlap());
	// System.out.println("final champion semiperimeter length: " +
	// champion.getSemiperimeterLength());
	// System.out.println("final champion bounding box area: " +
	// champion.getBoundingBoxArea());
	//			
	// System.out.println("final net length (bb):  " + m_bb.compute());
	// }
	// }

	/**
	 * Method to run the genetic algorithm to find a good placement.
	 * 
	 * @param nodesToPlace
	 *            a list of all nodes that are to be placed.
	 * @param allNetworks
	 *            a list of all networks that connect the nodes.
	 */
	protected void runPlacement(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks) {
		// For a runtime of less than 20 seconds, do only simulated annealing
		if (maxRuntime >= 19900) placeUnified(nodesToPlace, allNetworks, maxRuntime / 2);
		placeAnnealing(nodesToPlace, allNetworks, maxRuntime);
		// placeIslands(nodesToPlace, allNetworks); // deprecated
	}

}
