
# How entities are ordered; used by __cmp__ in GeometryEntity
ordering_of_classes = [
    "Point",
    "Segment",
    "Ray",
    "Line",
    "Triangle",
    "RegularPolygon",
    "Polygon",
    "Circle",
    "Ellipse",
    "Curve"
]

class GeometryEntity(tuple):
    """The base class for any geometrical entity."""

    def __new__(cls, *args, **kwargs):
        return tuple.__new__(cls, args)

    def __getnewargs__(self):
        return tuple(self)

    @staticmethod
    def do_intersection(e1, e2):
        """
        Determines the intersection between two geometrical entities. Returns
        a list of all of the intersections.
        """
        try:
            return e1.intersection(e2)
        except Exception:
            pass

        try:
            return e2.intersection(e1)
        except NotImplementedError:
            n1,n2 = type(e1).__name__, type(e2).__name__
            raise NotImplementedError("Unable to determine intersection between '%s' and '%s'" % (n1, n2))

    def is_similar(self, other):
        """
        Return True if self and other are similar. Two entities are similar
        if a uniform scaling (enlarging or shrinking) of one of the entities
        will allow one to obtain the other.

        Notes:
        ======
            - This method is not intended to be used directly but rather
              through the are_similar() method found in util.py.
            - An entity is not required to implement this method.
            - If two different types of entities can be similar, it is only
              required that one of them be able to determine this.
        """
        raise NotImplementedError()

    def intersection(self, o):
        """
        Returns a list of all of the intersections of this entity and another
        entity.

        Notes:
        ======
            - This method is not intended to be used directly but rather
              through the intersection() method found in util.py.
            - An entity is not required to implement this method.
            - If two different types of entities can intersect, it is only
              required that one of them be able to determine this.
        """
        raise NotImplementedError()


    @staticmethod
    def extract_entities(args, remove_duplicates=True):
        """
        Takes a set of arguments and extracts all of the GeometryEntity
        instances (recursively). Returns a tuple of all of the instances
        found.

        Notes:
        ======
            - Duplicates of entities are removed if the remove_duplicates
              argument is set to True, otherwise duplicates remain.
              The default is True.
            - Anything that is not a GeometryEntity instance is simply
              ignored.
            - Ordering of arguments is always maintained. If duplicates
              are removed then the entry with the lowest index is kept.
        """
        ret = list()
        for arg in args:
            if isinstance(arg, GeometryEntity):
                ret.append(arg)
            elif isinstance(arg, (list, tuple, set)):
                ret.extend(GeometryEntity.extract_entities(arg))
        if remove_duplicates:
            temp = set(ret)
            ind, n = 0, len(ret)
            for counter in xrange(0, n):
                x = ret[ind]
                if x in temp:
                    temp.remove(x)
                    ind += 1
                else:
                    del ret[ind]
        return tuple(ret)

    def __ne__(self, o):
        return not self.__eq__(o)

    def __radd__(self, a):
        return a.__add__(self)

    def __rsub__(self, a):
        return a.__sub__(self)

    def __rmul__(self, a):
        return a.__mul__(self)

    def __rdiv__(self, a):
        return a.__div__(self)

    def __str__(self):
        from sympy.printing import sstr
        return type(self).__name__ + sstr (tuple(self))

    def __repr__(self):
        from sympy.printing import srepr
        return type(self).__name__ + srepr(tuple(self))

    def __cmp__(self, other):
        n1 = self.__class__.__name__
        n2 = other.__class__.__name__
        c = cmp(n1, n2)
        if not c: return 0

        i1 = -1
        for cls in self.__class__.__mro__:
            try:
                i1 = ordering_of_classes.index(cls.__name__)
                break
            except ValueError:
                i1 = -1
        if i1 == -1: return c

        i2 = -1
        for cls in other.__class__.__mro__:
            try:
                i2 = ordering_of_classes.index(cls.__name__)
                break
            except ValueError:
                i2 = -1
        if i2 == -1: return c

        return cmp(i1, i2)
