/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: DeltaIndividual.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, 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.placement.genetic2;


import com.sun.electric.util.math.Orientation;
import com.sun.electric.tool.placement.PlacementFrame.PlacementNode;
import com.sun.electric.tool.placement.genetic2.metrics.DeltaBBMetric;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;


/**
 * This kind of individual only saves the deltas (differences) to a common
 * reference placement. This saves memory and speeds up calculations.
 * @see Population
 */
public class DeltaIndividual extends Individual<DeltaIndividual>
{
	public List<Block> blocks; // the genome, contains the positions and rotations of the PlacementNodes

	private double[] hashes;
	
	double[] overlaps;
	double[] netLengths;
	double spread;
	
	/**
	 * Constructor to randomly initialize the genome (blocks).
	 * @param nodesToPlace The PlacementNode objects.
	 * @param allNetworks The sets of connected PlacementPort objects.
	 * @param rand The random number generator of the population's thread.
	 */
	DeltaIndividual(Reference ref, Random rand)
	{		
		super(ref);
		blocks = new ArrayList<Block>();
		hashes = new double[3];
		
		netLengths = new double[ref.netLengths.length];
		for(int i = 0; i < ref.netLengths.length; i++) netLengths[i] = ref.netLengths[i];
		overlaps = new double[ref.overlaps.length];
		for(int i = 0; i < ref.overlaps.length; i++) overlaps[i] = ref.overlaps[i];

		mutate(rand);
		evaluate();
	}
	
	public void reboot(Random rand)
	{
		blocks.clear();
		
		mutate(rand);
		evaluate();
	}
	
	public void prepareForTest()
	{
		for(int i = 0; i < 10; i++)
		{
			int nodeNr = i;
			PlacementNode n = nodesToPlace.get(nodeNr);
			
			blocks.add(new Block(
					n.getPlacementX(),
					n.getPlacementY(),
					n.getWidth(),
					n.getHeight(),
					n.getPlacementOrientation(),
					nodeNr));
		}
		Collections.sort(blocks);
	}

	public void setProgress(double p)
	{
		this.p = p;
	}
	
	public void writeToPlacement(List<PlacementNode> nodesToPlace)
	{
		for(Block b : blocks)
		{
			nodesToPlace.get(b.getNr()).setPlacement(b.getX(), b.getY());
			nodesToPlace.get(b.getNr()).setOrientation(b.getOrientation());
		}
	}
	
	/**
	 * Method to compare the badness with another individual.
	 * @param other The Individual to be used as a reference.
	 * @return -1 if other has a higher badness, else 1.
	 */
	public int compareTo(DeltaIndividual other)
	{
		if(getBadness() < other.getBadness())
		{
			return -1;
		}
		else 
		{
			return 1;
		}
	}
	
	/**
	 * Method to copy the genes (Blocks) and badness from another Individual.
	 * @param other The Individual to be cloned.
	 */
	public void copyFrom(DeltaIndividual other)
	{
		if(this == other) return;
		
		blocks.clear();
		for(Block b : other.blocks)
		{
			blocks.add(new Block(b));
		}
		
		netLengths = new double[ref.netLengths.length];
		for(int i = 0; i < ref.netLengths.length; i++) netLengths[i] = ref.netLengths[i];
		overlaps = new double[ref.overlaps.length];
		for(int i = 0; i < ref.overlaps.length; i++) overlaps[i] = ref.overlaps[i];
		
		setBadness(other.badnessComponents);
		//mutate(rand);
		//evaluate();
		//System.out.println("bsize: " + blocks.size());
	}

