/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: EDIFEquiv.java
 *
 * 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.io.output;

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.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.io.IOTool;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.MutableInteger;
import com.sun.electric.util.math.Orientation;

import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Reads in a configuration file that specifies the equivalent Electric nodes for Nodes
 * found in (or to be written to) the EDIF file.  This allows mapping of Electric primitives
 * and Cells to primitives and cells in the target Tool which will read in the EDIF, or vice versa.
 * Most importantly, it specifies equivalences between ports on the two nodes which may be in
 * different locations.  Differing sizes of nodes does not matter.
 * <P>
 * This is currently only being used with Cadence Virtuoso Composer.
 */
public class EDIFEquiv {



    private HashMap<Object,NodeEquivalence> equivsByNodeProto;      // key: Electric hash (getElectricKey()), value: NodeEquivalence
    private HashMap<Object,NodeEquivalence> equivsByExternal;       // key: External hash (getExternalKey()), value: NodeEquivalence
    private HashMap<String,VariableEquivalence> exportByVariable;       // key: External hash (electric varname), value: VariableEquivalence
    private HashMap<String,VariableEquivalence> exportFromVariable;     // key: External hash (external varname), value: VariableEquivalence
    private HashMap<String,FigureGroupEquivalence> exportByFigureGroup;    // key: External hash (electric figureGroupName), value: FigureGroupEquivalence
    private HashMap<String,FigureGroupEquivalence> exportFromFigureGroup;  // key: External hash (external figureGroupName), value: FigureGroupEquivalence
    private HashMap<String,GlobalEquivalence> exportByGlobal;    		// key: External hash (electric figureGroupName), value: GlobalEquivalence
    private HashMap<String,GlobalEquivalence> exportFromGlobal;  		// key: External hash (external figureGroupName), value: GlobalEquivalence

    /**
     * Get the node equivalence for the NodeInst.  This must be a NodeInst, not a
     * NodeProto, because different transistor primitives share the same Primitive
     * prototype.
     * @param ni the NodeInst to look up
     * @return null if none found
     */
    public NodeEquivalence getNodeEquivalence(NodeInst ni) {
        NodeProto np = ni.getProto();
        PrimitiveNode.Function func = np.getFunction();
        PortCharacteristic exportType = null;
        if (!ni.isCellInstance()) {
            func = ni.getFunction();
//            func = pn.getTechnology().getPrimitiveFunction(pn, ni.getTechSpecific());
            // if this is an off page node and one of it's ports is exported, find out type
            if (np == Schematics.tech().offpageNode) {
                for (Iterator<PortProto> it = ni.getParent().getPorts(); it.hasNext(); ) {
                    Export e = (Export)it.next();
                    if (e.getOriginalPort().getNodeInst() == ni) {
                        exportType = e.getCharacteristic();
                        break;
                    }
                }
            }
        }
        return equivsByNodeProto.get(getElectricKey(np, func, exportType));
    }

	/**
	 * Method to get the VariableEquivalence that maps Electric variable names to external names.
	 * @param varName the Electric variable name.
	 * @return the VariableEquivalence that tells how to do the mapping (null if none).
	 */
	public VariableEquivalence getElectricVariableEquivalence(String varName)
	{
		VariableEquivalence ve = exportByVariable.get(varName);
		return ve;
	}

	/**
	 * Method to get the VariableEquivalence that maps external variable names to Electric names.
	 * @param varName the external variable name.
	 * @return the VariableEquivalence that tells how to do the mapping (null if none).
	 */
	public VariableEquivalence getExternalVariableEquivalence(String varName)
	{
		VariableEquivalence ve = exportFromVariable.get(varName);
		return ve;
	}

	/**
	 * Method to get the FigureGroupEquivalence that maps Electric figuregroup names to external names.
	 * @param fgName the Electric figuregroup name.
	 * @return the FigureGroupEquivalence that tells how to do the mapping (null if none).
	 */
	public FigureGroupEquivalence getElectricFigureGroupEquivalence(String fgName)
	{
		FigureGroupEquivalence fge = exportByFigureGroup.get(fgName);
		return fge;
	}

