/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: SeaOfGatesCellBuilder.java
 *
 * Copyright (c) 2011, 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.routing.seaOfGates;

import com.sun.electric.database.CellBackup;
import com.sun.electric.database.CellRevision;
import com.sun.electric.database.CellTree;
import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.Environment;
import com.sun.electric.database.ImmutableArcInst;
import com.sun.electric.database.ImmutableCell;
import com.sun.electric.database.ImmutableNodeInst;
import com.sun.electric.database.Snapshot;
import com.sun.electric.database.geometry.EGraphics;
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.hierarchy.View;
import com.sun.electric.database.id.ArcProtoId;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.id.PortProtoId;
import com.sun.electric.database.id.PrimitiveNodeId;
import com.sun.electric.database.text.CellName;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.technology.AbstractShapeBuilder;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.PrimitivePort;
import com.sun.electric.technology.TechPool;
import com.sun.electric.tool.Tool;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.NeededRoute;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.RouteNode;
import com.sun.electric.tool.routing.seaOfGates.SeaOfGatesEngine.SearchVertex;
import com.sun.electric.util.math.FixpRectangle;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.Orientation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Date;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.TreeMap;

/**
 *
 * @author dn146861
 */
class SeaOfGatesCellBuilder {

    static final boolean USE_MUTABLE_DATABASE = false;
    
    private final EditingPreferences ep;
    // oldSnapshot
    private final Snapshot oldSnapshot;
    private final Tool oldTool;
    private final Environment oldEnvironment;
    private final CellTree oldCellTree;
    private final CellBackup oldCellBackup;
    private final CellTree[] oldCellTrees;
    private final TechPool oldTechPool;
    private final CellRevision oldCellRevision;
    private final ImmutableCell oldCell;
    final CellId cellId;
    final Cell cell;
    final String resultCellName;
    private Cell resultCell;
    // curSnapshot
    private Snapshot curSnapshot;
    private final CellBackup[] curCellBackups;
    private CellTree curCellTree;
    private CellBackup curCellBackup;
    // nodes
    private final BitSet curNodes = new BitSet();
    private int curNodesCount;
    private int lastNodeId;
    private final TreeMap<Name, MaxNodeSuffix> maxNodeSuffixesOrdered = new TreeMap<Name, MaxNodeSuffix>(Name.STRING_NUMBER_ORDER);
    private final IdentityHashMap<PrimitiveNodeId, MaxNodeSuffix> maxNodeSuffixes = new IdentityHashMap<PrimitiveNodeId, MaxNodeSuffix>();
    private final IdentityHashMap<SeaOfGatesEngine.RouteNode, ImmutableNodeInst> addedNodesToNodeInst = new IdentityHashMap<SeaOfGatesEngine.RouteNode, ImmutableNodeInst>();
    // arcs
    private final List<ImmutableArcInst> curArcs = new ArrayList<ImmutableArcInst>();
    private int maxArcSuffix;

    // result CellBackup
    private final MyShapeBuilder resultShapeBuilder;
    
