/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Yana.java
 * Written by: Andreas Uebelhoer, Alexander Bieles, Emre Selegin (Team 6)
 *
 * Copyright (c) 2010, Static Free Software. 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.routing.experimentalLeeMoore1;

import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
import org.slf4j.LoggerFactory;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.variable.UserInterface;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.routing.experimentalLeeMoore1.LeeMoore.RoutingArray;
import com.sun.electric.tool.routing.experimentalLeeMoore1.LeeMoore.Tupel;
import com.sun.electric.util.ElapseTimer;

public class yana extends BenchmarkRouter {

	protected final boolean output = enableOutput.getBooleanValue();

	/*
	 * GUI
	 */
	private static int progressMax = 0;
	private static int progress = 0;
	private static UserInterface gui;

	/*
	 * Parameters
	 */
	// public RoutingParameter maxThreadsParameter = new
	// RoutingParameter("threads", "Number of Threads to use:", 4);
	public static int maxThreads;
	public RoutingParameter numPartitionsParameter = new RoutingParameter("partitions", "Number of partitions to use:",
			26);  // Factory value must be constant
//			Math.max(26, Math.max(Runtime.getRuntime().availableProcessors(), numThreads.getIntValue())));
	// public RoutingParameter numPartitionsParameter = new
	// RoutingParameter("partitions", "Number of partitions to use:", 5);
	public static int numPartitions;
	public RoutingParameter regionDivideMethodParameter = new RoutingParameter("region-generation-method",
			"Method used to devide the grid into regions:", 1);
	public static int regionDivideMethod;
	// 1=optimal balanced in x and y direction
	// 2=simple stripes
	// 3=adapted so that regions have the same ratio than the grid dimensions
	public RoutingParameter maxLayerUseParameter = new RoutingParameter("maxLayerUse",
			"Restrict number of layers to use", 5);
	public static int maxLayerUse;
	public RoutingParameter minimumRegionBorderLengthParameter = new RoutingParameter("minimumRegionBorderLength",
			"Minumum length the regions must have:", 20);
	public static int minimumRegionBorderLength;
	// public RoutingParameter widthWhereJustOneWireIsPermittedParameter = new
	// RoutingParameter("widthWhereJustOneWireIsPermitted",
	// "Wire-thickness + 2*spacing-length to next wire", 9);
	public static int distanceBetweenWires; // should be = wirethickness +
											// 2*requiredspacing
	// if there are to much regions, so that this constraint cant be held, than
	// the numpartitions will be reduced until this constraint is fulfilled
	/*
	 * Others
	 */
	public static CyclicBarrier barrierRouting;
	public static CyclicBarrier barrierWiring;
	public static ConcurrentHashMap<Integer, Boolean> unroutedNets = new ConcurrentHashMap<Integer, Boolean>();

	/**
	 * Method to return the name of this routing algorithm.
	 * 
	 * @return the name of this routing algorithm.
	 */
	@Override
	public String getAlgorithmName() {
		return "Lee/Moore - 1";
	}

