/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: StartUpStageWorker.java
 * Written by Team 7: Felix Schmidt
 *
 * 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.forceDirected2.forceDirected.staged;

import com.sun.electric.tool.placement.PlacementFrame.PlacementNetwork;
import com.sun.electric.tool.placement.PlacementFrame.PlacementNode;
import com.sun.electric.tool.placement.forceDirected2.PlacementForceDirectedStaged;
import com.sun.electric.tool.placement.forceDirected2.forceDirected.util.CheckboardingField;
import com.sun.electric.tool.placement.forceDirected2.forceDirected.util.CheckboardingPattern;
import com.sun.electric.tool.placement.forceDirected2.metrics.AbstractMetric;
import com.sun.electric.tool.placement.forceDirected2.metrics.BBMetric;
import com.sun.electric.tool.placement.forceDirected2.utils.PlacementProperties;
import com.sun.electric.tool.placement.forceDirected2.utils.concurrent.Stage;
import com.sun.electric.tool.placement.forceDirected2.utils.concurrent.StageWorker;
import com.sun.electric.tool.placement.forceDirected2.utils.output.PNGOutput;
import com.sun.electric.util.math.MutableInteger;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class StartUpStageWorker extends StageWorker {

	protected static Map<PlacementNode, Map<PlacementNode, MutableInteger>> connectivityMap = null;
	private double velocityFactor;

	private List<PlacementNode> nodesToPlace;

	private List<PlacementNetwork> allNetworks;
	private int widthCheckBoarding;
	private int heightCheckBoarding;
	private double fieldSize;
	private CheckboardingPattern checkPattern;

	public StartUpStageWorker(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks) {
		this.nodesToPlace = nodesToPlace;
		this.allNetworks = allNetworks;
	}

	protected double bestSize() {

		double result = 0.0;

		for (PlacementNode node : this.nodesToPlace) {
			double cellMax = (node.getHeight() > node.getWidth()) ? node.getHeight() : node.getWidth();
			result = (cellMax > result) ? cellMax : result;
		}

		return result;
	}

	protected void calculateVelocityFactor() {
		this.velocityFactor = 1;
		double tmp = this.nodesToPlace.size();
		while (tmp > 1) {
			tmp /= 10;
			this.velocityFactor /= 10;
		}
	}

	/**
	 * Find the best CheckboardingPatternLayout
	 */
	protected void createCheckboardingPattern() {
		int findBestSizeMax = (int) Math.ceil(Math.sqrt(this.nodesToPlace.size()));
		int bestField = this.nodesToPlace.size() * 2;
		int bestIdx = 0;
		int divergence = PlacementProperties.getInstance().getDivergence();

		int start = ((findBestSizeMax - divergence) < 1) ? 1 : findBestSizeMax - divergence;
		int end = ((findBestSizeMax + divergence) > this.nodesToPlace.size()) ? this.nodesToPlace.size() : findBestSizeMax + divergence;

		for (int i = start; i < end; ++i) {
			// field = i * ceil(#nodes / i)
			int field = (i * (int) Math.ceil((double) this.nodesToPlace.size() / (double) i));
			if (field < bestField) {
				bestField = field;
				bestIdx = i;
			}
		}

		this.widthCheckBoarding = (bestIdx * 1);
		this.heightCheckBoarding = (int) (Math.ceil((double) this.nodesToPlace.size() / (double) bestIdx) * 1);

		this.checkPattern = new CheckboardingPattern(this.widthCheckBoarding, this.heightCheckBoarding, this.fieldSize, this.fieldSize);
	}

	protected void fillCheckboardingPattern() {
		Random rand = new Random(System.currentTimeMillis());
		List<Integer> places = new ArrayList<Integer>(this.heightCheckBoarding * this.widthCheckBoarding);

		for (int i = 0; i < (this.heightCheckBoarding * this.widthCheckBoarding); i++) {
			places.add(new Integer(i));
		}

		for (PlacementNode node : this.nodesToPlace) {

			int listPos = rand.nextInt(places.size());
			int nodePos = places.remove(listPos).intValue();

			int x = nodePos % this.widthCheckBoarding, y = nodePos / this.widthCheckBoarding;
			CheckboardingField field = this.checkPattern.getField(x, y);
			node.setPlacement(field.getLocation().getX(), field.getLocation().getY());
			field.setNode(node);
		}
	}

	public void run() {
		this.calculateVelocityFactor();

		this.fieldSize = this.bestSize();
		this.createCheckboardingPattern();
		this.fillCheckboardingPattern();

		PlacementForceDirectedStaged.setCheckboardingPattern(this.checkPattern);

		if (PlacementProperties.getInstance().getIterations() != 0) {

			PlacementProperties properties = PlacementProperties.getInstance();
			List<StageWorker> forces = new ArrayList<StageWorker>();
			List<StageWorker> move = new ArrayList<StageWorker>();
			List<StageWorker> overlap = new ArrayList<StageWorker>();
			List<StageWorker> endWorker = new ArrayList<StageWorker>();

			double threshold = PlacementProperties.getInstance().getOverlappingThreshold();

			for (int i = 0; i < properties.getNumOfThreads(); i++) {
				forces.add(new CalculateForcesStageWorker(StartUpStageWorker.connectivityMap, this.allNetworks));
				move.add(new PlaceNodesStageWorker(this.velocityFactor));
				overlap.add(new OverlapWorker(threshold, i));
			}

			long finalTimeStamp = System.currentTimeMillis() + PlacementProperties.getInstance().getTimeout() * 1000;

			AbstractMetric bb = new BBMetric(this.nodesToPlace, this.allNetworks);
			PNGOutput out = new PNGOutput(this.nodesToPlace, this.allNetworks);
			endWorker.add(new EndWorker(PlacementProperties.getInstance().getIterations(), (StartUpStage) this.stage, this.widthCheckBoarding,
					this.heightCheckBoarding, this.checkPattern, this.velocityFactor, out, bb, finalTimeStamp));

			Stage calculateForces = new Stage(forces);
			Stage moveNodes = new Stage(move);
			Stage resolveOverlap = new Stage(overlap);
			Stage endStage = new Stage(endWorker);

			calculateForces.getNextStages().add(moveNodes);
			moveNodes.getNextStages().add(resolveOverlap);
			resolveOverlap.getNextStages().add(endStage);
			endStage.getNextStages().add(calculateForces);

			((StartUpStage) this.stage).getStages().add(calculateForces);
			((StartUpStage) this.stage).getStages().add(moveNodes);
			((StartUpStage) this.stage).getStages().add(resolveOverlap);
			((StartUpStage) this.stage).getStages().add(endStage);

			calculateForces.start();
			moveNodes.start();
			resolveOverlap.start();
			endStage.start();

			int totalNumOfPorts = 0;
			for (PlacementNode node : this.nodesToPlace) {
				totalNumOfPorts += node.getPorts().size();
			}

			int stepWidth = 10;
			for (int i = 0; i < this.heightCheckBoarding; i += stepWidth) {
				for (int j = 0; j < this.widthCheckBoarding; j += stepWidth) {
					CheckboardingField[][] fields = this.checkPattern.getFields(j, i, stepWidth, stepWidth);
					calculateForces.getInput(this).add(new PlacementDTO(fields, i * this.widthCheckBoarding + j));
				}
			}

			try {
				calculateForces.join();
				moveNodes.join();
				resolveOverlap.join();
				endStage.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			this.stage.stop();
		}

	}

	public void setStage(StartUpStage stage) {
		this.stage = stage;
	}

}