    SeaOfGatesCellBuilder(Snapshot snapshot, Cell cell, String resultCellName, EditingPreferences ep) {
        this.ep = ep;

        this.cell = cell;
        cellId = cell.getId();
        this.resultCellName = resultCellName;
        resultCell = null;
        if (USE_MUTABLE_DATABASE && resultCellName != null)
        {
	        oldCellTree = null;
	        oldCellBackup = null;
	        oldCellTrees = null;
	        oldTechPool = null;
	        oldCellRevision = null;
	        oldCell = null;
	        oldSnapshot = null;
	        oldTool = null;
	        oldEnvironment = null;
	        curCellBackups = null;
            resultShapeBuilder = null;
	        return;
		}

        oldSnapshot = snapshot;
        oldTool = oldSnapshot.tool;
        oldEnvironment = oldSnapshot.environment;
        oldCellTree = snapshot.getCellTree(cellId);
        oldCellBackup = oldCellTree.top;
        oldCellTrees = oldCellTree.getSubTrees();
        oldTechPool = oldCellTree.techPool;
        oldCellRevision = oldCellBackup.cellRevision;
        oldCell = oldCellRevision.d;


        int maxCellIndex = -1;
        if (!USE_MUTABLE_DATABASE && resultCellName != null) {
            CellId resultCellId = cellId.libId.newCellId(CellName.newName(resultCellName, View.LAYOUT, 1));
            maxCellIndex = resultCellId.cellIndex;
            resultShapeBuilder = new MyShapeBuilder(resultCellId);
        } else {
            resultShapeBuilder = null;
        }
        for (CellTree cellTree : oldSnapshot.cellTrees) {
            if (cellTree != null) {
                maxCellIndex = Math.max(maxCellIndex, cellTree.top.cellRevision.d.cellId.cellIndex);
            }
        }
        curCellBackups = new CellBackup[maxCellIndex + 1];
        for (CellTree cellTree : oldSnapshot.cellTrees) {
            if (cellTree != null) {
                curCellBackups[cellTree.top.cellRevision.d.cellId.cellIndex] = cellTree.top;
            }
        }

        lastNodeId = -1;
        for (ImmutableNodeInst n : oldCellRevision.nodes) {
            int nodeId = n.nodeId;
            assert !curNodes.get(nodeId);
            curNodes.set(nodeId);
            lastNodeId = Math.max(lastNodeId, nodeId);
        }
        curNodesCount = oldCellRevision.nodes.size();
        if (!USE_MUTABLE_DATABASE && resultShapeBuilder != null) {
            CellId resultCellId = resultShapeBuilder.cellId;
            curCellBackups[resultCellId.cellIndex] = resultShapeBuilder.commit();
            // Instantiate result cell into original cell
            int nodeId = ++lastNodeId;
            Name baseName = resultCellId.cellName.getBasename();
            MaxNodeSuffix maxSuffix = maxNodeSuffixesOrdered.get(baseName);
            if (maxSuffix == null) {
                maxSuffix = new MaxNodeSuffix(this, baseName);
                maxNodeSuffixesOrdered.put(baseName, maxSuffix);
            }
            Name name = maxSuffix.getNextName();
            TextDescriptor nameTd = ep.getNodeTextDescriptor();
            Orientation orient = Orientation.IDENT;
            EPoint anchor = EPoint.ORIGIN;
            EPoint size = EPoint.ORIGIN;
            int flags = 0;
            int techBits = 0;
            TextDescriptor protoTd = ep.getInstanceTextDescriptor();
            ImmutableNodeInst n = ImmutableNodeInst.newInstance(nodeId, resultCellId, name, nameTd,
                orient, anchor, size, flags, techBits, protoTd);
            maxSuffix.add(n);
            assert !curNodes.get(nodeId);
            curNodes.set(nodeId);
            curNodesCount++;
        }

        maxArcSuffix = -1;
        for (ImmutableArcInst a : oldCellRevision.arcs) {
            int arcId = a.arcId;
            while (curArcs.size() <= arcId)
                curArcs.add(null);
            assert curArcs.get(arcId) == null;
            curArcs.set(arcId, a);
            Name name = a.name;
            if (name.isTempname()) {
                assert name.getBasename() == ImmutableArcInst.BASENAME;
                maxArcSuffix = Math.max(maxArcSuffix, name.getNumSuffix());
            }
        }

        curSnapshot = oldSnapshot;
        curCellTree = oldCellTree;
        curCellBackup = oldCellBackup;
    }

