from __future__ import absolute_import
# #START_LICENSE###########################################################
#
#
# This file is part of the Environment for Tree Exploration program
# (ETE).  http://etetoolkit.org
#
# ETE is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ETE is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ETE.  If not, see <http://www.gnu.org/licenses/>.
#
#
#                     ABOUT THE ETE PACKAGE
#                     =====================
#
# ETE is distributed under the GPL copyleft license (2008-2015).
#
# If you make use of ETE in published work, please cite:
#
# Jaime Huerta-Cepas, Joaquin Dopazo and Toni Gabaldon.
# ETE: a python Environment for Tree Exploration. Jaime BMC
# Bioinformatics 2010,:24doi:10.1186/1471-2105-11-24
#
# Note that extra references to the specific methods implemented in
# the toolkit may be available in the documentation.
#
# More info at http://etetoolkit.org. Contact: huerta@embl.de
#
#
# #END_LICENSE#############################################################
import random

from PyQt5.QtGui import QFont, QFontMetrics, QRadialGradient
from PyQt5.QtWidgets import QGraphicsEllipseItem

from ete3.treeview.faces import _label_painter
from PyQt5.QtCore import QRect, Qt

from .qt import QRectF, QGraphicsSimpleTextItem, QGraphicsPixmapItem, \
    QGraphicsRectItem, QTransform, QBrush, QPen, QColor, QGraphicsItem

from .main import FACE_POSITIONS, _leaf
from .node_gui_actions import _NodeActions as _ActionDelegator
from math import pi, cos, sin
import six
from six.moves import range
try:
    from numpy import isfinite as _isfinite, ceil
except ImportError:
    pass
else:
    isfinite = lambda n: n and _isfinite(n)

class _TextFaceItem(QGraphicsSimpleTextItem, _ActionDelegator):
    def __init__(self, face, node, text):
        QGraphicsSimpleTextItem.__init__(self, text)
        _ActionDelegator.__init__(self)
        self.node = node
        self.face = face
        self._bounding_rect = self.face.get_bounding_rect()
        self._real_rect = self.face.get_real_rect()
    def boundingRect(self):
        return self._bounding_rect

class _ImgFaceItem(QGraphicsPixmapItem, _ActionDelegator):
    def __init__(self, face, node, pixmap):
        QGraphicsPixmapItem.__init__(self, pixmap)
        _ActionDelegator.__init__(self)
        self.node = node

class _BackgroundFaceItem(QGraphicsRectItem):
    def __init__(self, face, node):
        QGraphicsRectItem.__init__(self)
        self.node = node

    def paint(self, painter, option, index):
        return

class _ItemFaceItem(QGraphicsRectItem, _ActionDelegator):
    '''
    in order to make item type face normal
    '''
    def __init__(self, x, y, width, height):
        QGraphicsRectItem.__init__(self, x, y, width, height)
        _ActionDelegator.__init__(self)

class _PieChartItem(QGraphicsRectItem, _ActionDelegator):
    def __init__(self, percents, width, height, colors, line_color=None, node=None):
        QGraphicsRectItem.__init__(self, 0, 0, width, height)
        _ActionDelegator.__init__(self) # in order to make mouse can selected node
        self.node = node
        self.percents = percents
        self.colors = colors
        self.line_color = line_color

    def paint(self, painter, option, widget):
        a = 5760
        angle_start = 0

        if not self.line_color:
            painter.setPen(Qt.NoPen)
        else:
            painter.setPen(QColor(self.line_color))

        for i, p in enumerate(self.percents):
            col = self.colors[i]
            painter.setBrush(QBrush(QColor(col)))
            angle_span = (p/100.) * a
            painter.drawPie(self.rect(), angle_start, angle_span )
            angle_start += angle_span

