/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: Decode.java
 * MOSIS CMOS PLA Generator.
 * Originally written by Wallace Kroeker at the University of Calgary
 * Translated to Java by Steven Rubin, Sun Microsystems.
 *
 * Copyright (c) 2005, 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.cmosPLA;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.Poly;
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.PortProto;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.util.math.Orientation;

import java.awt.geom.Rectangle2D;

/**
 * Class to generate the decoder part of MOSIS CMOS PLAs.
 */
public class Decode
{
	private PLA pla;
    private EditingPreferences ep;

	Decode(PLA pla, EditingPreferences ep)
	{
		this.pla = pla;
        this.ep = ep;
	}

	Cell decodeGen(Library library, Cell pmosCell, Cell nmosCell, String cellName, boolean inputsOnTop)
	{
		Cell cell = Cell.makeInstance(ep, library, cellName);
		NodeInst pNode = pla.makeInstance(cell, pmosCell, 0, 0, false);
		if (pNode == null) return null;
		pNode.rotate(Orientation.RRR);  // rotate by 90 degrees

		Rectangle2D nodeRect = pNode.getBounds();
		double x = nodeRect.getWidth();
		if (x < 0) x = -x;
		x += 12; // SEPARATION

		NodeInst nNode = pla.makeInstance(cell, nmosCell, x, 0, false);
		if (nNode == null) return null;
		nNode.rotate(Orientation.RRR);  // rotate by 90 degrees

		NodeInst [] both = decodeBufs(pmosCell, pNode, nmosCell, nNode, cell, x, inputsOnTop);
		if (both == null) return null;
		if (decodeRoute(pmosCell, pNode, nmosCell, nNode, cell, both[0], both[1], inputsOnTop)) return null;
		decExp(nmosCell, nNode, cell);

		return cell;
	}

	private NodeInst [] decodeBufs(Cell pmosCell, NodeInst pNode, Cell nmosCell,
		NodeInst nNode, Cell decodeCell, double X, boolean inputsOnTop)
	{
		NodeInst lastNode = null;
		NodeInst newNode = null;
		NodeInst firstPNode = null, lastPNode = null, lastNNode = null;
		int rows = 2;
		Variable var = pmosCell.getVar("PLA_access_rows");
		if (var != null) rows = ((Integer)var.getObject()).intValue(); else
			System.out.println("ACCESS_rows defaulting to 2");

		Export pwrWPort = null, pwrEPort = null, gndWPort = null, gndEPort = null;
		char side = 'w';
		if (inputsOnTop) side = 'e';
		Rectangle2D nodeBounds = pNode.getBounds();
		double lowX = nodeBounds.getMinX();
		double lowY = nodeBounds.getMinY();
		double highY = nodeBounds.getMaxY();

		Rectangle2D bufBounds = pla.decoderInv.getBounds();
		double diff = bufBounds.getHeight();
		if (diff < 0) diff = -diff;
		int limit = rows / 4;
		if (limit == 0 || (rows % 4) != 0) limit++;
		int cnt = 0;
		double y = highY;
		if (inputsOnTop) y = lowY - diff;
		y -= 13;
		double xOffset = lowX - 38;
		if ((rows %4) == 0) xOffset = lowX - 18;
		pwrWPort = pla.decoderInv.findExport("PWR.m-2.w");
		pwrEPort = pla.decoderInv.findExport("PWR.m-2.e");
		gndWPort = pla.decoderInv.findExport("GND.m-2.w");
		gndEPort = pla.decoderInv.findExport("GND.m-2.e");
		for (int i = limit; i > 0; i--)
		{
			newNode = pla.makeInstance(decodeCell, pla.decoderInv, i*50 + xOffset, y, !inputsOnTop);
			if (newNode == null) return(null);
			NodeInst newNode2 = pla.makeInstance(decodeCell, pla.decoderInv, X + i*50 + xOffset, y, !inputsOnTop);
			if (newNode2 == null) return(null);
			if (lastPNode == null && lastNNode == null)
			{
				Export.newInstance(decodeCell, newNode2.findPortInstFromProto(pwrEPort), "PWR.m-2.e", ep);
				Export.newInstance(decodeCell, newNode2.findPortInstFromProto(gndEPort), "GND.m-2.e", ep);
				firstPNode = lastPNode = newNode;
				lastNNode = lastNode = newNode2;
			} else
			{
				// get wired
				if (pwrWPort != null && pwrEPort != null)
				{
					pla.makeWire(pla.m2Arc, 14, lastPNode, pwrWPort, newNode, pwrEPort, decodeCell);
					pla.makeWire(pla.m2Arc, 14, lastNNode, pwrWPort, newNode2, pwrEPort, decodeCell);
				}
				if (gndWPort != null && gndEPort != null)
				{
					pla.makeWire(pla.m2Arc, 14, lastNNode, gndWPort, newNode, gndEPort, decodeCell);
					pla.makeWire(pla.m2Arc, 14, lastNNode, gndWPort, newNode2, gndEPort, decodeCell);
				}
				lastPNode = newNode;
				lastNNode = newNode2;
			}
			for (int x = cnt; x < cnt+4; x++)
			{
				String aName = "ACCESS" + x + ".m-1." + side;
				Export pPort = pmosCell.findExport(aName);
				Export nPort = pla.decoderInv.findExport("LINE" + (x % 4) + ".p.n");
				if (pPort != null && nPort != null)
					pla.makeWire(pla.pArc, 2, pNode, pPort, newNode, nPort, decodeCell);
				pPort = nmosCell.findExport(aName);
				if (nPort != null && pPort != null)
					pla.makeWire(pla.pArc, 2, nNode, pPort, newNode2, nPort, decodeCell);
			}
			cnt += 4;
		}
		if (pwrWPort != null && pwrEPort != null)
		{
			pla.makeWire(pla.m2Arc, 14, lastNNode, pwrWPort, firstPNode, pwrEPort, decodeCell);
			Export.newInstance(decodeCell, lastPNode.findPortInstFromProto(pwrWPort), "PWR.m-2.w", ep);
		}
		if (gndWPort != null && gndEPort != null)
		{
			pla.makeWire(pla.m2Arc, 14, lastNNode, gndWPort, firstPNode, gndEPort, decodeCell);
			Export.newInstance(decodeCell, lastPNode.findPortInstFromProto(gndWPort), "GND.m-2.w", ep);
		}
		NodeInst [] both = new NodeInst[2];
		both[0] = lastPNode;
		both[1] = lastNode;
		return both;
	}