	/**
	 * Method to get the FigureGroupEquivalence that maps external figuregroup names to Electric names.
	 * @param fgName the external figuregroup name.
	 * @return the FigureGroupEquivalence that tells how to do the mapping (null if none).
	 */
	public FigureGroupEquivalence getExternalFigureGroupEquivalence(String fgName)
	{
		FigureGroupEquivalence fge = exportFromFigureGroup.get(fgName);
		return fge;
	}

	/**
	 * Method to get the GlobalEquivalence that maps Electric global names to external names.
	 * @param gName the Electric global name.
	 * @return the GlobalEquivalence that tells how to do the mapping (null if none).
	 */
	public GlobalEquivalence getElectricGlobalEquivalence(String gName)
	{
		GlobalEquivalence ge = exportByGlobal.get(gName);
		return ge;
	}

	/**
	 * Method to get the GlobalEquivalence that maps external global names to Electric names.
	 * @param gName the external global name.
	 * @return the GlobalEquivalence that tells how to do the mapping (null if none).
	 */
	public GlobalEquivalence getExternalGlobalEquivalence(String gName)
	{
		GlobalEquivalence ge = exportFromGlobal.get(gName);
		return ge;
	}

	/**
     * Get the node equivalence for the external reference.
     * @param extLib
     * @param extCell
     * @param extView
     * @return  null if none found
     */
    public NodeEquivalence getNodeEquivalence(String extLib, String extCell, String extView) {
        Object key = getExternalKey(extLib, extCell, extView);
        return equivsByExternal.get(key);
    }

    /**
     * Get a list of NodeEquivalences
     * @return  a list of NodeEquivalence objects
     */
    public List<NodeEquivalence> getNodeEquivs() {
        return new ArrayList<NodeEquivalence>(equivsByExternal.values());
    }

    /**
     * Translate a port location on an Electric node to a the equivalent port
     * location on the equivalent external node instance.
     * @param connPoint the electric connection point
     * @param pi the port inst
     * @return the connection point on the equivalent external node instance
     */
    public Point2D translatePortConnection(Point2D connPoint, PortInst pi) {
        NodeInst ni = pi.getNodeInst();
        NodeEquivalence equiv = getNodeEquivalence(ni);
        if (equiv == null) return connPoint;
        PortEquivalence pe = equiv.getPortEquivElec(pi.getPortProto().getName());
        if (pe == null) return connPoint;
        FixpTransform af2 = ni.getOrient().concatenate(Orientation.fromAngle(-equiv.rotation*10)).pureRotate();
        return pe.translateElecToExt(connPoint, af2);
    }

    /**
     * Translate a port location on an external node instance to the equivalent
     * port location on the equivalent Electric node instance
     * @param connPoint the connection point on the external node instance
     * @param externalLib the external node's library
     * @param externalCell the external node
     * @param externalView the external node's view
     * @param externalPort the external node's port in question
     * @return the connection point on the equivalent electric node instance
     */
    public Point2D translatePortConnection(Point2D connPoint, String externalLib, String externalCell,
                                           String externalView, String externalPort, String orientation) {
        NodeEquivalence equiv = getNodeEquivalence(externalLib, externalCell, externalView);
        if (equiv == null) return connPoint;
        PortEquivalence pe = equiv.getPortEquivExt(externalPort);
        if (pe == null) return connPoint;
        int angle = 0;
        boolean mirroredAboutXAxis = false;
        boolean mirroredAboutYAxis = false;
        if (orientation.indexOf("R90") != -1) angle = 900;
        if (orientation.indexOf("R180") != -1) angle = 1800;
        if (orientation.indexOf("R270") != -1) angle = 2700;
        if (orientation.indexOf("MX") != -1) mirroredAboutXAxis = true;
        if (orientation.indexOf("MY") != -1) mirroredAboutYAxis = true;
        Orientation orient = Orientation.fromJava(angle, mirroredAboutYAxis, mirroredAboutXAxis);
        orient = orient.concatenate(Orientation.fromAngle(equiv.rotation*10));
        FixpTransform af2 = orient.pureRotate();
        Point2D ret = pe.translateExtToElec(connPoint, af2);
        if (equiv.xOffset != 0 || equiv.yOffset != 0)
        	ret = new Point2D.Double(ret.getX()+equiv.xOffset, ret.getY()+equiv.yOffset);
        return ret;
    }

