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

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.tool.generator.layout.LayoutLib;
import com.sun.electric.tool.generator.layout.TechType;
import com.sun.electric.tool.Job;

//---------------------------------- FillCell --------------------------------
public class FillCell {
	public static final String VDD_NAME = "vdd";
	public static final String GND_NAME = "gnd";
	public static final PortCharacteristic VDD_CHARACTERISTIC = 
		PortCharacteristic.PWR;
	public static final PortCharacteristic GND_CHARACTERISTIC = 
		PortCharacteristic.GND;
	
	private int vddNum, gndNum;
	private final TechType tech;
    private final EditingPreferences ep;

	private String vddName() {
		int n = vddNum++;
		return VDD_NAME + (n==0 ? "" : ("_"+n));
	}
	private String gndName() {
		int n = gndNum++;
		return GND_NAME + (n==0 ? "" : ("_"+n));
	}

	public void exportPerimeter(VddGndStraps lay, Cell cell) {
		for (int i=0; i<lay.numGnd(); i++) {
			exportStripeEnds(i, lay, true, cell);
		}
		for (int i=0; i<lay.numVdd(); i++) {
			exportStripeEnds(i, lay, false, cell);
		}
	}
	public void exportPerimeter(VddGndStraps[] lays, int botLay, int topLay,
                                ExportConfig exptConfig, Cell cell) {
		int[] perim = exptConfig.getPerimeterExports(botLay, topLay);
		for (int i=0; i<perim.length; i++) {
			VddGndStraps lay = lays[perim[i]];
			if (lay!=null) exportPerimeter(lay, cell);
		}
	}
	private void exportStripeEnds(int n, VddGndStraps lay, 
			                      boolean gnd, Cell cell) {
		PrimitiveNode pin = lay.getPinType();
		ArcProto metal = lay.getMetalType();
		double edge = (lay.isHorizontal() ? lay.getCellWidth() : lay.getCellHeight())/2;
		double center = gnd ? lay.getGndCenter(n) : lay.getVddCenter(n);
		double width = gnd ? lay.getGndWidth(n) : lay.getVddWidth(n);
		PortInst piLeft = gnd ? lay.getGnd(n, 0) : lay.getVdd(n, 0);
        PortInst piRight = gnd ? lay.getGnd(n, 1) : lay.getVdd(n, 1);
		if (lay.isHorizontal()) {
			export(-edge, center, pin, metal, piLeft, width,
				   gnd ? gndName() : vddName(), gnd, cell, lay.addExtraArc());
			export(edge, center, pin, metal, piRight, width,
				   gnd ? gndName() : vddName(), gnd, cell, lay.addExtraArc());
		} else {
			export(center, -edge, pin, metal, piLeft, width,
				   gnd ? gndName() : vddName(), gnd, cell, lay.addExtraArc());
			export(center, edge, pin, metal, piRight, width,
				   gnd ? gndName() : vddName(), gnd, cell, lay.addExtraArc());
		}
	}
	private void export(double x, double y, PrimitiveNode pin,
						ArcProto metal, PortInst conn, double w,
						String name, boolean gnd, Cell cell,
						boolean withExtraArc)
    {
        Export e = null;
        if (false) // withExtraArc)
        {
            PortInst pi = LayoutLib.newNodeInst(pin, ep, x, y, G.DEF_SIZE, G.DEF_SIZE,
                                                0, cell).getOnlyPortInst();
            G.noExtendArc(metal, ep, w, conn, pi);
            e = Export.newInst(cell, pi, name, ep);
        }
        else
            e = Export.newInst(cell, conn, name, ep);
		e.setCharacteristic(gnd ? GND_CHARACTERISTIC :
								  VDD_CHARACTERISTIC);
	}
	public void exportWiring(VddGndStraps lay, Cell cell) {
		for (int i=0; i<lay.numGnd(); i++) {
			exportStripeCenter(i, lay, true, cell);
		}
		for (int i=0; i<lay.numVdd(); i++) {
			exportStripeCenter(i, lay, false, cell);
		}
	}
	public void exportWiring(VddGndStraps[] lays, int botLay, int topLay,
                             ExportConfig exptConfig, Cell cell) {
		int[] intnl = exptConfig.getInternalExports(botLay, topLay);
		for (int i=0; i<intnl.length; i++) {
			VddGndStraps lay = lays[intnl[i]];
			if (lay!=null) exportWiring(lay, cell);
		}
	}
	private void exportStripeCenter(int n, VddGndStraps lay, 
			                        boolean gnd, Cell cell) {
		PrimitiveNode pin = lay.getPinType();
		ArcProto metal = lay.getMetalType();
		double center = gnd ? lay.getGndCenter(n) : lay.getVddCenter(n);
		double width = gnd ? lay.getGndWidth(n) : lay.getVddWidth(n);
		PortInst pi = gnd ? lay.getGnd(n, 0) : lay.getVdd(n, 0); // Doesn't matter which bar end is taken
		if (lay.isHorizontal()) {
			export(0, center, pin, metal, pi, width,
				   gnd ? gndName() : vddName(), gnd, cell, lay.addExtraArc());
		} else {
			export(center, 0, pin, metal, pi, width,
				   gnd ? gndName() : vddName(), gnd, cell, lay.addExtraArc());
		}
	}
	private void appendExportLayerNumbers(StringBuffer buf, 
                                          String type, int[] layerNbs) {
		if (layerNbs.length>0) {
			buf.append(type);
			for (int i=0; i<layerNbs.length; i++) {
				buf.append(layerNbs[i]);
			}
		}
	}
	private String fillName(int lo, int hi, ExportConfig expConfig) {
		StringBuffer buf = new StringBuffer();
		buf.append("fill");
		if (lo!=1 || hi!=tech.getNumMetals()) {
			for (int i=lo; i<=hi; i++)  buf.append(i);
		}
		if (expConfig==ExportConfig.PERIMETER) {
			// append nothing
		} else if (expConfig==ExportConfig.PERIMETER_AND_INTERNAL) {
			buf.append("w");
		} else {
			int[] perim = expConfig.getPerimeterExports(lo, hi);
			appendExportLayerNumbers(buf, "p", perim);
			int[] intnl = expConfig.getInternalExports(lo, hi);
			appendExportLayerNumbers(buf, "w", intnl);
		}
		buf.append("{lay}");
		return buf.toString();
	}

