/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: ArcInst.java
 *
 * Copyright (c) 2003, 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.database.topology;

import com.sun.electric.database.EObjectInputStream;
import com.sun.electric.database.EObjectOutputStream;
import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.ImmutableArcInst;
import com.sun.electric.database.constraint.Constraints;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.EDatabase;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.id.PrimitivePortId;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.variable.DisplayedText;
import com.sun.electric.database.variable.EditWindow0;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.BoundsBuilder;
import com.sun.electric.technology.PrimitivePort;
import com.sun.electric.technology.TechPool;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.Orientation;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.NotSerializableException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
 * An ArcInst is an instance of an ArcProto (a wire type)
 * An ArcInst points to its prototype, the Cell on which it has been
 * instantiated, and the connection at either end of the wire.
 * The geometry of the wire (width and length) is captured in the
 * bounds of the Geometric portion of this object.
 * <P>
 * ArcInst objects have properties that constrain them. Here is the notion of "Fixed angle":
 * <P>
 * <CENTER><IMG SRC="doc-files/ArcInst-1.gif"></CENTER>
 * <P>
 * Here is the notion of rigid arcs:
 * <P>
 * <CENTER><IMG SRC="doc-files/ArcInst-2.gif"></CENTER>
 * <P>
 * Here is the notion of slidable arcs:
 * <P>
 * <CENTER><IMG SRC="doc-files/ArcInst-3.gif"></CENTER>
 * <P>
 * Constraints propagate hierarchically:
 * <P>
 * <CENTER><IMG SRC="doc-files/ArcInst-4.gif"></CENTER>
 */
public class ArcInst extends Geometric implements Comparable<ArcInst> {

    /**
     * the default angle of an arc (used to be 0)
     */
    public static final int DEFAULTANGLE = ImmutableArcInst.DEFAULTANGLE;
    /**
     * empty array of ArcInsts.
     */
    public static final ArcInst[] NULL_ARRAY = {};
    /**
     * The index of the tail of this ArcInst.
     */
    public static final int TAILEND = ImmutableArcInst.TAILEND;
    /**
     * The index of the head of this ArcInst.
     */
    public static final int HEADEND = ImmutableArcInst.HEADEND;
    /**
     * Key of the obsolete variable holding arc name.
     */
    public static final Variable.Key ARC_NAME = Variable.newKey("ARC_name");
    /**
     * Minimal distance of arc end to port polygon.
     */
    static final double MINPORTDISTANCE = DBMath.getEpsilon() * 0.71; // sqrt(0.5)
    // -------------------------- private data ----------------------------------
    /**
     * Owner of this ArcInst.
     */
    private final Topology topology;
    /**
     * persistent data of this ArcInst.
     */
    ImmutableArcInst d;
    /**
     * bounds after transformation.
     */
    private ERectangle visBounds;
    /**
     * PortInst on tail end of this arc instance
     */	/*package*/

    final PortInst tailPortInst;
    /**
     * PortInst on head end of this arc instance
     */	/*package*/

    final PortInst headPortInst;

//	/** 0-based index of this ArcInst in Cell. */			private int arcIndex = -1; //scanline
    /**
     * Private constructor of ArcInst.
     * @param topology the Topology of the ArcInst.
     * @param d persistent data of ArcInst.
     * @param headPort the head end PortInst.
     * @param tailPort the tail end PortInst.
     */
    public ArcInst(Topology topology, ImmutableArcInst d, PortInst headPort, PortInst tailPort) {
        this.topology = topology;

        // initialize this object
        assert topology == headPort.getNodeInst().topology;
        assert topology == tailPort.getNodeInst().topology;
        assert d.headNodeId == headPort.getNodeInst().getNodeId();
        assert d.tailNodeId == tailPort.getNodeInst().getNodeId();
        assert d.headPortId == headPort.getPortProto().getId();
        assert d.tailPortId == tailPort.getPortProto().getId();

        this.d = d;

        // create node/arc connections and place them properly
        tailPortInst = tailPort;
        headPortInst = headPort;
    }

    private Object writeReplace() {
        return new ArcInstKey(this);
    }

    private static class ArcInstKey extends EObjectInputStream.Key<ArcInst> {

        /**
         * Constructor for ArcInstKey. Although it is not invoked locally, it is used by reflection.
         */
        public ArcInstKey() {
        }

        private ArcInstKey(ArcInst ai) {
            super(ai);
        }

        @Override
        public void writeExternal(EObjectOutputStream out, ArcInst ai) throws IOException {
            if (ai.getDatabase() != out.getDatabase() || !ai.isLinked()) {
                throw new NotSerializableException(ai + " not linked");
            }
            out.writeObject(ai.topology.cell);
            out.writeInt(ai.getArcId());
        }

        @Override
        public ArcInst readExternal(EObjectInputStream in) throws IOException, ClassNotFoundException {
            Cell cell = (Cell) in.readObject();
            int arcId = in.readInt();
            ArcInst ai = cell.getArcById(arcId);
            if (ai == null) {
                throw new InvalidObjectException("ArcInst from " + cell);
            }
            return ai;
        }
    }

    /**
     * Comparator class for sorting ArcInst by their length.
     */
    public static class ArcsByLength implements Comparator<ArcInst> {

        /**
         * Method to sort ArcInst by their length.
         */
        public int compare(ArcInst a1, ArcInst a2) {
            double len1 = a1.getHeadLocation().distance(a1.getTailLocation());
            double len2 = a2.getHeadLocation().distance(a2.getTailLocation());
            if (len1 == len2) {
                return 0;
            }
            if (len1 < len2) {
                return 1;
            }
            return -1;
        }
    }

    /**
     * **************************** CREATE, DELETE, MODIFY *****************************
     */

    /**
     * Method to create a new ArcInst with appropriate defaults, connecting two PortInsts.
     * Since no coordinates are given, the ArcInst connects to the center of the PortInsts.
     * @param type the prototype of the new ArcInst.
     * @param ep EditingPreferences with default sizes and text descriptors.
     * @param head the head end PortInst.
     * @param tail the tail end PortInst.
     * @return the newly created ArcInst, or null if there is an error.
     * @deprecated but kept around for Python and BSH access
     */
    @Deprecated
    public static ArcInst makeInstance(ArcProto type, PortInst head, PortInst tail) {
    	EditingPreferences ep = EditingPreferences.getInstance();
	    ImmutableArcInst a = type.getDefaultInst(ep);
	    return newInstanceBase(type, ep, type.getDefaultLambdaBaseWidth(ep), head, tail, null, null, null, DEFAULTANGLE, a.flags);
	}

    /**
     * Method to create a new ArcInst with appropriate defaults, connecting two PortInsts.
     * Since no coordinates are given, the ArcInst connects to the center of the PortInsts.
     * @param type the prototype of the new ArcInst.
     * @param ep EditingPreferences with default sizes and text descriptors.
     * @param head the head end PortInst.
     * @param tail the tail end PortInst.
     * @return the newly created ArcInst, or null if there is an error.
     */
    public static ArcInst makeInstance(ArcProto type, EditingPreferences ep,
            PortInst head, PortInst tail) {
        ImmutableArcInst a = type.getDefaultInst(ep);
        return newInstanceBase(type, ep, type.getDefaultLambdaBaseWidth(ep), head, tail, null, null, null, DEFAULTANGLE, a.flags);
    }

    /**
     * Method to create a new ArcInst with appropriate defaults, connecting two PortInsts.
     * Since no coordinates are given, the ArcInst connects to the center of the PortInsts.
     * @param type the prototype of the new ArcInst.
     * @param ep EditingPreferences with default sizes and text descriptors.
     * @param baseWidth the base width of the new ArcInst. The width must be > 0.
     * @param head the head end PortInst.
     * @param tail the tail end PortInst.
     * @return the newly created ArcInst, or null if there is an error.
     */
    public static ArcInst makeInstanceBase(ArcProto type, EditingPreferences ep,
            double baseWidth, PortInst head, PortInst tail) {
        ImmutableArcInst a = type.getDefaultInst(ep);
        return newInstanceBase(type, ep, baseWidth, head, tail, null, null, null, DEFAULTANGLE, a.flags);
    }

    /**
     * Method to create a new ArcInst with appropriate defaults, connecting two PortInsts at specified locations.
     * This is more general than the version that does not take coordinates.
     * @param type the prototype of the new ArcInst.
     * @param ep EditingPreferences with default sizes and text descriptors.
     * @param head the head end PortInst.
     * @param tail the tail end PortInst.
     * @param headPt the coordinate of the head end PortInst.
     * @param tailPt the coordinate of the tail end PortInst.
     * @param name the name of the new ArcInst
     * @return the newly created ArcInst, or null if there is an error.
     */
    public static ArcInst makeInstance(ArcProto type, EditingPreferences ep,
            PortInst head, PortInst tail,
            Point2D headPt, Point2D tailPt, String name) {
        ImmutableArcInst a = type.getDefaultInst(ep);
        return newInstanceBase(type, ep, type.getDefaultLambdaBaseWidth(ep), head, tail, headPt, tailPt, name, DEFAULTANGLE, a.flags);
    }

