/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: StitchFillJob.java
 *
 * Copyright (c) 2008, 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.generator.layout.fill;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.IdMapper;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.EDatabase;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.hierarchy.View;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Geometric;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.VarContext;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.Technology;
import com.sun.electric.tool.Job;
import com.sun.electric.tool.JobException;
import com.sun.electric.tool.routing.AutoStitch;
import com.sun.electric.tool.routing.AutoStitch.AutoOptions;
import com.sun.electric.tool.routing.InteractiveRouter;
import com.sun.electric.tool.routing.Route;
import com.sun.electric.tool.routing.Router;
import com.sun.electric.tool.routing.SimpleWirer;
import com.sun.electric.tool.user.CellChangeJobs;
import com.sun.electric.tool.user.ExportChanges;
import com.sun.electric.tool.user.ui.WindowFrame;
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.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

/**
 * Fill Generator working on unconnected arcs
 * User: Gilda Garreton
 */
public class StitchFillJob extends Job
{
    private Cell topCell;
    private Library outLibrary;
    private List<Cell> generatedCells = new ArrayList<Cell>();
    private boolean evenHorizontal = true; // even metal layers are horizontal. Basic assumption
    private List<String> globalLayersWithExports = new ArrayList<String>();   // global definition of layers with exports

//    private static final boolean doFill = false;

    public StitchFillJob(Cell cell, Library lib, boolean doItNow)
    {
        super("Fill generator job", null, Type.CHANGE, null, null, Priority.USER);
        this.topCell = cell;
        this.outLibrary = lib;

        if (doItNow) // call from regressions
        {
            try
            {
                if (doIt())
                    terminateOK();

            } catch (JobException e)
            {
                e.printStackTrace();
            }
        }
        else
            startJob(); // queue the job
    }

    /**
     * Method to replace old cell by new cell if it was instantiated in other cells. It deletes the old cell after.
     * @param oldC
     * @param newC
     */
    private static Cell cleanReplacement(Cell oldC, Library lib, String fillCellName, EditingPreferences ep)
    {
        if (oldC != null)
        {
            String fakeName = oldC.getName()+"TMPStitch";
            assert(lib.findNodeProto(fakeName) == null); // we don't want to use an existing name
            IdMapper idMapper = oldC.rename(fakeName, null);
            oldC = idMapper.get(oldC.getId()).inDatabase(EDatabase.serverDatabase());
        }
        Cell newCell = Cell.makeInstance(ep, lib, fillCellName);
        if (oldC != null)
        {
            List<NodeInst> nodesToReplace = new ArrayList<NodeInst>();
            for(Iterator<NodeInst> nIt = oldC.getInstancesOf(); nIt.hasNext(); )
                nodesToReplace.add(nIt.next());
            for (NodeInst ni: nodesToReplace) {
                NodeInst replaced = ni.replace(newCell, ep, false, false, false);
                if (replaced == null)
                    System.out.println("Warning: conflicts while replacing existing cell in StitchFill");
//                assert replaced != null;
            }
            oldC.kill();
        }
        return newCell;
    }

