/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: GeometrySearch.java
 *
 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.extract;

import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.HierarchyEnumerator;
import com.sun.electric.database.hierarchy.Nodable;
import com.sun.electric.database.text.Name;
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.variable.VarContext;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.tool.user.GraphicsPreferences;
import com.sun.electric.tool.user.Highlighter;
import com.sun.electric.tool.user.ui.LayerVisibility;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.FixpTransform;

import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Class to search hierarchically at a point and return all objects found.
 */
public class GeometrySearch extends HierarchyEnumerator.Visitor
{
    private List<GeometrySearchResult> found;
    private ERectangle geomBBnd;
    private boolean visibleObjectsOnly;
    private final LayerVisibility lv;
    private final GraphicsPreferences gp;
    private int cellsProcessed;         // for debug

    /**
     * Class that holds results of the search (a packaged Geometric and VarContext).
     */
	public static class GeometrySearchResult
	{
		private Geometric geom;
		private VarContext context;

		GeometrySearchResult(Geometric g, VarContext c)
		{
			geom = g;
			context = c;
		}

		public Geometric getGeometric() { return geom; }

		public VarContext getContext() { return context; }

		public String describe()
	    {
	        String contextstr = "current cell";
	        if (context != VarContext.globalContext) contextstr = getInstPath(context);
	        return geom + " in " + contextstr;
	    }

		/**
		 * Return the concatenation of all instances names left to right
	     * from the root to the leaf. Begin with the string with a separator
	     * and place a separator between adjacent instance names.
	     * @param vc the context of the search.
	     */
	    public String getInstPath(VarContext vc)
	    {
	        if (vc == VarContext.globalContext) return "";
	        String prefix = vc.pop()==VarContext.globalContext ? "" : getInstPath(vc.pop());
	        Nodable no = vc.getNodable();
	        if (no == null)
	        {
	            System.out.println("VarContext.getInstPath: context with null NodeInst?");
	        }
	        String me = no.getName();
	        if (no instanceof NodeInst)
	        {
	            // nodeInst, we want netlisted name, assume zero index of arrayed node
	            Name name = no.getNameKey();
	            me = name.subname(0).toString();
	        }
	        me = no.getProto().getName() + "[" + me + "]";
	        if (prefix.equals("")) return me;
	        return prefix + " / " + me;
	    }
	}

    public GeometrySearch(LayerVisibility lv, GraphicsPreferences gp)
    {
        this.lv = lv;
        this.gp = gp;
    }

    /**
     * Find a Primitive Node or Arc at a point in a cell.  The geometric found may exist down
     * the hierarchy from the given cell.
     * @param cell the cell in which the point resides
     * @param point a point to search under
     * @param visibleObjectsOnly true to consider only Geometries that are visible
     * @return a List of all search results
     */
    public List<GeometrySearchResult> searchGeometries(Cell cell, EPoint point, boolean visibleObjectsOnly)
    {
    	found = new ArrayList<GeometrySearchResult>();
        geomBBnd = ERectangle.fromLambda(point.getX(), point.getY(), 0, 0);
        this.visibleObjectsOnly = visibleObjectsOnly;
//    	PrimitiveNode.resetAllVisibility();
        cellsProcessed = 0;
        HierarchyEnumerator.enumerateCell(cell, VarContext.globalContext, this);
        return found;
    }

    public int getCellsProcessed() { return cellsProcessed; }

    /**************************************************************************************************************
     *  Enumerator class
     **************************************************************************************************************/

    public boolean enterCell(HierarchyEnumerator.CellInfo info)
    {
        Cell cell = info.getCell();
        FixpTransform xformToRoot = null;
        try
        {
            xformToRoot = info.getTransformToRoot().createInverse();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        assert(xformToRoot!=null);
        Rectangle2D rect = new Rectangle2D.Double();
        rect.setRect(geomBBnd);
        DBMath.transformRect(rect, xformToRoot);
        cellsProcessed++;

        boolean continueDown = false;
        for(Iterator<Geometric> it = cell.searchIterator(rect, false); it.hasNext(); )
        {
            Geometric geom = it.next();

            // PrimitiveInst or Cell
            if (geom instanceof NodeInst)
            {
                NodeInst oNi = (NodeInst)geom;
                if (oNi.isCellInstance())
                {
                    // keep searching
                    continueDown = true;
                } else
                {
                    // primitive found, ignore nodes that are fully invisible
                	double dist = Highlighter.distToNode(rect, oNi, null, gp.isShowTempNames());
                	if (dist > 0) continue;
                    PrimitiveNode node = (PrimitiveNode)oNi.getProto();
                    if (visibleObjectsOnly && !lv.isVisible(node)) continue;
                    found.add(new GeometrySearchResult(geom, info.getContext()));
                }
            } else
            {
                // arc, ignore arcs that and fully invisible
            	ArcInst ai = (ArcInst)geom;
            	double dist = Highlighter.distToArc(rect, ai, null, gp.isShowTempNames());
            	if (dist > 0) continue;
                ArcProto ap = ai.getProto();
                if (visibleObjectsOnly && !lv.isVisible(ap)) continue;
                found.add(new GeometrySearchResult(geom, info.getContext()));
            }
        }
        return continueDown;
    }

    public void exitCell(HierarchyEnumerator.CellInfo info) {}

    public boolean visitNodeInst(Nodable no, HierarchyEnumerator.CellInfo info)
    {
        if (visibleObjectsOnly && !no.getNodeInst().isExpanded()) return false;
        return true;
    }
}