    // hash map key
    private Object getElectricKey(NodeProto np, PrimitiveNode.Function func, PortCharacteristic portType) {
        if (func == null) func = PrimitiveNode.Function.UNKNOWN;
        if (portType == null) portType = PortCharacteristic.UNKNOWN;
        return np.getName() + " " + func.toString() + " " + portType.getName();
    }

    // hash map key
    private Object getExternalKey(String externalLib, String externalCell, String externalView) {
        return externalLib + " " + externalCell + " " + externalView;
    }

    // size of elecPorts and extPorts lists must be equal
    private void addNodeEquiv(NodeProto np, PrimitiveNode.Function func, PortCharacteristic exportType, int rot,
                         String extLib, String extCell, String extView, List<Port> elecPorts, List<Port> extPorts) {
        List<PortEquivalence> portEquivs = new ArrayList<PortEquivalence>();
        if (elecPorts.size() != extPorts.size()) {
            System.out.println("Error, port lists differ in size!");
            return;
        }
        for (int i=0; i<elecPorts.size(); i++) {
            PortEquivalence pe = new PortEquivalence(elecPorts.get(i), extPorts.get(i));
            portEquivs.add(pe);
        }
        NodeEquivalence equiv = new NodeEquivalence(np, func, exportType, extLib, extCell, extView, rot, portEquivs);
        equivsByExternal.put(getExternalKey(extLib, extCell, extView), equiv);
        equivsByNodeProto.put(getElectricKey(np, func, exportType), equiv);
    }

    /**
     * Create a new EDIF equivalence object. This contains equivalence information between
     * Electric cells/prims and Exteranal EDIF cells/prims. The equivalence information is
     * read from a configuration file. The file has the format:
     * <pre>
     * C Lib Cell View rotation { porta(x,y), ... } ExternalLib ExternalCell ExternalView { porta(x,y), ... }
     * P Tech NodeName Function rotation { porta(x,y), ... } ExternalLib ExternalCell ExternalView { porta(x,y), ... }
     * F FigureGroup ExternalFigureGroup
     * V VariableName ExternalVariableName [appendToElectricOutput]
     * # comment
     * </pre>
     * 'C' is for Cell, and 'P' is for Primitive. The left hand size specifies the Electric cell/node,
     * while the right hand side specifies the External tool's cell/node.  The list of ports must be the
     * same in length, and specify the x,y coordinate of the port.  This coordinate is on the prototype of
     * the node, or also when the node is default size at 0,0.  Note that Electric port locations should
     * be locations after the node has been rotated, if rot is not 0.  Rotation should be in tenth-degrees.
     *
     * For 'F', an association between internal FigureGroup names and external names is declared.
     * For 'V', an association between internal Variable names and external names is declared.  You
     * can also specify a string to be append to all matching Variable values.
     */
    public EDIFEquiv(String configurationFile) {
        equivsByNodeProto = new HashMap<Object,NodeEquivalence>();
        equivsByExternal = new HashMap<Object,NodeEquivalence>();
		exportByVariable = new HashMap<String,VariableEquivalence>();
		exportFromVariable = new HashMap<String,VariableEquivalence>();
		exportByFigureGroup = new HashMap<String,FigureGroupEquivalence>();
		exportFromFigureGroup = new HashMap<String,FigureGroupEquivalence>();
		exportByGlobal = new HashMap<String,GlobalEquivalence>();
		exportFromGlobal = new HashMap<String,GlobalEquivalence>();

		String file = configurationFile;
		if (file.length() == 0)
		{
            System.out.println("No EDIF configuration information being used");
            return;
		}
        File fd = new File(file);
        if (!fd.exists()) {
            System.out.println("Error: EDIF configuration file not found: "+fd.getAbsolutePath());
            return;
        }
        System.out.println("Using EDIF configuration file: "+fd.getAbsolutePath());
        try {
            FileReader reader = new FileReader(fd);
            BufferedReader bufReader = new BufferedReader(reader);
            String line = "";
            int lineno = 1;
            while ((line = bufReader.readLine()) != null) {
                readLine(line, lineno);
                lineno++;
            }
        } catch (IOException e) {
            System.out.println("Error reading EDIF config file ("+fd.getAbsolutePath()+"): "+e.getMessage());
            return;
        }
    }

