/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: GenePlacementLeftRightAlignedDrop.java
 * Written by Team 3: Christian Wittner, Ivan Dimitrov
 * 
 * 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.genetic1.g1;

import java.util.ArrayList;
import java.util.Arrays;

import com.sun.electric.tool.placement.genetic1.Chromosome;
import com.sun.electric.tool.placement.genetic1.GenePlacement;

public class GenePlacementLeftRightAlignedDrop implements GenePlacement {

	private static int placementWidth = -1;
//	private static final boolean DEBUG = false;

	int genePosition2Index[];

	enum Direction {
		LEFT2RIGHT, RIGHT2LEFT
	};

	Direction direction;

	// list of x-,y-coordinates
	// first level list secound level int[][0] = x int[][1]=y
	int[][] currentLine, previousLine, swapLine;

	public GenePlacementLeftRightAlignedDrop(int placementWidth,
			int chromosomeSize, PlacementNodeProxy[] nodeProxies) {
		this.nodeProxies = nodeProxies;
		GenePlacementLeftRightAlignedDrop.placementWidth = placementWidth;
		assert (placementWidth != -1);
		setChromosomeSize(chromosomeSize);
	}

	private void setChromosomeSize(int chromosomeSize) {
		int assumedLineLength = (int) (Math.sqrt(chromosomeSize) * 1.1);

		currentLine = new int[assumedLineLength][];
		previousLine = new int[assumedLineLength][];

		for (int i = 0; i < assumedLineLength; i++) {
			currentLine[i] = new int[2];
			previousLine[i] = new int[2];
		}

		assert (isLineZero(currentLine));
		assert (isLineZero(previousLine));

		genePosition2Index = new int[chromosomeSize];
	}

	PlacementNodeProxy[] nodeProxies;