	private VddGndStraps[] findHoriVert(VddGndStraps lay1, VddGndStraps lay2) {
		if (lay1.isHorizontal()) {
			Job.error(lay2.isHorizontal(), "adjacent layers both horizontal");
			return new VddGndStraps[] {lay1, lay2};
		} else {
			Job.error(!lay2.isHorizontal(), "adjacent layers both vertical");
			return new VddGndStraps[] {lay2, lay1};
		}
	}

	/** Move via's edge inside by 1 lambda if via's edge is on cell's edge */
	private static class ViaDim {
		public final double x, y, w, h;
		public ViaDim(VddGndStraps lay, double x, double y, double w, double h) {
			if (x+w/2 == lay.getCellWidth()/2) {
				w -= 1;
				x -= .5;
			} else if (x-w/2 == -lay.getCellWidth()/2) {
				w -= 1;
				x += .5;
			}
			if (y+h/2 == lay.getCellHeight()/2) {
				h -= 1;
				y -= .5;
			} else if (y-h/2 == -lay.getCellHeight()/2) {
				h -= 1;
				y += .5;
			}
			this.x = x;
			this.y = y;
			this.w = w;
			this.h = h;
		}
	}

	private void connectVddStraps(VddGndStraps horLay, int horNdx,
								  VddGndStraps verLay, int verNdx, Cell cell) {
		double w = verLay.getVddWidth(verNdx);
		double x = verLay.getVddCenter(verNdx);
		ArcProto verMetal = verLay.getMetalType();
        // Try to select the closest pin in the other layer , even to the left, odd to the right
		PortInst verPort = (horNdx%2==0) ? verLay.getVdd(verNdx, 0) : verLay.getVdd(verNdx, 1);
		double h = horLay.getVddWidth(horNdx);
		double y = horLay.getVddCenter(horNdx);
		ArcProto horMetal = horLay.getMetalType();
		PrimitiveNode viaType = tech.getViaFor(verMetal, horMetal);
		PortInst horPort = (verNdx%2==0) ? horLay.getVdd(horNdx, 0) : horLay.getVdd(horNdx, 1);
        // Line below will detect mixture of technologies.
        Job.error(viaType==null, "can't find via for metal layers " + verMetal + " " + horMetal);

		ViaDim d = new ViaDim(horLay, x, y, w, h);

		PortInst via = LayoutLib.newNodeInst(viaType, ep, d.x, d.y, d.w, d.h, 0,
		                                     cell).getOnlyPortInst();

        G.noExtendArc(horMetal, ep, h, horPort, via);
        G.noExtendArc(verMetal, ep, w, via, verPort);
//		LayoutLib.newArcInst(horMetal, G.DEF_SIZE, horPort, via);
//		LayoutLib.newArcInst(verMetal, G.DEF_SIZE, via, verPort);
	}

