"""
Maestro 3D spheres.
The sphere module allows creation and drawing of spheres.  Clients draw using
Group instances not through Sphere instances.
Control over the center, radius, color, resolution and opacity of a sphere are
provided. See the Sphere class for more info.
To draw any number of spheres, create the Sphere instance and add it to a Group
instance. Then invoke the Group's draw() method.
Copyright Schrodinger, LLC. All rights reserved.
"""
import schrodinger
from . import common
from .common import OPACITY_MAX
from .common import OPACITY_MIN
from .common import TRANSPARENCY_MAX
from .common import TRANSPARENCY_MIN
from .common import Group
# NOTE: SphereGroup is deprecated; use Group class.
SphereGroup = Group
maestro = schrodinger.get_maestro()
RESOLUTION_MIN = 4
RESOLUTION_MAX = 50
RESOLUTION_DEFAULT = 16
TRANSPARENCY_DEFAULT = 0.0  # for backwards-compatibility
OPACITY_DEFAULT = 1.0
# Constants used to calculate bounding box:
BOUNDING_BOX_INIT_VALUE = 100000000.0
#############################################################################
#                              CLASSES                                      #
#############################################################################
[docs]class SphereCore(common.Primitive):
    """
    Base class for Sphere and MaestroSphere classes.
    See doc string for __init__ for details on instantiation.
    Spheres should be added to a SphereGroup and drawing done via
    SphereGroup.  See SphereGroup documentation.
    """
    _blend = None
    _smooth_point = None
    _cull = None
    _lighting = None
    _lighting_model = None
    _shading_model = None
[docs]    def __init__(
            self,
            x=None,
            y=None,
            z=None,
            color=None,
            r=None,
            g=None,
            b=None,  # for backwards-compatability
            radius=None,
            transparency=None,  # for backwards-compatability
            opacity=OPACITY_DEFAULT,
            resolution=RESOLUTION_DEFAULT):
        """
        Constructor requires:
        x, y, z:  coordinate specifying center of sphere in Angstroms.
        color:    One of:
                    Color object
                    Color name (string)
                    Tuble of (R, G, B) (each 0.0-1.0)
        radius:   radius of the sphere in Angstroms
        Optional arguments:
        opacity:  0.0 (transparent) through 1.0 (opaque)
                       Defaults to 1.0
        resolution:    4 to 50
                       Defaults to 16
        """
        if x is None or y is None or z is None:
            raise ValueError(
                "Must specify x, y and z values to define the sphere center")
        elif radius is None:
            raise ValueError("Must specify a value for radius")
        else:
            self.x = x
            self.y = y
            self.z = z
            self.radius = radius
        if color is not None:
            self.r, self.g, self.b = common.color_arg_to_rgb(color)
        elif r is not None and g is not None and b is not None:
            # for backwards-compatability
            self.r = float(r)
            self.g = float(g)
            self.b = float(b)
        else:
            raise ValueError("Must specify a color")
        # Clamp to range of 0.0 and 1.0, inclusive
        if transparency is not None:  # for backwards-compatability
            if transparency < TRANSPARENCY_MIN:
                self.opacity = OPACITY_MAX
            elif transparency > TRANSPARENCY_MAX:
                self.opacity = OPACITY_MIN
            else:
                self.opacity = 1.0 - transparency
        else:  # Use opacity
            if opacity < OPACITY_MIN:
                self.opacity = OPACITY_MIN
            elif opacity > OPACITY_MAX:
                self.opacity = OPACITY_MAX
            else:
                self.opacity = opacity
        # Clamp the resolution
        if resolution < RESOLUTION_MIN:
            self.resolution = RESOLUTION_MIN
        elif resolution > RESOLUTION_MAX:
            self.resolution = RESOLUTION_MAX
        else:
            self.resolution = resolution
        return 
    def _calculateBoundingBox(self, mat):
        xyzmin = []
        xyzmax = []
        for k in range(6):
            xyzmin.append(BOUNDING_BOX_INIT_VALUE)
            xyzmax.append(-BOUNDING_BOX_INIT_VALUE)
        vertex = []
        vertex.append(
            (self.x - self.radius, self.y - self.radius, self.z + self.radius))
        vertex.append(
            (self.x + self.radius, self.y - self.radius, self.z + self.radius))
        vertex.append(
            (self.x + self.radius, self.y + self.radius, self.z + self.radius))
        vertex.append(
            (self.x - self.radius, self.y + self.radius, self.z + self.radius))
        vertex.append(
            (self.x - self.radius, self.y - self.radius, self.z - self.radius))
        vertex.append(
            (self.x + self.radius, self.y - self.radius, self.z - self.radius))
        vertex.append(
            (self.x + self.radius, self.y + self.radius, self.z - self.radius))
        vertex.append(
            (self.x - self.radius, self.y + self.radius, self.z - self.radius))
        tmp = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
        for i in range(8):
            for k in range(3):
                tmp[k + 3] = vertex[i][k]
            tmp[0] = mat[0][0] * tmp[3] + mat[0][1] * tmp[4] + mat[0][2] * tmp[
                5] + mat[0][3]
            tmp[1] = mat[1][0] * tmp[3] + mat[1][1] * tmp[4] + mat[1][2] * tmp[
                5] + mat[1][3]
            tmp[2] = mat[2][0] * tmp[3] + mat[2][1] * tmp[4] + mat[2][2] * tmp[
                5] + mat[2][3]
            for k in range(6):
                if xyzmin[k] > tmp[k]:
                    xyzmin[k] = tmp[k]
                if xyzmax[k] < tmp[k]:
                    xyzmax[k] = tmp[k]
        return (xyzmin, xyzmax) 