    private static final Pattern portsPat = Pattern.compile("(.+?)\\{(.+?)\\}(.+?)\\{(.+?)\\}");
    /**
     * Read one line of the configuration file.  See Constructor for file format.
     * @param line
     * @param lineno
     * @return false on error
     */
    private boolean readLine(String line, int lineno) {
        line = line.trim();
        if (line.equals("")) return true;
        if (line.startsWith("#")) return true;

		// special case for Variable equivalences
		if (line.startsWith("V"))
		{
	        String [] parts = line.split("\\s+");
			String append = "";
			double scale = TextUtils.atof(parts[3]);
			if (parts.length == 5) append = parts[4];
			VariableEquivalence ve = new VariableEquivalence(parts[1], parts[2], scale, append);
			exportByVariable.put(parts[1], ve);
			exportFromVariable.put(parts[2], ve);
			return true;
		}

		// special case for FigureGroup equivalences
		if (line.startsWith("F"))
		{
	        String [] parts = line.split("\\s+");
			FigureGroupEquivalence fge = new FigureGroupEquivalence(parts[1], parts[2]);
			exportByFigureGroup.put(parts[1], fge);
			exportFromFigureGroup.put(parts[2], fge);
			return true;
		}

		// special case for Global equivalences
		if (line.startsWith("G"))
		{
	        String [] parts = line.split("\\s+");
			GlobalEquivalence ge = new GlobalEquivalence(parts[1], parts[2]);
			exportByGlobal.put(parts[1], ge);
			exportFromGlobal.put(parts[2], ge);
			return true;
		}

		// grab port parts
        Matcher mat = portsPat.matcher(line);
        if (!mat.find()) {
            System.out.println("Wrong number of curly brackets for ports on line "+lineno);
            return false;
        }
        String elec = mat.group(1).trim();
        String elec_ports = mat.group(2).trim();
        String ext = mat.group(3).trim();
        String ext_ports = mat.group(4).trim();
        String [] parts;

        // internal definition
        NodeProto np = null;
        PrimitiveNode.Function func = null;
        PortCharacteristic portType = null;
        int rot = 0;
        parts = elec.split("\\s+");
        if (parts.length < 1) {
            System.out.println("No Electric arguments on line "+lineno);
            return false;
        }
        boolean keyE = parts[0].equalsIgnoreCase("E") ? true : false;
        boolean keyP = parts[0].equalsIgnoreCase("P") ? true : false;
        boolean keyC = parts[0].equalsIgnoreCase("C") ? true : false;
        if (keyP && parts.length != 5) {
            System.out.println("Wrong number of arguments for Electric Primitive, expected 'P tech node func rot' on line "+lineno);
            return false;
        }
        if (keyE && parts.length != 6) {
            System.out.println("Wrong number of arguments for Electric Primitive, expected 'E tech node func rot porttype' on line "+lineno);
            return false;
        }
        if (keyC && parts.length != 5) {
            System.out.println("Wrong number of arguments for Electric cell, expected 'C lib cell view rot' on line "+lineno);
            return false;
        }
        if (keyP || keyE) {
            // primitive node
        	np = null;
            Technology tech = Technology.findTechnology(parts[1]);
            if (tech == null)
            {
                System.out.println("Could not find Technology " + parts[1] + " on line " + lineno);
                return false;
            }
            np = tech.findNodeProto(parts[2]);
            if (np == null)
            {
                System.out.println("Could not find " + parts[2] + " in technology " +
                	parts[1] + " on line " + lineno);
                return false;
            }
            for (PrimitiveNode.Function function : PrimitiveNode.Function.getFunctions())
            {
                if (parts[3].equals(function.getName()) || parts[3].equals(function.getShortName()) ||
                	parts[3].equals(function.getConstantName()))
                {
                    func = function;
                    break;
                }
            }
            if (func == null)
            {
                System.out.println("Could not find Function " + parts[3] + " on line " + lineno);
                return false;
            }
            try {
                rot = Integer.parseInt(parts[4]);
            } catch (NumberFormatException e) {
                System.out.println("Rotation " + parts[4] + " is not an integer on line " + lineno);
            }
            if (keyE) {
                // get port type
                portType = PortCharacteristic.findCharacteristic(parts[5]);
                if (portType == null) {
                    System.out.println("Unable to find Export type " + parts[5] + " on line " + lineno);
                    return false;
                }
            }
        } else if (keyC)
        {
            // cell
            Library lib = Library.findLibrary(parts[1]);
            if (lib == null)
            {
                System.out.println("Could not find Library " + parts[1] + " on line " + lineno);
                return false;
            }
            np = lib.findNodeProto(parts[2]+"{"+parts[3]+"}");
            if (np == null)
            {
                System.out.println("Could not find Cell " + parts[2] + ", view " + parts[3] + " in library " + parts[1] +
                	" on line " + lineno);
                return false;
            }
            func = PrimitiveNode.Function.UNKNOWN;
            try {
                rot = Integer.parseInt(parts[4]);
            } catch (NumberFormatException e) {
                System.out.println("Rotation " + parts[4] + " is not an integer on line " + lineno);
            }
        } else {
            System.out.println("Unrecognized key " + parts[0] + ", expected 'P', 'C', or 'E' on line " + lineno);
            return false;
        }

        // external definition
        String extlib, extname, extview;
        parts = ext.split("\\s+");
        if (parts.length != 3) {
            System.out.println("Wrong number of arguments for external lib, expected 'lib name view' on line "+lineno);
            return false;
        }
        extlib = parts[0];
        extname = parts[1];
        extview = parts[2];

        // port equivalences
        List<Port> elecPorts = parsePortsList(elec_ports, lineno);
        List<Port> extPorts = parsePortsList(ext_ports, lineno);
        if (elecPorts.size() != extPorts.size()) {
            System.out.println("Port lists are not the same size on line "+lineno);
            return false;
        }

        addNodeEquiv(np, func, portType, rot, extlib, extname, extview, elecPorts, extPorts);
        return true;
    }

