"""
Maestro 3D boxes.
The box module allows creation and drawing of boxes and parallelepipeds.
A box is a special case of a parallelepiped where angles between edges are 90°
and edges are parallel to the XYZ axes.
Control over the positioning, color, and opacity of a box are provided. See the
MaestroBos and MaestroParallelepiped classes for more info.
To draw any number of boxes, create the class instances, add them 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 FILL
from .common import LINE
from .common import OPACITY_MAX
from .common import OPACITY_MIN
from .common import Group  # noqa: F401
DASHED_LINE = 2
maestro = schrodinger.get_maestro()
try:
    from schrodinger.ui import maestro_ui
except ImportError:
    maestro_ui = None
OPACITY_DEFAULT = 1.0
# Constants used to calculate bounding box:
BOUNDING_BOX_INIT_VALUE = 100000000.0
#############################################################################
#                              CLASSES                                      #
#############################################################################
[docs]class MaestroParallelepiped(common.Primitive):
    """
    Class for placing a parallelepiped into the Workspace.  Can be used for
    drawing boxes that are not positioned with edges parallel to 3 axes.
    3D objects should be added to a graphics3d.common.Group. See the
    graphics3d.common.Group documentation.
    See note on transparency in the module level docstring.
    API Example::
        import schrodinger.maestro.maestro as maestro
        import schrodinger.graphics3d.common as common
        import schrodinger.graphics3d.box as box
        box_grp = common.Group()
        st = maestro.workspace_get()
        bx = box.Parallelepiped(
            points=[origin, xyz2, xyz3, xyz4]
            color='red',
            opacity=1.0,
            style=box.LINE
        )
        # Add the primitive to the container.
        box_grp.add(bx)
        # Hide the markers.
        box_grp.hide()
        # Remove the markers and the callback.
        box_grp.clear()
    """
[docs]    def __init__(self,
                 points,
                 color,
                 opacity=OPACITY_DEFAULT,
                 line_width=1.0,
                 style=LINE):
        """
        Constructor requires:
        points: List of 4 (x, y, z) coordinates, representing half of the
                  corners of the box. First coordinate must be adjacent to
                  each of the other 3 coordinates.
        color:    One of:
                    Color object
                    Color name (string)
                    Tuple of (R, G, B) (each 0.0-1.0)
        Optional arguments:
        opacity:  0.0 (invisible) through 1.0 (opaque)
                       Defaults to 1.0
        line_width:    Line width. Default is 1.0.
        style:         FILL, LINE or DASHED_LINE.  Default is LINE.
        """
        r, g, b = common.color_arg_to_rgb(color)
        # Clamp to range of 0.0 and 1.0, inclusive
        if opacity < OPACITY_MIN or opacity > OPACITY_MAX:
            raise ValueError(
                "Invalid opacity: %s (must be betwen 0.0 and 1.0)" % opacity)
        if style != FILL and style != LINE and style != DASHED_LINE:
            raise ValueError("Style must be one of: FILL, LINE, DASHED_LINE")
        if len(points) != 4:
            raise ValueError("Must specify 4 points")
        assert len(points) == 4
        assert len(points[0]) == 3
        points = [maestro_ui.MM_GraphicsVec3d(*xyz) for xyz in points]
        parallelepiped = maestro_ui.MM_GraphicsParallelepiped(points)
        # Create 8 vertices, for boundary calculation. Also used for
        # create_polyhedron() call.
        self.vertices = [
            (p.x(), p.y(), p.z()) for p in parallelepiped.getPoints()
        ]
        if style == FILL:
            # Will be creating a polygon 3D object, with filled sides
            # Parallelepiped objects don't support side filling.
            faces = [
                [0, 2, 4, 1],
                [0, 1, 5, 3],
                [0, 3, 6, 2],
                [2, 6, 7, 4],
                [1, 4, 7, 5],
                [3, 5, 7, 6],
            ]
            normals = [common.create_normal([self.vertices[i] for i in indices])
                       for indices in faces]  # yapf: disable
            assert len(normals) == 6
            obj = maestro.create_polyhedron(self.vertices, faces, normals, r, g,
                                            b, opacity, style)
        else:
            # Will be creating a parallelepiped 3D object, with lines.
            # Polygon objects don't support variable line widths.
            # style:  0=solid lines, 1=short dashed lines, 2=dashed cylinders
            if style == DASHED_LINE:
                style = 1
            elif style == LINE:
                style = 0
            obj = maestro.create_parallelepiped(parallelepiped, r, g, b,
                                                line_width, style)
        maestro_objects = [obj]
        common.Primitive.__init__(self, maestro_objects) 
    def _calculateBoundingBox(self, mat):
        xyzmin = []
        xyzmax = []
        for k in range(6):
            xyzmin.append(BOUNDING_BOX_INIT_VALUE)
            xyzmax.append(-BOUNDING_BOX_INIT_VALUE)
        tmp = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
        for xyz in self.vertices:
            for k in range(3):
                tmp[k + 3] = xyz[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 MaestroBox(MaestroParallelepiped):
    """
    Class for placing a 3D box into the Workspace. Edges of the box will
    be aligned with X/Y/Z axes. For custom rotation, or to create "skewed"
    boxes, use MaestroParallelepiped class.
    3D objects should be added to a graphics3d.common.Group. See the
    graphics3d.common.Group documentation.
    See note on transparency in the module level docstring.
    API Example::
        import schrodinger.maestro.maestro as maestro
        import schrodinger.graphics3d.common as common
        import schrodinger.graphics3d.box as box
        box_grp = common.Group()
        st = maestro.workspace_get()
        bx = box.Box(
            center=(x, y, z),
            extents=(10, 10, 10),
            color='red',
            opacity=1.0,
            style=box.LINE
        )
        # Add the primitive to the container.
        box_grp.add(bx)
        # Hide the markers.
        box_grp.hide()
        # Remove the markers and the callback.
        box_grp.clear()
    """
[docs]    def __init__(self,
                 center,
                 extents,
                 color,
                 opacity=OPACITY_DEFAULT,
                 line_width=1.0,
                 style=LINE):
        """
        Constructor requires:
        center:   List of 3 Angstrom values indicating the center coordinate
                  of the box.
        extents:  List of 3 float values in Angstroms -
                  x length, y length, z length.
        color:    One of:
                    Color object
                    Color name (string)
                    Tuple of (R, G, B) (each 0.0-1.0)
        Optional arguments:
        opacity:  0.0 (invisible) through 1.0 (opaque)
                       Defaults to 1.0
        line_width:    Line width. Default is 1.0.
        style:         FILL, LINE or DASHED_LINE.  Default is LINE.
        """
        xoff = 0.5 * extents[0]
        yoff = 0.5 * extents[1]
        zoff = 0.5 * extents[2]
        points = [
            [center[0] - xoff, center[1] - yoff, center[2] - zoff],
            [center[0] + xoff, center[1] - yoff, center[2] - zoff],
            [center[0] - xoff, center[1] + yoff, center[2] - zoff],
            [center[0] - xoff, center[1] - yoff, center[2] + zoff],
        ] # yapf: disable
        super().__init__(points, color, opacity, line_width, style)