	/**
	 * Method to do Simple routing.
	 */
	@Override
	protected void runRouting(Cell cell, List<RoutingSegment> segmentsToRoute, List<RoutingLayer> allLayers,
			List<RoutingContact> allContacts, List<RoutingGeometry> blockages) {

		// printChipStatistics(cell, segmentsToRoute, allLayers, allContacts,
		// blockages);

		ElapseTimer timer = ElapseTimer.createInstance().start();
		if (output)
			System.out.println("electric goes yana...");

		int maxRuntimeMs = this.maxRuntime.getIntValue() * 1000;

		maxThreads = (numThreads.getIntValue() < 1) ? 1 : numThreads.getIntValue();
		numPartitions = (numPartitionsParameter.getIntValue() < 1) ? 1 : numPartitionsParameter.getIntValue();
		minimumRegionBorderLength = (minimumRegionBorderLengthParameter.getIntValue() < 2) ? 2
				: minimumRegionBorderLengthParameter.getIntValue();
		// distanceBetweenWires =
		// (widthWhereJustOneWireIsPermittedParameter.getIntValue() < 1) ? 1 :
		// widthWhereJustOneWireIsPermittedParameter.getIntValue();
		regionDivideMethod = regionDivideMethodParameter.getIntValue();
		// regionDivideMethod can be any value, but if it is an unkown one,
		// there will be just one region

		maxLayerUse = maxLayerUseParameter.getIntValue();
		if (maxLayerUse <= 0 || maxLayerUse > countLayers(allLayers)) {
			maxLayerUse = countLayers(allLayers);
		}

		int dist = 0;
		int width = 0;
		int currentLayer = 0;
		for (RoutingLayer rl : allLayers) {
			if (rl.isMetal() && currentLayer < maxLayerUse) {
				if (rl.getMinSpacing(rl) > dist) {
					dist = (int) Math.round(rl.getMinSpacing(rl));
				}
				if (rl.getMinWidth() > width) {
					width = (int) Math.round(rl.getMinWidth());
				}
				currentLayer++;
			}
		}

		// distance: free space needed between two objects on the same layer
		// width: width of a wire
		// assumption: vias are width+1 thick (except vias from the last layer
		if (width < 5) {
			width++;
		}
		distanceBetweenWires = dist + width;

		if (output)
			System.out.println("Running " + maxThreads + " Threads on " + numPartitions + " partitions each minimum "
					+ minimumRegionBorderLength + " length and " + distanceBetweenWires + " wire spacing.");
		initProgress();

		initializeWiringClass(allLayers, allContacts);

		// for testing purposes: identify blocking cells -> will be deleted
		// later
		// Iterator<NodeInst> iter = cell.getNodes();
		// while (iter.hasNext()) {
		// NodeInst node = iter.next();
		// if (node.getName().contains("blockage")) {
		// for (RoutingLayer rl : allLayers) {
		// RoutingGeometry blockage = new RoutingGeometry(rl, node.getBounds(),
		// 0);
		// blockages.add(blockage);
		// }
		// }
		// }

		// create barriers
		barrierRouting = new CyclicBarrier(maxThreads, new Runnable() {

			public void run() {
				WorkPool.prepare();
			}
		});

		barrierWiring = new CyclicBarrier(maxThreads);

		// configure the Tupel-class static variables
		Tupel.setOffset(cell.getBounds().getLambdaX(), cell.getBounds().getLambdaY(), 10, output);

		// the global routing array, where the routing will be done from each
		// thread
		RoutingArray ra = new RoutingArray(cell, maxLayerUse, blockages, 3);

		// DEBUG
		// System.out.println("Interessantes Tupel: " +
		// Tupel.convertElectricToRoutingArrayCoordinate_X(208) + "," +
		// Tupel.convertElectricToRoutingArrayCoordinate_Y(101));

		// set start and end points as blocked
		markStartEndAsBlocked(segmentsToRoute, ra);

		// create all workers
		WorkerThread[] workerObjects = new WorkerThread[maxThreads];
		Thread[] workerThreads = new Thread[maxThreads];
		for (int i = 0; i < maxThreads; i++) {
			workerObjects[i] = new WorkerThread(i);
			workerThreads[i] = new Thread(workerObjects[i]);
			// do not start the threads here, because they have to be
			// initialized first
		}
		maxRuntimeMs = (int) (timer.currentTimeLong()) + maxRuntimeMs; // calculate
																		// runtime
																		// without
																		// initialization
		WorkerThread.init(workerObjects, maxLayerUse, ra, segmentsToRoute, maxRuntimeMs, output);

		// start all threads
		for (int i = 0; i < maxThreads; i++) {
			workerThreads[i].start();
		}

		// wait until threads have finished their work. afterwards we can do the
		// return
		try {
			for (int i = 0; i < maxThreads; i++) {
				workerThreads[i].join();
			}
		} catch (InterruptedException ex) {
			LoggerFactory.getLogger(yana.class).error("InterruptedExcpetion in workerThread.join", ex);
		}
		if (output)
			System.out.println("yana complete...");
		closeProgress();
		if (output) {
			timer.end();
			System.out.println("YANA-time: " + timer);
		}
		System.gc();
	}

	private void initializeWiringClass(List<RoutingLayer> allLayers, List<RoutingContact> allContacts) {
		// map metal layers which can be used for routing
		int metalLayers = 0;
		HashMap<String, Integer> metalLayerMap = new HashMap<String, Integer>();
		for (RoutingLayer rl : allLayers) {
			if (rl.isMetal()) {
				metalLayerMap.put(rl.getName(), metalLayers);
				metalLayers++;
			}
		}
		Wiring.init(allLayers, metalLayerMap, allContacts, output);
	}

	private int countLayers(List<RoutingLayer> allLayers) {
		int layer = 0;
		for (RoutingLayer l : allLayers) {
			if (l != null && l.isMetal()) {
				layer++;
			}
		}
		return layer;
	}

