# 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/>.

"""
Double auction top-of-book (L1) graphics.

"""
from typing import Tuple

import pyqtgraph as pg

from piker.ui.qt import (
    QPointF,
    QtCore,
    QtGui,
)
from ._axes import YAxisLabel
from ._style import hcolor
from ._pg_overrides import PlotItem


class LevelLabel(YAxisLabel):
    '''
    Y-axis (vertically) oriented, horizontal label that sticks to
    where it's placed despite chart resizing and supports displaying
    multiple fields.


    TODO: replace the rectangle-text part with our new ``Label`` type.

    '''
    _x_br_offset: float = -16
    _y_txt_h_scaling: float = 2

    # adjustment "further away from" anchor point
    _x_offset = 0
    _y_offset = 0

    # fields to be displayed in the label string
    _fields = {
        'level': 0,
        'level_digits': 2,
    }
    # default label template is just a y-level with so much precision
    _fmt_str = '{level:,.{level_digits}f} '

    def __init__(
        self,
        chart,
        parent,

        color: str = 'default_light',

        orient_v: str = 'bottom',
        orient_h: str = 'right',

        opacity: float = 1,

        # makes order line labels offset from their parent axis
        # such that they don't collide with the L1/L2 lines/prices
        # that are displayed on the axis
        adjust_to_l1: bool = False,

        **axis_label_kwargs,
    ) -> None:

        super().__init__(
            chart,
            parent=parent,
            use_arrow=False,
            opacity=opacity,
            **axis_label_kwargs
        )

        # TODO: this is kinda cludgy
        self._hcolor: pg.Pen = None
        self.color: str = color

        # orientation around axis options
        self._orient_v = orient_v
        self._orient_h = orient_h

        self._adjust_to_l1 = adjust_to_l1

        self._v_shift = {
            'top': -1.,
            'bottom': 0.,
            'middle': 1 / 2.
        }[orient_v]

        self._h_shift = {
            'left': -1.,
            'right': 0.,
        }[orient_h]

        self.fields = self._fields.copy()
        # ensure default format fields are in correct
        self.set_fmt_str(self._fmt_str, self.fields)

        self.setZValue(10)

    @property
    def color(self):
        return self._hcolor

    @color.setter
    def color(self, color: str) -> None:
        self._hcolor = color
        self._pen = self.pen = pg.mkPen(
            hcolor(color),
            width=3,
        )

    def update_on_resize(self, vr, r):
        """Tiis is a ``.sigRangeChanged()`` handler.

        """
        self.update_fields(self.fields)

    def update_fields(
        self,
        fields: dict = None,
    ) -> None:
        '''
        Update the label's text contents **and** position from
        a view box coordinate datum.

        '''
        self.fields.update(fields)
        level = self.fields['level']

        # map "level" to local coords
        abs_xy = self._pi.mapFromView(QPointF(0, level))

        self.update_label(
            abs_xy,
            self.fields,
        )

    def update_label(
        self,
        abs_pos: QPointF,  # scene coords
        fields: dict,
    ) -> None:

        # write contents, type specific
        h, w = self.set_label_str(fields)

        if self._adjust_to_l1:
            self._x_offset = self._pi.chart_widget._max_l1_line_len

        self.setPos(QPointF(
            self._h_shift * (w + self._x_offset),
            abs_pos.y() + self._v_shift * h
        ))
        # XXX: definitely need this!
        self.update()

    def set_fmt_str(
        self,
        fmt_str: str,
        fields: dict,
    ) -> (str, str):
        # test that new fmt str can be rendered
        self._fmt_str = fmt_str
        self.set_label_str(fields)
        self.fields.update(fields)
        return fmt_str, self.label_str

    def set_label_str(
        self,
        fields: dict,
    ):
        # use space as e3 delim
        self.label_str = self._fmt_str.format(
            **fields).replace(',', ' ')

        br = self.boundingRect()
        h, w = br.height(), br.width()
        return h, w

    def size_hint(self) -> Tuple[None, None]:
        return None, None

    def draw(
        self,
        p: QtGui.QPainter,
        rect: QtCore.QRectF

    ) -> None:

        p.setPen(self._pen)
        rect = self.rect

        if self._orient_v == 'bottom':
            lp, rp = rect.topLeft(), rect.topRight()

        elif self._orient_v == 'top':
            lp, rp = rect.bottomLeft(), rect.bottomRight()

        p.drawLine(
            *map(int, [
                lp.x(),
                lp.y(),
                rp.x(),
                rp.y(),
            ])
        )

        p.fillRect(
            self.rect,
            self.bg_color,
        )

    def highlight(self, pen) -> None:
        self._pen = pen
        self.update()

    def unhighlight(self):
        self._pen = self.pen
        self.update()


class L1Label(LevelLabel):

    text_flags = (
        QtCore.Qt.TextFlag.TextDontClip
        | QtCore.Qt.AlignmentFlag.AlignLeft
    )

    def set_label_str(
        self,
        fields: dict,
    ) -> None:
        """Make sure the max L1 line module var is kept up to date.

        """
        h, w = super().set_label_str(fields)

        # Set a global "max L1 label length" so we can
        # look it up on order lines and adjust their
        # labels not to overlap with it.
        chart = self._pi.chart_widget
        chart._max_l1_line_len: float = max(
            chart._max_l1_line_len,
            w,
        )

        return h, w


class L1Labels:
    '''
    Level 1 bid ask labels for dynamic update on price-axis.

    '''
    def __init__(
        self,
        plotitem: PlotItem,
        digits: int = 2,
        size_digits: int = 3,
        font_size: str = 'small',
    ) -> None:

        chart = self.chart = plotitem.chart_widget

        raxis = plotitem.getAxis('right')
        kwargs = {
            'chart': plotitem,
            'parent': raxis,

            'opacity': .9,
            'font_size': font_size,
            'fg_color': 'default_light',
            'bg_color': chart.view_color,  # normally 'papas_special'
        }

        # TODO: add humanized source-asset
        # info format.
        fmt_str = (
            ' {size:.{size_digits}f} u'
            # '{level:,.{level_digits}f} '
        )
        fields = {
            'level': 0,
            'level_digits': digits,
            'size': 0,
            'size_digits': size_digits,
        }

        bid = self.bid_label = L1Label(
            orient_v='bottom',
            **kwargs,
        )
        bid.set_fmt_str(
            fmt_str='\n' + fmt_str,
            fields=fields,
        )
        bid.show()

        ask = self.ask_label = L1Label(
            orient_v='top',
            **kwargs,
        )
        ask.set_fmt_str(
            fmt_str=fmt_str,
            fields=fields)
        ask.show()