class _StackedBarItem(QGraphicsRectItem, _ActionDelegator):
    def __init__(self, percents, width, height, colors, line_color=None, node=None):
        QGraphicsRectItem.__init__(self, 0, 0, width, height)
        _ActionDelegator.__init__(self) # in order to make mouse can selected node
        self.node = node
        self.percents = percents
        self.colors = colors
        self.line_color = line_color

    def paint(self, painter, option, widget):
        total_w = self.rect().width()
        total_h = self.rect().height()
        painter.setBrush(Qt.NoBrush)

        if not self.line_color:
            painter.setPen(Qt.NoPen)
        else:
            painter.setPen(QColor(self.line_color))

        x = 0
        for i, p in enumerate(self.percents):
            col = self.colors[i]
            w = (p * total_w) / 100. # assuming p is between 0 and 100
            painter.fillRect(QRectF(x, 0, w, total_h), QColor(col))
            x += w

class _BarChartItem(QGraphicsRectItem, _ActionDelegator):
    def __init__(self, values, deviations, width, height, colors, labels,
                 min_value, max_value, label_fsize, label_font, scale_fsize, node=None):
        QGraphicsRectItem.__init__(self, 0, 0, width, height)
        _ActionDelegator.__init__(self) # in order to make mouse can selected node
        self.node = node
        self.values = values
        self.colors = colors
        self.width = float(width)
        self.height = float(height)
        self.draw_border = True
        self.draw_grid = False
        self.draw_scale = True
        self.labels = labels
        self.max_value = max_value
        self.min_value = min_value
        self.deviations = deviations
        self.label_fsize = label_fsize
        self.label_font = label_font
        self.scale_fsize = scale_fsize

        self.set_real_size()

    def set_real_size(self):
        label_height = 0
        scale_width = 0
        margin = 2

        if self.max_value is None:
            max_value = max([v+d for v,d in zip(self.values, self.deviations) if isfinite(v)])
        else:
            max_value = self.max_value

        if self.min_value is None:
            min_value = min([v+d for v,d in zip(self.values, self.deviations) if isfinite(v)])
        else:
            min_value = self.min_value

        if self.draw_scale:
            max_string = "% 7.2f" %max_value
            min_string = "% 7.2f" %min_value
            fm = QFontMetrics(QFont("Verdana", self.scale_fsize))
            max_string_metrics = fm.boundingRect(QRect(), \
                Qt.AlignLeft, \
                max_string)
            min_string_metrics = fm.boundingRect(QRect(), \
                Qt.AlignLeft, \
                min_string)
            scale_width = margin + max(max_string_metrics.width(),
                min_string_metrics.width())

        if self.labels:
            # fm = QFontMetrics(QFont("Verdana", self.label_fsize))
            fm = QFontMetrics(self.label_font)
            longest_label = sorted(self.labels, key=lambda x: len(x))[-1]
            label_height = fm.boundingRect(QRect(), Qt.AlignLeft, longest_label).width() + margin
            label_width = fm.height() * len(self.labels)
            self.width = max(label_width, self.width)

        self.setRect(0, 0, self.width + scale_width, self.height + label_height)


    def paint(self, p, option, widget):
        colors = self.colors
        values = self.values
        deviations = self.deviations
        p.setBrush(Qt.NoBrush)
        margin = 2
        spacer = 3
        spacing_length = (spacer*(len(values)-1))
        height = self.height

        if self.max_value is None:
            max_value = max([v+d for v,d in zip(values, deviations) if isfinite(v)])
        else:
            max_value = self.max_value

        if self.min_value is None:
            min_value = min([v+d for v,d in zip(values, deviations) if isfinite(v)])
        else:
            min_value = self.min_value

        plot_width = self.width
        plot_height = self.height

        x_alpha = float((plot_width - spacing_length) / (len(values)))
        if x_alpha < 1:
            raise ValueError("BarChartFace is too small")

        y_alpha = float ( (plot_height-3) / float(max_value - min_value) )
        x = 0
        y = 0

        # Mean and quartiles y positions
        mean_line_y = y + (plot_height / 2.0)
        line2_y = mean_line_y + (plot_height/4.0)
        line3_y = mean_line_y - (plot_height/4.0)

        if self.draw_border:
            p.setPen(QColor("black"))
            p.drawRect(x, y + 1, plot_width, plot_height)

        if self.draw_scale:
            p.setFont(QFont("Verdana", self.scale_fsize))
            font_height = QFontMetrics(p.font()).height()
            max_string = "% 7.2f" %max_value
            min_string = "% 7.2f" %min_value
            p.drawText(plot_width + margin, font_height-2, max_string)
            p.drawText(plot_width + margin, plot_height - 2, min_string)
            p.drawLine(plot_width + margin - 1, 1, plot_width + margin - 1, plot_height+1)
            p.drawLine(plot_width + margin - 1, 1, plot_width + margin + 2, 1)
            p.drawLine(plot_width + margin - 1, plot_height+1, plot_width + margin + 2, plot_height+1)

        if self.draw_grid:
            dashedPen = QPen(QBrush(QColor("#ddd")), 0)
            dashedPen.setStyle(Qt.DashLine)
            p.setPen(dashedPen)
            p.drawLine(x+1, mean_line_y, plot_width - 2, mean_line_y)
            p.drawLine(x+1, line2_y, plot_width - 2, line2_y )
            p.drawLine(x+1, line3_y, plot_width - 2, line3_y )

        # Draw bars
        # p.setFont(QFont("Verdana", self.label_fsize))
        p.setFont(self.label_font)
        label_height = self.rect().height() - self.height
        label_width = QFontMetrics(p.font()).height()
        for pos in range(len(values)):
            # first and second X pixel positions
            x1 = x
            x = x1 + x_alpha + spacer

            std =  deviations[pos]
            val = values[pos]

            if self.labels:
                p.save()
                p.translate(x1, plot_height+2)
                p.rotate(90)
                p.drawText(0, -x_alpha, label_height, x_alpha, Qt.AlignVCenter, str(self.labels[pos]))
                #p.drawRect(0, -x_alpha, label_height, x_alpha)
                p.restore()

            # If nan value, skip
            if not isfinite(val):
                continue

            color = QColor(colors[pos])
            # mean bar high
            mean_y1     = int((val - min_value) * y_alpha)
            # Draw bar border
            p.setPen(QColor("black"))

            # Fill bar with custom color
            p.fillRect(x1, height - mean_y1, x_alpha, mean_y1, QBrush(color))

            # Draw error bars
            if std != 0:
                dev_up_y1   = int((val + std - min_value) * y_alpha)
                dev_down_y1 = int((val - std - min_value) * y_alpha)
                center_x = x1 + (x_alpha / 2)
                p.drawLine(center_x, plot_height - dev_up_y1, center_x, plot_height - dev_down_y1)
                p.drawLine(center_x + 1, plot_height - dev_up_y1, center_x -1, plot_height - dev_up_y1)
                p.drawLine(center_x + 1, plot_height - dev_down_y1, center_x -1, plot_height - dev_down_y1)