	/**
	 * print some statistics about the chip
	 * 
	 * 
	 * @param cell
	 * @param segmentsToRoute
	 * @param allLayers
	 * @param allContacts
	 * @param blockages
	 */
	public void printChipStatistics(Cell cell, List<RoutingSegment> segmentsToRoute, List<RoutingLayer> allLayers,
			List<RoutingContact> allContacts, List<RoutingGeometry> blockages) {

		int metalLayers = 0;

		for (RoutingLayer rl : allLayers) {
			if (rl.isMetal()) {
				metalLayers++;
			}
		}

		int[] layerDistance = new int[metalLayers];
		int[] layerMembership = new int[metalLayers];
		int[][] crossLayerMembership = new int[metalLayers][metalLayers];
		int singleLayer = 0;
		int crossLayer = 0;

		int maxNetID = Integer.MIN_VALUE;

		for (RoutingSegment rs : segmentsToRoute) {
			maxNetID = Math.max(maxNetID, rs.getNetID());

			int minStart = Integer.MAX_VALUE;
			int maxStart = Integer.MIN_VALUE;
			for (RoutingLayer rl : rs.getStartLayers()) {
				minStart = Math.min(minStart, rl.getMetalNumber());
				maxStart = Math.max(maxStart, rl.getMetalNumber());
			}
			int minFinish = Integer.MAX_VALUE;
			int maxFinish = Integer.MIN_VALUE;
			for (RoutingLayer rl : rs.getFinishLayers()) {
				minFinish = Math.min(minFinish, rl.getMetalNumber());
				maxFinish = Math.max(maxFinish, rl.getMetalNumber());
			}

			if (minStart == maxStart && minFinish == maxFinish) {
				int distance = Math.abs(minStart - minFinish);
				layerDistance[distance]++;
				if (minStart == minFinish) {
					singleLayer++;
					layerMembership[minStart - 1]++;
				} else {
					int min = Math.min(minStart, minFinish);
					int max = Math.max(minStart, minFinish);
					crossLayerMembership[min - 1][max - 1]++;
					crossLayer++;
				}
			}
		}

		int[] nets = new int[maxNetID + 1];

		for (RoutingSegment rs : segmentsToRoute) {
			nets[rs.getNetID()]++;
		}

		int numberOfNets = 0;
		int maxSegments = Integer.MIN_VALUE;

		for (int i = 0; i < nets.length; i++) {
			if (nets[i] != 0) {
				numberOfNets++;
			}
			maxSegments = Math.max(maxSegments, nets[i]);
		}

		int[] numberOfSegments = new int[maxSegments];
		for (int i = 0; i < nets.length; i++) {
			if (nets[i] != 0) {
				numberOfSegments[nets[i] - 1]++;
			}
		}

		int[] blockagesOnLayer = new int[metalLayers];

		for (RoutingGeometry rg : blockages) {
			blockagesOnLayer[rg.getLayer().getMetalNumber() - 1]++;
		}

		System.out.println("################### Cell statistics  ########################");
		System.out.println();
		System.out.println();
		System.out.print("#Metal-Layers: " + metalLayers + " { ");
		for (RoutingLayer rl : allLayers) {
			if (rl.isMetal()) {
				System.out.print(rl.getMetalNumber() + ", ");
			}
		}
		System.out.println("}\n");

		System.out.println("#Segments: " + segmentsToRoute.size());
		System.out.println("\t on one layer:  " + singleLayer);
		for (int i = 0; i < layerMembership.length; i++) {
			if (layerMembership[i] != 0) {
				System.out.println("\t\t Metal-" + (i + 1) + ": " + layerMembership[i]);
			}
		}
		System.out.println("\t on two layers: " + crossLayer);
		for (int i = 0; i < crossLayerMembership.length; i++) {
			for (int j = 0; j < crossLayerMembership[0].length; j++) {
				if (crossLayerMembership[i][j] != 0) {
					System.out.println("\t\t Metal-" + (i + 1) + " - Metal-" + (j + 1) + ": "
							+ crossLayerMembership[i][j]);
				}
			}
		}
		System.out.println();

		System.out.println("layer distance distribution");
		for (int i = 0; i < layerDistance.length; i++) {
			if (layerDistance[i] != 0) {
				System.out.println("\t distance " + i + ": " + layerDistance[i]);
			}
		}
		System.out.println();

		System.out.println("#Blockages: " + blockages.size());
		for (int i = 0; i < blockagesOnLayer.length; i++) {
			if (blockagesOnLayer[i] != 0) {
				System.out.println("\t on Metal-" + (i + 1) + ": " + blockagesOnLayer[i]);
			}
		}
		System.out.println();

		int countNumSegments = 0;

		System.out.println("#Nets: " + numberOfNets);
		for (int i = 0; i < numberOfSegments.length; i++) {
			if (numberOfSegments[i] != 0) {
				countNumSegments += numberOfSegments[i] * (i + 1);
				System.out.print("\t " + (i + 1) + " segments: " + numberOfSegments[i] + "{ ");
				for (int j = 0; j < nets.length; j++) {
					if (nets[j] == (i + 1)) {
						System.out.print(j + ", ");
					}
				}
				System.out.println("}");
			}
		}
		System.out.println("\tsum: " + countNumSegments + " segments");

		System.out.println("############################################################");
		System.out.println();
	}