    @Override
    public boolean doIt() throws JobException
    {
        EditingPreferences ep = getEditingPreferences();
        Point2D center = new Point2D.Double(0, 0);

        // Get cells from EditWindows
        if (topCell == null)
        {
            Technology tech = null;
            List<NodeInst> fillCells = new ArrayList<NodeInst>();
            List<Geometric> fillGeoms = new ArrayList<Geometric>();
            String fillCellName = "NewFill{lay}";

            Library lib = Library.getCurrent();
            if (lib == null)
            {
                System.out.println("No current library available.");
                return false;
            }


            Cell oldCell = lib.findNodeProto(fillCellName);
            // Delete previous version
            // Does a clean replacement and removal of the old cell
            Cell newCell = cleanReplacement(oldCell, lib, fillCellName, ep);

            for (Iterator<WindowFrame> itW = WindowFrame.getWindows(); itW.hasNext();)
            {
                WindowFrame w = itW.next();
                Cell c = w.getContent().getCell();

                if (c == null)
                {
                    System.out.println("No cell in window '" + w.getTitle() + "'");
                    continue;
                }
                if (c.getView() != View.LAYOUT)
                {
                    System.out.println("Cell '" + c + "' is not a layout cell");
                    continue;
                }

                Technology t = c.getTechnology();
                if (tech == null) tech = t;
                else if (tech != t)
                    System.out.println("Mixing technologies in fill generator: " +
                        tech.getTechName() + " " + t.getTechName());

                NodeInst ni = NodeInst.makeInstance(c, ep, center, c.getDefWidth(), c.getDefHeight(), newCell);
                fillCells.add(ni);
                fillGeoms.add(ni);
            }
            // actual fill generation
            doTheJob(newCell, fillCellName, fillCells, fillGeoms, null, false,Collections.<String>emptyList(), this, ep);
            return true; // done at this point
        }

        String[] instructions = topCell.getTextViewContents();

        if (instructions == null)
        {
            System.out.println("No fill instructions found.");
            return false;
        }
        for (String line : instructions)
        {
            if (line.startsWith("//"))
                continue; // commented line

            // metal direction
            if (line.startsWith("$"))
            {
                StringTokenizer parse = new StringTokenizer(line, "= $", false);
                int count = 0;
                while (parse.hasMoreTokens())
                {
                    assert(count < 2);
                    String value = parse.nextToken();
                    switch (count)
                    {
                        case 0:
                            assert(value.toLowerCase().equals("horizontal")); // only parameter supported for now
                            break;
                        case 1:
                            if (value.toLowerCase().equals("odd"))
                                evenHorizontal = false;
                            else if (value.toLowerCase().equals("even"))
                                evenHorizontal = true;
                            else
                            {
                                System.out.println("Invalid instruction '" + value + "' in stitch instructions doc.");
                            }
                            break;
                        default:
                            assert(false); // no case
                    }
                    count++;
                }
                continue;
            }
            else if (line.startsWith("@exports"))
            {
                // format @export = {9, 10}
                int index = line.indexOf("=");
                assert(index != -1);
                // exports is the only command for now
                StringTokenizer parse = new StringTokenizer(line.substring(index+1), " {,}", false);
                while (parse.hasMoreTokens())
                {
                    String value = parse.nextToken();
                    globalLayersWithExports.add(value);
                }
                continue;
            }
            StringTokenizer parse = new StringTokenizer(line, ":", false);
            int count = 0;
            String fillCellName = null, rest = null;

            while (parse.hasMoreTokens())
            {
                assert(count < 2);
                String value = parse.nextToken();
                switch (count)
                {
                    case 0: fillCellName = value;
                        break;
                    default:
                        rest = value;
                        break;
                }
                count++;
            }
            if (fillCellName == null) continue; // no parsing matching

            // getting possible options like "w" or "2x4"
            int index = fillCellName.indexOf("(");
            boolean wideOption = false;
            List<TileInfo> tileList = new ArrayList<TileInfo>();
            List<String> localLayersWithExports = new ArrayList<String>();

            // default: take the global definition
            localLayersWithExports.addAll(globalLayersWithExports);

            if (index != -1)
            {
                // Getting options like (W), (2x3) or (e[n;y] (exports)
                String options = fillCellName.substring(index);
                fillCellName = fillCellName.substring(0, index);
                parse = new StringTokenizer(options, "(), ", false);
                while (parse.hasMoreTokens())
                {
                    String option = parse.nextToken();
                    String lowerCase = option.toLowerCase();
                    // wide option where exports are added in lowest layer
                    if (lowerCase.equals("w"))  // wide option
                    {
                        wideOption = true;
                        fillCellName += "W";
                    }
                    else if (lowerCase.startsWith("e")) // overwrites global definition
                    {
                        localLayersWithExports.clear();
                        StringTokenizer lNames = new StringTokenizer(option.substring(1), "[];", false);
                        while (lNames.hasMoreTokens())
                        {
                            String lName = lNames.nextToken();
                            localLayersWithExports.add(lName);
                        }
                    }
                    else if (lowerCase.contains("x"))    // tiles
                    {
                        index = lowerCase.indexOf("x");
                        String str = lowerCase.substring(0, index);
                        int x = Integer.valueOf(str).intValue();
                        str = lowerCase.substring(index+1, lowerCase.length());
                        int y = Integer.valueOf(str).intValue();
                        // tile information
                        TileInfo tile = new TileInfo(x, y);
                        tileList.add(tile);
                    }
                }
            }

            if (rest == null)
            {
                System.out.println("Error parsing the fill instructions");
                return false;
            }
            // extracting fill subcells
            parse = new StringTokenizer(rest, " ,", false);
            String newName = fillCellName+"{lay}";
            Cell oldCell = outLibrary.findNodeProto(newName);
            // Delete previous version
            // Does a clean replacement and removal of the old cell
            Cell newCell = cleanReplacement(oldCell, outLibrary, newName, ep);

            Technology tech = null;
            List<NodeInst> fillCells = new ArrayList<NodeInst>();
            List<Geometric> fillGeoms = new ArrayList<Geometric>();

            while (parse.hasMoreTokens())
            {
                String fillCell = parse.nextToken();
                index = fillCell.indexOf("("); // options like (I)
                boolean instanceFlag = false;
                if (index != -1)
                {
                    instanceFlag = fillCell.toLowerCase().indexOf("(i") != -1; // only (I) option for now
                    fillCell = fillCell.substring(0, index);
                }
                Cell c = topCell.getLibrary().findNodeProto(fillCell+"{lay}");

                if (c == null)
                {
                    System.out.println("Cell '" + fillCell + "' does not exist");
                    continue;
                }
                if (c.getView() != View.LAYOUT)
                {
                    System.out.println("Cell '" + c + "' is not a layout cell");
                    continue;
                }
                Technology t = c.getTechnology();

                if (tech == null) tech = t;
                else if (tech != t)
                    System.out.println("Mixing technologies in fill generator: " + tech.getTechName() + " " + t.getTechName());
                NodeInst ni = NodeInst.makeInstance(c, ep, center, c.getDefWidth(), c.getDefHeight(), newCell);
                if (!instanceFlag)
                    fillCells.add(ni);
                fillGeoms.add(ni);
            }
            newCell.setTechnology(tech);
            generatedCells.add(newCell);

            // actual fill generation
            doTheJob(newCell, fillCellName, fillCells, fillGeoms, tileList, wideOption, localLayersWithExports, this, ep);
        }
        return true;
    }

    public List<Cell> getGeneratedCells() {return generatedCells;}

    /**
     * Method that executes the different functions to get the tool working
     */
    private static void doTheJob(Cell newCell, String fillCellName, List<NodeInst> fillCells,
                                 List<Geometric> fillGeoms, List<TileInfo> tileList,
                                 boolean wideOption, List<String> layersWithExports, StitchFillJob job, EditingPreferences ep)
    {
        // Re-exporting
        ExportChanges.reExportNodes(newCell, fillGeoms, false, true, false, true, true, ep);
//        if (!doFill) return;

        // flattening subcells
        new CellChangeJobs.ExtractCellInstances(newCell, fillCells, Integer.MAX_VALUE, true, true, true);

        // generation of master fill
        generateFill(newCell, wideOption, job.evenHorizontal, layersWithExports, job.getEditingPreferences());

        // tiles generation. Flat generation for now
        generateTiles(newCell, fillCellName, tileList, job);
    }

    /**
     * Class to store tile information
     */
    private class TileInfo
    {
        int x, y; // size in x and y axes

        TileInfo(int a, int b)
        {
            x = a;
            y = b;
        }
    }