    /**
     * Method to create a new ArcInst with appropriate defaults, connecting two PortInsts at specified locations.
     * This is more general than the version that does not take coordinates.
     * @param type the prototype of the new ArcInst.
     * @param ep EditingPreferences with default sizes and text descriptors.
     * @param baseWidth the base width of the new ArcInst. The width must be > 0.
     * @param head the head end PortInst.
     * @param tail the tail end PortInst.
     * @param headPt the coordinate of the head end PortInst.
     * @param tailPt the coordinate of the tail end PortInst.
     * @param name the name of the new ArcInst
     * @return the newly created ArcInst, or null if there is an error.
     */
    public static ArcInst makeInstanceBase(ArcProto type, EditingPreferences ep,
            double baseWidth, PortInst head, PortInst tail,
            Point2D headPt, Point2D tailPt, String name) {
        ImmutableArcInst a = type.getDefaultInst(ep);
        return newInstanceBase(type, ep, baseWidth, head, tail, headPt, tailPt, name, DEFAULTANGLE, a.flags);
    }

    /**
     * Method to create a new ArcInst connecting two PortInsts.
     * Since no coordinates are given, the ArcInst connects to the center of the PortInsts.
     * @param type the prototype of the new ArcInst.
     * @param ep EditingPreferences with default sizes and text descriptors.
     * @param baseWidth the base width of the new ArcInst. The width must be > 0.
     * @param head the head end PortInst.
     * @param tail the tail end PortInst.
     * @return the newly created ArcInst, or null if there is an error.
     */
    public static ArcInst newInstanceBase(ArcProto type, EditingPreferences ep,
            double baseWidth, PortInst head, PortInst tail) {
        return newInstanceBase(type, ep, baseWidth, head, tail, null, null, null, DEFAULTANGLE, ImmutableArcInst.DEFAULT_FLAGS);
    }

    /**
     * Method to create a new ArcInst connecting two PortInsts at specified locations.
     * This is more general than the version that does not take coordinates.
     * @param type the prototype of the new ArcInst.
     * @param ep EditingPreferences with default sizes and text descriptors.
     * @param baseWidth the base width of the new ArcInst. The width must be > 0.
     * @param head the head end PortInst.
     * @param tail the tail end PortInst.
     * @param headPt the coordinate of the head end PortInst.
     * @param tailPt the coordinate of the tail end PortInst.
     * @param name the name of the new ArcInst
     * @param defAngle default angle in case port points coincide
     * @return the newly created ArcInst, or null if there is an error.
     */
    public static ArcInst newInstanceBase(ArcProto type, EditingPreferences ep,
            double baseWidth, PortInst head, PortInst tail,
            Point2D headPt, Point2D tailPt, String name, int defAngle) {
        return newInstanceBase(type, ep, baseWidth, head, tail, headPt, tailPt, name, defAngle, ImmutableArcInst.DEFAULT_FLAGS);
    }

    /**
     * Method to create a new ArcInst connecting two PortInsts at specified locations.
     * This is more general than the version that does not take coordinates.
     * @param type the prototype of the new ArcInst.
     * @param ep EditingPreferences with default sizes and text descriptors.
     * @param baseWidth the base width of the new ArcInst. The width must be > 0.
     * @param head the head end PortInst.
     * @param tail the tail end PortInst.
     * @param headPt the coordinate of the head end PortInst.
     * @param tailPt the coordinate of the tail end PortInst.
     * @param name the name of the new ArcInst
     * @param defAngle default angle in case port points coincide
     * @param flags flags of the new ArcInst
     * @return the newly created ArcInst, or null if there is an error.
     */
    public static ArcInst newInstanceBase(ArcProto type, EditingPreferences ep,
            double baseWidth, PortInst head, PortInst tail,
            Point2D headPt, Point2D tailPt, String name, int defAngle, int flags) {
//        if (type.isNotUsed())
//        {
////            System.out.println("Cannot create arc instance of " + type + " because prototype is unused");
////            return null;
//        }

        long gridExtendOverMin = DBMath.lambdaToGrid(0.5 * baseWidth) - type.getBaseExtend().getGrid();
//		if (gridFullWidth < type.getMaxLayerGridOffset())
//			gridFullWidth = type.getDefaultGridFullWidth();

        // if points are null, create them as would newInst
        EPoint headP;
        if (headPt == null) {
            headP = head.getCenter();
        } else {
            headP = EPoint.snap(headPt);
        }
        EPoint tailP;
        if (tailPt == null) {
            tailP = tail.getCenter();
        } else {
            tailP = EPoint.snap(tailPt);
        }

        // make sure points are valid
        Cell parent = head.getNodeInst().topology.cell;
        Poly headPoly = head.getPoly();
        if (!stillInPoly(headP, headPoly)) {
            System.out.println("Error in " + parent + ": head of " + type.getName()
                    + " arc at (" + headP.getX() + "," + headP.getY() + ") does not fit in "
                    + head + " which is centered at (" + headPoly.getCenterX() + "," + headPoly.getCenterY() + ")");
            return null;
        }
        Poly tailPoly = tail.getPoly();
        if (!stillInPoly(tailP, tailPoly)) {
            System.out.println("Error in " + parent + ": tail of " + type.getName()
                    + " arc at (" + tailP.getX() + "," + tailP.getY() + ") does not fit in "
                    + tail + " which is centered at (" + tailPoly.getCenterX() + "," + tailPoly.getCenterY() + ")");
            return null;
        }

        TextDescriptor nameDescriptor = ep.getArcTextDescriptor();
        Name nameKey = name != null ? Name.findName(name) : null;
        if (nameKey != null && !nameKey.isTempname()) {
            // adjust the name descriptor for "smart" text placement
            long gridBaseWidth = 2 * (gridExtendOverMin + type.getBaseExtend().getGrid());
            nameDescriptor = getSmartTextDescriptor(defAngle, DBMath.gridToLambda(gridBaseWidth), nameDescriptor, ep);
        }
        return newInstanceNoCheck(parent, type, name, nameDescriptor, head, tail, headP, tailP, gridExtendOverMin, defAngle, flags);
    }

    /**
     * Method to create a new ArcInst connecting two PortInsts at specified locations.
     * This is more general than the version that does not take coordinates.
     * WARNING: this method does not check endpoint coordinates so make sure they are correct
     * to prevent database corruption
     * @param parent the parent Cell of this ArcInst
     * @param protoType the ArcProto of this ArcInst.
     * @param name the name of this ArcInst
     * @param nameDescriptor text descriptor of name of this ArcInst
     * @param headPort the head end PortInst.
     * @param tailPort the tail end PortInst.
     * @param headPt the coordinate of the head end PortInst.
     * @param tailPt the coordinate of the tail end PortInst.
     * @param gridExtendOverMin the extend of this ArcInst over minimal-width arc of this type in grid units.
     * @param angle angle in tenth-degrees.
     * @param flags flag bits.
     * @return the newly created ArcInst, or null if there is an error.
     */
    public static ArcInst newInstanceNoCheck(Cell parent, ArcProto protoType, String name, TextDescriptor nameDescriptor,
            PortInst headPort, PortInst tailPort, EPoint headPt, EPoint tailPt, long gridExtendOverMin, int angle, int flags) {
        parent.checkChanging();
        Topology topology = parent.getTopology();

        // make sure fields are valid
        if (protoType == null || headPort == null || tailPort == null || !headPort.isLinked() || !tailPort.isLinked()) {
            return null;
        }
        if (headPt == null || tailPt == null) {
            return null;
        }

        if (topology != headPort.getNodeInst().topology || topology != tailPort.getNodeInst().topology) {
            System.out.println("ArcProto.newInst: the 2 PortInsts are in different Cells!");
            System.out.println("Cell " + parent.getName());
            System.out.println("Head " + headPort.getNodeInst().topology.cell.getName());
            System.out.println("Tail " + tailPort.getNodeInst().topology.cell.getName());
            return null;
        }

        // make sure the arc can connect to these ports
        PortProto headProto = headPort.getPortProto();
        PrimitivePort headPrimPort = headProto.getBasePort();
        if (!headPrimPort.connectsTo(protoType)) {
            System.out.println("Cannot create " + protoType + " from (" + headPt.getX() + "," + headPt.getY()
                    + ") to (" + tailPt.getX() + "," + tailPt.getY() + ") in " + parent
                    + " because the 'from' port (" + headProto.getName() + " on node "
                    + headPort.getNodeInst().describe(false) + ") does not connect to " + protoType);
            return null;
        }
        PortProto tailProto = tailPort.getPortProto();
        PrimitivePort tailPrimPort = tailProto.getBasePort();
        if (!tailPrimPort.connectsTo(protoType)) {
            System.out.println("Cannot create " + protoType + " from (" + headPt.getX() + "," + headPt.getY()
                    + ") to (" + tailPt.getX() + "," + tailPt.getY() + ") in " + parent
                    + " because the 'to' port (" + tailProto.getName() + " on node "
                    + tailPort.getNodeInst().describe(false) + ") does not connect to " + protoType);
            return null;
        }

        if (nameDescriptor == null) {
            throw new NullPointerException();
        }
        Name nameKey = name != null ? Name.findName(name) : null;
        if (nameKey == null || checkNameKey(nameKey, topology)
                || nameKey.isBus() && protoType != Schematics.tech().bus_arc) {
            nameKey = topology.getArcAutoname();
//		} else
//		{
//			// adjust the name descriptor for "smart" text placement
//            long gridBaseWidth = 2*(gridExtendOverMin + protoType.getGridBaseExtend());
//			nameDescriptor = getSmartTextDescriptor(angle, DBMath.gridToLambda(gridBaseWidth), nameDescriptor);
        }
        TechPool techPool = parent.getTechPool();
        if (!(tailProto.getId() instanceof PrimitivePortId && techPool.getPrimitivePort((PrimitivePortId) tailProto.getId()).isNegatable())) {
            flags = ImmutableArcInst.TAIL_NEGATED.set(flags, false);
        }
        if (!(headProto.getId() instanceof PrimitivePortId && techPool.getPrimitivePort((PrimitivePortId) headProto.getId()).isNegatable())) {
            flags = ImmutableArcInst.HEAD_NEGATED.set(flags, false);
        }
        if (protoType.getTechnology().isNoNegatedArcs()) {
            flags = ImmutableArcInst.TAIL_NEGATED.set(flags, false);
            flags = ImmutableArcInst.HEAD_NEGATED.set(flags, false);
        }

        // for zero-length arcs with unknown angles, set it if the port angles are inflexible
        if (angle < 0 && tailPt.equals(headPt))
        {
        	PrimitivePort hPort = headPort.getPortProto().getBasePort();
        	PrimitivePort tPort = tailPort.getPortProto().getBasePort();
        	if (hPort.getAngleRange() == 0)
        	{
        		// a zero angle range forces the angle of the arc
        		NodeInst ni = headPort.getNodeInst();
				int nodeAngle = ni.getAngle();
				if (ni.isYMirrored()) nodeAngle = (3600 - nodeAngle)%3600;
				if (ni.isXMirrored()) nodeAngle = (1800 - nodeAngle + 3600)%3600;
				angle = ((3600-hPort.getAngle()*10) + nodeAngle) % 3600;
        	} else if (tPort.getAngleRange() == 0)
        	{
        		// a zero angle range forces the angle of the arc
        		NodeInst ni = tailPort.getNodeInst();
				int nodeAngle = ni.getAngle();
				if (ni.isYMirrored()) nodeAngle = (3600 - nodeAngle)%3600;
				if (ni.isXMirrored()) nodeAngle = (1800 - nodeAngle + 3600)%3600;
				angle = (tPort.getAngle()*10 + nodeAngle) % 3600;
        	}
        }

        CellId parentId = parent.getId();
        // search for spare arcId
        int arcId;
        do {
            arcId = parentId.newArcId();
        } while (parent.getArcById(arcId) != null);
        ImmutableArcInst d = ImmutableArcInst.newInst(arcId, protoType.getId(), nameKey, nameDescriptor,
                tailPort.getNodeInst().getNodeId(), tailProto.getId(), tailPt,
                headPort.getNodeInst().getNodeId(), headProto.getId(), headPt,
                gridExtendOverMin, angle, flags);
        ArcInst ai = new ArcInst(topology, d, headPort, tailPort);

        // attach this arc to the two nodes it connects
        headPort.getNodeInst().redoGeometric();
        tailPort.getNodeInst().redoGeometric();

        // add this arc to the cell
        topology.addArc(ai);

        // handle change control, constraint, and broadcast
        Constraints.getCurrent().newObject(ai);
        return ai;
    }