	/**
	 * Initializes progress bar
	 */
	public static void initProgress() {
		gui = Job.getUserInterface();
		gui.startProgressDialog("Yana is working for you", null);
		gui.setProgressNote("Running...");
		gui.setProgressValue(0);
	}

	/**
	 * Increases maximum value used in progress bar
	 * 
	 * @param size
	 *            value to add to current maximum value
	 */
	synchronized public static void setProgressMax(int size) {
		progressMax += size;
	}

	/**
	 * Update progress bar. Marks one job as done
	 */
	synchronized public static void updateProgress() {
		progress++;
		// System.out.println("Progress: "+(double)progress/progressMax*100);
		gui.setProgressNote("Routing segment " + progress + "/" + progressMax);
		gui.setProgressValue((int) ((double) progress / progressMax * 100));
	}

	/**
	 * Hides progess bar
	 */
	public static void closeProgress() {
		gui.stopProgressDialog();
	}

	/**
	 * Mark start and end points of a routing segment as blocked
	 * 
	 * @param segmentsToRoute
	 *            Routing Segments
	 * @param ra
	 *            Routing Array
	 */
	private void markStartEndAsBlocked(List<RoutingSegment> segmentsToRoute, RoutingArray ra) {
		Tupel[] startEndPoints = new Tupel[18];
		int i;
		for (RoutingSegment rs : segmentsToRoute) {
			// original points
			i = 0;

			Tupel start = new Tupel(rs.getStartEnd().getLocation(), rs.getStartLayers().get(0).getMetalNumber() - 1);
			Tupel end = new Tupel(rs.getFinishEnd().getLocation(), rs.getFinishLayers().get(0).getMetalNumber() - 1);

			// DEBUG
			// if(start.getX_InsideRoutingArray()==93 &&
			// start.getY_InsideRoutingArray()==50){
			// System.out.println("GOT IT");
			// }
			// if(end.getX_InsideRoutingArray()==93 &&
			// end.getY_InsideRoutingArray()==50){
			// System.out.println("GOT IT");
			// }

			startEndPoints[i++] = start;
			startEndPoints[i++] = end;
			// points right and top of the original points
			startEndPoints[i++] = new Tupel(start.getX_InsideRoutingArray() + 1, start.getY_InsideRoutingArray(), start
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(start.getX_InsideRoutingArray(), start.getY_InsideRoutingArray() + 1, start
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(start.getX_InsideRoutingArray() + 1, start.getY_InsideRoutingArray() + 1,
					start.getLayer(), false);
			startEndPoints[i++] = new Tupel(start.getX_InsideRoutingArray() - 1, start.getY_InsideRoutingArray(), start
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(start.getX_InsideRoutingArray(), start.getY_InsideRoutingArray() - 1, start
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(start.getX_InsideRoutingArray() - 1, start.getY_InsideRoutingArray() - 1,
					start.getLayer(), false);
			startEndPoints[i++] = new Tupel(start.getX_InsideRoutingArray() + 1, start.getY_InsideRoutingArray() - 1,
					start.getLayer(), false);
			startEndPoints[i++] = new Tupel(start.getX_InsideRoutingArray() - 1, start.getY_InsideRoutingArray() + 1,
					start.getLayer(), false);

			startEndPoints[i++] = new Tupel(end.getX_InsideRoutingArray() + 1, end.getY_InsideRoutingArray(), end
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(end.getX_InsideRoutingArray(), end.getY_InsideRoutingArray() + 1, end
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(end.getX_InsideRoutingArray() + 1, end.getY_InsideRoutingArray() + 1, end
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(end.getX_InsideRoutingArray() - 1, end.getY_InsideRoutingArray(), end
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(end.getX_InsideRoutingArray(), end.getY_InsideRoutingArray() - 1, end
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(end.getX_InsideRoutingArray() - 1, end.getY_InsideRoutingArray() - 1, end
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(end.getX_InsideRoutingArray() + 1, end.getY_InsideRoutingArray() - 1, end
					.getLayer(), false);
			startEndPoints[i++] = new Tupel(end.getX_InsideRoutingArray() - 1, end.getY_InsideRoutingArray() + 1, end
					.getLayer(), false);

			ra.reserveForRouting(startEndPoints, rs.getNetID());
		}
		ra.markReserved();
	}

	public yana() {
		super();
	}

	public static void markSegmentAsUnroutable(RoutingSegment rs) {
		unroutedNets.put(rs.hashCode(), false);
	}

	public static boolean isRouteable(RoutingSegment rs) {
		if (unroutedNets.get(rs.hashCode()) == null) {
			// segment can be routed
			return true;
		} else {
			return false;
		}
	}
}
