/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Placer.java
 *
 * Copyright (c) 2003, 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.generator.layout;

import com.sun.electric.database.EditingPreferences;
import java.util.ArrayList;

import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.tool.generator.layout.gates.WellTie;
import com.sun.electric.tool.Job;

class Placer {
	private static final boolean VERBOSE = false;
	
	public static final int P = 0;        // PMOS only gate
	public static final int N = 1;        // NMOS only gate
	public static final int PN = 2;       // PMOS and NMOS gate
	
	private StdCellParams stdCell;
	private Cell part;
	private double rowHeight;
	
	private ArrayList<Inst> buildInsts = new ArrayList<Inst>();
	private ArrayList<Net> buildNets = new ArrayList<Net>();
	
	private static void error(boolean pred, String msg) {
		Job.error(pred, msg);
	}
	
	// ---------------------- private classes --------------------------
	interface PermutationAction {
		// return true if you want to stop checking other permutations
		boolean usePermutation(int[] permutation);
		boolean prunePermutation(int[] permutation, boolean[] placed,
								 int depth);
	}
	
	private static class PermChecker implements PermutationAction {
		int[] bestPermutation = null;
		double bestCost = Double.MAX_VALUE;
		ArrayList<Inst> insts;
		ArrayList<Net> nets;
		double leftX;
		ArrayList<Inst> permInsts = new ArrayList<Inst>();
		long nbChecked, maxPerms;
		
		private void abutLeftRight(int[] permutation) {
			for (int i=0; i<permutation.length; i++) {
				permInsts.set(i, insts.get(permutation[i]));
			}
			Placer.abutLeftRight(leftX, permInsts);
		}
		
		// returns true to abort checking
		public boolean usePermutation(int[] permutation) {
			error(permutation.length!=insts.size(), "wrong permutation size");
			abutLeftRight(permutation);
			double cost = getCostX(nets);
			if (cost<bestCost) {
				bestCost = cost;
				bestPermutation = permutation.clone();
				//System.out.println("Cost= "+cost);
			}
			nbChecked++;
			if (nbChecked>=maxPerms) {
				if (VERBOSE) {
					System.out.println("Abort after checking "+maxPerms+" permutations");
				}
				return true;
			}
			return false;
		}
		// Return the X coordinate of the right boundary of the rightmost
		// placed cell.  Any ports to the left of this are placed. Any
		// ports to the right of this are unplaced.  Return -1 if we
		// shouldn't prune.
		private double abutLeftRight(int[] permutation, boolean[] placed,
									 int depth) {
			double pX=leftX, nX=leftX;
			for (int i=0; i<=depth; i++) {
				Inst inst = insts.get(permutation[i]);
				if (inst.isN()) {
					// NMOS only gate
					inst.moveTo(nX, 0);
					nX += inst.getWidth();
				} else if (inst.isP()) {
					// PMOS only gate
					inst.moveTo(pX, 0);
					pX += inst.getWidth();
				} else {
					// full height NMOS and PMOS gate
					double x = Math.max(nX, pX);
					inst.moveTo(x, 0);
					pX = nX = x+inst.getWidth();
				}
			}
			if (pX!=nX) {
				// Abut failed because I don't know how to find a set of
				// positions representing a tight lower bound on the cost of
				// the unplaced instances.  We will simply choose not to prune
				// in this case.
				return -1;
			}
			
			// Stack all unplaced cells on top of each other. Cost can't
			// possibly be less than this.
			for (int i=0; i<placed.length; i++) {
				if (!placed[i]) {
					Inst inst = insts.get(i);
					inst.moveTo(nX, 0);
				}
			}
			return nX;
		}
		public boolean prunePermutation(int[] permutation, boolean[] placed,
										int depth) {
			// If we've placed all but one, there's nothing to prune.
			// If we've placed all but two, we only save checking 4 permutations.
			// Only consider pruning if there are three or more unplaced cells.
			int nbUnplaced = placed.length - (depth+1);
			if (nbUnplaced<=2) return false;
			
			double maxX = abutLeftRight(permutation, placed, depth);
			if (maxX==-1) return false;
			
			double cost = getPlacedCostX(nets, maxX);
			boolean prune = cost>=bestCost;
			/*
			  if (prune) {
			  if (nbUnplaced>=5) 
			  System.out.println("Pruning at placed/total: "+(depth+1)+"/"+placed.length);
			  }
			*/
			return prune;
		}
//		private double getBestCost() {return bestCost;}
		private ArrayList<Inst> getBestPermutation() {
			ArrayList<Inst> best = new ArrayList<Inst>();
			for (int i=0; i<bestPermutation.length; i++)
				best.add(insts.get(bestPermutation[i]));
			return best;
		}
		PermChecker(ArrayList<Inst> insts, ArrayList<Net> nets, double leftX,
					int maxPerms) {
			this.insts=insts;  this.nets=nets;  this.leftX=leftX;
			this.maxPerms=maxPerms; nbChecked=0;
			
			for (int i=0; i<insts.size(); i++)  permInsts.add(null);
		}
	}
	