    /**
     * Method to delete this ArcInst.
     */
    public void kill() {
        if (!isLinked()) {
            System.out.println("ArcInst already killed");
            return;
        }
        checkChanging();

        // remove this arc from the two nodes it connects
        headPortInst.getNodeInst().redoGeometric();
        tailPortInst.getNodeInst().redoGeometric();

        // remove this arc from the cell
        topology.removeArc(this);

        // handle change control, constraint, and broadcast
        Constraints.getCurrent().killObject(this);
    }

    /**
     * Method to change the width and end locations of this ArcInst.
     * @param dHeadX the change to the X coordinate of the head of this ArcInst.
     * @param dHeadY the change to the Y coordinate of the head of this ArcInst.
     * @param dTailX the change to the X coordinate of the tail of this ArcInst.
     * @param dTailY the change to the Y coordinate of the tail of this ArcInst.
     */
    public void modify(double dHeadX, double dHeadY, double dTailX, double dTailY) {
        // save old arc state
        ImmutableArcInst oldD = d;

        // change the arc
        EPoint tail = d.tailLocation;
        if (dTailX != 0 || dTailY != 0) {
            tail = EPoint.fromLambda(tail.getX() + dTailX, tail.getY() + dTailY);
        }
        EPoint head = d.headLocation;
        if (dHeadX != 0 || dHeadY != 0) {
            head = EPoint.fromLambda(head.getX() + dHeadX, head.getY() + dHeadY);
        }
        lowLevelModify(d.withLocations(tail, head));

        // track the change
        Constraints.getCurrent().modifyArcInst(this, oldD);
    }

    /**
     * Method to change the width this ArcInst.
     * @param lambdaBaseWidth new base width of the ArcInst in lambda units.
     */
    public void setLambdaBaseWidth(double lambdaBaseWidth) {
        setGridBaseWidth(DBMath.lambdaToSizeGrid(lambdaBaseWidth));
    }

    /**
     * Method to change the width this ArcInst.
     * @param gridBaseWidth new base width of the ArcInst in lambda units.
     */
    public void setGridBaseWidth(long gridBaseWidth) {
        if (gridBaseWidth == getGridBaseWidth()) {
            return;
        }

        // save old arc state
        ImmutableArcInst oldD = d;

        // change the arc
        lowLevelModify(d.withGridExtendOverMin(gridBaseWidth / 2 - getProto().getBaseExtend().getGrid()));

        // track the change
        Constraints.getCurrent().modifyArcInst(this, oldD);
    }

    /**
     * Method to replace this ArcInst with one of another type.
     * @param ap the new type of arc.
     * @param ep EditingPreferences
     * @return the new ArcInst (null on error).
     */
    public ArcInst replace(ArcProto ap, EditingPreferences ep) {
        // check for connection allowance
        if (!headPortInst.getPortProto().connectsTo(ap) || !tailPortInst.getPortProto().connectsTo(ap)) {
            System.out.println("Cannot replace " + this + " with one of type " + ap.getName()
                    + " because the nodes cannot connect to it");
            return null;
        }

        // first create the new nodeinst in place
        ArcInst newar = ArcInst.newInstanceBase(ap, ep, getLambdaBaseWidth(), headPortInst, tailPortInst, d.headLocation, d.tailLocation, null, ArcInst.DEFAULTANGLE);
        if (newar == null) {
            System.out.println("Cannot replace " + this + " with one of type " + ap.getName()
                    + " because the new arc failed to create");
            return null;
        }

        // copy all variables on the arcinst
        newar.copyPropertiesFrom(this);

        // now delete the original nodeinst
        kill();
        newar.setName(getName(), ep);
        return newar;
    }

    /**
     * **************************** LOW-LEVEL IMPLEMENTATION *****************************
     */
    /**
     * Returns persistent data of this ArcInst.
     * @return persistent data of this ArcInst.
     */
    @Override
    public ImmutableArcInst getD() {
        return d;
    }

    /**
     * Modifies persistent data of this ArcInst.
     * @param newD new persistent data.
     * @param notify true to notify Undo system.
     * @return true if persistent data was modified.
     */
    public boolean setD(ImmutableArcInst newD, boolean notify) {
        checkChanging();
        ImmutableArcInst oldD = d;
        if (newD == oldD) {
            return false;
        }
        topology.cell.setTopologyModified();
        d = newD;
        if (notify) {
            Constraints.getCurrent().modifyArcInst(this, oldD);
        }
        return true;
    }

    public void setDInUndo(ImmutableArcInst newD) {
        checkUndoing();
        d = newD;
    }

    /**
     * Method to add a Variable on this ArcInst.
     * It may add repaired copy of this Variable in some cases.
     * @param var Variable to add.
     */
    public void addVar(Variable var) {
        if (setD(d.withVariable(var), true)) {
            // check for side-effects of the change
            checkPossibleVariableEffects(var.getKey());
        }
    }

    /**
     * Method to handle special case side-effects of setting variables on this NodeInst.
     * Overrides the general method on ElectricObject.
     * Currently it handles changes to the number-of-degrees on a circle node.
     * @param key the Variable key that has changed on this NodeInst.
     */
    public void checkPossibleVariableEffects(Variable.Key key) {
        if (key == ImmutableArcInst.ARC_RADIUS) {
            lowLevelModify(d);
        }
    }

    /**
     * Method to delete a Variable from this ArcInst.
     * @param key the key of the Variable to delete.
     */
    @Override
    public void delVar(Variable.Key key) {
        setD(d.withoutVariable(key), true);
    }

    /**
     * Method to copy all variables from another ArcInst to this ArcInst.
     * @param other the other ArcInst from which to copy Variables.
     */
    public void copyVarsFrom(ArcInst other) {
        checkChanging();
        for (Iterator<Variable> it = other.getVariables(); it.hasNext();) {
            addVar(it.next());
        }
    }

    /**
     * Low-level method to change the width and end locations of this ArcInst.
     * New persistent data may differ from old one only by width and end locations
     * @param d the new persistent data of this ArcInst.
     */
    public void lowLevelModify(ImmutableArcInst d) {
        // first remove from the R-Tree structure
        boolean renamed = this.d.name != d.name;
        if (renamed) {
            topology.removeArc(this);
        }

        // now make the change
        setD(d, false);
        if (renamed) {
            topology.addArc(this);
        }

        topology.setArcsDirty();

        // update end shrinkage information ?????????????????????
//		headPortInst.getNodeInst().updateShrinkage();
//		tailPortInst.getNodeInst().updateShrinkage();
    }

    /**
     * **************************** GRAPHICS *****************************
     */
    /**
     * Method to return the full width of this ArcInst in grid units.
     * @return the full width of this ArcInst in grid units.
     */
    public long getGridFullWidth() {
        return 2 * (d.getGridExtendOverMin() + getProto().getMaxLayerExtend().getGrid());
    }

    /**
     * Method to return the base width of this ArcInst in lambda units.
     * @return the base width of this ArcInst in lambda units.
     */
    public double getLambdaBaseWidth() {
        return DBMath.gridToLambda(getGridBaseWidth());
    }