	/**
	 * Method to do a crossover of two genomes and write the result to the Individual's genome.
	 * Mutation and evaluation is also performed.
	 * @param mom One donor of genes.
	 * @param dad The other donor of genes.
	 */
	public void deriveFrom(DeltaIndividual mom, DeltaIndividual dad, Random rand)
	{
		if(this == mom || this == dad) return;
		
		blocks.clear();
		
		// add everything from both parents to blocks and sort
		for(Block b : mom.blocks)
		{
			blocks.add(new Block(b));
		}
		for(Block b : dad.blocks)
		{
			blocks.add(new Block(b));
		}
		Collections.sort(blocks);
		
		int pos = 0;
		int usedNr = -1; // usedNr helps avoid duplicate blocks
		int prevNr = -1;
		
		while(pos < blocks.size())
		{
			if((rand.nextDouble() > 0.5 && blocks.get(pos).getNr() != prevNr) || blocks.get(pos).getNr() == usedNr)
			{
				prevNr = blocks.get(pos).getNr();
				blocks.remove(pos);
			}
			else
			{
				prevNr = blocks.get(pos).getNr();
				usedNr = blocks.get(pos).getNr();
				pos++;
			}
		}
		
		mutate(rand);		
		// recalculate the badness
		evaluate();
	}
	/*
	public double distanceGenome(DeltaIndividual other)
	{
		Block orig = new Block();
		Iterator<Block> ib = other.blocks.iterator();
		Block otherBlock = ib.next();
		for(Block b : blocks)
		{
			while(b.getNr() > otherBlock.getNr() && ib.hasNext())
			{
				otherBlock = ib.next();
			}
			if(otherBlock.getNr() == b.getNr())
			{
				d += (b.getX()-otherBlock.getX())*(b.getX()-otherBlock.getX());
				d += (b.getY()-otherBlock.getY())*(b.getY()-otherBlock.getY());
			}
			else
			{
				orig.valuesFrom(nodesToPlace.get(b.getNr()));
				d += (b.getX()-orig.getX())*(b.getX()-orig.getX());
				d += (b.getY()-orig.getY())*(b.getY()-orig.getY());
			}
		}
		return Math.sqrt(Math.sqrt(d));
	}
	*/
	public double distance(DeltaIndividual other)
	{
		double d = 0.0;
		/*
		for(int i = 0; i < overlaps.length; i++)
		{
			d += (overlaps[i]-other.overlaps[i])*(overlaps[i]-other.overlaps[i]);
		}
		*/
		for(int i = 0; i < netLengths.length; i++)
		{
			d += Math.abs(netLengths[i]-other.netLengths[i]);

		}
		
		return d;
		
	}
	
	public double distance()
	{
		double d = 0.0;
		/*
		for(int i = 0; i < overlaps.length; i++)
		{
			d += (overlaps[i]-other.overlaps[i])*(overlaps[i]-other.overlaps[i]);
		}
		*/
		for(int i = 0; i < netLengths.length; i++)
		{
			d += Math.abs(netLengths[i]-ref.netLengths[i]);

		}
		
		return d;
		
	}
	
	/**
	 * Method to randomly swap the position of 2 blocks.
	 */
	public void swapBlocks(Random rand)
	{
		int a = rand.nextInt(nodesToPlace.size());
		int b = rand.nextInt(nodesToPlace.size());
		
		Block one = insertBlock(rand, a, false);
		Block two = insertBlock(rand, b, false);
		
		double ax = one.getX();
		double ay = one.getY();
		one.setPos(two.getX(), two.getY());
		two.setPos(ax, ay);
	}
	/*
	public void swapBlocks(Random rand)
	{
		int a = rand.nextInt(blocks.size());
		int b = rand.nextInt(blocks.size());
		
		double ax = blocks.get(a).getX();
		double ay = blocks.get(a).getY();
		blocks.get(a).setPos(blocks.get(b).getX(), blocks.get(b).getY());
		blocks.get(b).setPos(ax, ay);
	}
	*/
	
	/**
	 * Method to fetch a node from the reference and insert a corresponding
	 * block into the list of deltas.
	 */
	public Block insertBlock(Random rand, int nodeNr, boolean mutation)
	{
		PlacementNode n = nodesToPlace.get(nodeNr);
		
		double xmut = 0.0;
		double ymut = 0.0;
		
		if(mutation)
		{
			xmut = ref.avgW*rand.nextGaussian();
			ymut = ref.avgW*rand.nextGaussian();
		}
		
		Block b = new Block(
				n.getPlacementX() + xmut,
				n.getPlacementY() + ymut,
				n.getWidth(),
				n.getHeight(),
				n.getPlacementOrientation(),
				nodeNr);
		
		blocks.add(b);
		Collections.sort(blocks);
		for(int i = 1; i < blocks.size(); i++)
		{
			if(blocks.get(i).getNr() == blocks.get(i-1).getNr())
			{
				if(rand.nextDouble()>0.5)
				{
					b = blocks.get(i-1);
					blocks.remove(i);
				}
				else
				{
					b = blocks.get(i);
					blocks.remove(i-1);
				}
			}
		}
		return b;
	}
	
