#
# Copyright (c) 2002, 2003 Art Haas
#
# This file is part of PythonCAD.
# 
# PythonCAD 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 2 of the License, or
# (at your option) any later version.
# 
# PythonCAD 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 PythonCAD; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
# A PointMapTree and PointBMapTree extend a basic Tree
#

import point
import util
import tolerance
import tree

class PointMapTree(tree.Tree):
    """A Tree class that containing objects that have Point objects.

The PointMapTree is based on the Tree class, so it shares all
the functionality of that class.

There is one extra method in the PointMapTree

mapPoint(): Find objects in the tree that a Point could fall upon.

Any class stored in this tree must provide a mapPoint() method.
The format of that method is as follows:

mapPoint(p[, tol])

p: Either a Point object or a two-float tuple

Optional argument:

tol: The tolerance used for testing the Point's proximity to the object.

A class should also have a __cmp__ method that can handle an instance
of the class being compared to a Point.
    """
    def __init__(self, t):
        """Instantiate a PointMapTree.

PointMapTree(t)

This function has one required argument:

t: The type of object stored in the tree.
        """
        tree.Tree.__init__(self, t)

    def mapPoint(self, p, tol=tolerance.TOL, count=2):
        """See if an object in the PointMapTree exists at a Point.

mapPoint(p [, tol, count])

This method has one required parameter:

p: Either a Point object or a tuple of two-floats

There are two optional arguments:

tol: A float equal or greater than 0 used to decide if the
     point is close enough to the objects held in the Tree.

count: An integer indicating the maximum number of objects
       to return. By default this value is 2.

The function returns a list of at tuples, each of the form

(obj, pt)

obj: Object that the Point is mapped to
pt: The projected Point on the object
        """
        _p = p
        if not isinstance(_p, (point.Point, tuple)):
            raise TypeError, "Invalid type for searching in PointMapTree: " + `_p`            
        if isinstance(_p, tuple):
            _x, _y = util.tuple_to_two_floats(_p)
            _p = point.Point(_x, _y)
        _t = tolerance.toltest(tol)
        _count = count
        if not isinstance(_count, int):
            _count = int(count)
        if _count < 0:
            raise ValueError, "Invalid count: %d" % _count
        _objlist = []
        for _obj in self:
            _pt = _obj.mapPoint(_p, _t)
            if _pt is not None:
                _objlist.append((_obj, _pt))
            if len(_objlist) == _count or cmp(_obj, _p) == 1:
                break
        return _objlist

class PointBMapTree(PointMapTree):
    """A Tree class that containing objects that have Point objects.

The PointBMapTree is based on the PointMapTree class, so it shares all
the functionality of that class. The PointBMapTree uses a binary
scan search on objects in the tree to find a particular object, where
the PointMapTree uses a linear search. This implies that any
PointBMapTree will only contain at most one object that a Point
object may be mapped on to.
    """
    def __init__(self, t):
        """Instantiate a PointBMapTree.

PointBMapTree(t)

This function has one required argument:

t: The type of object stored in the tree.
        """
        PointMapTree.__init__(self, t)

    def mapPoint(self, p, tol=tolerance.TOL):
        """See if an object in the PointBMapTree exists at a Point.

mapPoint(p [, tol])

This method has one required parameter:

p: Either a Point object or a tuple of two-floats

There is a single optional argument:

tol: A float equal or greater than 0 used to decide if the
     point is close enough to the objects held in the Tree.

The function returns a list of at tuples, each of the form

(obj, pt)

obj: Object that the Point is mapped to
pt: The projected Point on the object

The list will contain at most two tuples.
        """
        _p = p
        if not isinstance(_p, (point.Point, tuple)):
            raise TypeError, "Invalid type for searching in PointMapTree: " + `_p`            
        if isinstance(_p, tuple):
            _x, _y = util.tuple_to_two_floats(_p)
            _p = point.Point(_x, _y)
        _t = tolerance.toltest(tol)
        _objlist = []
        _scanlist = []        
        _lo = 0
        _hi = len(self)
        while _lo < _hi:
            _mid = (_hi+_lo)//2
            if _mid in _scanlist:
                break
            _scanlist.append(_mid)
            _obj = self[_mid]
            _res = cmp(_obj, _p)
            if _res == -1:
                _lo = _mid + 1
            elif _res == 1:
                _hi = _mid
            else:
                _pt = _obj.mapPoint(_p, _t)
                if _pt is not None:
                    _objlist.append((_obj, _pt))
                break
        return _objlist