	// ------------------------ Private methods ------------------------
	private static void updateElectric(ArrayList<Inst> insts, double rowHeight) {
		for (int i=0; i<insts.size(); i++) {
			insts.get(i).updateElectric(rowHeight);
		}
	}
	
	private static void abutLeftRight(double leftX, ArrayList<Inst> insts) {
		double pX=leftX, nX=leftX;
		for (int i=0; i<insts.size(); i++) {
			Inst inst = insts.get(i);
			if (inst.isN()) {
				// NMOS only gate
				inst.moveTo(nX, 0);
				nX += inst.getWidth();
			} else if (inst.isP()) {
				// PMOS only gate
				inst.moveTo(pX, 0);
				pX += inst.getWidth();
			} else {
				// full height NMOS and PMOS gate
				double x = Math.max(nX, pX);
				inst.moveTo(x, 0);
				pX = nX = x+inst.getWidth();
			}
		}
	}
	
	private static double getCostX(ArrayList<Net> nets) {
		double cost = 0;
		for (int i=0; i<nets.size(); i++) {
			cost += nets.get(i).getCostX();
		}
		return cost;
	}
//	private static double getCost2row(ArrayList<Net> nets) {
//		double cost = 0;
//		for (int i=0; i<nets.size(); i++) {
//			cost += nets.get(i).getCost2row();
//		}
//		return cost;
//	}
	
	// Any ports to the right of maxX are unplaced. Compute the cost as
	// if they are all at maxX.
	private static double getPlacedCostX(ArrayList<Net> nets, double maxX) {
		double cost = 0;
		for (int i=0; i<nets.size(); i++) {
			cost += nets.get(i).getPlacedCostX(maxX);
		}
		return cost;
	}
	
	// returns true to stop checking
	private static boolean forEachPermutation1(int depth, boolean[] mask,
											   int[] permutation,
											   PermutationAction action) {
		int sz = mask.length;
		if (depth>sz-1) {
			// do something with permutation
			return action.usePermutation(permutation);
		}
		for (int i=0; i<sz; i++) {
			if (mask[i]==false) {
				permutation[depth] = i;
				mask[i] = true;
				if (!action.prunePermutation(permutation, mask, depth)) {
					if (forEachPermutation1(depth+1, mask, permutation,
											action)) {
						return true;
					}
				}
				mask[i] = false;
			}
		}
		return false;
	}
	
	// find all permuatations of nb objects
	private static void forEachPermutation(int nb, PermutationAction action) {
		error(nb==0, "permutations of nothing?");
		int[] permutation = new int[nb];
		boolean[] mask = new boolean[nb];
		for (int i=0; i<nb; i++)  mask[i]=false;
		forEachPermutation1(0, mask, permutation, action);
	}
	
	private static long factorial(int i) {
		if (i==0) return 0;
		if (i==1) return 1;
		return i * factorial(i-1);
	}
	