    /**
     * Method to instantiate RouteResolution
     * @param resolution RouteResolution
     */
    synchronized void instantiate(SeaOfGatesEngine.RouteResolution resolution) {
        if (USE_MUTABLE_DATABASE && resultCellName != null) {
        	if (resultCell == null) {
				resultCell = Cell.makeInstance(ep, cell.getLibrary(), resultCellName + "{lay}");
				NodeInst.makeInstance(resultCell, ep, EPoint.fromLambda(0, 0), 0, 0, cell);
        	}
        	EDatabase db = EDatabase.currentDatabase();
            for (SeaOfGatesEngine.RouteNode rn : resolution.nodesToRoute) {
                if (rn.exists()) {
                    continue;
                }
	            PrimitiveNodeId protoId = (PrimitiveNodeId)rn.getProtoId();
	            PrimitiveNode pNp = protoId.inDatabase(db);
	            ERectangle full = ((PrimitiveNode)pNp).getFullRectangle();
	            double wid = rn.getSize().getX() + full.getLambdaWidth();
	            double hei = rn.getSize().getY() + full.getLambdaHeight();
            	NodeInst niDummy = NodeInst.makeDummyInstance(pNp, ep, rn.getTechBits(), rn.getLoc(), wid, hei, rn.getOrient());
            	FixpTransform trans = niDummy.rotateOut();
            	Poly[] polys = pNp.getTechnology().getShapeOfNode(niDummy);
            	for(int i=0; i<polys.length; i++) {
            		Poly poly = polys[i];
            		poly.transform(trans);
		            EPoint anchor = poly.getCenter();
            		Layer layer = poly.getLayer();
		            FixpRectangle bounds = poly.getBounds2D();
            		PrimitiveNode lNp = layer.getPureLayerNode();
//            		PrimitiveNodeId lProtoId = lNp.getId();

//		            MaxNodeSuffix maxSuffix = maxNodeSuffixes.get(lProtoId);
//		            if (maxSuffix == null) {
//		            	Name baseName = lNp.getPrimitiveFunction(0).getBasename();
//		                maxSuffix = maxNodeSuffixesOrdered.get(baseName);
//		                if (maxSuffix == null) {
//		                    maxSuffix = new MaxNodeSuffix(this, baseName);
//		                    maxNodeSuffixesOrdered.put(baseName, maxSuffix);
//		                }
//		                maxNodeSuffixes.put(lProtoId, maxSuffix);
//		            }
//		            int nodeId = ++lastNodeId;
//		            Name name = maxSuffix.getNextName();
//		            TextDescriptor nameTd = ep.getNodeTextDescriptor();
//		            EPoint size = EPoint.fromLambda(bounds.getWidth(), bounds.getHeight());
//		            int flags = 0;
//		            int techBits = 0;
//		            TextDescriptor protoTd = ep.getInstanceTextDescriptor();
		            NodeInst.makeInstance(lNp, ep, anchor, bounds.getWidth(), bounds.getHeight(), resultCell);
		            
//		            ImmutableNodeInst n = ImmutableNodeInst.newInstance(nodeId, lProtoId, name, nameTd,
//		                Orientation.IDENT, anchor, size, flags, techBits, protoTd);
//		            maxSuffix.add(n);
//		            assert !curNodes.get(nodeId);
//		            curNodes.set(nodeId);
//		            curNodesCount++;
//		            addedNodesToNodeInst.put(rn, n);
            	}
            }

            for (SeaOfGatesEngine.RouteArc ra : resolution.arcsToRoute) {
	            ArcProtoId protoId = ra.getProtoId();
	            ArcProto ap = protoId.inDatabase(db);
	            Layer layer = ap.getArcLayers()[0].getLayer();
	            PrimitiveNode np = layer.getPureLayerNode();
//        		PrimitiveNodeId lProtoId = np.getId();

	            SeaOfGatesEngine.RouteNode tail = ra.getTail();
	            EPoint tailLocation = tail.getLoc();
		        SeaOfGatesEngine.RouteNode head = ra.getHead();
	            EPoint headLocation = head.getLoc();
	            long gridExtendOverMin = ra.getGridExtendOverMin() + ap.getDefaultGridBaseWidth(ep);

//	            MaxNodeSuffix maxSuffix = maxNodeSuffixes.get(lProtoId);
//	            if (maxSuffix == null) {
//	            	Name baseName = np.getPrimitiveFunction(0).getBasename();
//	                maxSuffix = maxNodeSuffixesOrdered.get(baseName);
//	                if (maxSuffix == null) {
//	                    maxSuffix = new MaxNodeSuffix(this, baseName);
//	                    maxNodeSuffixesOrdered.put(baseName, maxSuffix);
//	                }
//	                maxNodeSuffixes.put(lProtoId, maxSuffix);
//	            }
//	            int nodeId = ++lastNodeId;
//	            Name name = maxSuffix.getNextName();
//	            TextDescriptor nameTd = ep.getNodeTextDescriptor();
//	            int flags = 0;
//	            int techBits = 0;
//	            TextDescriptor protoTd = ep.getInstanceTextDescriptor();

	            long[] gridCoords = new long[4];
	            makeGridBox(gridCoords, tailLocation, true, headLocation, true, gridExtendOverMin);
	            EPoint anchor = EPoint.fromGrid((gridCoords[0]+gridCoords[2])/2, (gridCoords[1]+gridCoords[3])/2);
	            long width = gridCoords[2]-gridCoords[0] - np.getDefaultGridBaseWidth(ep);
	            long height = gridCoords[3]-gridCoords[1] - np.getDefaultGridBaseHeight(ep);
	            EPoint size = EPoint.fromGrid(width, height);
	            NodeInst.makeInstance(np, ep, anchor, size.getX(), size.getY(), resultCell);
	            
	            
//	            ImmutableNodeInst n = ImmutableNodeInst.newInstance(nodeId, lProtoId, name, nameTd,
//	                Orientation.IDENT, anchor, size, flags, techBits, protoTd);
//	            maxSuffix.add(n);
//	            assert !curNodes.get(nodeId);
//	            curNodes.set(nodeId);
//	            curNodesCount++;
            }
            return;
        }

        // if placing results in original cell (typical) then delete unrouted arcs in that cell
        if (resultShapeBuilder == null)
        {
            for (int nodeId : resolution.nodesIDsToKill)
            {
                assert curNodes.get(nodeId);
                curNodes.clear(nodeId);
                curNodesCount--;
            }

            for (int arcId : resolution.arcsIDsToKill)
            {
                assert curArcs.get(arcId) != null;
                curArcs.set(arcId, null);
            }
        }

        for (SeaOfGatesEngine.RouteNode rn : resolution.nodesToRoute) {
            if (rn.exists()) {
                continue;
            }
            int nodeId = ++lastNodeId;
            PrimitiveNodeId protoId = (PrimitiveNodeId) rn.getProtoId();
            MaxNodeSuffix maxSuffix = maxNodeSuffixes.get(protoId);
            if (maxSuffix == null) {
                Name baseName = rn.getBaseName();
                maxSuffix = maxNodeSuffixesOrdered.get(baseName);
                if (maxSuffix == null) {
                    maxSuffix = new MaxNodeSuffix(this, baseName);
                    maxNodeSuffixesOrdered.put(baseName, maxSuffix);
                }
                maxNodeSuffixes.put(protoId, maxSuffix);
            }
            Name name = maxSuffix.getNextName();
            TextDescriptor nameTd = ep.getNodeTextDescriptor();
            Orientation orient = rn.getOrient();
            EPoint anchor = rn.getLoc();
            EPoint size = rn.getSize();
            int flags = 0;
            int techBits = rn.getTechBits();
            TextDescriptor protoTd = ep.getInstanceTextDescriptor();
            ImmutableNodeInst n = ImmutableNodeInst.newInstance(nodeId, protoId, name, nameTd,
                orient, anchor, size, flags, techBits, protoTd);
            if (resultShapeBuilder != null) {
                oldTechPool.getPrimitiveNode(protoId).genShape(resultShapeBuilder, n);
            } else {
              maxSuffix.add(n);
              assert !curNodes.get(nodeId);
              curNodes.set(nodeId);
              curNodesCount++;
            }
            rn.setTapConnection(n);
            addedNodesToNodeInst.put(rn, n);
        }

        for (SeaOfGatesEngine.RouteArc ra : resolution.arcsToRoute) {
            int arcId = curArcs.size();
            ArcProtoId protoId = ra.getProtoId();
            assert maxArcSuffix < Integer.MAX_VALUE;
            Name name = null;
            Name newName = null;
            if (ra.getName() != null) newName = Name.findName(ra.getName());
            if (newName != null && !newName.isTempname())
        	{
//            	System.out.println("NAMING NETWORK "+newName);
        		name = newName;
            } else
            {
            	name = ImmutableArcInst.BASENAME.findSuffixed(++maxArcSuffix);
            }
            TextDescriptor nameTd = ep.getArcTextDescriptor();

            SeaOfGatesEngine.RouteNode tail = ra.getTail();
            int tailNodeId = tail.exists() ? tail.getNodeId() : addedNodesToNodeInst.get(tail).nodeId;
            PortProtoId tailProtoId = tail.getPortProtoId();
            EPoint tailLocation = tail.getLoc();

            SeaOfGatesEngine.RouteNode head = ra.getHead();
            int headNodeId = head.exists() ? head.getNodeId() : addedNodesToNodeInst.get(head).nodeId;
            PortProtoId headProtoId = head.getPortProtoId();
            EPoint headLocation = head.getLoc();

            long gridExtendOverMin = ra.getGridExtendOverMin();
            int angle = ImmutableArcInst.DEFAULTANGLE;
            int flags = ra.getFlags(ep);

            ImmutableArcInst a = ImmutableArcInst.newInstance(arcId, protoId, name, nameTd,
                tailNodeId, tailProtoId, tailLocation,
                headNodeId, headProtoId, headLocation,
                gridExtendOverMin, angle, flags);
            if (resultShapeBuilder != null) {
                resultShapeBuilder.genShapeOfArc(a);
            } else {
                curArcs.add(a);
            }
        }

    	// if routing into original cell, add unrouted arcs that didn't get routed
        if (resultShapeBuilder != null) return;
        ArcProtoId unroutedId = oldTechPool.getGeneric().unrouted_arc.getId();
        long unroutedGridExtend = oldTechPool.getGeneric().unrouted_arc.getDefaultInst(ep).getGridExtendOverMin();
        int unroutedFlags = oldTechPool.getGeneric().unrouted_arc.getDefaultInst(ep).flags;
        for (SeaOfGatesEngine.RouteAddUnrouted rau : resolution.unroutedToAdd) {
            int arcId = curArcs.size();
            ArcProtoId protoId = unroutedId;
            assert maxArcSuffix < Integer.MAX_VALUE;
            Name name = ImmutableArcInst.BASENAME.findSuffixed(++maxArcSuffix);
            TextDescriptor nameTd = ep.getArcTextDescriptor();

            int tailNodeId = rau.getTailId();
            PortProtoId tailProtoId = rau.getTailPortProtoId();
            EPoint tailLocation = rau.getTailLocation();

            int headNodeId = rau.getHeadId();
            PortProtoId headProtoId = rau.getHeadPortProtoId();
            EPoint headLocation = rau.getHeadLocation();

            long gridExtendOverMin = unroutedGridExtend;
            int angle = ImmutableArcInst.DEFAULTANGLE;
            int flags = unroutedFlags;

            ImmutableArcInst a = ImmutableArcInst.newInstance(arcId, protoId, name, nameTd,
                    tailNodeId, tailProtoId, tailLocation,
                    headNodeId, headProtoId, headLocation,
                    gridExtendOverMin, angle, flags);
            curArcs.add(a);
        }
        resolution.clearRoutes();
    }