    /**
     * Method to return the base width of this ArcInst in grid units.
     * @return the base width of this ArcInst in grid units.
     */
    public long getGridBaseWidth() {
        return 2 * (d.getGridExtendOverMin() + getProto().getBaseExtend().getGrid());
    }

    /**
     * Method to return the length of this ArcInst in lambda units.
     * @return the length of this ArcInst in lambda units.
     */
    public double getLambdaLength() {
        return d.getLambdaLength();
    }

    /**
     * Method to return the length of this ArcInst in grid units.
     * @return the length of this ArcInst in grid units.
     */
    public double getGridLength() {
        return d.getGridLength();
    }

    /**
     * Returns true if length of this ArcInst is zero.
     * @return true if length of this ArcInst is zero.
     */
    public boolean isZeroLength() {
        return d.isZeroLength();
    }

    /**
     * Method to return the rotation angle of this ArcInst.
     * This is an angle of direction from tailLocation to headLocation.
     * @return the rotation angle of this ArcInst (in tenth-degrees).
     * Undefined-angles return -1;
     */
    public int getAngle() {
        return d.getAngle();
    }

    /**
     * Method to return the rotation angle of this ArcInst.
     * This is an angle of direction from tailLocation to headLocation.
     * @return the rotation angle of this ArcInst (in tenth-degrees).
     * Undefined-angles return 0;
     */
    public int getDefinedAngle() {
        int angle = d.getAngle();
        if (angle == -1) {
            return 0;
        }
        return angle;
    }

    /**
     * Method to set the rotation angle of this ArcInst.
     * @param angle the rotation angle of this ArcInst (in tenth-degrees).
     * In general, you should not call this method because the
     * constructors and modification methods update this correctly.
     * If, however, you have a zero-length arc and want to explicitly set
     * its angle, then use this method.
     */
    public void setAngle(int angle) {
        checkChanging();
        ImmutableArcInst oldD = d;
        lowLevelModify(d.withAngle(angle));
        assert topology != null;
        Constraints.getCurrent().modifyArcInst(this, oldD);
    }

    /**
     * Returns the polygons that describe this ArcInst.
     * @param polyBuilder Poly builder.
     * @return an iterator on Poly objects that describes this ArcInst graphically.
     * These Polys include displayable variables on the ArcInst.
     */
    @Override
    public Iterator<Poly> getShape(Poly.Builder polyBuilder) {
        return polyBuilder.getShape(this);
    }

    /**
     * Method to return the bounds of this ArcInst.
     * TODO: dangerous to give a pointer to our internal field; should make a copy of visBounds
     * @return the bounds of this ArcInst.
     */
    @Override
    public ERectangle getBounds() {
        if (!topology.validArcBounds) {
            topology.computeArcBounds();
        }
        assert visBounds != null;
        return visBounds;
    }

    void computeBounds(BoundsBuilder b, long[] gridCoords) {
        if (b.genBoundsEasy(d, gridCoords)) {
            long gridMinX = gridCoords[0];
            long gridMinY = gridCoords[1];
            long gridMaxX = gridCoords[2];
            long gridMaxY = gridCoords[3];
            if (visBounds != null
                    && gridMinX == visBounds.getGridMinX() && gridMinY == visBounds.getGridMinY()
                    && gridMaxX == visBounds.getGridMaxX() && gridMaxY == visBounds.getGridMaxY()) {
                return;
            }
            visBounds = ERectangle.fromGrid(gridMinX, gridMinY, gridMaxX - gridMinX, gridMaxY - gridMinY);
        } else {
            b.clear();
            b.genShapeOfArc(d);
            visBounds = b.makeBounds(null, visBounds);
        }
    }

    /**
     * Method to create a Poly object that describes an ArcInst in lambda units.
     * The ArcInst is described by its width and style.
     * @param gridWidth the width of the Poly in grid units.
     * @param style the style of the ArcInst.
     * @return a Poly that describes the ArcInst in lambda units.
     */
    public Poly makeLambdaPoly(long gridWidth, Poly.Type style) {
        Poly.Builder polyBuilder = Poly.threadLocalLambdaBuilder();
        polyBuilder.setup(topology.cell);
        return polyBuilder.makePoly(getD(), gridWidth, style);
    }

//	/**
//	 * Method to create a Poly object that describes an ArcInst in grid units.
//	 * The ArcInst is described by its width and style.
//	 * @param gridWidth the width of the Poly in grid units.
//	 * @param style the style of the ArcInst.
//	 * @return a Poly that describes the ArcInst in grid units.
//	 */
//    public Poly makeGridPoly(long gridWidth, Poly.Type style) {
//        CellBackup.Memoization m = parent != null ? parent.getMemoization() : null;
//        return getD().makeGridPoly(m, gridWidth, style);
//    }
    /**
     * Method to fill polygon "poly" with the outline in lambda units of the curved arc in
     * this ArcInst whose width in grid units is "gridWidth". The style of the polygon is set to "style".
     * If there is no curvature information in the arc, the routine returns null,
     * otherwise it returns the curved polygon.
     */
    public Poly curvedArcLambdaOutline(Poly.Type style, long gridWidth, long gridRadius) {
        Poly.Builder polyBuilder = Poly.threadLocalLambdaBuilder();
        polyBuilder.setup(topology.cell);
        Variable radius = Variable.newInst(ImmutableArcInst.ARC_RADIUS, DBMath.gridToLambda(gridRadius), TextDescriptor.EMPTY);
        return polyBuilder.makePoly(getD().withVariable(radius), gridWidth, style);
    }

//	/**
//	 * Method to return a list of Polys that describes all text on this ArcInst.
//	 * @param hardToSelect is true if considering hard-to-select text.
//	 * @param wnd the window in which the text will be drawn.
//	 * @return an array of Polys that describes the text.
//	 */
//	public Poly [] getAllText(boolean hardToSelect, EditWindow0 wnd)
//	{
//		int dispVars = numDisplayableVariables(false);
//		int totalText = dispVars;
//		if (totalText == 0) return null;
//		Poly [] polys = new Poly[totalText];
//		addDisplayableVariables(getBounds(), polys, 0, wnd, false);
//		return polys;
//	}
    /**
     * Method to add all displayable Variables on this ArcInst to an array of Poly objects.
     * @param rect a rectangle describing the bounds of the object on which the Variables will be displayed.
     * @param polys an list of Poly objects that will be filled with the displayable Variables.
     * @param wnd window in which the Variables will be displayed.
     * @param multipleStrings true to break multi-line text into multiple Polys.
     * @param showTempNames show temporary names on nodes and arcs
     */
    @Override
    public void addDisplayableVariables(Rectangle2D rect, List<Poly> polys, EditWindow0 wnd, boolean multipleStrings, boolean showTempNames) {
        if (isUsernamed() && d.nameDescriptor.isDisplay()) {
            double cX = rect.getCenterX();
            double cY = rect.getCenterY();
            TextDescriptor td = d.nameDescriptor;
            double offX = td.getXOff();
            double offY = td.getYOff();
            TextDescriptor.Position pos = td.getPos();
            Poly.Type style = pos.getPolyType();

            Poly.Point[] pointList;
            if (style == Poly.Type.TEXTBOX) {
                pointList = Poly.makePoints(rect);
            } else {
                pointList = new Poly.Point[1];
                pointList[0] = Poly.fromLambda(cX + offX, cY + offY);
            }
            Poly poly = new Poly(pointList);
            poly.setStyle(style);
            poly.setString(getNameKey().toString());
            poly.setTextDescriptor(td);
            poly.setLayer(null);
            poly.setDisplayedText(new DisplayedText(this, ARC_NAME));
            polys.add(poly);
        }
        super.addDisplayableVariables(rect, polys, wnd, multipleStrings, false);
    }

    /**
     * Method to get all displayable Variables on this ArcInst to an array of Poly objects.
     * @param wnd window in which the Variables will be displayed.
     * @param showTempNames show temporary names on nodes and arcs.
     * @return an array of Poly objects with displayable variables.
     */
    public Poly[] getDisplayableVariables(EditWindow0 wnd, boolean showTempNames) {
        return getDisplayableVariables(getBounds(), wnd, true, false);
    }

    /**
     * **************************** CONNECTIONS *****************************
     */
    /**
     * Method to return the Connection on the tail end of this ArcInst.
     * @return the Connection on the tail end of this ArcInst.
     */
    public TailConnection getTail() {
        return new TailConnection(this);
    }

    /**
     * Method to return the Connection on the head end of this ArcInst.
     * @return the Connection on the head end of this ArcInst.
     */
    public HeadConnection getHead() {
        return new HeadConnection(this);
    }

    /**
     * Method to return the connection at an end of this ArcInst.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     */
    public Connection getConnection(int connIndex) {
        switch (connIndex) {
            case ImmutableArcInst.TAILEND:
                return new TailConnection(this);
            case ImmutableArcInst.HEADEND:
                return new HeadConnection(this);
            default:
                throw new IllegalArgumentException("Bad end " + connIndex);
        }
    }

    /**
     * Method to return the PortInst on tail of this ArcInst.
     * @return the PortInst on tail.
     */
    public PortInst getTailPortInst() {
        return tailPortInst;
    }

    /**
     * Method to return the PortInst on head of this ArcInst.
     * @return the PortInst on head.
     */
    public PortInst getHeadPortInst() {
        return headPortInst;
    }