	// try every permutation: exponential
	private static ArrayList<Inst> exhaustive(ArrayList<Inst> insts, ArrayList<Net> nets,
										double leftX, int maxPerms) {
		PermChecker checker = new PermChecker(insts, nets, leftX, maxPerms);
		int nbGates = insts.size();
		if (nbGates==0) return new ArrayList<Inst>();
		if (VERBOSE) {
			System.out.print("Number of gates: "+nbGates);
			System.out.println(", Number of permutations: "+
							   factorial(nbGates));
		}
		forEachPermutation(nbGates, checker);
		return checker.getBestPermutation();
	}
	
	// The distance between ties should be no more than maxDist.  The
	// distance from the edge to the closest tie should be maxDist/2.
	private static ArrayList<Inst> insertWellTies(ArrayList<Inst> insts,
											StdCellParams stdCell, Cell part) {
        EditingPreferences ep = stdCell.getEditingPreferences();
		// In order to patch right most well gaps, add a full height dummy
		// instance at the end.  Remove it after we're done.
		insts = (ArrayList) insts.clone(); // don't modify input list
		Inst dummy = new Inst(PN, 0, null);
		insts.add(dummy);
		
		// build smallest well ties containing 1 well contact.
		Cell pTie = WellTie.makePart(false, true, 0, stdCell);
		Cell nTie = WellTie.makePart(true, false, 0, stdCell);
		
		// default width of WellTie containing 1 contact
		final double tieWid = pTie.getBounds().getWidth();
		
		// maximum distance from right edge of well tie to closest contact
		final double tieEdgeToContDist = WellTie.edgeToContDist();
		
		double pX=0, nX=0;
		double maxDist = stdCell.getWellTiePitch();
		double pSp=maxDist/2, nSp=maxDist/2;
		for (int i=0; i<insts.size(); i++) {
			Inst inst = insts.get(i);
			double instWid = inst.getWidth();
			if (instWid > maxDist) {
				System.out.println("The gate: "+
								  inst.getNodeInst().getProto().getName()+
								   " is larger than the well tap spacing!!!");
			}
			
			boolean nAbuts = nX>=pX;
			double testDist = inst==dummy ? maxDist/2 : maxDist;
			if (inst.isN() || (inst.isPN() && nAbuts)) {
				// NMOS abuts NMOS
				if ((nSp + instWid > testDist) && nSp!=0) {
					// insert NMOS well tie
					NodeInst ni = LayoutLib.newNodeInst(nTie,ep,0,0,0,0,0,part);
					insts.add(i, new Inst(N, tieWid, ni));
					nX += tieWid;
					nSp = 0;
				} else if (inst.isPN()) {
					// abut full height gate
					double gap = nX-pX;
					if (gap>0) {
						// There's a PMOS well gap. Patch it.
						Cell patch = WellTie.makePart(false, true,gap,stdCell);
						NodeInst ni =
							LayoutLib.newNodeInst(patch,ep,0,0,0,0,0,part);
						insts.add(i, new Inst(P, gap, ni));
						i++;
						pSp = gap>=tieWid ? tieEdgeToContDist : pSp + gap;
					}
					pX = nX = nX + instWid;
					nSp += instWid;
					pSp += instWid;
				} else {
					// abut half height NMOS
					nX += instWid;
					nSp += instWid;
				}
			} else if (inst.isP() || (inst.isPN() && !nAbuts)) {
				// PMOS abuts PMOS
				if ((pSp + instWid > testDist) && pSp!=0) {
					// insert PMOS well tie
					NodeInst ni = LayoutLib.newNodeInst(pTie,ep,0,0,0,0,0,part);
					insts.add(i, new Inst(P, tieWid, ni));
					pX += tieWid;
					pSp = 0;
				} else if (inst.isPN()) {
					// abut full height gate
					double gap = pX-nX;
					if (gap>0) {
						// There's a NMOS well gap. Patch it.
						Cell patch = WellTie.makePart(true, false,gap,stdCell);
						NodeInst ni =
							LayoutLib.newNodeInst(patch,ep,0,0,0,0,0,part);
 						insts.add(i, new Inst(N, gap, ni));
						i++;
						nSp = gap>=tieWid ? tieEdgeToContDist : nSp + gap;
					}
					pX = nX = pX + instWid;
					pSp += instWid;
					nSp += instWid;
				} else {
					// abut half height PMOS
					pX += instWid;
					pSp += instWid;
				}
			}
		}
		insts.remove(dummy);
		return insts;
	}
	