    private class MyShapeBuilder extends AbstractShapeBuilder {
        private final CellId cellId;
        private final TextDescriptor nameTd = ep.getNodeTextDescriptor();
        private final TextDescriptor protoTd = ep.getInstanceTextDescriptor();
        private CellBackup cellBackup;
        private final List<ImmutableNodeInst> nodes = new ArrayList<ImmutableNodeInst>();
        
        MyShapeBuilder(CellId cellId) {
            setup(oldTechPool);
            this.cellId = cellId;
            Date creationDate = new Date();
            ImmutableCell resultCell = ImmutableCell.newInstance(cellId, creationDate.getTime()).withTechId(oldCellBackup.cellRevision.d.techId);
            cellBackup = CellBackup.newInstance(resultCell, oldTechPool);
//            nodes.add(ImmutableNodeInst.newInstance(0, Generic.tech().cellCenterNode.getId(),
//                Name.findName("art@0"), nameTd,
//                Orientation.IDENT, EPoint.ORIGIN, EPoint.ORIGIN,
//                0, 0, protoTd));
        }

        @Override
        protected void addPoly(int numPoints, Poly.Type style, Layer layer, EGraphics graphicsOverride, PrimitivePort pp)
        {
            assert style == Poly.Type.CROSSED && numPoints > 0;
            long fixpLX = Long.MAX_VALUE;
            long fixpLY = Long.MAX_VALUE;
            long fixpHX = Long.MIN_VALUE;
            long fixpHY = Long.MIN_VALUE;
            for (int i = 0; i < numPoints; i++) {
                long x = coords[i*2];
                long y = coords[i*2 + 1];
                fixpLX = Math.min(fixpLX, x);
                fixpLY = Math.min(fixpLY, y);
                fixpHX = Math.max(fixpHX, x);
                fixpHY = Math.max(fixpHY, y);
            }
            assert fixpLX <= fixpHX && fixpLY <= fixpHY;
            int nodeId = nodes.size();
            PrimitiveNodeId protoId = layer.getPureLayerNode().getId();
            Name name = Name.findName("plnode@" + nodeId);
            EPoint anchor = EPoint.fromFixp((fixpLX + fixpHX) >> 1, (fixpLY + fixpHY) >> 1);
            EPoint size = EPoint.fromFixp(fixpHX - fixpLX, fixpHY - fixpLY);
            nodes.add(ImmutableNodeInst.newInstance(nodeId, protoId,
                name, nameTd,
                Orientation.IDENT, anchor, size,
                0, 0, protoTd));
        }