    /**
     * Method to tell whether this ArcInst is connected directly to another
     * Geometric object (that is, an arcinst connected to a nodeinst).
     * The method returns true if they are connected.
     * @param geom other Geometric object.
     * @return true if this and other Geometric objects are connected.
     */
    @Override
    public boolean isConnected(Geometric geom) {
        return tailPortInst.getNodeInst() == geom || headPortInst.getNodeInst() == geom;
    }

    /**
     * Method to return the PortInst on an end of this ArcInst.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     * @return the PortInst at an end.
     */
    public PortInst getPortInst(int connIndex) {
        switch (connIndex) {
            case ImmutableArcInst.TAILEND:
                return tailPortInst;
            case ImmutableArcInst.HEADEND:
                return headPortInst;
            default:
                throw new IllegalArgumentException("Bad end " + connIndex);
        }
    }

    /**
     * Method to return the Location on tail of this ArcInst.
     * @return the Location on tail.
     */
    public EPoint getTailLocation() {
        return d.tailLocation;
    }

    /**
     * Method to return the Location on head of this ArcInst.
     * @return the Location on head.
     */
    public EPoint getHeadLocation() {
        return d.headLocation;
    }

    /**
     * Method to return the Location on an end of this ArcInst.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     * @return the Location on an end.
     */
    public EPoint getLocation(int connIndex) {
        switch (connIndex) {
            case ImmutableArcInst.TAILEND:
                return d.tailLocation;
            case ImmutableArcInst.HEADEND:
                return d.headLocation;
            default:
                throw new IllegalArgumentException("Bad end " + connIndex);
        }
    }

    /**
     * Method to tell whether a tail connection on this ArcInst contains a port location.
     * @param pt the point in question.
     * @param reduceForArc if true reduce width by width offset of it prototype.
     * @return true if the point is inside of the port.
     */
    public boolean tailStillInPort(Point2D pt, boolean reduceForArc) {
        return stillInPort(ImmutableArcInst.TAILEND, pt, reduceForArc);
    }

    /**
     * Method to tell whether a head connection on this ArcInst contains a port location.
     * @param pt the point in question.
     * @param reduceForArc if true reduce width by width offset of it prototype.
     * @return true if the point is inside of the port.
     */
    public boolean headStillInPort(Point2D pt, boolean reduceForArc) {
        return stillInPort(ImmutableArcInst.HEADEND, pt, reduceForArc);
    }

    /**
     * Method to tell whether a connection on this ArcInst contains a port location.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     * @param pt the point in question.
     * @param reduceForArc if true reduce width by width offset of it prototype.
     * @return true if the point is inside of the port.
     */
    public boolean stillInPort(int connIndex, Point2D pt, boolean reduceForArc) {
        // determine the area of the nodeinst
        PortInst pi = getPortInst(connIndex);
        Poly poly = pi.getPoly();
        if (reduceForArc) {
            double wid = getLambdaBaseWidth();
            poly.reducePortPoly(pi, wid, getAngle());
        }
        return stillInPoly(pt, poly);
//		if (poly.isInside(pt)) return true;
//		if (poly.polyDistance(pt.getX(), pt.getY()) < MINPORTDISTANCE) return true;
//
//		// no good
//		return false;
    }

    private static boolean stillInPoly(Point2D pt, Poly poly) {
        return poly.isInside(pt) || poly.polyDistance(pt.getX(), pt.getY()) < MINPORTDISTANCE;
    }

    /**
     * **************************** TEXT *****************************
     */
    /**
     * Method to return the name of this ArcInst.
     * @return the name of this ArcInst.
     */
    public String getName() {
        return d.name.toString();
    }

    /**
     * Returns true if this ArcInst was named by user.
     * @return true if this ArcInst was named by user.
     */
    public boolean isUsernamed() {
        return d.isUsernamed();
    }

    /**
     * Method to return the name key of this ArcInst.
     * @return the name key of this ArcInst, null if there is no name.
     */
    public Name getNameKey() {
        return d.name;
    }

    /**
     * Method to rename this ArcInst.
     * This ArcInst must be linked to database.
     * @param name new name of this geometric.
     * @param ep EditingPreferences with default sizes and text descriptors.
     * @return true on error
     */
    public boolean setName(String name, EditingPreferences ep) {
        assert isLinked();
        Name key;
        boolean doSmart = false;
        if (name != null && name.length() > 0) {
            if (name.equals(getName())) {
                return false;
            }
            if (!isUsernamed()) {
                doSmart = true;
            }
            key = Name.findName(name);
        } else {
            if (!isUsernamed()) {
                return false;
            }
            key = topology.getArcAutoname();
        }
        if (checkNameKey(key, topology) || key.isBus() && getProto() != Schematics.tech().bus_arc) {
            return true;
        }
        ImmutableArcInst oldD = d;
        lowLevelModify(d.withName(key));
        if (doSmart) {
            TextDescriptor td = ep.getArcTextDescriptor();
            TextDescriptor smartDescriptor = getSmartTextDescriptor(getAngle(), getLambdaBaseWidth(), td, ep);
            setTextDescriptor(ARC_NAME, smartDescriptor);
        }

        // apply constraints
        Constraints.getCurrent().modifyArcInst(this, oldD);
        return false;
    }

    /**
     * Method to return a "smart" text descriptor for an arc.
     * @param angle the angle of the arc (in tenths of a degree).
     * @param width the width of the arc.
     * @param prev the former text descriptor of the arc.
     * @return a new text descriptor that handles smart placement.
     */
    private static TextDescriptor getSmartTextDescriptor(int angle, double width, TextDescriptor prev, EditingPreferences ep) {
        // assigning valid name: do smart text placement
        if (angle == -1) {
            angle = 0;
        }
        if ((angle % 1800) == 0) {
            // horizontal arc
            int smart = ep.getSmartHorizontalPlacementArc();
            if (smart == 1) {
                // arc text above
                return prev.withPos(TextDescriptor.Position.UP).withOff(0, width / 2);
            } else if (smart == 2) {
                // arc text below
                return prev.withPos(TextDescriptor.Position.DOWN).withOff(0, -width / 2);
            }
        } else if ((angle % 1800) == 900) {
            // vertical arc
            int smart = ep.getSmartVerticalPlacementArc();
            if (smart == 1) {
                // arc text to the left
                return prev.withPos(TextDescriptor.Position.LEFT).withOff(-width / 2, 0);
            } else if (smart == 2) {
                // arc text to the right
                return prev.withPos(TextDescriptor.Position.RIGHT).withOff(width / 2, 0);
            }
        }
        return prev;
    }

    /**
     * Method to check the new name key of an ArcInst.
     * @param name new name key of this ArcInst.
     * @param parent parent Cell used for error message
     * @return true on error.
     */
    private static boolean checkNameKey(Name name, Topology topology) {
        Cell parent = topology.cell;
        if (!name.isValid()) {
            System.out.println(parent + ": Invalid name \"" + name + "\" wasn't assigned to arc" + " :" + Name.checkName(name.toString()));
            return true;
        }
        if (name.isBus()) {
            if (name.isTempname()) {
                System.out.println(parent + ": Temporary name \"" + name + "\" can't be bus");
                return true;
            }
            if (!parent.busNamesAllowed()) {
                System.out.println(parent + ": Bus name \"" + name + "\" can be in icons and schematics only");
                return true;
            }
        }
        if (name.isTempname() && name.getBasename() != ImmutableArcInst.BASENAME) {
            System.out.println(parent + ": Temporary arc name \"" + name + "\" must have prefix net@");
            return true;
        }
        if (name.hasEmptySubnames()) {
            if (name.isBus()) {
                System.out.println(parent + ": Name \"" + name + "\" with empty subnames wasn't assigned to arc");
            } else {
                System.out.println(parent + ": Cannot assign empty name \"" + name + "\" to arc");
            }
            return true;
        }
        if (topology.hasTempArcName(name)) {
            System.out.println(parent + " already has ArcInst with temporary name \"" + name + "\"");
            return true;
        }
        return false;
    }

    /**
     * Returns the TextDescriptor on this ArcInst selected by variable key.
     * This key may be a key of variable on this ArcInst or the
     * special keys:
     * <code>ArcInst.ARC_NAME</code>
     * The TextDescriptor gives information for displaying the Variable.
     * @param varKey key of variable or special key.
     * @return the TextDescriptor on this ArcInst.
     */
    public TextDescriptor getTextDescriptor(Variable.Key varKey) {
        if (varKey == ARC_NAME) {
            return d.nameDescriptor;
        }
        return super.getTextDescriptor(varKey);
    }

    /**
     * Updates the TextDescriptor on this ArcInst selected by varKey.
     * The varKey may be a key of variable on this ArcInst or
     * the special key ArcInst.ARC_NAME.
     * If varKey doesn't select any text descriptor, no action is performed.
     * The TextDescriptor gives information for displaying the Variable.
     * @param varKey key of variable or special key.
     * @param td new value TextDescriptor
     */
    @Override
    public void setTextDescriptor(Variable.Key varKey, TextDescriptor td) {
        if (varKey == ARC_NAME) {
            setD(d.withNameDescriptor(td), true);
            return;
        }
        super.setTextDescriptor(varKey, td);
    }

    /**
     * Method to determine whether a variable key on ArcInst is deprecated.
     * Deprecated variable keys are those that were used in old versions of Electric,
     * but are no longer valid.
     * @param key the key of the variable.
     * @return true if the variable key is deprecated.
     */
    @Override
    public boolean isDeprecatedVariable(Variable.Key key) {
        if (key == ARC_NAME) {
            return true;
        }
        return super.isDeprecatedVariable(key);
    }