	/**
	 * Method to mutate a random amount of Blocks.
	 */
	public void mutate(Random rand)
	{
		if(blocks.size() < 50 && (blocks.size() == 0 || rand.nextDouble() > 0.5))
		{
			int nodeNr = rand.nextInt(nodesToPlace.size());
			insertBlock(rand, nodeNr, true);
		}
		/*else*/ if(rand.nextDouble() > 0.5 && blocks.size() > 1)
		{
			blocks.remove(rand.nextInt(blocks.size()));
		}
		
		int disturbedPositions = Math.abs((int)(rand.nextGaussian()*3.0));
		int disturbedOrientations = Math.abs((int)(rand.nextGaussian()*1.0));
		int swaps = Math.abs((int)(rand.nextGaussian()*2.0*(1-p)));
		
		for(int i = 0; i < disturbedPositions; i++)
		{
			blocks.get(rand.nextInt(blocks.size())).disturb(ref.avgW, rand); // move cell to a random position
		}
		/*
		for(int i = 0; i < disturbedPositions; i++)
		{
			blocks.get(rand.nextInt(blocks.size())).disturbToCenter(ref.avgW, rand); // move cell to a random position
		}
		*/
		
		for(int i = 0; i < disturbedOrientations; i++)
		{
			blocks.get(rand.nextInt(blocks.size())).disturbOrientation(rand); // change Orientation randomly
		}
		for(int i = 0; i < swaps; i++)
		{
			swapBlocks(rand); // swap random blocks
		}
	}
	
	/**
	 * Calculates the overlap of the complete placement.
	 * This function uses the overlap of the Reference and only calculates the
	 * changes (the changes are represented by blocks).
	 * @return the sum of the overlap areas.
	 */
	public double calculateOverlap()
	{
		double overlap = ref.badnessComponents[1]; // refOverlap
		for(int i = 0; i < nodesToPlace.size(); i++)
		{
			overlaps[i] = ref.overlaps[i];
		}

		Block otherOrig = new Block();
		Block orig = new Block();

		Block otherBlock;
		Block b;
		
		for(int n = 0; n < blocks.size(); n++)
		{
			b = blocks.get(n);
			
			// UniformGrid for fast collision detection with reference blocks
			overlap += ref.grid.collide(b, blocks, nodesToPlace, overlaps);
		
			
			orig.valuesFrom(nodesToPlace.get(b.getNr()));
			
			// collision detection for delta blocks
			for(int i = 0; i < n; i++)
			{
				otherBlock = blocks.get(i);
				otherOrig.valuesFrom(nodesToPlace.get(otherBlock.getNr()));
				overlap -= orig.intersectionArea(otherOrig);
				overlap += b.intersectionArea(otherBlock);
				
				overlaps[b.getNr()] -= orig.intersectionArea(otherOrig);
				overlaps[b.getNr()] += b.intersectionArea(otherBlock);
			}
			
			
		}
		return overlap;
	}
	
	// old calculation, not accelerated by UniformGrid
	public double old_calculateOverlap()
	{
		double overlap = ref.badnessComponents[1]; // refOverlap
		for(int i = 0; i < nodesToPlace.size(); i++)
		{
			overlaps[i] = ref.overlaps[i];
		}

		Block other = new Block();
		Block orig = new Block();
		Iterator<Block> ib = blocks.iterator();
		Block deltaBlock = ib.next();
		Block b;
		
		for(int n = 0; n < blocks.size(); n++)
		{
			b = blocks.get(n);
			orig.valuesFrom(nodesToPlace.get(b.getNr()));
			ib = blocks.iterator();
			deltaBlock = ib.next();
			
			for(int i = 0; i < nodesToPlace.size(); i++)
			{
				if(i > deltaBlock.getNr() && ib.hasNext())
				{
					deltaBlock = ib.next();
				}
				
				if(i != b.getNr()) // not myself
				{				
					if(i < b.getNr() && deltaBlock.getNr() == i) // for the predecessors, look also at the deltas
					{
						other.valuesFrom(nodesToPlace.get(i));
						overlap -= orig.intersectionArea(other);
						
						overlaps[b.getNr()] -= orig.intersectionArea(other);
						overlaps[b.getNr()] += b.intersectionArea(deltaBlock);
						
						
						overlap += b.intersectionArea(deltaBlock);
					}
					else if(deltaBlock.getNr() != i) // for the successors, look only at the non-deltas
					{
						other.valuesFrom(nodesToPlace.get(i));
						overlap -= orig.intersectionArea(other);
						
						overlaps[i] -= orig.intersectionArea(other);
						overlaps[i] += b.intersectionArea(other);

						overlap += b.intersectionArea(other);
					}
					else
					{
						// do nothing for the successors who are deltas (skip them),
						// they will calculate their overlap with this block
					}
				}
			}
		}
		return overlap;
	}