	public void placeChromosome(Chromosome chToPlace) {

		assert (chToPlace != null);
		assert (currentLine != null && previousLine != null) : "set chromsome size before first usage";
		assert (placementWidth != -1) : "set placement width before first usage";
		assert (chToPlace.isIndex2GenePosValid());
		assert (isLineZero(previousLine));

		direction = Direction.LEFT2RIGHT;

		int tempGenXPosition = 0;
		int tempGenYPosition = 0;
		int tWidth, tHeight;
		int offX = 0;
		int positionInCurrentLine = 0;
		int startPosition2SearchInPreviousLine = 0;
		generateGenePos2IndexMapping(chToPlace.Index2GenePositionInChromosome,
				genePosition2Index);
		int[] yCalculationResult;

		int nodeIndex;

		// iterate over all genes we have to place
		for (int i = 0; i < genePosition2Index.length; i++) {

			// get cell width from placement node proxy and add padding
			{
				nodeIndex = genePosition2Index[i];

				// if we have a gene representing a placement node get width and
				// height from placementnodeproxy
				tWidth = nodeProxies[nodeIndex].width + chToPlace.GeneXPadding[nodeIndex];
				tHeight = nodeProxies[nodeIndex].height+ chToPlace.GeneYPadding[nodeIndex];

				// if gene is rotated sideways swap width and height
				if (chToPlace.GeneRotation[nodeIndex] == 900
						|| chToPlace.GeneRotation[nodeIndex] == 2700) {
					int swap = tHeight;
					tHeight = tWidth;
					tWidth = swap;
				}

			}

			// as we positions genes' center we need to use half width and
			// height
			tWidth = tWidth >> 1;
			tHeight = tHeight >> 1;

			// calculate x position
			// if going left to right add to the x offset
			if (direction == Direction.LEFT2RIGHT)
				tempGenXPosition = offX + tWidth;
			else
				// if going right to left subtract from x offset
				tempGenXPosition = offX - tWidth;

			// cell doesn't fit current line start new one
			if (direction == Direction.LEFT2RIGHT
					&& (tempGenXPosition + tWidth) > placementWidth) {

				// identical for both line switches
				{
					swapLine = currentLine;
					currentLine = previousLine;
					previousLine = swapLine;
					assert (isLineInXOrder(previousLine,
							positionInCurrentLine - 1, direction));

					// remember until which position the current line was used
					// to
					// store valid y values
					startPosition2SearchInPreviousLine = positionInCurrentLine;
					positionInCurrentLine = 0;
				}

				// unique for left2rigth
				{
					// switch to right2left
					direction = Direction.RIGHT2LEFT;
					offX = placementWidth;
					tempGenXPosition = placementWidth - tWidth;
				}

			} else if (direction == Direction.RIGHT2LEFT
					&& (tempGenXPosition - tWidth) < 0) {

				// identical for both line switches
				{
					swapLine = currentLine;
					currentLine = previousLine;
					previousLine = swapLine;
					assert (isLineInXOrder(previousLine,
							positionInCurrentLine - 1, direction));

					// remember until which position the current line was used
					// to
					// store valid y values
					startPosition2SearchInPreviousLine = positionInCurrentLine;
					positionInCurrentLine = 0;
				}

				// unique for left2rigth
				{
					// switch to left2right
					direction = Direction.LEFT2RIGHT;
					offX = 0;
					tempGenXPosition = tWidth;
				}
			}

			tWidth = tWidth << 1;

			// calculate y value for center of current cell to place
			if (direction == Direction.LEFT2RIGHT) {
				yCalculationResult = calculateY(offX, offX + tWidth, tHeight,
						startPosition2SearchInPreviousLine, direction);
			} else {
				yCalculationResult = calculateY(offX, offX - tWidth, tHeight,
						startPosition2SearchInPreviousLine, direction);
			}

			tempGenYPosition = yCalculationResult[1];
			startPosition2SearchInPreviousLine = yCalculationResult[0];

			// store the yValue at to of the current cell as contour reference
			// line for
			// the next loop
			if (direction == Direction.LEFT2RIGHT)
				storeYValue(positionInCurrentLine, tempGenYPosition + tHeight,
						offX, offX + tWidth);
			else
				storeYValue(positionInCurrentLine, tempGenYPosition + tHeight,
						offX, offX - tWidth);

			chToPlace.GeneXPos[nodeIndex] = tempGenXPosition;
			chToPlace.GeneYPos[nodeIndex] = tempGenYPosition;

			if (direction == Direction.LEFT2RIGHT)
				offX += tWidth;
			else
				offX -= tWidth;

			positionInCurrentLine++;

		}

		// clear previous line for next use
		for (int i = 0; i < previousLine.length; i++) {
			previousLine[i][0] = 0;
			previousLine[i][1] = 0;
		}
	}

	/**
	 * As the chromosome stores the position of a given index in an array in
	 * index order this method converts it into an array in gene position order
	 * pointing to the indexes.
	 * 
	 * @param index2GenePositionInChromosome
	 *            Array in index order. (first entry is position of node with
	 *            index 0)
	 * 
	 * @param returnValueGenerateGenePos2IndexMapping
	 *            Reference to the array to store the return value. The gene
	 *            position to index map.
	 */
	public static void generateGenePos2IndexMapping(
			int[] index2GenePositionInChromosome,
			int[] returnValueGenerateGenePos2IndexMapping) {
		assert (index2GenePositionInChromosome.length == returnValueGenerateGenePos2IndexMapping.length);

		for (int index = 0; index < index2GenePositionInChromosome.length; index++)
			returnValueGenerateGenePos2IndexMapping[index2GenePositionInChromosome[index]] = index;

	}

	private boolean isLineInXOrder(int[][] previousLine, int linePosition,
			Direction direction) {

		if (direction == Direction.LEFT2RIGHT)
			for (int i = 0; i < linePosition; i++) {
				assert (previousLine[i][0] <= placementWidth && previousLine[i][0] >= 0);
				if (previousLine[i][0] >= previousLine[i + 1][0])
					return false;
			}
		else
			for (int i = 0; i < linePosition; i++) {
				assert (previousLine[i][0] <= placementWidth && previousLine[i][0] >= 0);
				if (previousLine[i][0] <= previousLine[i + 1][0])
					return false;
			}
		return true;

	}

