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

import java.util.Set;
import java.util.HashSet;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Point2D;

import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.GenMath;

/**
 * User: Gilda
 * Date: Feb 14, 2006
 */
public class ObjectQTree {
    private static int MAX_NUM_CHILDREN = 4;
    private static int MAX_NUM_NODES = 10;
    private ObjectQNode root;

    /**
     * Constructor
     * @param box represents the bounding box of the root leaf
     */
    public ObjectQTree(Rectangle2D box)
    {
        root = new ObjectQNode(box);
    }

    /**
     * Method to insert new element into qTree
     * @param newObj
     * @param rect
     * @return true if the element was inserted
     */
    public boolean add(Object newObj, Rectangle2D rect)
    {
        return (root.insert(new ObjectNode(newObj, rect)));
    }

    /**
     * Method to print the qTree elements
     */
    public void print()
    {
        if (root != null)
            root.print();
    }

    /**
     * Method to find set of elements overlapping the search box.
     * @param searchB
     * @return Set containing all objects inside the given bounding box
     */
    public Set find(Rectangle2D searchB)
    {
        return (root.find(searchB, null));
    }

    private static class ObjectNode
    {
        private Object elem;
        private Rectangle2D rect;
        private boolean isEmpty;

        ObjectNode(Object e, Rectangle2D r)
        {
            this.elem = e;
            this.rect = r;
            this.isEmpty = r.isEmpty();
        }

        boolean intersectsWithBox(Rectangle2D box)
        {
            if (!isEmpty)
                return box.intersects(rect);
            // if empty. Along the perimeter of the bounding box is also valid

            return DBMath.pointInRect(new Point2D.Double(rect.getMinX(), rect.getMinY()), box);
        }
        Rectangle2D getBounds() {return rect;}
    }

    private static class ObjectQNode
    {
		private Set<ObjectNode> nodes; // If Set, no need to check whether they are duplicated or not. Java will do it for you
		private ObjectQNode[] children;
        private Rectangle2D box; // bounding box of this quadrant

		/**
		 *
		 */
		private ObjectQNode (Rectangle2D b) { box = b; }

        public void print()
        {
            System.out.println("Node Box " + box.toString());
            if (nodes != null)
                for (ObjectNode node : nodes)
                    System.out.println("Node " + node.elem.toString());
            if (children != null)
			{
				for (int i = 0; i < MAX_NUM_CHILDREN; i++)
					if (children[i] != null)
                    {
                        System.out.print("Quadrant " + i);
                        children[i].print();
                    }
            }
        }

        /**
		 * To make sure new element is inserted in all childres
		 * @param centerX To avoid calculation inside function from object box
		 * @param centerY To avoid calculation inside function from object box
		 * @param obj ObjectNode to insert
		 * @return True if element was inserted
		 */
		protected boolean insertInAllChildren(double centerX, double centerY,
                                              ObjectNode obj)
		{
			int loc = GenMath.getQuadrants(centerX, centerY, obj.getBounds());
			boolean inserted = false;
			double w = box.getWidth()/2;
			double h = box.getHeight()/2;
			double x = box.getX();
			double y = box.getY();

			for (int i = 0; i < MAX_NUM_CHILDREN; i++)
			{
				if (((loc >> i) & 1) == 1)
				{
					Rectangle2D bb = GenMath.getQTreeBox(x, y, w, h, centerX, centerY, i);

					if (children[i] == null) children[i] = new ObjectQNode(bb);

					boolean done = children[i].insert(obj);

					inserted = (inserted) ? inserted : done;
				}
			}
			return (inserted);
		}

        protected Set<Object> find(Rectangle2D searchBox, Set<Object> list)
        {
            if (!box.intersects(searchBox)) return list;
            if (list == null)
                list = new HashSet<Object>();
            if (children != null)
            {
                for (int i = 0; i < MAX_NUM_CHILDREN; i++)
                {
                    list = children[i].find(searchBox, list);
                }
            }
            else
            {
                if (nodes != null)
                {
                    for (ObjectNode node : nodes)
                    {
                        // consider if node.rect is empty  -> it is a point (degenerated rectangle)
                        if (node.intersectsWithBox(searchBox))
//                        if (searchBox.intersects(node.rect))
                            list.add(node.elem);
                    }
                }
            }
            return list;
        }

		/**
		 * Method to insert the element in each quadrant
		 * @param obj ObjectNode to insert
		 * @return if node was really inserted
		 */
		protected boolean insert(ObjectNode obj)
		{
            // if bb is not empty, we check intersection. If empty (point), we check if it is contained in box
            if (!obj.intersectsWithBox(box))
			{
				// new element is outside of bounding box. Might need flag to avoid
				// double checking if obj is coming from findAndRemove
				return (false);
			}

			double centerX = box.getCenterX();
            double centerY = box.getCenterY();

			// Node has been split
			if (children != null)
			{
				return (insertInAllChildren(centerX, centerY, obj));
			}
			if (nodes == null)
			{
				nodes = new HashSet<ObjectNode>();
			}
			boolean inserted; // = false;

			if (nodes.size() < MAX_NUM_NODES)
			{
				inserted = nodes.add(obj);
			}
			else
			{
				// subdivides into MAX_NUM_CHILDREN. Might work only for 2^n
				children = new ObjectQNode[MAX_NUM_CHILDREN];
				double w = box.getWidth()/2;
				double h = box.getHeight()/2;
				double x = box.getX();
				double y = box.getY();

				// Redistributing existing elements in children
				for (int i = 0; i < MAX_NUM_CHILDREN; i++)
				{
					Rectangle2D bb = GenMath.getQTreeBox(x, y, w, h, centerX, centerY, i);
					children[i] = new ObjectQNode(bb);

					for (ObjectNode node : nodes)
					{
						children[i].insert(node);
					}
				}
//				nodes.clear(); // not sure about this clear yet
				nodes = null;
				inserted = insertInAllChildren(centerX, centerY, obj);
			}
			return (inserted);
		}
	}
}