    /**
     * String should be of the format (ignore spaces):
     * a(x,y), b(x,y), ... z(x,y)
     * @param portsList
     * @return a list of Port objects
     */
    private List<Port> parsePortsList(String portsList, int lineno) {
        // split by commas not contained in ()
        boolean opened = false;
        List<Port> ports = new ArrayList<Port>();
        int i = 0, last = 0;
        for (i=0; i<portsList.length(); i++) {
            char c = portsList.charAt(i);
            if (c == '(') {
                if (opened == true) {
                    System.out.println("Unmatched open parenthesis in ports list on line "+lineno);
                    return ports;
                }
                opened = true;
                continue;
            }
            if (c == ')') {
                if (opened == false) {
                    System.out.println("Unmatched close parenthesis in ports list on line "+lineno);
                    return ports;
                }
                opened = false;
                continue;
            }
            if (c == ',') {
                if (opened == true) // ignore commands in coords (x,y)
                    continue;
                String portDef = portsList.substring(last, i);
                last = i+1;
                Port port = parsePort(portDef, lineno);
                if (port != null) ports.add(port);
            }
        }
        // last one does not have trailing comma
        if (last < i) {
            String portDef = portsList.substring(last, i);
            Port port = parsePort(portDef, lineno);
            if (port != null) ports.add(port);
        }
        return ports;
    }