    /**
     * Method to generate tiles of a given cell according to the description provided.
     * The function will use the auto stitch tool so no hierarchy is expected.
     * @param template Cell to use as template
     * @param fillCellName Root name of the template cell
     * @param tileList List of dimensions to create
     * @param job Job running the task
     */
    private static void generateTiles(Cell template, String fillCellName, List<TileInfo> tileList, Job job)
    {
        if (tileList == null) return; // nothing to tile
        EditingPreferences ep = job.getEditingPreferences();

        // tiles generation. Flat generation for now
        for (TileInfo info : tileList)
        {
            String newTileName = fillCellName + info.x + "x" + info.y + "{lay}";
            Cell newTile = Cell.makeInstance(ep, template.getLibrary(), newTileName);
            // generate the array
            List<NodeInst> niList = new ArrayList<NodeInst>();
            Rectangle2D rect = template.getBounds();
            double width = rect.getWidth();
            double height = rect.getHeight();
            double xPos = 0, yPos;

            for (int i = 0; i < info.x; i++)
            {
                yPos = 0;
                for (int j = 0; j < info.y; j++)
                {
                    NodeInst newNi = NodeInst.makeInstance(template, ep,
                    new Point2D.Double(xPos, yPos), width, height, newTile, Orientation.IDENT, null);
                    niList.add(newNi);
                    yPos += height;
                }
                xPos += width;
            }
            AutoOptions prefs = new AutoOptions(true);
            prefs.createExports = true;
            AutoStitch.runAutoStitch(newTile, niList, null, job, null, null, true, false, ep, prefs, true, null);
        }
    }