        @Override
        protected void addBox(Layer layer)
        {
            int nodeId = nodes.size();
            PrimitiveNodeId protoId = layer.getPureLayerNode().getId();
            Name name = Name.findName("plnode@" + nodeId);
            long fixpLX = coords[0];
            long fixpLY = coords[1];
            long fixpHX = coords[2];
            long fixpHY = coords[3];
            EPoint anchor = EPoint.fromFixp((fixpLX + fixpHX) >> 1, (fixpLY + fixpHY) >> 1);
            EPoint size = EPoint.fromFixp(fixpHX - fixpLX, fixpHY - fixpLY);
            nodes.add(ImmutableNodeInst.newInstance(nodeId, protoId,
                name, nameTd,
                Orientation.IDENT, anchor, size,
                0, 0, protoTd));
        }
        
        CellBackup commit() {
            cellBackup = cellBackup.with(cellBackup.cellRevision.d,
                nodes.toArray(new ImmutableNodeInst[nodes.size()]),
                null, null, oldTechPool);
            return cellBackup;
        }
    }
    
    private void makeGridBox(long[] gridCoords, EPoint tailLocation, boolean tailExtended, EPoint headLocation, boolean headExtended, long gridExtend)
    {
        long et = tailExtended ? gridExtend : 0;
        long eh = headExtended ? gridExtend : 0;
        long m;
        long lx, ly, hx, hy;
        int angle = GenMath.figureAngle(tailLocation, headLocation);
        switch (angle) {
            case -1:
            case 0:
                m = tailLocation.getGridY();
                lx = tailLocation.getGridX() - et;
                ly = m - gridExtend;
                hx = headLocation.getGridX() + eh;
                hy = m + gridExtend;
                break;
            case 900:
                m = tailLocation.getGridX();
                lx = m - gridExtend;
                ly = tailLocation.getGridY() - et;
                hx = m + gridExtend;
                hy = headLocation.getGridY() + eh;
                break;
            case 1800:
                m = tailLocation.getGridY();
                lx = headLocation.getGridX() - eh;
                ly = m - gridExtend;
                hx = tailLocation.getGridX() + et;
                hy = m + gridExtend;
                break;
            case 2700:
                m = tailLocation.getGridX();
                lx = m - gridExtend;
                ly = headLocation.getGridY() - eh;
                hx = m + gridExtend;
                hy = tailLocation.getGridY() + et;
                break;
            default:
                throw new AssertionError();
        }
        gridCoords[0] = lx;
        gridCoords[1] = ly;
        gridCoords[2] = hx;
        gridCoords[3] = hy;
    }