    /**
     * String should be of the format (ignore spaces):
     * a(x,y)
     * @param port
     * @return a Port object
     */
    private Port parsePort(String port, int lineno) {
        boolean ignorePort = false;
        if (port.trim().equals("NA")) return new Port("NA", new Point2D.Double(0,0), true);
        String [] fields = port.split("[(),]");
        if (fields.length != 3) {
            System.out.println("Expected port format portname(x,y), but got "+port+" on line "+lineno);
            return null;
        }
        double x = 0, y = 0;
        try {
            x = Double.parseDouble(fields[1]);
            y = Double.parseDouble(fields[2]);
        } catch (NumberFormatException e) {
            System.out.println("Could not convert port coordinate to number: "+port+", on line "+lineno);
            return null;
        }
        String name = fields[0].trim();
        if (name.equals("NA")) ignorePort = true;
        if (name.equals("\\NA")) name = "NA";
        return new Port(fields[0].trim(), new Point2D.Double(x, y), ignorePort);
    }

    public void print() {
        for (NodeEquivalence ne : equivsByNodeProto.values()) {
            System.out.println(ne.toString());
        }
    }

    // ==================================================================================
    //                            Equivalence Classes

    public static class NodeEquivalence {
        public final NodeProto np;
        public final PrimitiveNode.Function function;
        public final PortCharacteristic exortedType;

        public final String externalLib;
        public final String externalCell;
        public final String externalView;
        public final List<PortEquivalence> portEquivs;
        public final int rotation;         // in degrees, rotate the electric prim by this value to match the cadence prim
        public final double xOffset, yOffset;

        private NodeEquivalence(NodeProto np, PrimitiveNode.Function func, PortCharacteristic exportedType,
                String externalLib, String externalCell, String externalView, int rotation, List<PortEquivalence> portEquivs) {
            this.np = np;
            this.function = func;
            this.exortedType = exportedType;
            this.externalLib = externalLib;
            this.externalCell = externalCell;
            this.externalView = externalView;
            this.rotation = rotation;
            this.portEquivs = portEquivs;

            // figure out offset
			double bestXOff = 0, bestYOff = 0;
			if (rotation == 0)
			{
				Map<Double,MutableInteger> xMap = new HashMap<Double,MutableInteger>();
				Map<Double,MutableInteger> yMap = new HashMap<Double,MutableInteger>();
				for(EDIFEquiv.PortEquivalence pe : portEquivs)
				{
					Double diffX = new Double(pe.getElecPort().loc.getX() - pe.getExtPort().loc.getX());
					MutableInteger xCount = xMap.get(diffX);
					if (xCount == null) xMap.put(diffX, xCount = new MutableInteger(0));
					xCount.increment();

					Double diffY = new Double(pe.getElecPort().loc.getY() - pe.getExtPort().loc.getY());
					MutableInteger yCount = yMap.get(diffY);
					if (yCount == null) yMap.put(diffY, yCount = new MutableInteger(0));
					yCount.increment();
				}

				int bestXInc = 0;
				for(Double diffX : xMap.keySet())
				{
					MutableInteger xCount = xMap.get(diffX);
					if (xCount.intValue() > bestXInc)
					{
						bestXInc = xCount.intValue();
						bestXOff = diffX.doubleValue();
					}
				}

				int bestYInc = 0;
				for(Double diffY : yMap.keySet())
				{
					MutableInteger yCount = yMap.get(diffY);
					if (yCount.intValue() > bestYInc)
					{
						bestYInc = yCount.intValue();
						bestYOff = diffY.doubleValue();
					}
				}
			}
			xOffset = bestXOff;
			yOffset = bestYOff;
        }
        /**
         * Get the PortEquivalence object for the Electric port name.
         * @param elecPortName
         * @return null if no such port
         */
        public PortEquivalence getPortEquivElec(String elecPortName) {
            for (PortEquivalence pe : portEquivs) {
                if (pe.getElecPort().name.equals(elecPortName))
                    return pe;
            }
            return null;
        }
        /**
         * Get the PortEquivalence object for the external node's port name.
         * @param extPortName
         * @return null if no such port
         */
        public PortEquivalence getPortEquivExt(String extPortName) {
            for (PortEquivalence pe : portEquivs) {
                if (pe.getExtPort().name.equals(extPortName))
                    return pe;
            }
            return null;
        }

        /**
         * Get a list of the external ports of this equivalence class
         * @return a list of EDIFEquiv.Port objects
         */
        public List<Port> getExtPorts() {
            List<Port> extPorts = new ArrayList<Port>();
            for (PortEquivalence pe : portEquivs) {
                extPorts.add(pe.getExtPort());
            }
            return extPorts;
        }