    /**
     * Main class to auto-stitch metal layers found in given cell
     * @param theCell Cell with the metal layer information
     * @param wideOption True if exports in the lowest layer will be added
     * @param evenHor
     * @param exportNames
     * @param ep EditingPreferences
     * @return True if auto-stitch ran without errors
     */
    private static boolean generateFill(Cell theCell, boolean wideOption, boolean evenHor, List<String> exportNames, EditingPreferences ep)
    {
        InteractiveRouter router  = new SimpleWirer(ep);
        List<Layer> listOfLayers = new ArrayList<Layer>(12);

        for (Iterator<Layer> itL = theCell.getTechnology().getLayers(); itL.hasNext(); )
        {
            Layer l = itL.next();
            if (!l.getFunction().isMetal()) continue;  // only metals
            l.getFunction().getLevel();
            listOfLayers.add(l);
        }

        Layer.getLayersSortedByRule(listOfLayers, Layer.LayerSortingType.ByFunctionLevel);
		Map<ArcProto,Integer> arcsCreatedMap = new HashMap<ArcProto,Integer>();
		Map<NodeProto,Integer> nodesCreatedMap = new HashMap<NodeProto,Integer>();
        List<Route> routeList = new ArrayList<Route>();
        Layer[] topLayers = new Layer[2];
        Layer bottomLayer = null;
        Map<String,Area> totalAreas = new HashMap<String,Area>();

        for (int i = 0; i < listOfLayers.size() - 1; i++)
        {
            Layer bottom = listOfLayers.get(i);
            boolean horizontal = isLayerHorizontal(bottom, evenHor);

            List<ArcInst> arcs = getArcsInGivenLayer(theCell.getArcs(), horizontal, bottom, null, null);
            Layer top = listOfLayers.get(i+1);
            Map<String,Area> remainingGeos = new HashMap<String,Area>();

            for (ArcInst ai : arcs)
            {
                // Picking only 1 export and considering the root name`
                Netlist netlist = theCell.getNetlist(); // getting new version of netlist after every routing.
                String bottomName = getExportRootName(ai, netlist);

                if (bottomName == null) continue; // nothing to export

                List<PinsArcPair> pairs = new ArrayList<PinsArcPair>();
                Rectangle2D bounds = ai.getBounds();
//                boolean horizontal = DBMath.isGreaterThan(bounds.getWidth(), bounds.getHeight());
                Area bottomA = new Area(bounds);

                if (bottomLayer == null || bottom == bottomLayer) // only for the bottom layer, the first one
                {
                    Area totalA = totalAreas.get(bottomName);
                    if (totalA == null)
                    {
                        totalA = new Area();
                        totalAreas.put(bottomName, totalA);
                    }
                    totalA.add(bottomA);
                }

                for(Iterator<Geometric> it = theCell.searchIterator(bounds); it.hasNext(); )
                {
                    Geometric nGeom = it.next();

                    if (!(nGeom instanceof ArcInst))
                    {
                        continue; // only arcs
                    }

                    ArcInst nai = (ArcInst)nGeom;
                    Layer nl = nai.getProto().getLayer(0);

                    if (nl != top) continue; // ascending order is easy

                    // Checking arc orientation with respect to layer
                    // If the orientation is not correct, then ignore it
                    // Must be perpendicular to the reference layer (bottom)
                    if (!isArcAligned(nai, !horizontal))
                        continue;

                    String topName = getExportRootName(nai, netlist);

                    if (topName == null || !topName.equals(bottomName)) continue; // no export matching

                    Rectangle2D nBnds = nai.getBounds();
                    Area topA = new Area(nBnds);
                    topA.intersect(bottomA);
                    Rectangle2D resultBnd = topA.getBounds2D();

                    // checking for full overlap. If horizontal -> width must be 100%
                    boolean fullCoverageX = (horizontal) ? DBMath.areEquals(resultBnd.getWidth(), nBnds.getWidth()) :
                        DBMath.areEquals(resultBnd.getHeight(), nBnds.getHeight());
                    boolean fullCoverageY = (horizontal) ? DBMath.areEquals(resultBnd.getHeight(), bounds.getHeight()) :
                        DBMath.areEquals(resultBnd.getWidth(), bounds.getWidth());

                    if (!fullCoverageX || !fullCoverageY)
                    {
                        // adding to list of pieces of geometries
                        Area a = remainingGeos.get(bottomName);
                        if (a == null)
                        {
                            a = new Area();
                            remainingGeos.put(bottomName, a);
                        }
                        a.add(topA);
                        continue; // skipping this case
                    }

                    EPoint insert = EPoint.fromLambda(resultBnd.getCenterX(), resultBnd.getCenterY());
                    boolean insertedAlready = false;
                    for (PinsArcPair pp : pairs)
                    {
                        if (pp.insert.equals(insert) && pp.cut.equals(resultBnd))
                        {
                            insertedAlready = true;
                            break;
                        }
                    }
                    if (!insertedAlready)
                        pairs.add(new PinsArcPair(nai, insert, resultBnd));
                }

                Collections.sort(pairs, pinsArcSort);
                ArcInst mostLeft = ai;
                routeList.clear();

                if (bottomLayer == null)
                    bottomLayer = bottom;

                // Mark the layer as possible one of the top ones
                if (!pairs.isEmpty())
                {
                    topLayers[0] = bottom;
                    topLayers[1] = top;
                }

                Area a = remainingGeos.get(bottomName);
                for (PinsArcPair pair : pairs)
                {
                    Route r = router.planRoute(theCell, mostLeft, pair.topArc, pair.insert, null, ep, true, true, pair.cut, null, evenHor);
                    //mostLeft = bottomSplit.rightArc;
                    routeList.add(r);

                    // Extract area from possible remaining geometry. This will happen
                    // when the wires are in zig-zag
                    if (a != null) // there is a remaining area
                    {
                        Area remove = new Area(pair.cut);
                        a.subtract(remove);
                    }
                }
                for (Route r : routeList)
                {
                    Router.createRouteNoJob(r, theCell, arcsCreatedMap, nodesCreatedMap, ep);
                }
            }

            // Adding remaining contacts due to non-100% overlap with original arcs
            for (Map.Entry<String,Area> e : remainingGeos.entrySet())
            {
                Area a = e.getValue();
                Netlist netlist = theCell.getNetlist();
                List<PolyBase> list = PolyBase.getPointsInArea(a, bottom, false, false);
                List<ArcInst> at = getArcsInGivenLayer(theCell.getArcs(), !horizontal, top, e.getKey(), netlist);
                List<PinsArcPair> pairs = new ArrayList<PinsArcPair>(2);

                // Add extra contacts. All points should be aligned in same horizontal or vertical line
                // first the top arcs. They should be less in number (less number of splits)
                for (PolyBase p : list)
                {
                    Rectangle2D resultBnd = p.getBounds2D();

                    // look for the first pair of arcs in bottom/top arcs that overlap with geometry
                    ArcInst topA = getArcInstOverlappingWithArea(resultBnd, at, horizontal, true); // perfect if fully contains the arc

                    if (topA != null)
                    {
                        EPoint insert = EPoint.fromLambda(resultBnd.getCenterX(), resultBnd.getCenterY());
                        pairs.add(new PinsArcPair(topA, insert, resultBnd));
                    }
                }
                // Sort the new pairs from left/top to right/bottom
                Collections.sort(pairs, pinsArcSort);

                // Look for bottomLayer arcs for those given positions
                List<ArcInst> ab = getArcsInGivenLayer(theCell.getArcs(), horizontal, bottom, e.getKey(), netlist);
                routeList.clear();

                for (PinsArcPair pair : pairs)
                {
                    ArcInst bottomA = getArcInstOverlappingWithArea(pair.cut, ab, horizontal, false);
                    if (bottomA != null)
                    {
                        //SplitContainter bottomSplit = splitArcAtPoint(bottomA, pair.insert);
                        //SplitContainter topSplit = splitArcAtPoint(pair.topArc, pair.insert);
                        Route r = router.planRoute(theCell,  bottomA, pair.topArc, pair.insert, null, ep, true, true, pair.cut, null, evenHor);
                        // remove the old one and add new arc
                        ab.remove(bottomA);
                        //ab.add(bottomSplit.rightArc);
                        topLayers[0] = bottom;
                        topLayers[1] = top;
                        routeList.add(r);
                    }
                    else if (Job.getDebug())
                    {
                         getArcInstOverlappingWithArea(pair.cut, ab, horizontal, false);
                        System.out.println("AFG: It couldn't find bottom layer for " + pair.cut);
                    }
                }
                for (Route r : routeList)
                {
                    Router.createRouteNoJob(r, theCell, arcsCreatedMap, nodesCreatedMap, ep);
                }

            }
        }

        // Connect cell instances
        for (Iterator<NodeInst> itNi = theCell.getNodes(); itNi.hasNext();)
        {
            NodeInst ni = itNi.next();

            // only cell instances
            if (!ni.isCellInstance()) continue;

            // Checking if the export overlaps with
            // Checking if there are exports overlapping with the arc
            List<String> doneExports = new ArrayList<String>();
            routeList.clear();
            SimpleWirer niRouter = new SimpleWirer(ep);

            Netlist netlist = theCell.getNetlist();
            for (Iterator<Export> itE = ni.getExports(); itE.hasNext(); )
            {
                Export ex = itE.next();
                String exportName = ex.getName();
                String rootName = extractRootName(exportName);
                boolean getAllPossibleConnection = true;

                if (!getAllPossibleConnection && doneExports.contains(rootName))   // exportName
                    continue; // export for this given NodeInst was done

                PrimitiveNode n = ex.getBasePort().getParent();
                Layer layer = n.getLayerIterator().next(); // assuming only 1
                PortInst pi = ex.getOriginalPort();
                PortProto epi = pi.getPortProto();
                EPoint exportCenter = pi.getCenter();
                NodeProto np = epi.getParent();
                if (!(np instanceof Cell))
                    continue; // not good for now.
                Cell c = (Cell)np;
                Netlist netl = c.getNetlist();
                Network jExp = netl.getNetwork((Export)epi, 0);

                // Look for all arcs in the cell instances that are on the given layer and network
                Cell jCell = jExp.getParent();
                SearchInHierarchy searchElems = new SearchInHierarchy(jExp, layer);
                HierarchyEnumerator.enumerateCell(jCell, VarContext.globalContext, searchElems);
                Area expA = searchElems.expA;

                // Algorithm will look for the closest connection.
                Rectangle2D bestCut = null; // represents the best cut
                ArcInst bestArc = null;
                Point2D bestCenter = null;
                double bestDistance = Double.MAX_VALUE;
                List<Network> netList = getNetworkFromName(netlist, rootName);

                Area[] theInters = new Area[]{new Area(), new Area()}; // for intersections above and below of the given Layer
                Layer[] theLayers = new Layer[2];
                List<ArcInst> ab = new ArrayList<ArcInst>();

                for (Network jNet : netList)
                {
                    for (Iterator<ArcInst> itA = jNet.getArcs(); itA.hasNext();)
                    {
                        ArcInst ai = itA.next();
                        Layer l = ai.getProto().getLayer(0);

                        if (l == layer)
                            System.out.println("found in same layer " + layer.getName() + " in export " + ex.getName());
                        else
                        {
                            int level = Layer.getNeighborLevel(l, layer);
                            if (Math.abs(level) == 1) // direct neighbor
                            {
                                int pos = (level == 1) ? 1 : 0;
                                // Not selecting the closest element yet
                                Area bnd = new Area(ai.getBounds());
                                bnd.intersect(expA);
                                if (!bnd.isEmpty())
                                {
                                    assert(theLayers[pos] == null || theLayers[pos] ==l);
                                    theInters[pos].add(bnd); // add piece to the big area
                                    theLayers[pos] = l;
                                    ab.add(ai);
                                }
                            }
                        }
                    }
                }

                for (int i = 0; i < theLayers.length; i++)
                {
                    Layer l = theLayers[i];
                    if (l == null) continue; // nothing found
                    Area a = theInters[i];
                    if (a.isEmpty()) continue; // nothing found

                    // get single polygons which represents the cuts
                    List<PolyBase> pols = Poly.getLoopsFromArea(a, l);
                    for (PolyBase p : pols)
                    {
                        Rectangle2D cut = p.getBounds2D();
                        Point2D center = EPoint.fromLambda(cut.getCenterX(), cut.getCenterY());
                        ArcInst ai = getArcInstOverlappingWithArea(cut, ab);
                        boolean horOrVer = DBMath.areEquals(center.getX(), exportCenter.getX()) ||
                                DBMath.areEquals(center.getY(), exportCenter.getY());
                        // angled arcs are not allowed.
                        if (!horOrVer)
                            continue;

                        if (getAllPossibleConnection)
                        {
                            Route r = niRouter.planRoute(theCell, ai, pi, center, null, ep, true, true, cut, null);
                            routeList.add(r);
                            doneExports.add(rootName);
                        }
                        else
                        {
                            double dist = DBMath.distBetweenPoints(center, exportCenter);
                            if (bestCenter == null || DBMath.isLessThan(dist, bestDistance))
                            {
                                // first time or the new distance is shorter
                                bestCenter = center;
                                bestCut = cut;
                                bestArc = ai;
                                bestDistance = dist;
                            }
                        }
                    }

                }

                if (!getAllPossibleConnection && bestCut != null) // best location found
                {
                    Route r = niRouter.planRoute(theCell,  bestArc, pi, bestCenter, null, ep, true, true, bestCut, null);
                    routeList.add(r);
                    doneExports.add(rootName);
                }
            }

            for (Route r : routeList)
            {
                Router.createRouteNoJob(r, theCell, arcsCreatedMap, nodesCreatedMap, ep);
            }
        }

        // Remove exports in lower layers
        Set<Export> toDelete = new HashSet<Export>();
        Map<String,Export> inBottomLayer = new HashMap<String,Export>(); // enough with 1 export stored

        Netlist netlist = theCell.getNetlist();
        // find the highest level metal for a given network
        Map<Network,Layer> maximumLayer = new HashMap<Network,Layer>();
        for (Iterator<Export> itE = theCell.getExports(); itE.hasNext();) {
            Export exp = itE.next();
            PrimitiveNode n = exp.getBasePort().getParent();
            Network net = netlist.getNetwork(exp, 0);
            Layer l = maximumLayer.get(net);
            for (Iterator<Layer> itL = n.getLayerIterator(); itL.hasNext();)
            {
                Layer lnew = itL.next();
                if (l == null) {
                    l = lnew;
                    continue;
                }
                if (lnew.getFunction().getLevel() > l.getFunction().getLevel()) {
                    l = lnew;
                }
            }
            maximumLayer.put(net, l);
        }

        for (Iterator<Export> itE = theCell.getExports(); itE.hasNext();)
        {
            Export exp = itE.next();
            PrimitiveNode n = exp.getBasePort().getParent();
            boolean found = false;
            String rootName = extractRootName(exp.getName());

            Layer topLayer = maximumLayer.get(netlist.getNetwork(exp, 0));
            int levelMax = topLayer.getFunction().getLevel();
            for (Iterator<Layer> itL = n.getLayerIterator(); itL.hasNext();)
            {
                Layer l = itL.next();
                // default case, two top layers
                int level = l.getFunction().getLevel();
                boolean exportOnLayer = exportNames.isEmpty() ? (level == levelMax || level == (levelMax-1)) : // old condition
                    exportNames.contains(l.getName());

                if (exportOnLayer) {
                    found = true;
                } else {
                    inBottomLayer.put(rootName, exp); // put the last one in the network.
                }
            }
            if (!found) // delete the export
                toDelete.add(exp);
        }

        // Add extra export in the middle of the bottom layer.
        if (wideOption)
        {
            Map<String,List<PinsArcPair>> newExports = new HashMap<String,List<PinsArcPair>>();
            if (bottomLayer == null)
            {
            	System.out.println("Error: No bottom layer found");
            	return false;
            }
            
            boolean horizontal = isLayerHorizontal(bottomLayer, evenHor);
            assert netlist == theCell.getNetlist();

            // For each export 1 export in the bottom layer should be insert
            for (Map.Entry<String,Export> e : inBottomLayer.entrySet())
            {
                Export exp = e.getValue();
                String rootName = extractRootName(exp.getName());
                Network net = netlist.getNetwork(exp, 0);
                // Collect first all the arcs in that layer
                List<ArcInst> arcs = getArcsInGivenLayer(net.getArcs(), horizontal, bottomLayer, null, null);
                Area area = totalAreas.get(rootName);
                List<PinsArcPair> l = new ArrayList<PinsArcPair>();

                // It could be multiple areas disconnected.
                List<PolyBase> list = PolyBase.getPointsInArea(area, bottomLayer, false, false);
                if (list == null) continue; // no matching export on bottom level
                for (PolyBase b : list)
                {
                    Rectangle2D resultBnd = b.getBounds2D();
                    EPoint insert = EPoint.fromLambda(resultBnd.getCenterX(), resultBnd.getCenterY());
                    PinsArcPair split; // looking for the first arc where the center is contained.
                    for (ArcInst ai : arcs)
                    {
                        Rectangle2D bnd = ai.getBounds();
                        if (bnd.contains(insert.getX(), insert.getY()))
                        {
//                            boolean horizontal = !DBMath.isGreaterThan(bnd.getWidth(), bnd.getHeight());
                            split = new PinsArcPair(ai, insert, null);
                            l.add(split);
                            break;
                        }
                    }
                }
                newExports.put(rootName, l);
            }

            for (Map.Entry<String,List<PinsArcPair>> e : newExports.entrySet())
            {
                List<PinsArcPair> pairs = e.getValue();
                for (PinsArcPair pair : pairs)
                {
                    SplitContainter split = splitArcAtPoint(pair.topArc, pair.insert, ep);
                    Export.newInstance(theCell, split.splitPin.getPortInst(0), e.getKey(), ep, PortCharacteristic.UNKNOWN);
                }
            }
        }

        // Delete after dealing with the wide option
        // At least one connection was done otherwise it will keep the original exports.
        // Option is mostly for debugging purposes.
        if (topLayers[0] != null || topLayers[1] != null)
            theCell.killExports(toDelete);

        // make sure at least one export on a network is the root name
        List<Network> nets = new ArrayList<Network>();
        for (Iterator<Network> itN = theCell.getNetlist().getNetworks(); itN.hasNext(); )
            nets.add(itN.next());

        // Collect the data to modify to avoid concurrency issues
        Map<Export, String> map = new HashMap<Export, String>();
        for (Network net: nets) {
            if (!net.isExported()) continue;
            String name = net.getName();
            String rootName = extractRootName(name);
            if (!name.equals(rootName)) {
                Export exp = net.getExports().next();
                map.put(exp, rootName);
            }
        }
        // Do the actual change now
        for (Map.Entry<Export,String> e : map.entrySet())
        {
            e.getKey().rename(e.getValue());
        }
        return true;
    }