    Snapshot commit() {
        if (USE_MUTABLE_DATABASE && resultCellName != null) return null;
        ImmutableNodeInst[] newNodes;
        ImmutableArcInst[] newArcs;

        synchronized (this) {
            newNodes = new ImmutableNodeInst[curNodesCount];
            int oldNodeIndex = 0;
            int newNodeIndex = 0;
            for (MaxNodeSuffix maxSuffix : maxNodeSuffixesOrdered.values()) {
                while (oldNodeIndex < maxSuffix.insertionPoint) {
                    ImmutableNodeInst n = oldCellRevision.nodes.get(oldNodeIndex++);
                    if (curNodes.get(n.nodeId)) {
                        newNodes[newNodeIndex++] = n;
                    }
                }
                for (ImmutableNodeInst n : maxSuffix.addedNodes) {
                    assert curNodes.get(n.nodeId);
                    newNodes[newNodeIndex++] = n;
                }
            }
            while (oldNodeIndex < oldCellRevision.nodes.size()) {
                ImmutableNodeInst n = oldCellRevision.nodes.get(oldNodeIndex++);
                if (curNodes.get(n.nodeId)) {
                    newNodes[newNodeIndex++] = n;
                }
            }
            assert newNodeIndex == newNodes.length;

            int arcCount = 0;
            for (ImmutableArcInst a: curArcs) {
                if (a != null)
                    arcCount++;
            }
            newArcs = new ImmutableArcInst[arcCount];
            arcCount = 0;
            for (ImmutableArcInst a: curArcs) {
                if (a != null)
                    newArcs[arcCount++] = a;
            }
            assert arcCount == newArcs.length;
            Arrays.sort(newArcs, ImmutableArcInst.ARCS_ORDER);
        }

        curCellBackup = curCellBackup.with(oldCell, newNodes, newArcs, null, oldTechPool);
        curCellBackups[cellId.cellIndex] = curCellBackup;
        if (resultShapeBuilder != null) {
            curCellBackups[resultShapeBuilder.cellId.cellIndex] = resultShapeBuilder.commit();
        }
        curSnapshot = curSnapshot.with(oldTool, oldEnvironment, curCellBackups, null);

        return curSnapshot;
    }

