# piker: trading gear for hackers
# Copyright (C) Tyler Goodlet (in stewardship for piker0)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program 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 Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

"""
Annotations for ur faces.

"""
from typing import Callable

from pyqtgraph import (
    Point,
    functions as fn,
    Color,
)
import numpy as np

from piker.ui.qt import (
    QtCore,
    QtGui,
    QtWidgets,
    QPointF,
    QRectF,
    QGraphicsPathItem,
)


def mk_marker_path(

    style: str,

) -> QGraphicsPathItem:
    '''
    Add a marker to be displayed on the line wrapped in
    a ``QGraphicsPathItem`` ready to be placed using scene coordinates
    (not view).

    **Arguments**
    style        String indicating the style of marker to add:
                  ``'<|'``, ``'|>'``, ``'>|'``, ``'|<'``, ``'<|>'``,
                  ``'>|<'``, ``'^'``, ``'v'``, ``'o'``

    This code is taken nearly verbatim from the
    `InfiniteLine.addMarker()` method but does not attempt do be aware
    of low(er) level graphics controls and expects for the output
    polygon to be applied to a ``QGraphicsPathItem``.

    '''
    path = QtGui.QPainterPath()

    if style == 'o':
        path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))

    # arrow pointing away-from the top of line
    if '<|' in style:
        p = QtGui.QPolygonF([Point(0.5, 0), Point(0, -0.5), Point(-0.5, 0)])
        path.addPolygon(p)
        path.closeSubpath()

    # arrow pointing away-from the bottom of line
    if '|>' in style:
        p = QtGui.QPolygonF([Point(0.5, 0), Point(0, 0.5), Point(-0.5, 0)])
        path.addPolygon(p)
        path.closeSubpath()

    # arrow pointing in-to the top of line
    if '>|' in style:
        p = QtGui.QPolygonF([Point(0.5, -0.5), Point(0, 0), Point(-0.5, -0.5)])
        path.addPolygon(p)
        path.closeSubpath()

    # arrow pointing in-to the bottom of line
    if '|<' in style:
        p = QtGui.QPolygonF([Point(0.5, 0.5), Point(0, 0), Point(-0.5, 0.5)])
        path.addPolygon(p)
        path.closeSubpath()

    if '^' in style:
        p = QtGui.QPolygonF([Point(0, -0.5), Point(0.5, 0), Point(0, 0.5)])
        path.addPolygon(p)
        path.closeSubpath()

    if 'v' in style:
        p = QtGui.QPolygonF([Point(0, -0.5), Point(-0.5, 0), Point(0, 0.5)])
        path.addPolygon(p)
        path.closeSubpath()

    # self._maxMarkerSize = max([m[2] / 2. for m in self.markers])

    return path


class LevelMarker(QGraphicsPathItem):
    '''
    An arrow marker path graphich which redraws itself
    to the specified view coordinate level on each paint cycle.

    '''
    def __init__(
        self,
        chart: 'ChartPlotWidget',  # noqa
        style: str,
        get_level: Callable[..., float],
        size: float = 20,
        keep_in_view: bool = True,
        on_paint: Callable | None = None,

    ) -> None:

        # get polygon and scale
        super().__init__()
        # self.setScale(size, size)
        self.setScale(size)

        # interally generates path
        self._style = None
        self.style = style

        self.chart = chart

        self.get_level = get_level
        self._on_paint = on_paint

        self.scene_x = lambda: chart.marker_right_points()[1]
        self.level: float = 0
        self.keep_in_view = keep_in_view

    @property
    def style(self) -> str:
        return self._style

    @style.setter
    def style(self, value: str) -> None:
        if self._style != value:
            polygon = mk_marker_path(value)
            self.setPath(polygon)
            self._style = value

    def path_br(self) -> QRectF:
        '''Return the bounding rect for the opaque path part
        of this item.

        '''
        return self.mapToScene(
            self.path()
        ).boundingRect()

    def delete(self) -> None:
        self.scene().removeItem(self)

    @property
    def h(self) -> float:
        return self.path_br().height()

    @property
    def w(self) -> float:
        return self.path_br().width()

    def position_in_view(self) -> None:
        '''
        Show a pp off-screen indicator for a level label.

        This is like in fps games where you have a gps "nav" indicator
        but your teammate is outside the range of view, except in 2D, on
        the y-dimension.

        '''
        level = self.get_level()
        view = self.chart.getViewBox()
        vr = view.state['viewRange']
        ymn, ymx = vr[1]

        # _, marker_right, _ = line._chart.marker_right_points()
        x = self.scene_x()

        if self.style == '>|':  # short style, points "down-to" line
            top_offset = self.h
            bottom_offset = 0
        else:
            top_offset = 0
            bottom_offset = self.h

        if level > ymx:  # pin to top of view
            self.setPos(
                QPointF(
                    x,
                    top_offset + self.h/3,
                )
            )

        elif level < ymn:  # pin to bottom of view
            self.setPos(
                QPointF(
                    x,
                    view.height() - (bottom_offset + self.h/3),
                )
            )

        else:
            # pp line is viewable so show marker normally
            self.setPos(
                x,
                self.chart.view.mapFromView(
                    QPointF(0, self.get_level())
                ).y()
            )

    def paint(
        self,

        p: QtGui.QPainter,
        opt: QtWidgets.QStyleOptionGraphicsItem,
        w: QtWidgets.QWidget

    ) -> None:
        '''
        Core paint which we override to always update
        our marker position in scene coordinates from a
        view cooridnate "level".

        '''
        if self.keep_in_view:
            self.position_in_view()

        super().paint(p, opt, w)

        if self._on_paint:
            self._on_paint(self)


def qgo_draw_markers(

    markers: list,
    color: Color,
    p: QtGui.QPainter,
    left: float,
    right: float,
    right_offset: float,

) -> float:
    '''
    Paint markers in ``pg.GraphicsItem`` style by first
    removing the view transform for the painter, drawing the markers
    in scene coords, then restoring the view coords.

    '''
    # paint markers in native coordinate system
    orig_tr = p.transform()

    start = orig_tr.map(Point(left, 0))
    end = orig_tr.map(Point(right, 0))
    up = orig_tr.map(Point(left, 1))

    dif = end - start
    # length = Point(dif).length()
    angle = np.arctan2(dif.y(), dif.x()) * 180 / np.pi

    p.resetTransform()

    p.translate(start)
    p.rotate(angle)

    up = up - start
    det = up.x() * dif.y() - dif.x() * up.y()
    p.scale(1, 1 if det > 0 else -1)

    p.setBrush(fn.mkBrush(color))
    # p.setBrush(fn.mkBrush(self.currentPen.color()))
    tr = p.transform()

    sizes = []
    for path, pos, size in markers:
        p.setTransform(tr)

        # XXX: we drop the "scale / %" placement
        # x = length * pos
        x = right_offset

        p.translate(x, 0)
        p.scale(size, size)
        p.drawPath(path)
        sizes.append(size)

    p.setTransform(orig_tr)
    return max(sizes)