	/**
	 * Method to calculate the bounding box area of the Blocks.
	 * @return the bounding box area of the Blocks.
	 */
	public double getBoundingBoxArea()
	{
		Iterator<PlacementNode> it = nodesToPlace.iterator();
		Iterator<Block> ib = blocks.iterator();
		Block deltaBlock = ib.next();
		
		Block b = new Block();
		PlacementNode n = it.next();
		b.setPos(n.getPlacementX(), n.getPlacementY());
		b.setOrientation(n.getPlacementOrientation());
		

		double left = b.getLeft();
		double top = b.getTop();
		double right = b.getRight();
		double bottom = b.getBottom();
		
		
		for(int i = 1; i < nodesToPlace.size(); i++)
		{
			
			n = it.next();
			b.setPos(n.getPlacementX(), n.getPlacementY());
			b.setOrientation(n.getPlacementOrientation());
			
			if(i > deltaBlock.getNr() && ib.hasNext())
			{
				deltaBlock = ib.next();
			}
			if(deltaBlock.getNr() == i)
			{
				if(deltaBlock.getLeft() < left) left = deltaBlock.getLeft();
				if(deltaBlock.getTop() > top) top = deltaBlock.getTop();
				if(deltaBlock.getRight() > right) right = deltaBlock.getRight();
				if(deltaBlock.getBottom() < bottom) bottom = deltaBlock.getBottom();
			}
			else
			{
				if(b.getLeft() < left) left = b.getLeft();
				if(b.getTop() > top) top = b.getTop();
				if(b.getRight() > right) right = b.getRight();
				if(b.getBottom() < bottom) bottom = b.getBottom();
			}
		}
		
		return (top-bottom)*(right-left);
	}
	
	/**
	 * Method to calculate the semiperimeter of the bounding box of the Blocks.
	 * @return the semiperimeter of the bounding box of the Blocks.
	 */
	public double getSemiperimeterLength()
	{
		Iterator<PlacementNode> it = nodesToPlace.iterator();
		Iterator<Block> ib = blocks.iterator();
		Block deltaBlock = ib.next();
		
		Block b = new Block();
		PlacementNode n = it.next();
		b.valuesFrom(n);
		
		double left = b.getLeft();
		double top = b.getTop();
		double right = b.getRight();
		double bottom = b.getBottom();
		
		if(deltaBlock.getNr() == 0)
		{
			if(deltaBlock.getLeft() < left) left = deltaBlock.getLeft();
			if(deltaBlock.getTop() > top) top = deltaBlock.getTop();
			if(deltaBlock.getRight() > right) right = deltaBlock.getRight();
			if(deltaBlock.getBottom() < bottom) bottom = deltaBlock.getBottom();
		}
		else
		{
			if(b.getLeft() < left) left = b.getLeft();
			if(b.getTop() > top) top = b.getTop();
			if(b.getRight() > right) right = b.getRight();
			if(b.getBottom() < bottom) bottom = b.getBottom();
		}

		
		
		for(int i = 1; i < nodesToPlace.size(); i++)
		{
			
			n = it.next();
			b.valuesFrom(n);
			
			if(i > deltaBlock.getNr() && ib.hasNext())
			{
				deltaBlock = ib.next();
			}
			if(deltaBlock.getNr() == i)
			{
				if(deltaBlock.getLeft() < left) left = deltaBlock.getLeft();
				if(deltaBlock.getTop() > top) top = deltaBlock.getTop();
				if(deltaBlock.getRight() > right) right = deltaBlock.getRight();
				if(deltaBlock.getBottom() < bottom) bottom = deltaBlock.getBottom();
			}
			else
			{
				if(b.getLeft() < left) left = b.getLeft();
				if(b.getTop() > top) top = b.getTop();
				if(b.getRight() > right) right = b.getRight();
				if(b.getBottom() < bottom) bottom = b.getBottom();
			}
		}
		
		return (top-bottom)+(right-left);
	}
	
	public double getNetLength()
	{
		return badnessComponents[0];
	}
	