	private void connectGndStraps(VddGndStraps horLay, int horNdx,
								  VddGndStraps verLay, int verNdx, Cell cell) {
		double w = verLay.getGndWidth(verNdx);
		double x = verLay.getGndCenter(verNdx);
		ArcProto verMetal = verLay.getMetalType();
        // Try to select the closest pin in the other layer , even to the left, odd to the right
		PortInst verPort = (horNdx%2==0) ? verLay.getGnd(verNdx, 0) : verLay.getGnd(verNdx, 1);
		double h = horLay.getGndWidth(horNdx);
		double y = horLay.getGndCenter(horNdx);
		ArcProto horMetal = horLay.getMetalType();
		PrimitiveNode viaType = tech.getViaFor(verMetal, horMetal);
		PortInst horPort = (verNdx%2==0) ? horLay.getGnd(horNdx, 0) : horLay.getGnd(horNdx, 1);
		Job.error(viaType==null, "can't find via for metal layers");

		ViaDim d = new ViaDim(horLay, x, y, w, h);

		PortInst via = LayoutLib.newNodeInst(viaType, ep, d.x, d.y, d.w, d.h, 0,
		 									 cell).getOnlyPortInst();

        G.noExtendArc(horMetal, ep, h, horPort, via);
        G.noExtendArc(verMetal, ep, w, via, verPort);
//		LayoutLib.newArcInst(horMetal, G.DEF_SIZE, horPort, via);
//		LayoutLib.newArcInst(verMetal, G.DEF_SIZE, via, verPort);
	}

	private void connectLayers(VddGndStraps loLayer, VddGndStraps hiLayer,
							   Cell cell) {
		VddGndStraps layers[] = findHoriVert(loLayer, hiLayer);
		VddGndStraps horLay = layers[0];
		VddGndStraps verLay = layers[1];
		for (int h=0; h<horLay.numVdd(); h++) {
			for (int v=0; v<verLay.numVdd(); v++) {
				connectVddStraps(horLay, h, verLay, v, cell);
			}
		}
		for (int h=0; h<horLay.numGnd(); h++) {
			for (int v=0; v<verLay.numGnd(); v++) {
				connectGndStraps(horLay, h, verLay, v, cell);
			}
		}
   	}

	protected Cell makeFillCell1(Library lib, Floorplan[] plans, int botLayer,
                               int topLayer, CapCell capCell, 
                               ExportConfig expCfg,
                               boolean metalFlex, boolean hierFlex) {
		String name = fillName(botLayer, topLayer, expCfg);
		Cell cell = Cell.newInst(lib, name);
		VddGndStraps[] layers = new VddGndStraps[topLayer+1];
		for (int i=topLayer; i>=botLayer; i--) {
			if (i==1) {
				layers[i] = new CapLayer(tech, ep, (CapFloorplan) plans[i], capCell,
						                 cell);
			} else {
                if (metalFlex && !hierFlex)
				    layers[i] = new MetalLayerFlex(tech, ep, i, plans[i], cell);
                else
                    layers[i] = new MetalLayer(tech, ep, i, plans[i], cell);
			}
			if (i!=topLayer) {
				// connect to upper level
				connectLayers(layers[i], layers[i+1], cell);
			}
		}
		exportPerimeter(layers, botLayer, topLayer, expCfg, cell);
		exportWiring(layers, botLayer, topLayer, expCfg, cell);

		double cellWidth = plans[topLayer].cellWidth;
		double cellHeight = plans[topLayer].cellHeight;
		LayoutLib.newNodeInst(tech.essentialBounds(), ep,
							  -cellWidth/2, -cellHeight/2,
							  G.DEF_SIZE, G.DEF_SIZE, 180, cell);
		LayoutLib.newNodeInst(tech.essentialBounds(), ep,
							  cellWidth/2, cellHeight/2,
							  G.DEF_SIZE, G.DEF_SIZE, 0, cell);
//        cell.setCharacteristicSpacing(cellWidth, cellHeight);
        return cell;
	}
	protected FillCell(TechType tech, EditingPreferences ep) {
		this.tech = tech;
        this.ep = ep;
	}
}