class _RectItem(QGraphicsRectItem, _ActionDelegator):
    def __init__(self, w, h, bgcolor, fgcolor, label=None, node=None):
        QGraphicsRectItem.__init__(self)
        _ActionDelegator.__init__(self) # in order to make mouse can selected node
        self.node = node
        self.label = label
        self.setRect(0, 0, w, h)
        if bgcolor:
            self.setBrush(QBrush(QColor(bgcolor)))
        else:
            self.setBrush(QBrush(Qt.NoBrush))
        if fgcolor:
            self.setPen(QPen(QColor(fgcolor)))
        else:
            self.setPen(QPen(Qt.NoPen))

    def paint(self, p, option, widget):
        super(_RectItem, self).paint(p, option, widget)
        _label_painter(self, p, option, widget)

class _SphereItem(QGraphicsEllipseItem, _ActionDelegator):
    def __init__(self, radius, color, solid=False, label=None, node=None):
        _ActionDelegator.__init__(self) # in order to make mouse can selected node
        self.node = node
        self.label = label
        r = radius
        d = r*2
        QGraphicsEllipseItem.__init__(self, 0, 0, d, d)
        if solid:
            self.setBrush(QBrush(QColor(color)))
        else:
            self.gradient = QRadialGradient(r, r, r,(d)/3,(d)/3)
            self.gradient.setColorAt(0.05, Qt.white)
            self.gradient.setColorAt(1, QColor(color))
            self.setBrush(QBrush(self.gradient))
        self.setPen(QPen(QColor(color)))

    def paint(self, p, option, widget):
        super(_SphereItem, self).paint(p, option, widget)
        _label_painter(self, p, option, widget)