	private void decExp(Cell nmosCell, NodeInst nNode, Cell decodeCell)
	{
		int x = 0;
		char side = 'n';
		String name = "DATA" + x + ".m-1." + side;
		Export pp = nmosCell.findExport(name);
		while (pp != null)
		{
			Export.newInstance(decodeCell, nNode.findPortInstFromProto(pp), name, ep);
			x++;
			name = "DATA" + x + ".m-1." + side;
			pp = nmosCell.findExport(name);
		}
	}

	private boolean decodeRoute(Cell pmosCell, NodeInst pNode, Cell nmosCell,
		NodeInst nNode, Cell decodeCell, NodeInst firstNode, NodeInst lastNode, boolean inputsOnTop)
	{
		// Make connection between N and P planes
		Export pPort = pmosCell.findExport("DATA0.m-1.n");
		Export nPort = nmosCell.findExport("DATA0.m-1.s");
		int cnt = 0;
		while (pPort != null && nPort != null)
		{
			// Connect them up here
			pla.makeWire(pla.m1Arc, 4, pNode, pPort, nNode, nPort, decodeCell);

			// now find the next ports
			cnt += 1;
			pPort = pmosCell.findExport("DATA" + cnt + ".m-1.n");
			nPort = nmosCell.findExport("DATA" + cnt + ".m-1.s");
		}

		// Make connection between PWR and P-plane
		pPort = pmosCell.findExport("DATA0.m-1.s");
		nPort = pmosCell.findExport("PWR0.m-1.s");
		if (pPort != null && nPort != null)
			pla.makeWire(pla.m1Arc, 4, pNode, pPort, pNode, nPort, decodeCell);
		Poly poly = pNode.findPortInstFromProto(nPort).getPoly();
		double pX = poly.getCenterX();
		double pY = poly.getCenterY();
		NodeInst pmNode1 = pla.makePin(decodeCell, pX-13, pY, 6, pla.m12Con);
		if (pmNode1 == null) return true;
		pla.makeWire(pla.m1Arc, 4, pNode, nPort, pmNode1, pmNode1.getProto().getPort(0), decodeCell);
		if (!inputsOnTop) // Buffers are at the top of the cell
		{
			PortProto pwrPort = firstNode.getProto().findPortProto("PWR.m-2.w");
			NodeInst pmNode2 = pla.makePin(decodeCell, pX-13, pY+24, 14, pla.m2Pin);
			if (pmNode2 == null) return true;
			pla.makeWire(pla.m2Arc, 14, pmNode1, pmNode1.getProto().getPort(0), pmNode2, pmNode2.getProto().getPort(0), decodeCell);
			pla.makeWire(pla.m2Arc, 14, firstNode, pwrPort, pmNode2, pmNode2.getProto().getPort(0), decodeCell);
		}
		nPort = pPort;
		pPort = pmosCell.findExport("DATA1.m-1.s");
		cnt = 1;
		int pwrCnt = 0;
		while (pPort != null && nPort != null)
		{
			// Connect them up here
			pla.makeWire(pla.m1Arc, 4, pNode, pPort, pNode, nPort, decodeCell);

			// now find the next ports
			cnt += 1;
			nPort = pPort;
			if ((cnt % 4) == 0)
			{
				pwrCnt += 1;
				String name = "PWR" + pwrCnt + ".m-1.s";
				pPort = pmosCell.findExport(name);
				if (pPort != null)
					pla.makeWire(pla.m1Arc, 4, pNode, pPort, pNode, nPort, decodeCell);
				poly = pNode.findPortInstFromProto(pPort).getPoly();
				pX = poly.getCenterX();
				pY = poly.getCenterY();
				NodeInst pmNode2 = pla.makePin(decodeCell, pX-13, pY, 6, pla.m12Con);
				if (pmNode2 == null) return true;
				pla.makeWire(pla.m1Arc, 4, pNode, pPort, pmNode2, pmNode2.getProto().getPort(0), decodeCell);
				pla.makeWire(pla.m2Arc, 14, pmNode1, pmNode1.getProto().getPort(0), pmNode2, pmNode2.getProto().getPort(0), decodeCell);
				pmNode1 = pmNode2;
				nPort = pPort;
			}
			pPort = pmosCell.findExport("DATA" + cnt + ".m-1.s");
		}
		if (inputsOnTop) // Buffers are at the top of the cell
		{
			PortProto pwrPort = firstNode.getProto().findPortProto("PWR.m-2.w");
			NodeInst pmNode2 = pla.makePin(decodeCell, pX-13, pY-24, 14, pla.m2Pin);
			if (pmNode2 == null) return true;
			pla.makeWire(pla.m2Arc, 14, pmNode1, pmNode1.getProto().getPort(0), pmNode2, pmNode2.getProto().getPort(0), decodeCell);
			pla.makeWire(pla.m2Arc, 14, firstNode, pwrPort, pmNode2, pmNode2.getProto().getPort(0), decodeCell);
		}

		// Make connection between GND and N-plane
		nPort = nmosCell.findExport("GND0.m-1.n");
		poly = nNode.findPortInstFromProto(nPort).getPoly();
		pX = poly.getCenterX();
		pY = poly.getCenterY();
		pmNode1 = pla.makePin(decodeCell, pX+13, pY, 6, pla.m12Con);
		if (pmNode1 == null) return true;
		pla.makeWire(pla.m1Arc, 4, nNode, nPort, pmNode1, pmNode1.getProto().getPort(0), decodeCell);
		if (!inputsOnTop) // Buffers are at the top of the cell
		{
			PortProto pwrPort = lastNode.getProto().findPortProto("GND.m-2.e");
			NodeInst pmNode2 = pla.makePin(decodeCell, pX+13, pY+90, 14, pla.m2Pin);
			if (pmNode2 == null) return true;
			pla.makeWire(pla.m2Arc, 14, pmNode1, pmNode1.getProto().getPort(0), pmNode2, pmNode2.getProto().getPort(0), decodeCell);
			pla.makeWire(pla.m2Arc, 14, lastNode, pwrPort, pmNode2, pmNode2.getProto().getPort(0), decodeCell);
		}
		cnt = 1;
		nPort = nmosCell.findExport("GND" + cnt + ".m-1.n");
		while (nPort != null)
		{
			// Connect them up here
			poly = nNode.findPortInstFromProto(nPort).getPoly();
			pX = poly.getCenterX();
			pY = poly.getCenterY();
			NodeInst pmNode2 = pla.makePin(decodeCell, pX+13, pY, 6, pla.m12Con);
			if (pmNode2 == null) return true;
			pla.makeWire(pla.m1Arc, 4, nNode, nPort, pmNode2, pmNode2.getProto().getPort(0), decodeCell);
			pla.makeWire(pla.m2Arc, 14, pmNode1, pmNode1.getProto().getPort(0), pmNode2, pmNode2.getProto().getPort(0), decodeCell);
			pmNode1 = pmNode2;
			cnt++;
			nPort = nmosCell.findExport("GND" + cnt + ".m-1.n");
		}
		if (inputsOnTop) // Buffers are at the bottom of the cell
		{
			PortProto pwrPort = lastNode.getProto().findPortProto("GND.m-2.e");
			NodeInst pmNode2 = pla.makePin(decodeCell, pX+13, pY-90, 14, pla.m2Pin);
			if (pmNode2 == null) return true;
			pla.makeWire(pla.m2Arc, 14, pmNode1, pmNode1.getProto().getPort(0), pmNode2, pmNode2.getProto().getPort(0), decodeCell);
			pla.makeWire(pla.m2Arc, 14, lastNode, pwrPort, pmNode2, pmNode2.getProto().getPort(0), decodeCell);
		}

		char side = 's';
		char acSide = 'e';
		if (inputsOnTop)
		{
			side = 'n';
			acSide = 'w';
		}
		int columns = 2;
		Variable var = pmosCell.getVar("PLA_access_rows");
		if (var != null) columns = ((Integer)var.getObject()).intValue() / 2; else
			System.out.println("DATA_cols defaulting to 2");

		int initOffset = 8;
		int sep = 7;
		cnt = 0;
		String name = "ACCESS" + cnt + ".m-1." + acSide;
		pPort = pmosCell.findExport(name);
		nPort = nmosCell.findExport(name);
		while (pPort != null && nPort != null)
		{
			// wire it
			int inCnt = cnt/2;
			poly = pNode.findPortInstFromProto(pPort).getPoly();
			pX = poly.getCenterX();

			poly = nNode.findPortInstFromProto(nPort).getPoly();
			double nX = poly.getCenterX();
			double nY = poly.getCenterY();

			double y, oY;
			if (inputsOnTop)
			{
				y = nY + initOffset + inCnt*sep;
				oY = y + (columns - inCnt - 1)*sep;
			} else
			{
				y = nY - initOffset - inCnt*sep;
				oY = y - (columns - inCnt - 1)*sep;
			}
			pmNode1 = pla.makePin(decodeCell, pX, y, 6, pla.mpCon);
			if (pmNode1 == null) return true;
			NodeInst pmNode2 = pla.makePin(decodeCell, nX, y, 6, pla.mpCon);
			if (pmNode2 == null) return true;
			pla.makeWire(pla.m1Arc, 4, pmNode1, pmNode1.getProto().getPort(0), pmNode2, pmNode2.getProto().getPort(0), decodeCell);
			pla.makeWire(pla.pArc, 2, pNode, pPort, pmNode1, pmNode1.getProto().getPort(0), decodeCell);
			pla.makeWire(pla.pArc, 2, nNode, nPort, pmNode2, pmNode2.getProto().getPort(0), decodeCell);

			pmNode1 = pla.makePin(decodeCell, nX, oY, 4, pla.m1Pin);
			if (pmNode1 == null) return true;
			pla.makeWire(pla.m1Arc, 4, pmNode1,
				pmNode1.getProto().getPort(0), pmNode2, pmNode2.getProto().getPort(0), decodeCell);
			Export.newInstance(decodeCell, pmNode1.findPortInstFromProto(pmNode1.getProto().getPort(0)), "INPUT" + inCnt + ".m-1." + side, ep);
			cnt += 2;  // Only route every second one
			name = "ACCESS" + cnt + ".m-1." + acSide;
			pPort = pmosCell.findExport(name);
			nPort = nmosCell.findExport(name);
		}
		return false;
	}
}