    /**
     * Method to describe this ArcInst as a string.
     * @param withQuotes to wrap description between quotes
     * @return a description of this ArcInst.
     */
    @Override
    public String describe(boolean withQuotes) {
        String description = getProto().describe();
        String name = (withQuotes) ? "'" + getName() + "'" : getName();
        if (name != null) {
            description += "[" + name + "]";
        }
        return description;
    }

    /**
     * Method to describe this ArcInst as a string.
     * Arc technology is always prepended.
     * @return a description of this ArcInst.
     */
    public String libDescribe() {
        StringBuilder sb = new StringBuilder();
        sb.append(getProto().getFullName());
        sb.append('[');
        sb.append(getName());
        sb.append(']');
        return sb.toString();
    }

    /**
     * Method to describe this ArcInst as a string.
     * Arc technology is never prepended.
     * @return a description of this ArcInst.
     */
    public String noLibDescribe() {
        StringBuilder sb = new StringBuilder();
        sb.append(getProto().getName());
        sb.append('[');
        sb.append(getName());
        sb.append(']');
        return sb.toString();
    }

    /**
     * Compares ArcInsts by their Cells and names.
     * @param that the other ArcInst.
     * @return a comparison between the ArcInsts.
     */
    public int compareTo(ArcInst that) {
        int cmp;
        if (this.topology != that.topology) {
            cmp = this.topology.cell.compareTo(that.topology.cell);
            if (cmp != 0) {
                return cmp;
            }
        }
        cmp = this.getName().compareTo(that.getName());
        if (cmp != 0) {
            return cmp;
        }
        return this.d.arcId - that.d.arcId;
    }

    /**
     * Returns a printable version of this ArcInst.
     * @return a printable version of this ArcInst.
     */
    public String toString() {
        return "arc " + describe(true);
    }

    /**
     * **************************** CONSTRAINTS *****************************
     */
    private void setFlag(ImmutableArcInst.Flag flag, boolean state) {
        checkChanging();
        if (setD(d.withFlag(flag, state), true)) {
            topology.setArcsDirty();
        }
    }

    /**
     * Method to set this ArcInst to be rigid.
     * Rigid arcs cannot change length or the angle of their connection to a NodeInst.
     * @param state
     */
    public void setRigid(boolean state) {
        setFlag(ImmutableArcInst.RIGID, state);
    }

    /**
     * Method to tell whether this ArcInst is rigid.
     * Rigid arcs cannot change length or the angle of their connection to a NodeInst.
     * @return true if this ArcInst is rigid.
     */
    public boolean isRigid() {
        return d.isRigid();
    }

    /**
     * Method to set this ArcInst to be fixed-angle.
     * Fixed-angle arcs cannot change their angle, so if one end moves,
     * the other may also adjust to keep the arc angle constant.
     * @param state
     */
    public void setFixedAngle(boolean state) {
        setFlag(ImmutableArcInst.FIXED_ANGLE, state);
    }

    /**
     * Method to tell whether this ArcInst is fixed-angle.
     * Fixed-angle arcs cannot change their angle, so if one end moves,
     * the other may also adjust to keep the arc angle constant.
     * @return true if this ArcInst is fixed-angle.
     */
    public boolean isFixedAngle() {
        return d.isFixedAngle();
    }

    /**
     * Method to set this ArcInst to be slidable.
     * Arcs that slide will not move their connected NodeInsts if the arc's end is still within the port area.
     * Arcs that cannot slide will force their NodeInsts to move by the same amount as the arc.
     * Rigid arcs cannot slide but nonrigid arcs use this state to make a decision.
     * @param state
     */
    public void setSlidable(boolean state) {
        setFlag(ImmutableArcInst.SLIDABLE, state);
    }

    /**
     * Method to tell whether this ArcInst is slidable.
     * Arcs that slide will not move their connected NodeInsts if the arc's end is still within the port area.
     * Arcs that cannot slide will force their NodeInsts to move by the same amount as the arc.
     * Rigid arcs cannot slide but nonrigid arcs use this state to make a decision.
     * @return true if this ArcInst is slidable.
     */
    public boolean isSlidable() {
        return d.isSlidable();
    }

    /**
     * **************************** PROPERTIES *****************************
     */
    /**
     * Method to determine whether this ArcInst is directional, with an arrow on one end.
     * Directional arcs have an arrow drawn on them to indicate flow.
     * It is only for documentation purposes and does not affect the circuit.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     * @return true if that end has a directional arrow on it.
     */
    public boolean isArrowed(int connIndex) {
        return d.isArrowed(connIndex);
    }

    /**
     * Method to determine whether this ArcInst is directional, with an arrow on the tail.
     * Directional arcs have an arrow drawn on them to indicate flow.
     * It is only for documentation purposes and does not affect the circuit.
     * @return true if the arc's tail has a directional arrow on it.
     */
    public boolean isTailArrowed() {
        return d.isTailArrowed();
    }

    /**
     * Method to determine whether this ArcInst is directional, with an arrow on the head.
     * Directional arcs have an arrow drawn on them to indicate flow.
     * It is only for documentation purposes and does not affect the circuit.
     * @return true if the arc's head has a directional arrow on it.
     */
    public boolean isHeadArrowed() {
        return d.isHeadArrowed();
    }

    /**
     * Method to determine whether this ArcInst is directional, with an arrow line drawn down the center.
     * Directional arcs have an arrow drawn on them to indicate flow.
     * It is only for documentation purposes and does not affect the circuit.
     * The body is typically drawn when one of the ends has an arrow on it, but it may be
     * drawn without an arrow head in order to continue an attached arc that has an arrow.
     * @return true if the arc's tail has an arrow line on it.
     */
    public boolean isBodyArrowed() {
        return d.isBodyArrowed();
    }

    /**
     * Method to set this ArcInst to be directional, with an arrow on one end.
     * Directional arcs have an arrow drawn on them to indicate flow.
     * It is only for documentation purposes and does not affect the circuit.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     * @param state true to show a directional arrow on the specified end.
     */
    public void setArrowed(int connIndex, boolean state) {
        switch (connIndex) {
            case ImmutableArcInst.TAILEND:
                setTailArrowed(state);
                break;
            case ImmutableArcInst.HEADEND:
                setHeadArrowed(state);
                break;
            default:
                throw new IllegalArgumentException("Bad end " + connIndex);
        }
    }

    /**
     * Method to set this ArcInst to be directional, with an arrow on the tail.
     * Directional arcs have an arrow drawn on them to indicate flow.
     * It is only for documentation purposes and does not affect the circuit.
     * @param state true to show a directional arrow on the tail.
     */
    public void setTailArrowed(boolean state) {
        setFlag(ImmutableArcInst.TAIL_ARROWED, state);
    }

    /**
     * Method to set this ArcInst to be directional, with an arrow on the head.
     * Directional arcs have an arrow drawn on them to indicate flow.
     * It is only for documentation purposes and does not affect the circuit.
     * @param state true to show a directional arrow on the head.
     */
    public void setHeadArrowed(boolean state) {
        setFlag(ImmutableArcInst.HEAD_ARROWED, state);
    }

    /**
     * Method to set this ArcInst to be directional, with an arrow line drawn down the center.
     * Directional arcs have an arrow drawn on them to indicate flow.
     * It is only for documentation purposes and does not affect the circuit.
     * The body is typically drawn when one of the ends has an arrow on it, but it may be
     * drawn without an arrow head in order to continue an attached arc that has an arrow.
     * @param state true to show a directional line on this arc.
     */
    public void setBodyArrowed(boolean state) {
        setFlag(ImmutableArcInst.BODY_ARROWED, state);
    }

    /**
     * Method to tell whether an end of ArcInst has its ends extended.
     * Extended arcs continue past their endpoint by half of their width.
     * Most layout arcs want this so that they make clean connections to orthogonal arcs.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     * @return true if that end of this ArcInst is extended.
     */
    public boolean isExtended(int connIndex) {
        return d.isExtended(connIndex);
    }

    /**
     * Method to tell whether the tail of this arc is extended.
     * Extended arcs continue past their endpoint by half of their width.
     * Most layout arcs want this so that they make clean connections to orthogonal arcs.
     * @return true if the tail of this arc is extended.
     */
    public boolean isTailExtended() {
        return d.isTailExtended();
    }

    /**
     * Method to tell whether the head of this arc is extended.
     * Extended arcs continue past their endpoint by half of their width.
     * Most layout arcs want this so that they make clean connections to orthogonal arcs.
     * @return true if the head of this arc is extended.
     */
    public boolean isHeadExtended() {
        return d.isHeadExtended();
    }

    /**
     * Method to set whether an end of this arc is extended.
     * Extended arcs continue past their endpoint by half of their width.
     * Most layout arcs want this so that they make clean connections to orthogonal arcs.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     * @param e true to set that end of this arc to be extended.
     */
    public void setExtended(int connIndex, boolean e) {
        switch (connIndex) {
            case ImmutableArcInst.TAILEND:
                setTailExtended(e);
                break;
            case ImmutableArcInst.HEADEND:
                setHeadExtended(e);
                break;
            default:
                throw new IllegalArgumentException("Bad end " + connIndex);
        }
    }

    /**
     * Method to set whether the tail of this arc is extended.
     * Extended arcs continue past their endpoint by half of their width.
     * Most layout arcs want this so that they make clean connections to orthogonal arcs.
     * @param e true to set the tail of this arc to be extended.
     */
    public void setTailExtended(boolean e) {
        setFlag(ImmutableArcInst.TAIL_EXTENDED, e);
    }

