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

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.PortProto;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

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

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

	Cell nmosGrid(Library library, String fileName, String cellName)
	{
		int y = 0;
		int x = 3;
		int xOffset = PLA.X_SEP;
		int yOffset = PLA.Y_SEP;
		int yMOffset = PLA.Y_MIR_SEP;
		Cell cell = Cell.makeInstance(ep, library, cellName);

		IO perFile = new IO();
		if (!perFile.readHeader(fileName))
		{
			System.out.println("Error reading height and width");
			return null;
		}
		int width = perFile.getWidth();
		int height = perFile.getHeight();
		int widthIn = perFile.getWidthIn();
		int heightIn = perFile.getHeightIn();

		cell.newVar("PLA_data_cols", Integer.valueOf(widthIn), ep);
		cell.newVar("PLA_access_rows", Integer.valueOf(heightIn), ep);
		cell.newVar("PLA_cols", Integer.valueOf(width), ep);
		cell.newVar("PLA_rows", Integer.valueOf(height), ep);

		// initialize the columns
		if (nmosInitColumns(width, x, y, xOffset, cell)) return null;

		// initialize the rows
		if (nmosInitRows(heightIn, x, y, yOffset, yMOffset, cell)) return null;

		y = -1 - yOffset;

		x = xOffset;
		int row = 0;
		int readRows = 0;

		while (readRows < heightIn)
		{
			// read in the two rows first
			int [] row1 = null, row2 = null;
			if (readRows < heightIn)
			{
				row1 = perFile.readRow();
				if (row1 == null) return null;
				readRows++;
			}

			if (readRows < heightIn)
			{
				row2 = perFile.readRow();
				if (row2 == null) return null;
				readRows++;
			}

			for (int i = 0; i < width; i++)
			{
				// place a nmos_one cell.  Connect to ground and cell up above
				if (((i % 5) == 0) && (i != 0)) // put in ground strap
				{
					if (gndStrap(i, (xOffset*i)+3, y, row, cell)) return null;
				} else
				{
					// put in bits
					if (row1[i] == 1)
					{
						// make an instance of a nmos_one
						if (nmosMakeOne(i, i*xOffset, y, false, row, cell)) return null;
					}
					if (row2[i] == 1)
					{
						// make an instance of a nmos_one
						// MIRROR object Horizontally
						if (nmosMakeOne(i, i*xOffset, y-yMOffset, true, row, cell)) return null;
					}
				}
			}

			// Now put the poly metal contact at on the other side of the row
			if (completeRow(row, xOffset*width, y, cell)) return null;
			if ((readRows % 4) == 0) y -= yOffset;

			row++;
			y -= 2*yOffset;
		}
		x = 3;
		if ((readRows % 4) == 0) y += 2*yOffset; else
			y += yOffset;
		if (finishColumns(width, x, y, xOffset, cell)) return null;

		// put a well node over the region
		Rectangle2D cellBounds = cell.getBounds();
		NodeInst.makeInstance(pla.pwNode, ep, new Point2D.Double(cellBounds.getCenterX(), cellBounds.getCenterY()),
			cellBounds.getWidth(), cellBounds.getHeight(), cell);

		perFile.done();

		return(cell);
	}

	private boolean gndStrap(int i, int x, int y, int row, Cell arrayCell)
	{
		PLA.UCItem newItem = new PLA.UCItem();

		// put in Substrate contact first
		newItem.nodeInst = pla.makePin(arrayCell, x, y+11, 14, pla.mwBut);
		if (newItem.nodeInst == null) return true;

		// wire in the first ground strap
		PortProto nodeColPort = newItem.nodeInst.getProto().getPort(0);
		PortProto lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().getPort(0);

		// connect to last column object
		pla.makeWire(pla.m1Arc, 4, pla.columnList[i].lastitem.nodeInst,
			lastColPort, newItem.nodeInst, nodeColPort, arrayCell);

		// only put this in a column list
		pla.columnList[i].lastitem.bottomItem = newItem;
		pla.columnList[i].lastitem = pla.columnList[i].lastitem.bottomItem;

		// put in metal diffusion contact
		newItem = new PLA.UCItem();
		newItem.nodeInst = pla.makePin(arrayCell, x, y+1, 14, pla.maCon);
		if (newItem.nodeInst == null) return true;

		// wire in the first ground strap
		nodeColPort = newItem.nodeInst.getProto().getPort(0);
		lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().getPort(0);

		// connect to last column object
		pla.makeWire(pla.m1Arc, 4, pla.columnList[i].lastitem.nodeInst,
			lastColPort, newItem.nodeInst, nodeColPort, arrayCell);

		// connect to last row object
		if (pla.rowList[row][1].lastitem.nodeInst.getProto() != pla.nmosOne)
			lastColPort = pla.rowList[row][1].lastitem.nodeInst.getProto().getPort(0); else
				lastColPort = pla.rowList[row][1].lastitem.nodeInst.getProto().findPortProto("GND.d.s");
		pla.makeWire(pla.aArc, 3/*14*/, pla.rowList[row][1].lastitem.nodeInst,
			lastColPort, newItem.nodeInst, nodeColPort, arrayCell);

		// insert into row and column lists
		pla.columnList[i].lastitem.bottomItem = newItem;
		pla.columnList[i].lastitem = pla.columnList[i].lastitem.bottomItem;
		pla.rowList[row][1].lastitem.rightItem = newItem;
		pla.rowList[row][1].lastitem = pla.rowList[row][1].lastitem.rightItem;
		return false;
	}

	private boolean nmosMakeOne(int i, int x, int y, boolean mirror, int row, Cell arrayCell)
	{
		/*
		 * a row has three positions 0 (a normal one object), 1 (a ground strap),
		 * and  2 (a mirrored one object).  NOTE: There is only a linked list of the
		 * ground objects
		 */
		int rowPos = 0;
		if (mirror)  // row index value for the one object
			rowPos = 2;

		NodeInst ni = pla.makeInstance(arrayCell, pla.nmosOne, x, y, mirror);
		if (ni == null) return true;

		// find the ports that need to be connected
		PortProto nodeColPort = ni.getProto().findPortProto("OUT.m-1.n");
		PortProto lastColPort;
		if (pla.columnList[i].lastitem.nodeInst.getProto() != pla.nmosOne) // a Metal pin
			lastColPort = pla.columnList[i].firstItem.nodeInst.getProto().getPort(0); else
				lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().findPortProto("OUT.m-1.n");

		// connect to last column object
		pla.makeWire(pla.m1Arc, 4, pla.columnList[i].lastitem.nodeInst, lastColPort, ni, nodeColPort, arrayCell);

		// connect to last ground object
		if (pla.rowList[row][1].lastitem == null)
		{
			System.out.println("No UCITEM at row " + row);
			return true;
		}
		if (pla.rowList[row][1].lastitem.nodeInst == null)
		{
			System.out.println("No NODEINST at row 1 " + row);
			return true;
		}

		if (pla.rowList[row][1].lastitem.nodeInst.getProto() != pla.nmosOne)
			lastColPort = pla.rowList[row][1].lastitem.nodeInst.getProto().getPort(0); else
				lastColPort =  pla.rowList[row][1].lastitem.nodeInst.getProto().findPortProto("GND.d.s");
		if (lastColPort == null)
		{
			System.out.println("No NODEPROTO for GND.d.s");
			return true;
		}
		nodeColPort = ni.getProto().findPortProto("GND.d.s");
		pla.makeWire(pla.aArc, 3/*14*/, pla.rowList[row][1].lastitem.nodeInst, lastColPort, ni, nodeColPort, arrayCell);

		// connect to last GATE object
		if (pla.rowList[row][rowPos].lastitem.nodeInst.getProto() != pla.nmosOne)
			lastColPort = pla.rowList[row][rowPos].lastitem.nodeInst.getProto().getPort(0); else
				lastColPort =  pla.rowList[row][rowPos].lastitem.nodeInst.getProto().findPortProto("GATE.p.e");
		nodeColPort = ni.getProto().findPortProto("GATE.p.w");
		pla.makeWire(pla.pArc, 0, pla.rowList[row][rowPos].lastitem.nodeInst, lastColPort,
			ni, nodeColPort, arrayCell);

		// put in column list
		pla.columnList[i].lastitem.bottomItem = new PLA.UCItem();
		pla.columnList[i].lastitem = pla.columnList[i].lastitem.bottomItem;
		pla.columnList[i].lastitem.nodeInst = ni;

		// put in ground row list
		pla.rowList[row][1].lastitem.rightItem = pla.columnList[i].lastitem;
		pla.rowList[row][1].lastitem = pla.rowList[row][1].lastitem.rightItem;

		// only keep track of the last item in this row
		// the ground list above (index 1) holds the complete list
		pla.rowList[row][rowPos].lastitem = pla.rowList[row][1].lastitem;
		return false;
	}

	private boolean completeRow(int row, int x, int y, Cell arrayCell)
	{
		PLA.UCItem newItem = new PLA.UCItem();
		newItem.nodeInst = pla.makePin(arrayCell, x, y+6, 6, pla.mpCon);
		if (newItem.nodeInst == null) return true;

		// connect to last GATE object
		PortProto lastColPort;
		if (pla.rowList[row][0].lastitem.nodeInst.getProto() != pla.nmosOne)
			lastColPort = pla.rowList[row][0].lastitem.nodeInst.getProto().getPort(0); else
				lastColPort =  pla.rowList[row][0].lastitem.nodeInst.getProto().findPortProto("GATE.p.e");
		PortProto nodeColPort = newItem.nodeInst.getProto().getPort(0);
		pla.makeWire(pla.pArc, 0, pla.rowList[row][0].lastitem.nodeInst, lastColPort, newItem.nodeInst, nodeColPort, arrayCell);
		pla.rowList[row][0].lastitem = newItem;

		// Now export port at beginning and end of this half of the row grouping
		PortProto pp = pla.rowList[row][0].firstItem.nodeInst.getProto().getPort(0);
		PortInst pi = pla.rowList[row][0].firstItem.nodeInst.findPortInstFromProto(pp);
		Export.newInst(arrayCell, pi, "ACCESS" + (row * 2) + ".m-1.w", ep);

		pp = pla.rowList[row][0].lastitem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][0].lastitem.nodeInst.findPortInstFromProto(pp);
		Export.newInst(arrayCell, pi, "ACCESS" + (row * 2) + ".m-1.e", ep);

		pp = pla.rowList[row][0].firstItem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][0].firstItem.nodeInst.findPortInstFromProto(pp);
		Export.newInst(arrayCell, pi, "ACCESS" + (row * 2) + ".p.w", ep);

		pp = pla.rowList[row][0].lastitem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][0].lastitem.nodeInst.findPortInstFromProto(pp);
		Export.newInst(arrayCell, pi, "ACCESS" + (row * 2) + ".p.e", ep);

		newItem = new PLA.UCItem();
		newItem.nodeInst = pla.makePin(arrayCell, x, y-4, 6, pla.mpCon);
		if (newItem.nodeInst == null) return true;

		// connect to last GATE object
		if (pla.rowList[row][2].lastitem.nodeInst.getProto() != pla.nmosOne)
			lastColPort = pla.rowList[row][2].lastitem.nodeInst.getProto().getPort(0); else
				lastColPort =  pla.rowList[row][2].lastitem.nodeInst.getProto().findPortProto("GATE.p.e");
		nodeColPort = newItem.nodeInst.getProto().getPort(0);
		pla.makeWire(pla.pArc, 0, pla.rowList[row][2].lastitem.nodeInst, lastColPort, newItem.nodeInst, nodeColPort, arrayCell);
		pla.rowList[row][2].lastitem = newItem;

		pp = pla.rowList[row][2].firstItem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][2].firstItem.nodeInst.findPortInstFromProto(pp);
		Export.newInst(arrayCell, pi, "ACCESS" + (row * 2 + 1) + ".m-1.w", ep);

		pp = pla.rowList[row][2].lastitem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][2].lastitem.nodeInst.findPortInstFromProto(pp);
		Export.newInst(arrayCell, pi, "ACCESS" + (row * 2 + 1) + ".m-1.e", ep);

		pp = pla.rowList[row][2].firstItem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][2].firstItem.nodeInst.findPortInstFromProto(pp);
		Export.newInst(arrayCell, pi, "ACCESS" + (row * 2 + 1) + ".p.w", ep);

		pp = pla.rowList[row][2].lastitem.nodeInst.getProto().getPort(0);
		pi = pla.rowList[row][2].lastitem.nodeInst.findPortInstFromProto(pp);
		Export.newInst(arrayCell, pi, "ACCESS" + (row * 2 + 1) + ".p.e", ep);
		return false;
	}

	private boolean nmosInitColumns(int width, int x, int y, int xOffset, Cell arrayCell)
	{
		int gndCnt = 0;
		for (int i = 0; i < width; i++)
		{
			pla.columnList[i].firstItem = new PLA.UCItem();
			pla.columnList[i].lastitem =  pla.columnList[i].firstItem;

			// put in a Ground  pin every 5th position
			String name;
			if ((i % 5) == 0)
			{
				name = "GND" + gndCnt + ".m-1.n";
				pla.columnList[i].firstItem.nodeInst = pla.makePin(arrayCell,
					xOffset * i + x, y, 14, pla.mwBut);
				if (pla.columnList[i].firstItem.nodeInst == null) return true;
				gndCnt++;
			} else
			{
				// must be a data pin, don't count ground pins
				name = "DATA" + (i - gndCnt) + ".m-1.n";
				pla.columnList[i].firstItem.nodeInst = pla.makePin(arrayCell, xOffset * i + x, y, 4, pla.m1Pin);
				if (pla.columnList[i].firstItem.nodeInst == null) return true;
			}
			PortProto pp = pla.columnList[i].firstItem.nodeInst.getProto().getPort(0);
			PortInst pi = pla.columnList[i].firstItem.nodeInst.findPortInstFromProto(pp);
			Export.newInst(arrayCell, pi, name, ep);
		}
		return false;
	}

	private boolean nmosInitRows(int heightIn, int x, int y, int yOffset, int yMOffset, Cell arrayCell)
	{
		int limit = (heightIn/2) + (heightIn % 2);
		for (int i = 0; i < limit; i++)
		{
			if (((i % 2) == 0) && (i != 0)) y -= yOffset;

			PLA.UCItem newItem = new PLA.UCItem();

			// put in Substrate contact first
			newItem.nodeInst = pla.makePin(arrayCell, x, y-yMOffset+10, 14, pla.mwBut);
			if (newItem.nodeInst == null) return true;

			// wire in the first ground strap
			PortProto nodeColPort = newItem.nodeInst.getProto().getPort(0);
			PortProto lastColPort = pla.columnList[0].lastitem.nodeInst.getProto().getPort(0);

			// connect to last column object
			pla.makeWire(pla.m1Arc, 4, pla.columnList[0].lastitem.nodeInst, lastColPort, newItem.nodeInst, nodeColPort, arrayCell);

			// only put this in a column list
			pla.columnList[0].lastitem.bottomItem = newItem;
			pla.columnList[0].lastitem = pla.columnList[0].lastitem.bottomItem;

			pla.rowList[i][0].firstItem = new PLA.UCItem();
			pla.rowList[i][0].lastitem =  pla.rowList[i][0].firstItem;
			pla.rowList[i][1].firstItem = new PLA.UCItem();
			pla.rowList[i][1].lastitem =  pla.rowList[i][1].firstItem;
			pla.rowList[i][2].firstItem = new PLA.UCItem();
			pla.rowList[i][2].lastitem =  pla.rowList[i][2].firstItem;

			pla.rowList[i][0].firstItem.nodeInst = pla.makePin(arrayCell, x-7, y-5, 6, pla.mpCon);
			if (pla.rowList[i][0].firstItem.nodeInst == null) return true;
			pla.rowList[i][1].firstItem.nodeInst = pla.makePin(arrayCell, x, y-yMOffset, 14, pla.maCon);
			if (pla.rowList[i][1].firstItem.nodeInst == null) return true;
			pla.rowList[i][2].firstItem.nodeInst = pla.makePin(arrayCell, x-7, y-15, 6, pla.mpCon);
			if (pla.rowList[i][2].firstItem.nodeInst == null) return true;

			// wire in the first ground strap
			nodeColPort = pla.rowList[i][1].firstItem.nodeInst.getProto().getPort(0);
			lastColPort = pla.columnList[0].lastitem.nodeInst.getProto().getPort(0);

			// connect to last column object
			pla.makeWire(pla.m1Arc, 4, pla.columnList[0].lastitem.nodeInst, lastColPort, pla.rowList[i][1].lastitem.nodeInst, nodeColPort, arrayCell);
			pla.columnList[0].lastitem.bottomItem = pla.rowList[i][1].lastitem;
			pla.columnList[0].lastitem = pla.columnList[0].lastitem.bottomItem;

			y -= 2*yOffset;
		}
		return false;
	}

	private boolean finishColumns(int width, int x, int y, int xOffset, Cell arrayCell)
	{
		int gndCnt = 0;
		for (int i = 0; i < width; i++)
		{
			PLA.UCItem newItem = new PLA.UCItem();

			// put in a Ground  pin every 5th position
			String name;
			PortProto lastColPort;
			if ((i % 5) == 0)
			{
				name = "GND" + gndCnt + ".m-1.s";
				newItem.nodeInst = pla.makePin(arrayCell, xOffset*i+x, y, 14, pla.mwBut);
				if (newItem.nodeInst == null) return true;
				lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().getPort(0);
				gndCnt++;
			} else
			{
				// must be a data pin, don't count ground pins
				name = "DATA" + (i - gndCnt) + ".m-1.s";
				newItem.nodeInst = pla.makePin(arrayCell, xOffset * i + x, y, 4, pla.m1Pin);
				if (newItem.nodeInst == null) return true;
				if (pla.columnList[i].lastitem.nodeInst.getProto() != pla.nmosOne)
					lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().getPort(0); else
						lastColPort = pla.columnList[i].lastitem.nodeInst.getProto().findPortProto("OUT.m-1.n");
			}

			// wire in the first ground strap
			PortProto nodeColPort = newItem.nodeInst.getProto().getPort(0);

			// connect to last column object
			pla.makeWire(pla.m1Arc, 4, pla.columnList[i].lastitem.nodeInst, lastColPort, newItem.nodeInst, nodeColPort, arrayCell);

			// only put this in a column list
			pla.columnList[i].lastitem.bottomItem = newItem;
			pla.columnList[i].lastitem = pla.columnList[i].lastitem.bottomItem;
			PortInst pi = newItem.nodeInst.findPortInstFromProto(newItem.nodeInst.getProto().getPort(0));
			Export.newInst(arrayCell, pi, name, ep);
		}
		return false;
	}
}