    private static class MaxNodeSuffix {

        final Name basename;
        final int insertionPoint;
        int maxSuffix;
        List<ImmutableNodeInst> addedNodes = new ArrayList<ImmutableNodeInst>();

        private MaxNodeSuffix(SeaOfGatesCellBuilder b, Name basename) {
            this.basename = basename;
            insertionPoint = b.searchNodeInsertionPoint(basename.toString());
            maxSuffix = -1;
            if (insertionPoint > 0) {
                Name name = b.oldCellRevision.nodes.get(insertionPoint - 1).name;
                if (name.isTempname() && name.getBasename() == basename) {
                    maxSuffix = name.getNumSuffix();
                }

            }
        }

        private Name getNextName() {
            return basename.findSuffixed(maxSuffix + 1);
        }

        private void add(ImmutableNodeInst n) {
            maxSuffix++;
            assert n.name.getBasename() == basename;
            assert n.name.getNumSuffix() == maxSuffix;
            addedNodes.add(n);
        }
    }

    private int searchNodeInsertionPoint(String basename) {
        assert basename.endsWith("@0");
        char nextChar = (char) (basename.charAt(basename.length() - 2) + 1);
        String nextName = basename.substring(0, basename.length() - 2) + nextChar;
        int index = oldCellRevision.nodes.searchByName(nextName);
        return index >= 0 ? index : -(index + 1);
    }

    private int searchArcInsertionPoint(String basename) {
        assert basename.endsWith("@0");
        char nextChar = (char) (basename.charAt(basename.length() - 2) + 1);
        String nextName = basename.substring(0, basename.length() - 2) + nextChar;
        int index = oldCellRevision.arcs.searchByName(nextName);
        return index >= 0 ? index : -(index + 1);
    }
}
