/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: BookshelfNets.java
 *
 * Copyright (c) 2010, 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.io.input.bookshelf;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.geometry.EPoint;
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.NodeProto;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.database.variable.Variable.Key;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Artwork;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.io.input.bookshelf.BookshelfNodes.BookshelfNode;
import com.sun.electric.tool.io.input.bookshelf.BookshelfNodes.BookshelfPin;
import com.sun.electric.util.CollectionFactory;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.Orientation;

import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author Felix Schmidt
 * 
 */
public class BookshelfNets implements BookshelfInputParser<Void> {

	private String fileName;
	private Library lib;
    private final EditingPreferences ep;
	private Map<String, PortInst> netIndex = CollectionFactory.createHashMap();

	public BookshelfNets(String fileName, Library lib, EditingPreferences ep) {
		this.fileName = fileName;
		this.lib = lib;
        this.ep = ep;
	}

	public Void parse() throws IOException {

		Job.getUserInterface().setProgressNote("Parse Net List File: Step 1/4");

		netIndex.clear();

		BufferedReader rin;
		try
		{
			File file = new File(fileName);
			FileReader freader = new FileReader(file);
			rin = new BufferedReader(freader);
		} catch (FileNotFoundException e) {
			System.out.println("ERROR: Cannot find Bookshelf Nets file: " + fileName);
			return null;
		}

		Map<String, List<BookshelfPin>> pins = CollectionFactory.createHashMap();

		// make a map of ports on each node
		Map<BookshelfNode, Set<String>> nodePorts = new HashMap<BookshelfNode, Set<String>>();
        Technology mocmos = Technology.getMocmosTechnology();

		List<BookshelfNet> allNets = new ArrayList<BookshelfNet>();
		String line;
		while ((line = rin.readLine()) != null) {
			if (line.startsWith("NetDegree")) {
				NetDesc desc = parseNetDesc(line);
				BookshelfNet net = new BookshelfNet(desc.netName);
				allNets.add(net);
				for (int i = 0; i < desc.elements; i++) {
					line = rin.readLine();
					BookshelfPin pin = parsePin(line, net);
					if (!pins.containsKey(pin.getNodeName())) {
						pins.put(pin.getNodeName(), new ArrayList<BookshelfPin>());
					}
					pins.get(pin.getNodeName()).add(pin);
					net.getPins().add(pin);

					// make a list of ports on each node
					BookshelfNode bn = BookshelfNode.findNode(pin.getNodeName());
					if (bn != null) {
						Set<String> portsOnNode = nodePorts.get(bn);
						if (portsOnNode == null)
							nodePorts.put(bn, portsOnNode = new TreeSet<String>());
						portsOnNode.add(pin.getLocation().getX() + "," + pin.getLocation().getY());
					}
				}
			}
		}

		Job.getUserInterface().setProgressNote("Parse Net List File: Step 2/4");

		// figure out what cell instances exist
		Map<String, List<BookshelfNode>> nodesBySize = new LinkedHashMap<String, List<BookshelfNode>>();
		int allNodesNum = BookshelfNode.getAllNodes().size();
		int counter = 0;
		for (BookshelfNode bn : BookshelfNode.getAllNodes()) {
			Job.getUserInterface().setProgressValue(counter * 100 / allNodesNum);
			String bnName = bn.getWidth() + "X" + bn.getHeight();
			bnName += "Y" + Boolean.toString(bn.isTerminal());
			Set<String> ports = nodePorts.get(bn);
			if (ports != null) {
				for (String portName : ports)
					bnName += "/" + portName;
			}
			List<BookshelfNode> nodes = nodesBySize.get(bnName);
			if (nodes == null)
				nodesBySize.put(bnName, nodes = new ArrayList<BookshelfNode>());
			nodes.add(bn);
			counter++;
		}

		Job.getUserInterface().setProgressNote("Parse Net List File: Step 3/4");

		// now create cells
		int cellNumber = 1;
		counter = 0;
		allNodesNum = nodesBySize.keySet().size();
		for (String desc : nodesBySize.keySet()) {
			Job.getUserInterface().setProgressValue(counter * 100 / allNodesNum);
			List<BookshelfNode> nodes = nodesBySize.get(desc);
			String[] parts = desc.split("/");
			String[] size = parts[0].split("Y")[0].split("X");
			String terminal = parts[0].split("Y")[1];
			double width = TextUtils.atof(size[0]);
			double height = TextUtils.atof(size[1]);
			Cell cell = Cell.makeInstance(ep, lib, "Cell" + cellNumber + "{lay}");
            cell.setTechnology(mocmos);
			for (BookshelfNode bn : nodes)
				bn.setPrototype(cell);
			cellNumber++;
			NodeProto np = Artwork.tech().boxNode;
			NodeInst nInst = NodeInst.makeInstance(np, ep, EPoint.fromLambda(0, 0), width, height, cell);
			if (terminal.equals(Boolean.toString(true))) {
				nInst.setLocked();
			}
			int portNum = 1;
			for (int i = 1; i < parts.length; i++) {
				String[] xy = parts[i].split(",");
				double x = TextUtils.atof(xy[0]);
				double y = TextUtils.atof(xy[1]);
				NodeProto pin = mocmos.findNodeProto("Metal-1-Pin"); //Artwork.tech().pinNode;
				NodeInst ni = NodeInst.makeInstance(pin, ep, EPoint.fromLambda(x, y), 0, 0, cell);
				PortInst pi = ni.getOnlyPortInst();
				Export.newInstance(cell, pi, "P" + portNum, ep);
				portNum++;
			}
			counter++;
		}

		// now create all the nodes
		Cell mainCell = Cell.makeInstance(ep, lib, lib.getName() + "{lay}");
        mainCell.setTechnology(mocmos);
		Collection<BookshelfNode> allNodes = BookshelfNode.getAllNodes();
		for (BookshelfNode bn : allNodes) {
			Cell np = bn.getPrototype();
			NodeInst ni = NodeInst.newInstance(np, ep, new Point2D.Double(bn.getX(), bn.getY()), bn.getWidth(), bn
					.getHeight(), mainCell, Orientation.IDENT, bn.getName());
			Key key = Variable.newKey("weight");
			Variable var = Variable.newInstance(key, new Integer(bn.getWeight()), ep.getNodeTextDescriptor());
			ni.addVar(var);
			
			bn.setInstance(ni);
		}

		Job.getUserInterface().setProgressNote("Parse Net List File: Step 4/4");

		// now run the nets
		allNodesNum = allNets.size();
		counter = 0;
		for (BookshelfNet bn : allNets) {
			Job.getUserInterface().setProgressValue(counter * 100 / allNodesNum);
			Job.getUserInterface().setProgressNote(
					"Parse Net List File: Step 4/4 (" + counter + "/" + allNodesNum + ")");
			BookshelfPin lastPin = null;
			for (BookshelfPin bp : bn.getPins()) {
				if (lastPin != null) {
					// find the exports
					BookshelfNode bn1 = BookshelfNode.findNode(lastPin.getNodeName());
					BookshelfNode bn2 = BookshelfNode.findNode(bp.getNodeName());
					EPoint ep1 = EPoint.fromLambda(lastPin.getLocation().getX(), lastPin.getLocation().getY());
					EPoint ep2 = EPoint.fromLambda(bp.getLocation().getX(), bp.getLocation().getY());

					// find the proper pin
					PortInst pi1 = null;
					for (Iterator<Export> it = bn1.getPrototype().getExports(); it.hasNext();) {
						Export e = it.next();
						NodeInst ni = e.getOriginalPort().getNodeInst();
						if (ni.getAnchorCenterX() == ep1.getX() && ni.getAnchorCenterY() == ep1.getY()) {
							pi1 = bn1.getInstance().findPortInstFromProto(e);
							break;
						}
					}

					// find the proper pin
					PortInst pi2 = null;
					for (Iterator<Export> it = bn2.getPrototype().getExports(); it.hasNext();) {
						Export e = it.next();
						NodeInst ni = e.getOriginalPort().getNodeInst();
						if (ni.getAnchorCenterX() == ep2.getX() && ni.getAnchorCenterY() == ep2.getY()) {
							pi2 = bn2.getInstance().findPortInstFromProto(e);
							break;
						}
					}
					if (pi1 == null) {
						System.out.println("UNABLE TO FIND PORT AT (" + lastPin.getLocation().getX() + ","
								+ lastPin.getLocation().getY() + ") ON INSTANCE " + bn1.getName() + " (CELL "
								+ bn1.getPrototype().describe(false) + ")");
						continue;
					}
					if (pi2 == null) {
						System.out.println("UNABLE TO FIND PORT AT (" + bp.getLocation().getX() + ","
								+ bp.getLocation().getY() + ") ON INSTANCE " + bn2.getName() + " (CELL "
								+ bn2.getPrototype().describe(false) + ")");
						continue;
					}
					EPoint p1 = EPoint.fromLambda(bn1.getX() + ep1.getLambdaX(), bn1.getY() + ep1.getLambdaY());
					EPoint p2 = EPoint.fromLambda(bn2.getX() + ep2.getLambdaX(), bn2.getY() + ep2.getLambdaY());
					// if (!pi1.getCenter().equals(p1)) {
					// System.out.println("pi1=" + pi1.getCenter() + " p1=" +
					// p1);
					// }
					// if (!pi2.getCenter().equals(p2)) {
					// System.out.println("pi2=" + pi2.getCenter() + " p2=" +
					// p2);
					// }
					ArcProto ap = Generic.tech().unrouted_arc;
					newInstance(mainCell, ap, bn.name, pi1, pi2, p1, p2, 0, 0, 0);
				}
				lastPin = bp;
				counter++;
			}

		}
		return null;
	}