class _FaceGroupItem(QGraphicsRectItem): # I was about to name this FaceBookItem :)
    def paint(self, painter, option, index):
        "Avoid little dots in acrobat reader"
        return

    def __init__(self, faces, node, as_grid=False):
        # This caused seg. faults. in some computers. No idea why.
        QGraphicsRectItem.__init__(self, 0, 0, 0, 0)

        self.as_grid = as_grid
        self.c2max_w = {}
        self.r2max_h = {}
        self.node = node
        self.column2faces = faces
        self.column2size = {}
        self.columns = sorted(set(self.column2faces.keys()))

        # Two dictionaries containing min column size. Can be used to
        # reserve some space to specific columns and draw FaceBlocks
        # like tables.
        self.column_widths = {}
        self.row_heights = {}

        self.w = 0
        self.h = 0
        # updates the size of this grid
        self.update_columns_size()

    def set_min_column_widths(self, column_widths):
        # column_widths is a dictionary of min column size. Can be
        # used to reserve horizontal space to specific columns
        self.column_widths = column_widths
        self.columns = sorted(set(list(self.column2faces.keys()) + list(self.column_widths.keys())))

    def set_min_column_heights(self, column_heights):
        # column_widths is a dictionary of min column size. Can be
        # used to reserve vertical space to specific columns
        self.row_heights = column_heights

    #def paint(self, painter, option, index):
    #    return

    def boundingRect(self):
        return QRectF(0,0, self.w, self.h)

    def rect(self):
        return QRectF(0,0, self.w, self.h)

    def get_size(self):
        return self.w, self.h

    def update_columns_size(self, norender=False):
        self.sizes = {}
        self.c2height = {}

        for c in self.columns:
            faces = self.column2faces.get(c, [])
            self.sizes[c] = {}
            total_height = 0
            for r, f in enumerate(faces):
                f.node = self.node
                if f.type == "pixmap" and not norender:
                    f.update_pixmap()
                elif f.type == "item" and not norender:
                    f.update_items(node=self.node)
                elif f.type == "text" and f.rotation:
                    f.tight_text = False

                width = f._width() + f.margin_right + f.margin_left
                height = f._height() + f.margin_top + f.margin_bottom

                if f.rotation:
                    if f.rotation == 90 or f.rotation == 270:
                        width, height = height, width
                    elif f.rotation == 180:
                        pass
                    else:
                        x0 =  width/2.0
                        y0 =  height/2.0
                        theta = (f.rotation * pi)/180
                        trans = lambda x, y: (x0+(x-x0)*cos(theta) + (y-y0)*sin(theta), y0-(x-x0)*sin(theta)+(y-y0)*cos(theta))
                        coords = (trans(0,0), trans(0,height), trans(width,0), trans(width,height))
                        x_coords = [e[0] for e in coords]
                        y_coords = [e[1] for e in coords]
                        width = max(x_coords) - min(x_coords)
                        height = max(y_coords) - min(y_coords)

                self.sizes[c][r] = [width, height]
                self.c2max_w[c] = max(self.c2max_w.get(c, 0), width)
                self.r2max_h[r] = max(self.r2max_h.get(r, 0), height)
                total_height += height
            self.c2height[c] = total_height

        if not self.sizes:
            return

        if self.as_grid:
            self.h = max( [sum([self.r2max_h[r] for r in six.iterkeys(rows)]) for c, rows in six.iteritems(self.sizes)])
        else:
            self.h = max( [self.c2height[c] for c in six.iterkeys(self.sizes)])

        self.w = sum(self.c2max_w.values())
        #self.setRect(0, 0, self.w+random.randint(1,5), self.h)
        #pen = QPen()
        #pen.setColor(QColor("red"))
        #self.setPen(pen)

    def setup_grid(self, c2max_w=None, r2max_h=None, as_grid=True):
        if c2max_w:
            self.c2max_w = c2max_w

        if r2max_h:
            self.r2max_h = r2max_h

        # complete missing face columns
        if self.columns:
            self.columns = list(range(min(self.c2max_w), max(self.c2max_w)+1))

        self.as_grid = as_grid
        self.update_columns_size(norender=True)
        return self.c2max_w, self.r2max_h


    def render(self):
        x = 0
        for c in self.columns:
            if c not in self.c2max_w:
                continue
            max_w = self.c2max_w[c]
            faces = self.column2faces.get(c, [])

            if self.as_grid:
                y = 0
            else:
                y = (self.h - self.c2height.get(c,0)) / 2

            for r, f in enumerate(faces):
                w, h = self.sizes[c][r]
                if self.as_grid:
                    max_h = self.r2max_h[r]
                else:
                    max_h = h

                f.node = self.node
                if f.type == "text":
                    obj = _TextFaceItem(f, self.node, f.get_text())
                    font = f._get_font()
                    obj.setFont(font)
                    obj.setBrush(QBrush(QColor(f.fgcolor)))
                elif f.type == "item":
                    obj = f.item
                else:
                    # Loads the pre-generated pixmap
                    obj = _ImgFaceItem(f, self.node, f.pixmap)

                obj.setAcceptHoverEvents(True)
                obj.setParentItem(self)

                x_offset, y_offset = 0, 0

                if max_w > w:
                    # Horizontally at the left
                    if f.hz_align == 0:
                        x_offset = 0
                    elif f.hz_align == 1:
                        # Horizontally centered
                        x_offset = (max_w - w) / 2
                    elif f.hz_align == 2:
                        # At the right
                        x_offset = (max_w - w)

                if max_h > h:
                    if f.vt_align == 0:
                        # Vertically on top
                        y_offset = 0
                    elif f.vt_align == 1:
                        # Vertically centered
                        y_offset = (max_h - h) / 2
                    elif f.hz_align == 2:
                        # Vertically at bottom
                        y_offset = (max_h - h)

                # Correct cases in which object faces has negative
                # starting points
                #obj_rect = obj.boundingRect()
                #_pos = obj_rect.topLeft()
                #_x = abs(_pos.x()) if _pos.x() < 0 else 0
                #_y = abs(_pos.y()) if _pos.y() < 0 else 0

                text_y_offset = -obj.boundingRect().y() if f.type == "text" else 0

                obj.setPos(x + f.margin_left + x_offset,
                           y + y_offset + f.margin_top + text_y_offset)

                if f.rotation and f.rotation != 180:
                    fake_rect = obj.boundingRect()
                    fake_w, fake_h = fake_rect.width(), fake_rect.height()
                    self._rotate_item(obj, f.rotation)
                    #wcorr = fake_w/2.0 - w/2.0
                    #ycorr = fake_h/2.0 - h/2.0
                    #print "Correctopm", fake_w/2.0 - w/2.0, fake_h/2.0 - h/2
                    #obj.moveBy(-wcorr, -ycorr)
                    obj.moveBy(((w/2) - fake_w/2.0), (h/2) - (fake_h/2.0))
                    #r = QGraphicsRectItem(0, 0, w, h)
                    #r.setParentItem(self)

                obj.rotable = f.rotable
                f.inner_background.apply(obj)
                f.inner_border.apply(obj)

                bg = f.background.apply(obj)
                border = f.border.apply(obj)
                if border:
                    border.setRect(x, y, max_w, max_h)
                    border.setParentItem(self)
                if bg:
                    bg.setRect(x, y, max_w, max_h)
                    bg.setParentItem(self)

                if f.opacity < 1:
                    obj.setOpacity(f.opacity)

                if self.as_grid:
                    y += max_h
                else:
                    y += h

                # set label of the face if possible
                try:
                    obj.face_label = f.label
                except AttributeError:
                    pass
                obj.face_type = str(type(f)).split(".")[-1]

            x += max_w

    def _rotate_item(self, item, rotation):
        if hasattr(item, "_real_rect"):
            rect = item._real_rect
        else:
            rect = item.boundingRect()
        x = rect.width()/2
        y = rect.height()/2
        matrix = item.transform()
        #item.setTransform(QTransform().translate(x, y).rotate(rotation).translate(-x, -y))
        item.setTransform(matrix.translate(x, y).rotate(rotation).translate(-x, -y))

    def rotate(self, rotation):
        "rotates item over its own center"
        for obj in self.childItems():
            if hasattr(obj, "rotable") and obj.rotable:
                if hasattr(obj, "_real_rect"):
                    # to avoid incorrect rotation of tightgly wrapped
                    # text items we need to rotate using the real
                    # wrapping rect and revert y_text correction.
                    yoff = obj.boundingRect().y()
                    rect = obj._real_rect
                    # OJO!! this only works for the rotation of text
                    # faces in circular mode. Other cases are
                    # unexplored!!
                    obj.moveBy(0, yoff*2)
                else:
                    yoff = None
                    rect = obj.boundingRect()
                x = rect.width() / 2
                y = rect.height() / 2
                matrix = obj.transform()
                #obj.setTransform(QTransform().translate(x, y).rotate(rotation).translate(-x, -y))
                obj.setTransform(matrix.translate(x, y).rotate(rotation).translate(-x, -y))

    def flip_hz(self):
        for obj in self.childItems():
            rect = obj.boundingRect()
            x =  rect.width() / 2
            y =  rect.height() / 2
            matrix = obj.transform()
            #obj.setTransform(QTransform().translate(x, y).scale(-1,1).translate(-x, -y))
            obj.setTransform(matrix.translate(x, y).scale(-1,1).translate(-x, -y))

    def flip_vt(self):
        for obj in self.childItems():
            rect = obj.boundingRect()
            x =  rect.width() / 2
            y =  rect.height() / 2
            matrix = obj.transform()
            #obj.setTransform(QTransform().translate(x, y).scale(1,-1).translate(-x, -y))
            obj.setTransform(matrix.translate(x, y).scale(1,-1).translate(-x, -y))



def update_node_faces(node, n2f, img):

    # Organize all faces of this node in FaceGroups objects
    # (tables of faces)
    faceblock = {}

    n2f[node] = faceblock
    for position in FACE_POSITIONS:
        # _temp_faces.position =
        #  1: [f1, f2, f3],
        #  2: [f4, f4],
        #  ...

        # In case there are fixed faces
        fixed_faces =  getattr(getattr(node, "faces", None) , position, {})

        # _temp_faces should be initialized by the set_style funcion
        all_faces = getattr(node._temp_faces, position)
        for column, values in six.iteritems(fixed_faces):
            all_faces.setdefault(column, []).extend(values)

        if position == "aligned" and img.draw_aligned_faces_as_table:
            as_grid = True
        else:
            as_grid = False

        faceblock[position] = _FaceGroupItem(all_faces, node, as_grid=as_grid)

    # all temp and fixed faces are now referenced by the faceblock, so
    # we can clear the node temp faces (don't want temp faces to be
    # replicated with copy or dumped with cpickle)
    node._temp_faces = None

    return faceblock