    /**
     * Class to detect other elements in the same network with same layer
     */
    private static class SearchInHierarchy extends HierarchyEnumerator.Visitor
    {
        Layer layer;
        Layer.Function.Set layerSet;
        Network jExp;
        Area expA;

        SearchInHierarchy(Network exp, Layer l)
        {
            jExp = exp;
            layer = l;
            layerSet = new Layer.Function.Set(l);
            expA = new Area();
        }

        public boolean enterCell(HierarchyEnumerator.CellInfo info)
		{
            Cell cell = info.getCell();
            FixpTransform rTrans = info.getTransformToRoot();

            // Checking only arcs
            for(Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
            {
                ArcInst ai = it.next();
                Network aNet = info.getNetlist().getNetwork(ai, 0);

                boolean found = HierarchyEnumerator.searchInExportNetwork(aNet, info, jExp);
                if (!found) continue; // different networks

                Technology tech = ai.getProto().getTechnology();
                Poly[] arcInstPolyList = tech.getShapeOfArc(ai);
                int tot = arcInstPolyList.length;
                for(int i=0; i<tot; i++)
                {
                    Poly poly = arcInstPolyList[i];
                    Layer l = poly.getLayer();

                    if (l != layer)
                        continue; // wrong layer
                    poly.transform(rTrans);
                    expA.add(new Area(poly.getBounds2D()));
                }
            }
            return true;
        }

        public void exitCell(HierarchyEnumerator.CellInfo info)
		{
        }

        public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info)
		{
            NodeInst ni = no.getNodeInst();

            if (ni.isCellInstance()) return true; // not interested in cells

            if (NodeInst.isSpecialNode(ni)) return true; // like center or pin

            // NOTE: in the past, only arcs were checked.
            // Now regular PrimitiveNodes need to be checked because the node extractor now adds pure layer nodes.

            // checking if any port in the node connects to the export in case
            boolean found = false;
            for (Iterator<PortInst> pIt = ni.getPortInsts(); pIt.hasNext();)
            {
                PortInst pi = pIt.next();
                Network nNet = info.getNetlist().getNetwork(no, pi.getPortProto(), 0);
                found = HierarchyEnumerator.searchInExportNetwork(nNet, info, jExp);
                if (found)
                    break; // found network
            }
            if (found)
            {
                Technology tech = ni.getProto().getTechnology();
                Poly[] nodeInstPolyList = tech.getShapeOfNode(ni, true, true, layerSet);
                assert nodeInstPolyList.length < 2; // either not found or only 1
                if (nodeInstPolyList.length == 1)
                {
                    FixpTransform rTrans = info.getTransformToRoot();
                    Poly poly = nodeInstPolyList[0];
                    poly.transform(rTrans);
                    expA.add(new Area(poly.getBounds2D()));
                }
            }

            return true;
        }
    }