	public void newInstance(Cell parent, ArcProto protoType, String name, PortInst headPort, PortInst tailPort,
			EPoint headPt, EPoint tailPt, long gridExtendOverMin, int angle, int flags) {
		if (true) {
            TextDescriptor td = ep.getArcTextDescriptor();
			ArcInst.newInstanceNoCheck(parent, protoType, name, td, headPort, tailPort, headPt, tailPt, gridExtendOverMin,
					angle, flags);
		} //else {
			//ArcInst.makeInstance(protoType, headPort, tailPort);
			// // make sure the arc can connect to these ports
			// PortProto headProto = headPort.getPortProto();
			// PortProto tailProto = tailPort.getPortProto();
			// Name nameKey = Name.findName(name);
			// TextDescriptor nameDescriptor =
			// TextDescriptor.getArcTextDescriptor();
			//
			// // search for spare arcId
			// CellId parentId = parent.getId();
			// int arcId;
			// do {
			// arcId = parentId.newArcId();
			// } while (parent.getArcById(arcId) != null);
			// ImmutableArcInst d = ImmutableArcInst.newInstance(arcId,
			// protoType.getId(), nameKey, nameDescriptor,
			// tailPort.getNodeInst().getD().nodeId, tailProto.getId(), tailPt,
			// headPort.getNodeInst().getD().nodeId, headProto.getId(), headPt,
			// gridExtendOverMin, angle, flags);
			// Topology topology = parent.getTopology();
			// ArcInst ai = new ArcInst(topology, d, headPort, tailPort);
			// topology.addArc(ai);
		//}
	}