    /**
     * Method to set whether the head of this arc is extended.
     * Extended arcs continue past their endpoint by half of their width.
     * Most layout arcs want this so that they make clean connections to orthogonal arcs.
     * @param e true to set the head of this arc to be extended.
     */
    public void setHeadExtended(boolean e) {
        setFlag(ImmutableArcInst.HEAD_EXTENDED, e);
    }

    /**
     * Method to tell whether an end of this arc is negated.
     * Negated arc have a negating bubble on them to indicate negation.
     * This is only valid in schematics technologies.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     * @return true if set that end of this arc is negated.
     */
    public boolean isNegated(int connIndex) {
        return d.isNegated(connIndex);
    }

    /**
     * Method to tell whether the tail of this arc is negated.
     * Negated arc have a negating bubble on them to indicate negation.
     * This is only valid in schematics technologies.
     * @return true if set the tail of this arc is negated.
     */
    public boolean isTailNegated() {
        return d.isTailNegated();
    }

    /**
     * Method to tell whether the head of this arc is negated.
     * Negated arc have a negating bubble on them to indicate negation.
     * This is only valid in schematics technologies.
     * @return true if set the head of this arc is negated.
     */
    public boolean isHeadNegated() {
        return d.isHeadNegated();
    }

    /**
     * Method to set whether an end of this arc is negated.
     * Negated arc have a negating bubble on them to indicate negation.
     * This is only valid in schematics technologies.
     * @param connIndex TAILEND (0) for the tail of this ArcInst, HEADEND (1) for the head.
     * @param n true to set that end of this arc to be negated.
     */
    public void setNegated(int connIndex, boolean n) {
        switch (connIndex) {
            case ImmutableArcInst.TAILEND:
                setTailNegated(n);
                break;
            case ImmutableArcInst.HEADEND:
                setHeadNegated(n);
                break;
            default:
                throw new IllegalArgumentException("Bad end " + connIndex);
        }
    }

    /**
     * Method to set whether the tail of this arc is negated.
     * Negated arc have a negating bubble on them to indicate negation.
     * This is only valid in schematics technologies.
     * @param n true to set the tail of this arc to be negated.
     */
    public void setTailNegated(boolean n) {
        if (!(d.tailPortId instanceof PrimitivePortId && getTechPool().getPrimitivePort((PrimitivePortId) d.tailPortId).isNegatable())) {
            n = false;
        }
        if (getProto().getTechnology().isNoNegatedArcs()) {
            n = false;
        }
        setFlag(ImmutableArcInst.TAIL_NEGATED, n);
    }

    /**
     * Method to set whether the head of this arc is negated.
     * Negated arc have a negating bubble on them to indicate negation.
     * This is only valid in schematics technologies.
     * @param n true to set the head of this arc to be negated.
     */
    public void setHeadNegated(boolean n) {
        if (!(d.headPortId instanceof PrimitivePortId && getTechPool().getPrimitivePort((PrimitivePortId) d.headPortId).isNegatable())) {
            n = false;
        }
        if (getProto().getTechnology().isNoNegatedArcs()) {
            n = false;
        }
        setFlag(ImmutableArcInst.HEAD_NEGATED, n);
    }

    /**
     * **************************** MISCELLANEOUS *****************************
     */
    /**
     * Method to check and repair data structure errors in this ArcInst.
     */
    public int checkAndRepair(boolean repair, List<Geometric> list, ErrorLogger errorLogger) {
        int errorCount = 0;
        ArcProto ap = getProto();

        Cell parent = topology.cell;
        if (ap.isNotUsed()) {
//            if (repair)
            if (errorLogger != null) {
                String msg = "Prototype of arc " + getName() + " is unused";
                if (repair) {
                    // Can't put this arc into error logger because it will be deleted.
                    Poly poly = makeLambdaPoly(getGridBaseWidth(), Poly.Type.CLOSED);
                    errorLogger.logError(msg, poly, parent, 1);
                } else {
                    errorLogger.logError(msg, this, parent, null, 1);
                }
            }
            if (repair) {
                list.add(this);
            }
            // This counts as 1 error, ignoring other errors
            return 1;
        }

        // see if the ends are in their ports
        if (!headStillInPort(d.headLocation, false)) {
            Poly poly = headPortInst.getPoly();
            String msg = parent + ", " + this
                    + ": head not in port, is at (" + d.headLocation.getX() + "," + d.headLocation.getY()
                    + ") distance to port is " + TextUtils.formatDistance(poly.polyDistance(d.headLocation.getX(), d.headLocation.getY()))
                    + " [port center is (" + poly.getCenterX() + "," + poly.getCenterY() + ")";
            if (DBMath.areEquals(d.headLocation.getX(), poly.getCenterX()) &&
            	DBMath.areEquals(d.headLocation.getY(), poly.getCenterY()))
        			msg += " but it may be irregular in shape...has " + poly.getPoints().length + " points";
    		msg += "]";
            System.out.println(msg);
            if (errorLogger != null) {
                List<Object> errorList = new ArrayList<Object>(2);
                errorList.add(headPortInst.getNodeInst());
                errorList.add(makeLambdaPoly(getGridBaseWidth(), Poly.Type.CLOSED));
                if (repair) {
                    errorLogger.logMessage(msg, errorList, parent, 1, true);
                } else {
                    List<Geometric> geomList = new ArrayList<Geometric>();
                    geomList.add(this);
                    geomList.add(headPortInst.getNodeInst());
                    errorLogger.logMessage(msg, geomList, parent, 1, true);
                }
            }
            if (repair) {
                Constraints.getCurrent().modifyArcInst(this, getD());
//				setD(d.withLocations(d.tailLocation, new EPoint(poly.getCenterX(), poly.getCenterY())), false);
//				updateGeometric();
            }
            errorCount++;
        }
        if (!tailStillInPort(d.tailLocation, false)) {
            Poly poly = tailPortInst.getPoly();
            String msg = parent + ", " + this
                    + ": tail not in port, is at (" + d.tailLocation.getX() + "," + d.tailLocation.getY()
                    + ") distance to port is " + TextUtils.formatDistance(poly.polyDistance(d.tailLocation.getX(), d.tailLocation.getY()))
                    + " [port center is (" + poly.getCenterX() + "," + poly.getCenterY() + ")";
            if (DBMath.areEquals(d.tailLocation.getX(), poly.getCenterX()) &&
            	DBMath.areEquals(d.tailLocation.getY(), poly.getCenterY()))
        			msg += " but it may be irregular in shape...has " + poly.getPoints().length + " points";
    		msg += "]";
            System.out.println(msg);
            if (errorLogger != null) {
                if (repair) {
                    List<Object> errorList = new ArrayList<Object>(2);
                    errorList.add(tailPortInst.getNodeInst());
                    errorList.add(makeLambdaPoly(getGridBaseWidth(), Poly.Type.CLOSED));
                    errorLogger.logMessage(msg, errorList, parent, 1, true);
                } else {
                    List<Geometric> geomList = new ArrayList<Geometric>();
                    geomList.add(this);
                    geomList.add(tailPortInst.getNodeInst());
                    errorLogger.logMessage(msg, geomList, parent, 1, true);
                }
            }
            if (repair) {
                Constraints.getCurrent().modifyArcInst(this, getD());
//				setD(d.withLocations(new EPoint(poly.getCenterX(), poly.getCenterY()), d.headLocation), false);
//				updateGeometric();
            }
            errorCount++;
        }
        return errorCount;
    }

    /**
     * Method to check invariants in this ArcInst.
     * @exception AssertionError if invariants are not valid
     */
    public void check(BoundsBuilder b) {
        b.clear();
        b.genShapeOfArc(d);
        assert b.makeBounds(null, visBounds) == visBounds;
    }
//    public void check(Poly.Builder polyBuilder) {
//        if (topology.validArcBounds && Job.getDebug()) {
//            double minX = Double.POSITIVE_INFINITY, minY = Double.POSITIVE_INFINITY;
//            double maxX = Double.NEGATIVE_INFINITY, maxY = Double.NEGATIVE_INFINITY;
//            for (Iterator<Poly> it = getShape(polyBuilder); it.hasNext();) {
//                Poly poly = it.next();
//                Rectangle2D bounds = poly.getBounds2D();
//                minX = Math.min(minX, bounds.getMinX());
//                minY = Math.min(minY, bounds.getMinY());
//                maxX = Math.max(maxX, bounds.getMaxX());
//                maxY = Math.max(maxY, bounds.getMaxY());
//            }
//            minX = GenMath.floorLong(minX);
//            minY = GenMath.floorLong(minY);
//            maxX = GenMath.ceilLong(maxX);
//            maxY = GenMath.ceilLong(maxY);
//            assert visBounds.getX() == DBMath.gridToLambda(minX);
//            assert visBounds.getY() == DBMath.gridToLambda(minY);
//            assert visBounds.getWidth() == DBMath.gridToLambda(maxX - minX);
//            assert visBounds.getHeight() == DBMath.gridToLambda(maxY - minY);
//        }
//    }

    /**
     * Method to get the arcId of this ArcInst.
     * The arcId is assign to ArcInst in chronological order
     * The arcId doesn't relate to alphanumeric ordering of arcs in the Cell.
     * @return the index of this ArcInst.
     */
    public final int getArcId() {
        return d.arcId;
    }

    /**
     * Returns true if this ArcInst is linked into database.
     * @return true if this ArcInst is linked into database.
     */
    @Override
    public boolean isLinked() {
        try {
            Cell parent = topology.cell;
            return parent.isLinked() && parent.getArcById(getArcId()) == this;
        } catch (IndexOutOfBoundsException e) {
            return false;
        }
    }

