#!/usr/bin/env python

"""
@package pmsco.graphics.cluster
graphics rendering module for clusters.

this module is experimental.
interface and implementation may change without notice.

at the moment we are evaluating rendering solutions.

@author Matthias Muntwiler, matthias.muntwiler@psi.ch

@copyright (c) 2017 by Paul Scherrer Institut @n
Licensed under the Apache License, Version 2.0 (the "License"); @n
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
  http://www.apache.org/licenses/LICENSE-2.0
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import sys
import os
import numpy as np
import argparse
import logging

logger = logging.getLogger(__name__)

try:
    import pymol2
except ImportError:
    logger.warning("error importing pymol2. cluster rendering using pymol2 disabled.")
    pymol2 = None

try:
    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
    # from matplotlib.backends.backend_pdf import FigureCanvasPdf
    # from matplotlib.backends.backend_svg import FigureCanvasSVG
except ImportError:
    Axes3D = None
    Figure = None
    FigureCanvas = None
    logger.warning("error importing matplotlib. cluster rendering using matplotlib disabled.")


def render_file(spath, view):
    sname = "cluster"
    opath = spath + ".png"

    pm = pymol2.PyMOL()
    cmd = pm.cmd
    pm.start()
    try:
        cmd.reinitialize()
        cmd.load(spath, sname)
        cmd.disable("all")
        cmd.enable(sname)

        cmd.set("orthoscopic", 1)
        cmd.bg_color("white")
        cmd.show_as("spheres")
        cmd.alter("all", "vdw=0.8")
        #cmd.show("sticks")

        #zoom selection-expression # selection to fill the viewer
        #orient selection-expression # largest dim horizontal, second-largest vertical

        #cmd.orient() --- should stick to fixed orientation
        #cmd.turn("x", -90)
        #cmd.turn("x", 0)
        #cmd.turn("y", 0)

        #cmd.clip("slab", 5.0)
        cmd.viewport(640, 640)
        cmd.zoom(complete=1)
        #pymol.cmd.rebuild() #--- necessary?

        cmd.png(opath)
    finally:
        pm.stop()


def render_cluster(clu):
    pass


def set_axes_equal(ax):
    """
    Make axes of 3D plot have equal scale so that spheres appear as spheres,
    cubes as cubes, etc..  This is one possible solution to Matplotlib's
    ax.set_aspect('equal') and ax.axis('equal') not working for 3D.

    @author https://stackoverflow.com/a/31364297

    @param ax: a matplotlib axis, e.g., as output from plt.gca().
    """

    x_limits = ax.get_xlim3d()
    y_limits = ax.get_ylim3d()
    z_limits = ax.get_zlim3d()

    x_range = abs(x_limits[1] - x_limits[0])
    x_middle = np.mean(x_limits)
    y_range = abs(y_limits[1] - y_limits[0])
    y_middle = np.mean(y_limits)
    z_range = abs(z_limits[1] - z_limits[0])
    z_middle = np.mean(z_limits)

    # The plot bounding box is a sphere in the sense of the infinity
    # norm, hence I call half the max range the plot radius.
    plot_radius = 0.5*max([x_range, y_range, z_range])

    ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
    ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
    ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])


def render_xyz_matplotlib(filename, data, canvas=None):
    """
    produce a graphics file from an array of 3d coordinates in the matplotlib scatter style.

    the default file format is PNG.

    this function requires the matplotlib module.
    if it is not available, the function raises an error.

    @param filename: path and name of the scan file.
        this is used to derive the output file path by adding the extension of the graphics file format.
    @param data: numpy array of shape (N,3).
    @param canvas: a FigureCanvas class reference from a matplotlib backend.
        if None, the default FigureCanvasAgg is used which produces a bitmap file in PNG format.

    @return (str) path and name of the generated graphics file.
        empty string if an error occurred.

    @raise TypeError if matplotlib is not available.
    """

    if canvas is None:
        canvas = FigureCanvas
    fig = Figure()
    canvas(fig)

    ax = fig.add_subplot(111, projection='3d')
    # ax.set_aspect('equal')
    try:
        # method available in matplotlib 2.1 and later
        ax.set_proj_type('ortho')
    except AttributeError:
        pass
    ax.scatter(data[:, 0], data[:, 1], data[:, 2], c='r', marker='o')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('z')
    set_axes_equal(ax)

    out_filename = "{0}.{1}".format(filename, canvas.get_default_filetype())
    fig.savefig(out_filename)
    return out_filename


def exec_cli():
    parser = argparse.ArgumentParser()
    parser.add_argument('-v', '--view', default='z')
    parser.add_argument(dest='files', nargs='+')
    args = parser.parse_args()
    for fil in args.files:
        render_file(fil, args.view)


if __name__ == '__main__':
    exec_cli()
    sys.exit(0)