	private BookshelfPin parsePin(String line, BookshelfNet net) {

		String[] splited = line.trim().split(" ");

		double xPos = 0;
		double yPos = 0;
		if (splited.length == 6) {
			xPos = Double.parseDouble(splited[4]);
			yPos = Double.parseDouble(splited[5]);
		}

		Point2D location = new Point2D.Double(xPos, yPos);
		return new BookshelfPin(location, net, splited[0]);
	}

	private NetDesc parseNetDesc(String line) {
		NetDesc result = new NetDesc();

		String[] splited = line.split(" ");
		result.elements = Integer.parseInt(splited[2]);
		result.netName = splited[splited.length - 1];

		return result;
	}

	private class NetDesc {
		public String netName;
		public int elements;
	}

	public static class BookshelfNet {
		private List<BookshelfNode> nodes;
		private List<BookshelfPin> pins;
		private String name;

		public BookshelfNet(String name) {
			nodes = CollectionFactory.createArrayList();
			pins = CollectionFactory.createArrayList();
			this.name = name;
		}

		/**
		 * @param nodes
		 *            the nodes to set
		 */
		public void setNodes(List<BookshelfNode> nodes) {
			this.nodes = nodes;
		}

		/**
		 * @return the nodes
		 */
		public List<BookshelfNode> getNodes() {
			return nodes;
		}

		/**
		 * @param pins
		 *            the pins to set
		 */
		public void setPins(List<BookshelfPin> pins) {
			this.pins = pins;
		}

		/**
		 * @return the pins
		 */
		public List<BookshelfPin> getPins() {
			return pins;
		}
	}

}