    /**
     * Method to return the Cell Topology that contains this ArcInst.
     * @return the Topology that contains this ArcInst.
     */
    @Override
    public Topology getTopology() {
        return topology;
    }

    /**
     * Routing to check whether changing of this cell allowed or not.
     * By default checks whole database change. Overridden in subclasses.
     */
    @Override
    public void checkChanging() {
        topology.cell.checkChanging();
    }

    /**
     * Method to determine the appropriate Cell associated with this ElectricObject.
     * @return the appropriate Cell associated with this ElectricicObject.
     */
    @Override
    public Cell whichCell() {
        return topology.cell;
    }

    /**
     * Returns database to which this ArcInst belongs.
     * @return database to which this ArcInst belongs.
     */
    @Override
    public EDatabase getDatabase() {
        return topology.cell.getDatabase();
    }

    /**
     * Method to return the prototype of this ArcInst.
     * @return the prototype of this ArcInst.
     */
    public ArcProto getProto() {
        return getTechPool().getArcProto(d.protoId);
    }

    /**
     * Copies all properties (variables, constraints, and textdescriptor)
     * from 'fraomAi' to this arcinst. This is basically the same as calling
     * copyVarsFrom(), copyConstraintsFrom(), and setTextDescriptor().
     * @param fromAi the arc from which to copy all arc properties
     */
    public void copyPropertiesFrom(ArcInst fromAi) {
        if (fromAi == null) {
            return;
        }
        copyVarsFrom(fromAi);
        copyConstraintsFrom(fromAi);
        copyTextDescriptorFrom(fromAi, ArcInst.ARC_NAME);
    }

    /**
     * Copies constraints (Rigid, Ends Extended, etc) from another arcinst to this arcinst
     * It copies also attributes of Connections (arrow/negated/extended)
     * @param fromAi the arcinst from which to copy constraints
     */
    public void copyConstraintsFrom(ArcInst fromAi) {
        checkChanging();
        if (fromAi == null) {
            return;
        }
        ImmutableArcInst oldD = d;
        int flags = fromAi.d.flags;
        if (!(d.tailPortId instanceof PrimitivePortId && getTechPool().getPrimitivePort((PrimitivePortId) d.tailPortId).isNegatable())) {
            flags = ImmutableArcInst.TAIL_NEGATED.set(flags, false);
        }
        if (!(d.headPortId instanceof PrimitivePortId && getTechPool().getPrimitivePort((PrimitivePortId) d.headPortId).isNegatable())) {
            flags = ImmutableArcInst.HEAD_NEGATED.set(flags, false);
        }
        if (getProto().getTechnology().isNoNegatedArcs()) {
            flags = ImmutableArcInst.TAIL_NEGATED.set(flags, false);
            flags = ImmutableArcInst.HEAD_NEGATED.set(flags, false);
        }
        lowLevelModify(d.withFlags(flags).withAngle(fromAi.getAngle()));
        assert topology != null;
        Constraints.getCurrent().modifyArcInst(this, oldD);
    }

//	/**
//	 * Low-level method to get the user bits.
//	 * The "user bits" are a collection of flags that are more sensibly accessed
//	 * through special methods.
//	 * This general access to the bits is required because the ELIB
//	 * file format stores it as a full integer.
//	 * This should not normally be called by any other part of the system.
//	 * @return the "user bits".
//	 */
//    public int lowLevelGetUserbits() { return userBits; }
//
//	/**
//	 * Low-level method to set the user bits.
//	 * The "user bits" are a collection of flags that are more sensibly accessed
//	 * through special methods.
//	 * This general access to the bits is required because the ELIB
//	 * file format stores it as a full integer.
//	 * This should not normally be called by any other part of the system.
//	 * @param diskBits the new "user bits".
//	 */
// 	public void lowLevelSetUserbits(int userBits)
// 	{
// 		boolean extensionChanged = (this.userBits&(TAILNOEXTEND|HEADNOEXTEND)) != (userBits&(TAILNOEXTEND|HEADNOEXTEND));
// 		this.userBits = userBits & DATABASE_BITS;
// 		if (isLinked() && extensionChanged) updateGeometric();
// 	}
//	/**
//	 * Method to copy the various state bits from another ArcInst to this ArcInst.
//	 * @param ai the other ArcInst to copy.
//	 */
//	public void copyStateBits(ArcInst ai) { checkChanging(); this.userBits = ai.userBits; Undo.otherChange(this); }
//	/**
//	 * Method to set default constraint information on this ArcInst.
//	 */
//	private void setDefaultConstraints(ArcProto protoType)
//	{
//        setRigid(protoType.isRigid());
//        setFixedAngle(protoType.isFixedAngle());
//        setSlidable(protoType.isSlidable());
//        setHeadExtended(protoType.isExtended());
//        setTailExtended(protoType.isExtended());
//        setHeadArrowed(protoType.isDirectional());
//        setBodyArrowed(protoType.isDirectional());
//	}
    /**
     * Method to set this ArcInst to be hard-to-select.
     * Hard-to-select ArcInsts cannot be selected by clicking on them.
     * Instead, the "special select" command must be given.
     * @param state
     */
    public void setHardSelect(boolean state) {
        setFlag(ImmutableArcInst.HARD_SELECT, state);
    }

    /**
     * Method to tell whether this ArcInst is hard-to-select.
     * Hard-to-select ArcInsts cannot be selected by clicking on them.
     * Instead, the "special select" command must be given.
     * @return true if this ArcInst is hard-to-select.
     */
    public boolean isHardSelect() {
        return d.isHardSelect();
    }

    /**
     * This function is to compare NodeInst elements. Initiative CrossLibCopy
     * @param obj Object to compare to
     * @param buffer To store comparison messages in case of failure
     * @return True if objects represent same ArcInst
     */
    public boolean compare(Object obj, StringBuffer buffer) {
        if (this == obj) {
            return (true);
        }

        // Better if compare classes? but it will crash with obj=null
        if (obj == null || getClass() != obj.getClass()) {
            return (false);
        }

        ArcInst a = (ArcInst) obj;
        if (getProto().getClass() != a.getProto().getClass()) {
            return (false);
        }

        // Not sure if I should define myEquals for Geometric
        ArcProto arcType = a.getProto();
        Technology tech = arcType.getTechnology();
        if (getProto().getTechnology() != tech) {
            if (buffer != null) {
                buffer.append("No same technology for arcs " + getName() + " and " + a.getName() + "\n");
            }
            return (false);
        }

        Poly[] polyList = getProto().getTechnology().getShapeOfArc(this);
        Poly[] aPolyList = tech.getShapeOfArc(a);

        if (polyList.length != aPolyList.length) {
            if (buffer != null) {
                buffer.append("No same number of geometries in " + getName() + " and " + a.getName() + "\n");
            }
            return (false);
        }

        // Remove noCheckList if equals is implemented
        // Sort them out by a key so comparison won't be O(n2)
        List<Poly> noCheckAgain = new ArrayList<Poly>();
        for (int i = 0; i < polyList.length; i++) {
            boolean found = false;
            for (int j = 0; j < aPolyList.length; j++) {
                // Already found
                if (noCheckAgain.contains(aPolyList[j])) {
                    continue;
                }
                if (polyList[i].compare(aPolyList[j], buffer)) {
                    found = true;
                    noCheckAgain.add(aPolyList[j]);
                    break;
                }
            }
            // polyList[i] doesn't match any element in noPolyList
            if (!found) {
                // No message otherwise all comparisons are found in buffer
                /*
                 if (buffer != null)
                 buffer.append("No corresponding geometry in " + getName() + " found in " + a.getName() + "\n");
                 */
                return (false);
            }
        }
        return (true);
    }

    /**
     * Method to crop given polygon against a connecting transistor. Function similar to Quick.cropActiveArc
     * @param poly
     * @return new polygon if was cropped otherwise the original
     */
    public Poly cropPerLayer(Poly poly) {
        // must be Manhattan
        Rectangle2D polyBounds = poly.getBox();
        if (polyBounds == null) {
            return poly;
        }
        polyBounds = new Rectangle2D.Double(polyBounds.getMinX(), polyBounds.getMinY(), polyBounds.getWidth(), polyBounds.getHeight());

        // search for adjoining transistor in the cell
        for (int i = 0; i < 2; i++) {
            PortInst pi = getPortInst(i);
            NodeInst ni = pi.getNodeInst();
            //if (!ni.isFET()) continue;

            // crop the arc against this transistor
            FixpTransform trans = ni.rotateOut();
            Technology tech = ni.getProto().getTechnology();
            Poly[] activeCropPolyList = tech.getShapeOfNode(ni);
            int nTot = activeCropPolyList.length;
            for (int k = 0; k < nTot; k++) {
                Poly nPoly = activeCropPolyList[k];
                if (nPoly.getLayer() != poly.getLayer()) {
                    continue;
                }
                nPoly.transform(trans);
                Rectangle2D nPolyBounds = nPoly.getBox();
                if (nPolyBounds == null) {
                    continue;
                }
                int result = Poly.cropBoxComplete(polyBounds, nPolyBounds);
                if (result == 1) {
                    // Empty polygon
                    return null;
                }
                if (result == -2) {
                    System.out.println("When is this case?");
                }
                Poly newPoly = new Poly(polyBounds);
                newPoly.setLayer(poly.getLayer());
                newPoly.setStyle(poly.getStyle());
                return newPoly;
            }
        }
        return poly;
    }

    /**
     * Method to determine if arc contains active diffusion
     * @return True if contains active diffusion
     */
    public boolean isDiffusionArc() {
        return getProto().getFunction().isDiffusion();
    }
}