	/**
	 * Calculates y-value for the center of the current block to place depending
	 * on the previously stored contour line.
	 * 
	 * 
	 * @param startX
	 *            x position at the beginning of the block to place
	 * @param endX
	 *            x position at the end of the block to place
	 * @param halfCellHeigth
	 *            half the height of the block to place
	 * @param startPosition2SearchInPreviousLine
	 *            position to start searching for the y-value in the previous
	 *            line.
	 * @param direction
	 *            direction telling if placement is currently left to right or
	 *            right to left.
	 * 
	 * @return int[] with int[0] = new start position for next search. int[1] =
	 *         calculated y value for current block.
	 */
	private int[] calculateY(int startX, int endX, int halfCellHeigth,
			int startPosition2SearchInPreviousLine, Direction direction) {

		int yValue = 0;
		int newSearchStartPosition = -1;
		// initialize for the case that our current block is on the height of
		// the starting block of the previous line
		int valueBeforeInterval = previousLine[startPosition2SearchInPreviousLine][1];
		int valueAfterInterval = previousLine[0][1];

		// will be true if a value between startX and endX is present in the
		// previous line
		boolean valueInRange = false;

		// find highest y between startX and endX in previous Line
		// have to step through it backward as we move in opposite direction
		// when placing
		// a line relative to it's previous line.
		int curY, curX;
		for (int pos = startPosition2SearchInPreviousLine; pos >= 0; pos--) {
			curY = previousLine[pos][1];
			curX = previousLine[pos][0];

			// store value before interval
			if (!valueInRange) {
				valueAfterInterval = valueBeforeInterval = curY;
				newSearchStartPosition = pos;
			}

			// if x value in previousLine is passed exit
			if (direction == Direction.LEFT2RIGHT && curX > endX
					|| direction == Direction.RIGHT2LEFT && curX < endX) {
				valueAfterInterval = curY;
				break;
			}

			// search for highest yValue within range of previous Line
			if (direction == Direction.LEFT2RIGHT) {
				if (curX > startX) {
					valueInRange = true;
					newSearchStartPosition = pos;
					if (curY > yValue) {
						yValue = curY;

					}
				}
			} else {
				if (curX < startX) {
					valueInRange = true;
					newSearchStartPosition = pos;
					if (curY > yValue) {
						yValue = curY;
					}
				}
			}
		}

		assert (valueAfterInterval >= 0);

		if (!valueInRange)
			yValue = Math.min(valueBeforeInterval, valueAfterInterval);

		yValue += halfCellHeigth;
		return new int[] { newSearchStartPosition, yValue };
	}

	private boolean isLineZero(int[][] line) {
		for (int[] p : line) {
			if (p[0] != 0 || p[1] != 0)
				return false;
		}

		return true;
	}

	/**
	 * For current line store the height of the current block.
	 * 
	 * @param currentLine
	 * @param positionInLine
	 * @param topyValue
	 *            Y value of top of the current cell.
	 * @param startX
	 *            X-value where current block starts.
	 * @param endX
	 *            X-value where current block ends.
	 */
	private void storeYValue(int positionInLine, int topyValue, int startX,
			int endX) {

		// if currentLine to short to store value add new point
		// intense but should rarely happen. best if it never happens but still
		// better than an index out of bounds exception
		if (currentLine.length <= positionInLine + 1) {
			ArrayList<int[]> expansionHelper = new ArrayList<int[]>(
					currentLine.length + 5);
			expansionHelper.addAll(Arrays.asList(currentLine));
			// grow by 5 slots
			for (int i = 0; i < 5; i++)
				expansionHelper.add(new int[2]);

			currentLine = expansionHelper.toArray(new int[expansionHelper
					.size()][]);
		}

		// for first cell in line just store yValue at startX
		if (positionInLine == 0) {
			currentLine[positionInLine][0] = startX;
			currentLine[positionInLine][1] = topyValue;

		} else {
			// see if yValue at the beginning of the block is smaller than
			// yValue if so set it to Y
			if (currentLine[positionInLine][1] < topyValue)
				currentLine[positionInLine][1] = topyValue;

		}

		assert (currentLine[positionInLine][0] == startX);

		// store YValue at endX
		currentLine[positionInLine + 1][0] = endX;
		currentLine[positionInLine + 1][1] = topyValue;

	}

}