	private static ArrayList<Inst> threeRegionPlace(ArrayList<Inst> insts, ArrayList<Net> nets,
											  int maxPerms) {
		ArrayList<Inst> nInsts=new ArrayList<Inst>(),  pInsts=new ArrayList<Inst>(),
			pnInsts=new ArrayList<Inst>();
		for (int i=0; i<insts.size(); i++) {
			Inst inst = insts.get(i);
			if (inst.isN()) {
				nInsts.add(inst);
			} else if (inst.isP()) {
				pInsts.add(inst);
			} else {
				pnInsts.add(inst);
			}
		}
		ArrayList<Inst> allInsts = new ArrayList<Inst>(pnInsts);
		allInsts.addAll(nInsts);
		allInsts.addAll(pInsts);
		abutLeftRight(0, allInsts);
		
		Inst lastFull = pnInsts.get(pnInsts.size()-1);
		double rightFullX = lastFull.getX() + lastFull.getWidth();
		
		ArrayList<Inst> ans = new ArrayList<Inst>(exhaustive(pnInsts, nets, 0, maxPerms));
		ans.addAll(exhaustive(nInsts, nets, rightFullX, maxPerms));
		ans.addAll(exhaustive(pInsts, nets, rightFullX, maxPerms));
		
		return ans;
	}
	
	// ------------------------ Public classes -----------------------------
	public static class Inst {
		double x;        // position of Cell reference
		int row;
		double w;
		boolean mirrorX, mirrorY;
		int type;           // P, N, or PN
		NodeInst nodeInst;  // allows us to position the part instance
		
		ArrayList<Port> ports = new ArrayList<Port>();
		
		Inst(int type, double width, NodeInst nodeInst) {
			error(type!=P && type!=N && type!=PN, "Placer.Inst: bad type: "+type);
			this.type=type; w=width;
			this.nodeInst = nodeInst;
		}
		
		int nbPorts() {return ports.size();}
		Port getPort(int i) {return ports.get(i);}
		public Port addPort(double ofstX, double ofstY) {
			Port p = new Port(this, ofstX, ofstY);
			ports.add(p);
			return p;
		}
		
		void moveTo(double x, int row) {this.x=x; this.row=row;}
		double getX() {return x;}
		double getMaxX() {return x+w;}
		int getRow() {return row;}
		
		void setMirrorX(boolean mirror) {mirrorX=mirror;}
		boolean getMirrorX() {return mirrorX;}
		void setMirrorY(boolean mirror) {mirrorY=mirror;}
		boolean getMirrorY() {return mirrorY;}
		boolean isN() {return type==N;}
		boolean isP() {return type==P;}
		boolean isPN() {return type==PN;}
		double getWidth() {return w;}
		NodeInst getNodeInst() {return nodeInst;}
		
		// Move NodeInsts in Electric.
		void updateElectric(double rowHeight) {
			LayoutLib.modNodeInst(nodeInst, x, row*rowHeight,0,0,false,false,0);
		}
	}
	
	public static class Port {
		Inst inst;
		double ofstX, ofstY;
		Port(Inst inst, double ofstX, double ofstY) {
			this.inst=inst; this.ofstX=ofstX; this.ofstY=ofstY;
		}
		double getX() {
			double offset = inst.getMirrorX() ? (inst.getWidth()-ofstX) : ofstX;
			return inst.getX() + offset;
		}
		int getRow() {return inst.getRow();}
		Inst getInst() {return inst;}
	}
	
