from typing import List
from ed_msgs.msg import EntityInfo
import PyKDL as kdl
from .util.equal_hash_mixin import EqualHashMixin
[docs]class Shape(EqualHashMixin):
    """Represents shape properties"""
    def __init__(self):
        pass
    @property
    def convex_hull(self) -> List[kdl.Vector]:
        return self._calc_convex_hull()
[docs]    def _calc_convex_hull(self):
        raise NotImplementedError(
            "_calc_convex_hull must be implemented by subclasses. "
            "Class {cls} has no implementation".format(cls=self.__class__.__name__)
        ) 
    @property
    def size(self) -> float:
        return self._calc_size()
[docs]    def _calc_size(self) -> float:
        raise NotImplementedError(
            "_calc_size must be implemented by subclasses. "
            "Class {cls} has no implementation".format(cls=self.__class__.__name__)
        ) 
    @property
    def x_min(self) -> float:
        return self._calc_x_min()
[docs]    def _calc_x_min(self) -> float:
        raise NotImplementedError(
            "_calc_x_min must be implemented by subclasses. "
            "Class {cls} has no implementation".format(cls=self.__class__.__name__)
        ) 
    @property
    def x_max(self) -> float:
        return self._calc_x_max()
[docs]    def _calc_x_max(self) -> float:
        raise NotImplementedError(
            "_calc_x_max must be implemented by subclasses. "
            "Class {cls} has no implementation".format(cls=self.__class__.__name__)
        ) 
    @property
    def y_min(self) -> float:
        return self._calc_y_min()
[docs]    def _calc_y_min(self) -> float:
        raise NotImplementedError(
            "_calc_y_min must be implemented by subclasses. "
            "Class {cls} has no implementation".format(cls=self.__class__.__name__)
        ) 
    @property
    def y_max(self) -> float:
        return self._calc_y_max()
[docs]    def _calc_y_max(self) -> float:
        raise NotImplementedError(
            "_calc_y_max must be implemented by subclasses. "
            "Class {cls} has no implementation".format(cls=self.__class__.__name__)
        ) 
    @property
    def z_min(self) -> float:
        return self._calc_z_min()
[docs]    def _calc_z_min(self) -> float:
        raise NotImplementedError(
            "_calc_z_min must be implemented by subclasses. "
            "Class {cls} has no implementation".format(cls=self.__class__.__name__)
        ) 
    @property
    def z_max(self) -> float:
        return self._calc_z_max()
[docs]    def _calc_z_max(self) -> float:
        raise NotImplementedError(
            "_calc_z_max must be implemented by subclasses. "
            "Class {cls} has no implementation".format(cls=self.__class__.__name__)
        )  
[docs]class RightPrism(Shape):
    """
    Represents a right prism, i.e., a  prism in which the joining edges and faces are perpendicular to the base
    faces. This is typical for the shapes resulting from the convex hull, z min and z max of EntityInfo
    """
    def __init__(self, convex_hull: List[kdl.Vector], z_min: float, z_max: float):
        """
        Constructor
        :param convex_hull: list with vectors representing the vertices of the convex hull. N.B.: these should only
            contain x and y values, the z-values are not relevant (the height information is contained in the z_min
            and z_max parameters).
        :param z_min: Minimum height [m] w.r.t. the center of the corresponding Entity
        :param z_max: Maximum height [m] w.r.t. the center of the corresponding Entity
        """
        super(RightPrism, self).__init__()
        self._convex_hull = convex_hull
        self._z_min = z_min
        self._z_max = z_max
[docs]    def _calc_x_max(self) -> float:
        return max(ch.x() for ch in self._convex_hull) 
[docs]    def _calc_x_min(self) -> float:
        return min(ch.x() for ch in self._convex_hull) 
[docs]    def _calc_y_max(self) -> float:
        return max(ch.y() for ch in self._convex_hull) 
[docs]    def _calc_y_min(self) -> float:
        return min(ch.y() for ch in self._convex_hull) 
[docs]    def _calc_z_max(self) -> float:
        return self._z_max 
[docs]    def _calc_z_min(self) -> float:
        return self._z_min 
[docs]    def _calc_convex_hull(self) -> List[kdl.Vector]:
        return self._convex_hull 
[docs]    def _calc_size(self) -> float:
        """
        Calculate the rough size of a shape
        >>> RightPrism([kdl.Vector(0, 0, 0), kdl.Vector(0, 1, 0), kdl.Vector(1, 1, 0), kdl.Vector(1, 0, 0)], z_min=0, z_max=1).size
        1.0
        >>> RightPrism([kdl.Vector(0, 0, 0), kdl.Vector(0, 2, 0), kdl.Vector(2, 2, 0), kdl.Vector(2, 0, 0)], z_min=0, z_max=2).size
        8.0
        """
        # TODO: this only uses the bounding box's size, which might not be accurate
        size_x = abs(self.x_max - self.x_min)
        size_y = abs(self.y_max - self.y_min)
        size_z = abs(self.z_max - self.z_min)
        return size_x * size_y * size_z  
[docs]def shape_from_entity_info(e: EntityInfo) -> Shape:
    """
    Creates a shape from the convex_hull, z_min and z_max of the EntityInfo object. If no convex hull is present,
    an empty Shape object is returned
    :param e: ed_msgs.msg.EntityInfo
    :return: Shape
    """
    # Check if we have a convex hull
    if not e.convex_hull:
        return Shape()
    return RightPrism(convex_hull=[kdl.Vector(p.x, p.y, p.z) for p in e.convex_hull], z_min=e.z_min, z_max=e.z_max) 
if __name__ == "__main__":
    import doctest
    doctest.testmod()