    /**
         * Method to collect arcs found in a given area. It doesn't check if the arc is horizontal or top level
         * @param resultBnd Search area
         * @param at List with arcs to search from
         * @return
         */
        private static ArcInst getArcInstOverlappingWithArea(Rectangle2D resultBnd, List<ArcInst> at)
        {
            Area topArea = new Area(resultBnd);
            for (ArcInst ai : at)
            {
                Rectangle2D r = ai.getBounds();

                // test if the current ai inserts with the given area
                // and it is fully contained along the axis the arc is aligned
                if (r.intersects(resultBnd))
                {
                    return ai;
                }
            }
            return null;
        }

    /**
     * Method to collect arcs found in a given area
     * @param resultBnd Search area
     * @param at List with arcs to search from
     * @param horizontal
     * @param topLayer
     * @return
     */
    private static ArcInst getArcInstOverlappingWithArea(Rectangle2D resultBnd, List<ArcInst> at, boolean horizontal, boolean topLayer)
    {
        Area topArea = new Area(resultBnd);
        for (ArcInst ai : at)
        {
            Rectangle2D r = ai.getBounds();

            // test if the current ai inserts with the given area
            // and it is fully contained along the axis the arc is aligned
            if (r.intersects(resultBnd))
            {
                Area rArea = new Area(r);
                rArea.intersect(topArea);
                Rectangle2D rect = rArea.getBounds2D();
                boolean validArc;
//                boolean horizontal = !DBMath.isGreaterThan(r.getWidth(), r.getHeight());

                if (horizontal && topLayer || !horizontal && !topLayer)  // top arc is aligned along Y or bottom arc along Y
                {
                    validArc = DBMath.areEquals(rect.getWidth(), r.getWidth());
                }
                else // top arc is aligned along X
                {
                    validArc = DBMath.areEquals(rect.getHeight(), r.getHeight());
                }

                if (validArc)
                {
                    // found
                    return ai;
                }
            }
        }
        return null;
    }