	public static class Net {
		ArrayList<Port> ports = new ArrayList<Port>();
		
		int nbPorts() {return ports.size();}
		Port getPort(int i) {return ports.get(i);}
		public void addPort(Port port) {ports.add(port);}
		
		double getCostX() {
			// handle special case because otherwise we return -infinity
			if (nbPorts()==0)  return 0;
			
			double minX = Double.MAX_VALUE;
			double maxX = Double.MIN_VALUE;
			for (int i=0; i<ports.size(); i++) {
				double x = getPort(i).getX();
				minX = Math.min(minX, x);
				maxX = Math.max(maxX, x);
			}
			return maxX-minX;
		}
		
		// Any port to the right of unplacedX is unplaced. Compute the
		// cost as if all those ports are at unplacedX.
		double getPlacedCostX(double unplacedX) {
			double minX = Double.MAX_VALUE;
			double maxX = Double.MIN_VALUE;
			for (int i=0; i<ports.size(); i++) {
				double x = getPort(i).getX();
				minX = Math.min(minX, x);
				maxX = Math.max(maxX, x);
			}
			maxX = Math.min(maxX, unplacedX);
			minX = Math.min(minX, unplacedX);
			return maxX-minX;
		}
		
		double getCost2row() {
			final int nbRows = 2;
			double[] minX = new double[nbRows],
				maxX = new double[nbRows];
//			double[] costX = new double[nbRows];
			for (int i=0; i<nbRows; i++) {
				minX[i] = Double.MAX_VALUE;  maxX[i] = Double.MIN_VALUE;
			}
			for (int i=0; i<ports.size(); i++) {
				Port port = getPort(i);
				double x = port.getX();
				int r = port.getRow();
				minX[r] = Math.min(minX[r], x);
				maxX[r] = Math.max(maxX[r], x);
			}
			double cost = 0;
			for (int i=0; i<nbRows; i++) {
				if (minX[i]!=Double.MAX_VALUE)  cost += maxX[i] - minX[i];
			}
			return cost;
		}
	}
	
	// -------------------------- public methods------------------------------
	public Placer(StdCellParams stdCell, Cell part) {
		this.stdCell=stdCell;  this.part=part;
		rowHeight = stdCell.getNmosWellHeight() + stdCell.getPmosWellHeight();
	}
	
	public Inst addInst(int type, double w, NodeInst nodeInst) {
		Inst inst = new Inst(type, w, nodeInst);
		buildInsts.add(inst);
		return inst;
	}
	
	public Net addNet() {
		Net net = new Net();
		buildNets.add(net);
		return net;
	}
	
	public ArrayList<NodeInst> place1row() {
		int maxPerms = stdCell.getNbPlacerPerms();
		ArrayList<Inst> insts = threeRegionPlace(buildInsts, buildNets, maxPerms);
		abutLeftRight(0, insts);
		double threeRegCost = getCostX(buildNets);
		
		if (stdCell.getExhaustivePlace()) {
			insts = exhaustive(insts, buildNets, 0, stdCell.getNbPlacerPerms());
			abutLeftRight(0, insts);
			double exhCost = getCostX(buildNets);
			double improve = Math.rint(1000 * (threeRegCost-exhCost)/threeRegCost)/10;
			if (VERBOSE) {
				System.out.println("exhaustive search improvement: "+improve+"%");
			}
		}
		
		insts = insertWellTies(insts, stdCell, part);
		abutLeftRight(0, insts);
		updateElectric(insts, rowHeight);
		
		Inst rightInst = insts.get(insts.size()-1);
		stdCell.addEssentialBounds(0, rightInst.getMaxX(), part);
		
		ArrayList<NodeInst> nodeInsts = new ArrayList<NodeInst>();
		for (int i=0; i<insts.size(); i++) {
			nodeInsts.add(insts.get(i).getNodeInst());
		}
		return nodeInsts;
	}
	
	public ArrayList place2row() {
		return new ArrayList();
	}
}