	/**
	 * Evaluate the individual by calculating an estimate of the network length and
	 * the overlap area between PlacementNodes or Delta-blocks.
	 */
	public void evaluate()
	{
		netLengths = new double[ref.netLengths.length];
		for(int i = 0; i < ref.netLengths.length; i++) netLengths[i] = ref.netLengths[i];
		overlaps = new double[ref.overlaps.length];
		for(int i = 0; i < ref.overlaps.length; i++) overlaps[i] = ref.overlaps[i];
		
		badnessComponents[0] = DeltaBBMetric.compute(blocks, netLengths);
		badnessComponents[1] = calculateOverlap();
		badnessComponents[2] = calculateSpread();
		//badnessComponents[2] = getSemiperimeterLength();
		
		hashes[0] = getXHash();
		hashes[1] = getYHash();
		hashes[2] = getRotHash();
	}

	
	public double getSize()
	{
		return blocks.size();
	}
	
	/**
	 * Method to return the evaluated "un-fitness" of the Individual.
	 * @return the "un-fitness" of the placement solution.
	 */
	public double getBadness()
	{
		double badness = 0.0;
		
		// wire length estimate:
		badness += badnessComponents[0];
		
		// overlap:
		badness += badnessComponents[1]*(1.0+300.0*p*p);
		
		// area:
		// badness += badnessComponents[2]*0.1;
		badness += 0.00001*badnessComponents[2];

		return badness;
	}
	
	public double calculateSpread()
	{
		spread = ref.spread;
		
		Block refBlock = new Block();
		
		for(Block b : blocks)
		{
			refBlock.valuesFrom(nodesToPlace.get(b.getNr()));
			
			spread-=Math.sqrt(refBlock.getX()*refBlock.getX() + refBlock.getY()*refBlock.getY())
					* refBlock.getWidth()*refBlock.getHeight();
			spread+=Math.sqrt(b.getX()*b.getX() + b.getY()*b.getY())
					* b.getWidth()*b.getHeight();
			
			//spread-=Math.abs(refBlock.getX())+Math.abs(refBlock.getY());
			//spread+=Math.abs(b.getX())+Math.abs(b.getY());
		}
		return spread;
	}
	
	public void setBadness(double[] otherComponents)
	{
		for(int i = 0; i < badnessComponents.length; i++)
		{
			badnessComponents[i] = otherComponents[i];
		}
	}
	
	public double getXHash()
	{
		double r = 0.0;
		for(Block b : blocks)
		{
			r+=b.getX()-nodesToPlace.get(b.getNr()).getPlacementX();
		}
		r = Math.abs(r);
		//while(r > 2.0) r/=10.0;
		r/=5000.0;
		return (Math.sin(r*Math.PI)+1.0)/2.0;
	}
	
	public double getYHash()
	{
		double r = 0.0;
		for(Block b : blocks)
		{
			r+=b.getY()-nodesToPlace.get(b.getNr()).getPlacementY();
		}
		r = Math.abs(r);
		//while(r > 2.0) r/=10.0;
		r/=5000.0;
		return (Math.sin(r*Math.PI)+1.0)/2.0;
	}
	
	public double getRotHash()
	{
		double r = 0.0;
		for(Block b : blocks)
		{
			Orientation o = b.getOrientation();
			if(o == nodesToPlace.get(b.getNr()).getPlacementOrientation()) r+=0.0;
			else if(o == Orientation.IDENT) r+=0.1;
			else if(o == Orientation.R) r-=0.2;
			else if(o == Orientation.RR) r+=0.3;
			else if(o == Orientation.RRR) r-=0.4;
			else if(o == Orientation.X) r+=0.5;
			else if(o == Orientation.XR) r-=0.6;
			else if(o == Orientation.XRR) r+=0.7;
			else if(o == Orientation.XRRR) r-=0.8;
			else if(o == Orientation.Y) r+=0.9;
			else if(o == Orientation.YR) r-=1.0;
			else if(o == Orientation.YRR) r+=1.1;
			else if(o == Orientation.YRRR) r-=1.2;
			else if(o == Orientation.XY) r+=1.3;
			else if(o == Orientation.XYR) r-=1.4;
			else if(o == Orientation.XYRR) r+=1.5;
			else if(o == Orientation.XYRRR) r-=1.6;

		}
		//while(r > 2.0) r/=10.0;
		r/=50.0;
		return (Math.sin(r*Math.PI)+1.0)/2.0;
	}
	
	public double getHash()
	{
		double b = getBadness();
		while(b > 2.0) b/=10.0;
		b = (Math.sin(b*Math.PI)+1.0)/2.0;
		double h = hashes[0]+hashes[1]+hashes[2]+b;
		return h;
	}
	
	public double[] getHashes()
	{
		return hashes;
	}
}