    /**
     * Method to get Network from a given network root name
     * @param netlist Netlist containing the network information
     * @param rootName Root name of the network
     * @return Network
     */
    private static List<Network> getNetworkFromName(Netlist netlist, String rootName)
    {
        List<Network> list = new ArrayList<Network>();

        for (Iterator<Network> it = netlist.getNetworks(); it.hasNext();)
        {
            Network net = it.next();
            if (net.getName().startsWith(rootName))
                list.add(net);
        }
        return list;
    }

    /**
     * Method to determine if Layer is aligned horizontally
     * @param layer Given Layer
     * @param evenH True if even layers are horizontal
     * @return True of layer is horizontal
     */
    private static boolean isLayerHorizontal(Layer layer, boolean evenH)
    {
    	if (layer == null || layer.getFunction() == null)
    		System.out.println(" ");
    	
        int metalNumber = layer.getFunction().getLevel();
        return (evenH && metalNumber%2==0) || (!evenH && metalNumber%2==1);
    }

    /**
     * Method to collect arcs in a given layer with a given export name from an iterator
     * @param itAi Arcs iterator
     * @param horizontal
     * @param layer Given layer
     * @param exportName Given export name. If null, any export name is valid
     * @param netlist  Given Netlist to find exports
     * @return List of ArcInsts
     */
    private static List<ArcInst> getArcsInGivenLayer(Iterator<ArcInst> itAi, boolean horizontal, Layer layer, String exportName, Netlist netlist)
    {
        List<ArcInst> arcs = new ArrayList<ArcInst>();

        for (; itAi.hasNext();)
        {
            ArcInst ai = itAi.next();

            // Checking arc orientation with respect to layer
            // If the orientation is not correct, then ignore it
            if (!isArcAligned(ai, horizontal))
                continue;

            Layer l = ai.getProto().getLayer(0);
            if (l != layer)
                continue;
            if (exportName != null)
            {
                Network jNet = netlist.getNetwork(ai, 0);
                Iterator<Export> itE = jNet.getExports();
                if (!itE.hasNext())
                {
                    if (Job.getDebug())
                        System.out.println("AFG: No export name associated to ArcInst '" + ai.getName() + "'");
                    continue; // no export
                }
                Export exp = itE.next(); // first is enough
                String expName = extractRootName(exp.getName());
                if (!expName.equals(exportName))
                    continue; // no match
            }
            arcs.add(ai);
        }
        return arcs;
    }

    /**
     * Method to check whether a given arc is properly oriented with respect to the expected input
     * @param ai the given ArcInst
     * @param horizontal True if the arc must be horizontal
     * @return True if the arc is aligned with the given direction
     */
    private static boolean isArcAligned(ArcInst ai, boolean horizontal)
    {
        EPoint head = ai.getHeadLocation();
        EPoint tail = ai.getTailLocation();
        if (horizontal)
            return (DBMath.areEquals(head.getY(), tail.getY()));
        return (DBMath.areEquals(head.getX(), tail.getX()));
    }

    /**
     * Method to extract the export root name from a given name
     * @param name String containing the export name
     * @return String containing the root name of the export
     */
    private static String extractRootName(String name)
    {
        int index = name.indexOf("_");
        if (index != -1) // remove any character after _
            name = name.substring(0, index);
        return name;
    }

    /**
     * Method to extract root name of the export in a given arc
     * @param ai arc with the export
     * @param netlist Given network to search in
     * @return Non-null string with the root name. Null if no export was found.
     */
    private static String getExportRootName(ArcInst ai, Netlist netlist)
    {
        Network jNet = netlist.getNetwork(ai, 0);
        // Picking only 1 export and considering the root name`
        // Assuming at 1 export per arc
        if (jNet == null)
            return null;
        assert(jNet != null);
        Iterator<String> exportNames = jNet.getExportedNames();
        if (!exportNames.hasNext())
            return null; // no export
        return extractRootName(exportNames.next());
    }

