"""Matplotlib backend for the draw method.
"""

# Checking if we can display graphics.
import os

import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, PathPatch, Rectangle
from mpl_toolkits.mplot3d import Axes3D, art3d

from .. import drawnet

# The previous backend-detection code caused problems on windows.
# This fragment now tries to set an interactive backend where available and otherwise defaults to Agg,
# which should mimic the previously intended behavior.
# has_display = bool(os.environ.get("DISPLAY", ""))
# if not has_display:
#     current_backend = matplotlib.get_backend()
#     interactive_backends = matplotlib.rcsetup.interactive_bk
#     if interactive_backends and current_backend not in interactive_backends:
#         for ib in interactive_backends:
#             try:
#                 matplotlib.use(ib, force=True)
#                 break
#             except (ImportError, RuntimeError):
#                 matplotlib.use("Agg")
#                 continue
#     else:
#         matplotlib.use("Agg")


defaultLayerColors = ["red", "green", "blue"]


def fix_attr(obj, attr, val):
    obj.__setattr__(attr, val)  # Just in case there is side effects
    newclass = type(type(obj).__name__, (type(obj),), {})
    setattr(newclass, attr, property(lambda s: val, lambda s, x: None))
    obj.__class__ = newclass


def fix_attr_range(obj, attr, ran):
    assert ran[0] <= obj.__getattribute__(attr) <= ran[1]
    obj.__setattr__("_" + attr, obj.__getattribute__(attr))
    oldclass = type(obj)
    newclass = type(oldclass.__name__, (oldclass,), {})

    def setter(s, val):
        if val < ran[0]:
            val = ran[0]
        elif val > ran[1]:
            val = ran[1]
        obj.__setattr__("_" + attr, val)

    def getter(s):
        return obj.__getattribute__("_" + attr)

    setattr(newclass, attr, property(getter, setter))
    obj.__class__ = newclass


class NetFigureMPL(drawnet.NetFigure):
    def draw(self, **kwargs):
        ax = kwargs["ax"] if "ax" in kwargs else None

        self.normalize_coords()

        if ax is None:
            self.fig = plt.figure(figsize=self.figsize)
            self.ax = self.fig.add_subplot(projection="3d")
        else:
            assert isinstance(ax, Axes3D), (
                "The axes need to have 3D projection. Use, for example, "
                "fig.add_subplot(111, projection='3d')"
            )
            self.ax = ax
            self.fig = self.ax.get_figure()

        self.draw_elements()

        self.ax.set_xlim3d(0, 1)
        self.ax.set_ylim3d(0, 1)
        self.ax.set_zlim3d(0, 2)
        self.ax.set_axis_off()

        fix_attr_range(self.ax, "elev", [0, 179])

        self.ax.azim = self.azim
        self.ax.elev = self.elev
        if self.camera_dist is not None:
            self.ax.dist = self.camera_dist
        if self.autoscale and len(self.layers) * self.layergap > 3:
            self.ax.autoscale_view()
        if self.show:
            plt.show()

        return self.fig


class NodeMPL(drawnet.Node):
    def draw(self):
        self.circle = Circle((self.x, self.y), self.size / 2.0, color=self.color)
        self.net.ax.add_patch(self.circle)
        art3d.pathpatch_2d_to_3d(self.circle, z=self.layer.z, zdir="z")
        fix_attr(self.circle, "zorder", self.layer.z + self.net.eps)

        if self.label is not None:
            self.labelObject = self.net.ax.text(
                self.x + self.size / 2.0,
                self.y + self.size / 2.0,
                self.layer.z + self.net.eps,
                str(self.label),
                **self.labelArgs,
            )
            fix_attr(self.labelObject, "zorder", self.layer.z + 2 * self.net.eps)


class LayerMPL(drawnet.Layer):
    def draw(self):
        assert self.z is not None
        if self.shape == "rectangle":
            self.layer = Rectangle((0, 0), 1, 1, alpha=self.alpha, color=self.color)
            if self.label is not None:
                self.labelObject = self.net.ax.text(
                    self.labelloc[0],
                    self.labelloc[1],
                    self.z,
                    str(self.label),
                    **self.labelArgs,
                )
        elif self.shape == "circle":
            self.layer = Circle((0.5, 0.5), 0.5, alpha=self.alpha, color=self.color)
            if self.label is not None:
                self.labelObject = self.net.ax.text(
                    self.labelloc[0],
                    self.labelloc[1],
                    self.z,
                    str(self.label),
                    **self.labelArgs,
                )
        self.net.ax.add_patch(self.layer)
        art3d.pathpatch_2d_to_3d(self.layer, z=self.z, zdir="z")
        fix_attr(self.layer, "zorder", self.z)


class EdgeMPL(drawnet.Edge):
    def draw(self):
        self.lines = []
        # find layers this edge is crossing
        if abs(self.node1.layer.z - self.node2.layer.z) > self.net.layergap:
            n = (
                int(
                    round(
                        abs(self.node1.layer.z - self.node2.layer.z)
                        / float(self.net.layergap)
                    )
                )
                + 1
            )
            import numpy

            xs = numpy.linspace(self.node1.x, self.node2.x, n)
            ys = numpy.linspace(self.node1.y, self.node2.y, n)
            zs = numpy.linspace(self.node1.layer.z, self.node2.layer.z, n)
            zorders = []
            for i in range(len(zs) - 1):
                zorders = (zs[i] + zs[i + 1]) / 2.0
        else:
            xs = [self.node1.x, self.node2.x]
            ys = [self.node1.y, self.node2.y]
            zs = [self.node1.layer.z, self.node2.layer.z]
        for i in range(len(zs) - 1):
            z = (zs[i] + zs[i + 1]) / 2.0 + self.z * self.net.eps
            line = self.net.ax.plot(
                xs[i : i + 2],
                ys[i : i + 2],
                zs=zs[i : i + 2],
                linestyle=self.style,
                zdir="z",
                color=self.color,
                linewidth=self.width,
                alpha=self.alpha,
            )[0]
            fix_attr(line, "zorder", z)
            self.lines.append(line)