        public String toString() {
            StringBuffer buf = new StringBuffer();
            buf.append("NodeEquivalence Elec: "+np.describe(false)+", func: "+function+"\n");
            buf.append("  Ext: "+externalLib+" "+externalCell+" "+externalView+"\n");
            for (PortEquivalence pe : portEquivs) {
                buf.append(pe.toString()+"\n");
            }
            return buf.toString();
        }
    }

    public static class PortEquivalence {
        private final Port elecPort;
        private final Port extPort;
        private PortEquivalence(Port elecPort, Port extPort) {
            this.elecPort = elecPort;
            this.extPort = extPort;
        }
        public Port getElecPort() { return elecPort; }
        public Port getExtPort() { return extPort; }
        /**
         * Translate the location of the electric port to the external port
         * @param point the location of the port in Electric.
         * @return the location of the port in EDIF.
         */
        public Point2D translateElecToExt(Point2D point, FixpTransform niPureRotation) {
            Point2D elecPoint = new Point2D.Double(elecPort.loc.getX(), elecPort.loc.getY());
            Point2D extPoint = new Point2D.Double(extPort.loc.getX(), extPort.loc.getY());
            if (niPureRotation != null) {
                elecPoint = niPureRotation.transform(elecPort.loc, elecPoint);
                extPoint = niPureRotation.transform(extPort.loc, extPoint);
            }
            return new Point2D.Double(point.getX()-(elecPoint.getX()-extPoint.getX()),
                                      point.getY()-(elecPoint.getY()-extPoint.getY()));
        }
        /**
         * Translate the location of the external port to the electric port
         * @param point the location of the port in EDIF.
         * @return the location of the port in Electric.
         */
        public Point2D translateExtToElec(Point2D point, FixpTransform niPureRotation) {
            Point2D elecPoint = new Point2D.Double(elecPort.loc.getX(), elecPort.loc.getY());
            Point2D extPoint = new Point2D.Double(extPort.loc.getX(), extPort.loc.getY());
            if (niPureRotation != null) {
                elecPoint = niPureRotation.transform(elecPort.loc, elecPoint);
                extPoint = niPureRotation.transform(extPort.loc, extPoint);
            }
            return new Point2D.Double(point.getX()-(elecPoint.getX()-extPoint.getX()),
                                      point.getY()-(elecPoint.getY()-extPoint.getY()));
        }
        public String toString() {
            return "PortEquiv Elec{ "+elecPort+" } - Ext{ "+extPort+" }";
        }
    }

    public static class VariableEquivalence {
        public final String elecVarName;
        public final String externVarName;
        public final double scale;
        public final String appendElecOutput;

        private VariableEquivalence(String elecVarName, String externVarName, double scale, String appendElecOutput) {
            this.elecVarName = elecVarName;
            this.externVarName = externVarName;
            this.scale = scale;
            this.appendElecOutput = appendElecOutput;
        }
    }

    public static class FigureGroupEquivalence {
        public final String elecFGName;
        public final String externFGName;

        private FigureGroupEquivalence(String elecFGName, String externFGName) {
            this.elecFGName = elecFGName;
            this.externFGName = externFGName;
        }
    }

    public static class GlobalEquivalence {
        public final String elecGName;
        public final String externGName;

        private GlobalEquivalence(String elecGName, String externGName) {
            this.elecGName = elecGName;
            this.externGName = externGName;
        }
    }

    public static class Port {
        public final String name;
        public final Point2D loc;
        public final boolean ignorePort;
        private Port(String name, Point2D loc, boolean ignorePort) {
            this.name = name;
            this.loc = loc;
            this.ignorePort = ignorePort;
        }
        public String toString() {
            return name+"("+loc.getX()+","+loc.getY()+")"+(ignorePort?"[ignored]":"");
        }
    }

    /** Unit Test */
    public static void mainTest() {
        EDIFEquiv eq = new EDIFEquiv(IOTool.getEDIFConfigurationFile());
        eq.print();
    }
}