    /**
     * Method to split an arc at a given point
     * @param ai arc to split
     * @param insert point to split at
     * @param ep EditingPreferences
     * @return SplitContainter representing the split pin and new arcs
     */
    private static SplitContainter splitArcAtPoint(ArcInst ai, EPoint insert, EditingPreferences ep)
    {
        // create the break pins
        ArcProto ap = ai.getProto();
        NodeProto np = ap.findPinProto();
        if (np == null) return null;
        NodeInst ni = NodeInst.makeInstance(np, ep, insert, np.getDefWidth(ep), np.getDefHeight(ep), ai.getParent());
        if (ni == null)
        {
            System.out.println("Cannot create pin " + np.describe(true));
            return null;
        }

        SplitContainter container = new SplitContainter();
        container.splitPin = ni;

        // get location of connection to these pins
        PortInst pi = ni.getOnlyPortInst();

        // now save the arc information and delete it
        PortInst headPort = ai.getHeadPortInst();
        PortInst tailPort = ai.getTailPortInst();
        Point2D headPt = ai.getHeadLocation();
        Point2D tailPt = ai.getTailLocation();
        double width = ai.getLambdaBaseWidth();
//        String arcName = ai.getName();

        // create the new arcs
        ArcInst newAi1 = ArcInst.makeInstanceBase(ap, ep, width, headPort, pi, headPt, insert, null);
        ArcInst newAi2 = ArcInst.makeInstanceBase(ap, ep, width, pi, tailPort, insert, tailPt, null);
        newAi1.setHeadNegated(ai.isHeadNegated());
        newAi1.setHeadExtended(ai.isHeadExtended());
        newAi1.setHeadArrowed(ai.isHeadArrowed());
        newAi1.setTailNegated(ai.isTailNegated());
        newAi1.setTailExtended(ai.isTailExtended());
        newAi1.setTailArrowed(ai.isTailArrowed());

        newAi2.setHeadNegated(ai.isHeadNegated());
        newAi2.setHeadExtended(ai.isHeadExtended());
        newAi2.setHeadArrowed(ai.isHeadArrowed());
        newAi2.setTailNegated(ai.isTailNegated());
        newAi2.setTailExtended(ai.isTailExtended());
        newAi2.setTailArrowed(ai.isTailArrowed());

        // Determining which arc is left/top
        if (isLeftTop(headPt, tailPt))
        {
            container.leftArc = newAi1;
            container.rightArc = newAi2;
        }
        else
        {
            container.leftArc = newAi2;
            container.rightArc = newAi1;
        }
        ai.kill();
//        if (arcName != null)
//        {
//            if (headPt.distance(insert) > tailPt.distance(insert))
//            {
//                newAi1.setName(arcName);
//                newAi1.copyTextDescriptorFrom(ai, ArcInst.ARC_NAME);
//            } else
//            {
//                newAi2.setName(arcName);
//                newAi2.copyTextDescriptorFrom(ai, ArcInst.ARC_NAME);
//            }
//        }
        return container;
    }

    /**
     * Internal class to keep potential connections
     */
    private static class PinsArcPair
    {
        private ArcInst topArc;
        private EPoint insert;
        private Rectangle2D cut;

        PinsArcPair(ArcInst topA, EPoint point, Rectangle2D c)
        {
            topArc = topA;
            insert = point;
            cut = c;
        }
    }

    private static boolean isLeftTop(Point2D p1, Point2D p2)
    {
        if (DBMath.areEquals(p1.getX(), p2.getX()))
        {
            return (!DBMath.isGreaterThan(p1.getY(), p2.getY()));
        }
        else if (DBMath.areEquals(p1.getY(), p2.getY()))
        {
            return (!DBMath.isGreaterThan(p1.getX(), p2.getX()));
        }
        else
        {
            // based on X
            return (!DBMath.isGreaterThan(p1.getX(), p2.getX()));
//            if (Job.getDebug())
//                System.out.println("Case not considered in FillJob:isLeftTop");
//            assert(false); // not considered yet
        }
//        return false;
    }

    /**
     * Class to store temporary info
     */
    private static class SplitContainter
    {
        NodeInst splitPin;
        ArcInst leftArc, rightArc; // to keep track of new arcs after the original one was split
    }

    /**
     * Class to store temporary area according to a given direction
     */
//    private static class ExtraArea
//    {
//        Area[] areas = new Area[2]; // 0 -> bottom is horizontal, 1 -> vertical
//
//        ExtraArea(Area a, boolean hoz)
//        {
//            int pos = (hoz) ? 0 : 1;
//            areas[pos] = a;
//        }
//
//        Area getArea(boolean hoz)
//        {
//            int pos = (hoz) ? 0 : 1;
//            return areas[pos];
//        }
//
//        void setArea(Area a, boolean hoz)
//        {
//            int pos = (hoz) ? 0 : 1;
//            assert(areas[pos] == null);
//            areas[pos] = a;
//        }
//    }

    /**
     * To sort PinsArcPair
     */
    private static final PinsArcPairSort pinsArcSort = new PinsArcPairSort();

    /**
     * Comparator class for sorting PinsArcPair by their insertion point
     */
    private static class PinsArcPairSort implements Comparator<PinsArcPair>
    {
        /**
         * Method to compare two PinsArcPair objects by their insertion point.
         * @param l1 one PinsArcPair.
         * @param l2 another PinsArcPair.
         * @return an integer indicating their sorting order.
         */
        public int compare(PinsArcPair l1, PinsArcPair l2)
        {
            EPoint p1 = l1.insert;
            EPoint p2 = l2.insert;
            return (isLeftTop(p2, p1)?1:-1);
        }
    }

}