[docs]class MaestroSphere(common._MaestroPrimitiveMixin, SphereCore):
    """
    Class to create a 3D sphere in Maestro.  This sphere will be drawn at the
    appropriate time in Maestro to interact properly with transparent objects.
    Spheres should be added to a Group and drawing done via the Group. See
    Group documentation.
    API Example::
        import schrodinger.maestro.maestro as maestro
        import schrodinger.graphics3d.common as common
        import schrodinger.graphics3d.sphere as sphere
        sphere_grp = common.Group()
        st = maestro.workspace_get()
        for at in st.atom:
            sph = sphere.MaestroSphere(
                x=at.x,
                y=at.y,
                z=at.z,
                radius=1.0,
                resolution=20,
                opacity=0.8,
                color='red'
            )
            # Add the primative to the container.
            sphere_grp.add(sph)
        # Unlike Sphere MaestroSphere simply needs to be shown to be drawn.
        # No special callback like there is for Spheres is needed.
        sphere_grp.show()
        # Hide the markers.
        sphere_grp.hide()
        # Remove the markers and the callback.
        sphere_grp.clear()
        maestro.workspace_draw_function_remove(sphere_grp.draw)
    """
[docs]    def __init__(
            self,
            x=None,
            y=None,
            z=None,
            color=None,
            r=None,
            g=None,
            b=None,  # for backwards-compatability
            radius=None,
            transparency=None,  # for backwards-compatability
            opacity=OPACITY_DEFAULT,
            resolution=RESOLUTION_DEFAULT,
            angle_dep_transparency=False):
        """
        Constructor requires:
        x, y, z:  coordinate specifying center of sphere in Angstroms.
        color:    One of:
                    Color object
                    Color name (string)
                    Tuple of (R, G, B) (each 0.0-1.0)
        radius:   radius of the sphere in Angstroms
        Optional arguments:
        opacity:  0.0 (transparent) through 1.0 (opaque)
                       Defaults to 1.0
        resolution:    4 to 50
                       Defaults to 16
        """
        self.sphere = None
        self._x = 0
        self._y = 0
        self._z = 0
        self._radius = 0
        SphereCore.__init__(self, x, y, z, color, r, g, b, radius, transparency,
                            opacity, resolution)
        self.angle_dep_transparency = angle_dep_transparency
        self.sphere = maestro.create_sphere(self.x, self.y, self.z, self.r,
                                            self.g, self.b, self.radius,
                                            self.opacity, self.resolution,
                                            self.angle_dep_transparency)
        maestro_objects = [self.sphere]
        common.Primitive.__init__(self, maestro_objects) 
    # Helper functions
[docs]    def setCoords(self):
        if self.sphere:
            maestro.set_coords(self.sphere, self._x, self._y, self._z) 
[docs]    def setXYZCoords(self, x, y, z):
        self._x = x
        self._y = y
        self._z = z
        self.setCoords() 
    # Accessors
    def _getX(self):
        return self._x
    def _setX(self, value):
        self._x = value
        self.setCoords()
    def _getY(self):
        return self._y
    def _setY(self, value):
        self._y = value
        self.setCoords()
    def _getZ(self):
        return self._z
    def _setZ(self, value):
        self._z = value
        self.setCoords()
    def _getRadius(self):
        return self._radius
    def _setRadius(self, value):
        self._radius = value
        if self.sphere:
            maestro.set_radius(self.sphere, self._radius)
    x = property(_getX, _setX, doc="X coordinate")
    y = property(_getY, _setY, doc="Y coordinate")
    z = property(_getZ, _setZ, doc="Z coordinate")
    radius = property(_getRadius, _setRadius, doc="Sphere's radius")