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

'''
Qt UI styling.

'''
from typing import Dict
import math

import pyqtgraph as pg
from qdarkstyle import DarkPalette

from .qt import (
    QtCore,
    QtGui,
    Qt,
    QCoreApplication,
)
from ..log import get_logger

from .. import config

log = get_logger(__name__)

_magic_inches = 0.0666 * (1 + 6/16)

# chart-wide fonts specified in inches
_font_sizes: Dict[str, Dict[str, float]] = {
    'hi': {
        'default':  _magic_inches,
        'small': 0.9 * _magic_inches,
    },
    'lo': {
        'default':  6.5 / 64,
        'small':  6 / 64,
    },
}


class DpiAwareFont:

    def __init__(
        self,
        name: str = 'Hack',
        _font_size_key: str = 'default',

    ) -> None:

        self._font_size_calc_key: str = _font_size_key
        self._font_size: int|None = None

        # Read preferred font size from main config file if it exists
        conf, path = config.load('conf', touch_if_dne=True)
        if ui_section := conf.get('ui'):
            if font_size := ui_section.get('font_size'):
                self._font_size = int(font_size)

            if not (name := ui_section.get('font_name')):
                name: str = 'Hack'

        self.name = name
        self._qfont = QtGui.QFont(name)
        self._qfm = QtGui.QFontMetrics(self._qfont)

        self._font_inches: float = None
        self._screen = None

    def _set_qfont_px_size(self, px_size: int) -> None:
        self._qfont.setPixelSize(px_size)
        self._qfm = QtGui.QFontMetrics(self._qfont)

    @property
    def screen(self) -> QtGui.QScreen:
        from ._window import main_window

        if self._screen is not None:
            try:
                self._screen.refreshRate()
            except RuntimeError:
                self._screen = main_window().current_screen()
        else:
            self._screen = main_window().current_screen()

        return self._screen

    @property
    def font(self):
        return self._qfont

    def scale(self) -> float:
        screen = self.screen
        return screen.logicalDotsPerInch() / screen.physicalDotsPerInch()

    @property
    def px_size(self) -> int:
        size: int = self._qfont.pixelSize()

        # XXX, when no Qt app has been spawned this will always be
        # invalid..
        # SO, just return any conf.toml value.
        if size == -1:
            if (conf_size := self._font_size) is None:
                raise ValueError(
                    f'No valid `{type(_font).__name__}.px_size` set?\n'
                    f'\n'
                    f'-> `ui.font_size` is NOT set in `conf.toml`\n'
                    f'-> no Qt app is active ??\n'
                )
            return conf_size

        return size

    def configure_to_dpi(self, screen: QtGui.QScreen | None = None):
        '''
        Set an appropriately sized font size depending on the screen DPI.

        If we end up needing to generalize this more here there are resources
        listed in the script in ``snippets/qt_screen_info.py``.

        '''
        if self._font_size is not None:
            self._set_qfont_px_size(self._font_size)
            return

        # NOTE: if no font size set either in the [ui] section of the
        # config or not yet computed from our magic scaling calcs,
        # then attempt to caculate it here!

        if screen is None:
            screen = self.screen

        # take the max since scaling can make things ugly in some cases
        pdpi = screen.physicalDotsPerInch()
        ldpi = screen.logicalDotsPerInch()

        # XXX: this is needed on sway/wayland where you set
        # ``QT_WAYLAND_FORCE_DPI=physical``
        if ldpi == 0:
            ldpi = pdpi

        mx_dpi = max(pdpi, ldpi)
        mn_dpi = min(pdpi, ldpi)
        scale = round(ldpi/pdpi, ndigits=2)

        if mx_dpi <= 97:  # for low dpi use larger font sizes
            inches = _font_sizes['lo'][self._font_size_calc_key]

        else:  # hidpi use smaller font sizes
            inches = _font_sizes['hi'][self._font_size_calc_key]

        dpi = mn_dpi

        mult = 1.0

        # No implicit DPI scaling was done by the DE so let's engage
        # some hackery ad-hoc scaling shiat.
        # dpi is likely somewhat scaled down so use slightly larger font size
        if scale >= 1.1 and self._font_size_calc_key:

            # no idea why
            if 1.2 <= scale:
                mult = 1.0375

            if scale >= 1.5:
                mult = 1.375

        # TODO: this multiplier should probably be determined from
        # relative aspect ratios or something?
        inches *= mult

        # XXX: if additionally we detect a known DE scaling factor we
        # also scale *up* our font size on top of the existing
        # heuristical (aka no clue why it works) scaling from the block
        # above XD
        if (
            hasattr(Qt, 'AA_EnableHighDpiScaling')
            and QCoreApplication.testAttribute(Qt.AA_EnableHighDpiScaling)
        ):
            inches *= round(scale)

        # TODO: we might want to fiddle with incrementing font size by
        # +1 for the edge cases above. it seems doing it via scaling is
        # always going to hit that error in range mapping from inches:
        # float to px size: int.
        self._font_inches = inches
        font_size = math.floor(inches * dpi)

        log.debug(
            f"screen:{screen.name()}\n"
            f"pDPI: {pdpi}, lDPI: {ldpi}, scale: {scale}\n"
            f"\nOur best guess font size is {font_size}\n"
        )
        # apply the size
        self._set_qfont_px_size(font_size)

    def boundingRect(self, value: str) -> QtCore.QRectF:

        screen = self.screen
        if screen is None:
            raise RuntimeError("You must call .configure_to_dpi() first!")

        unscaled_br = self._qfm.boundingRect(value)

        return QtCore.QRectF(
            0,
            0,
            unscaled_br.width(),
            unscaled_br.height(),
        )


# use inches size to be cross-resolution compatible?
_font = DpiAwareFont()
_font_small = DpiAwareFont(_font_size_key='small')


def _config_fonts_to_screen() -> None:
    'configure global DPI aware font sizes'

    global _font, _font_small
    _font.configure_to_dpi()
    _font_small.configure_to_dpi()


def get_fonts() -> tuple[
    DpiAwareFont,
    DpiAwareFont,
]:
    '''
    Get the singleton font pair (of instances) from which all other
    UI/UX should be "scaled around".

    See `DpiAwareFont` for (internal) deats.

    '''
    return _font, _font_small


# TODO: re-compute font size when main widget switches screens?
# https://forum.qt.io/topic/54136/how-do-i-get-the-qscreen-my-widget-is-on-qapplication-desktop-screen-returns-a-qwidget-and-qobject_cast-qscreen-returns-null/3

# splitter widget config
_xaxis_at = 'bottom'

# charting config
CHART_MARGINS = (0, 0, 2, 2)
_min_points_to_show = 6
_tina_mode = False


def enable_tina_mode() -> None:
    """Enable "tina mode" to make everything look "conventional"
    like your pet hedgehog always wanted.
    """
    # white background (for tinas like our pal xb)
    pg.setConfigOption('background', 'w')


def hcolor(name: str) -> str:
    """Hex color codes by hipster speak.

    This is an internal set of color codes hand picked
    for certain purposes.

    """
    return {

        # lives matter
        'black': '#000000',
        'erie_black': '#1B1B1B',
        'licorice': '#1A1110',
        'papas_special': '#06070c',
        'svags': '#0a0e14',

        # fifty shades
        'original': '#a9a9a9',
        'gray': '#808080',  # like the kick
        'grayer': '#4c4c4c',
        'grayest': '#3f3f3f',
        'cadet': '#91A3B0',
        'marengo': '#91A3B0',
        'gunmetal': '#91A3B0',
        'battleship': '#848482',

        # default ohlc-bars/curve gray
        'bracket': '#666666',  # like the logo
        'pikers': '#616161',  # a trader shade of..
        'beast': '#161616',  # in the dark alone.

        # bluish
        'charcoal': '#36454F',

        # work well for filled polygons which want a 'bracket' feel
        # going light to dark
        'davies': '#555555',
        'i3': '#494D4F',
        'jet': '#343434',

        # from ``qdarkstyle`` palette
        'default_darkest': DarkPalette.COLOR_BACKGROUND_1,
        'default_dark': DarkPalette.COLOR_BACKGROUND_2,
        'default': DarkPalette.COLOR_BACKGROUND_3,
        'default_light': DarkPalette.COLOR_BACKGROUND_4,
        'default_lightest': DarkPalette.COLOR_BACKGROUND_5,
        'default_spotlight': DarkPalette.COLOR_BACKGROUND_6,

        'white': '#ffffff',  # for tinas and sunbathers

        # blue zone
        'dad_blue': '#326693',  # like his shirt
        'vwap_blue': '#0582fb',
        'dodger_blue': '#1e90ff',  # like the team?
        'panasonic_blue': '#0040be',  # from japan
        # 'bid_blue': '#0077ea',  # like the L1
        'bid_blue': '#3094d9',  # like the L1
        'aquaman': '#39abd0',

        # traditional
        'tina_green': '#00cc00',
        'tina_red': '#fa0000',

        'cucumber': '#006400',
        'cool_green': '#33b864',
        'dull_green': '#74a662',
        'hedge_green': '#518360',
        'lilypad_green': '#839c84',

        # orders and alerts
        'alert_yellow': '#e2d083',
        'alert_yellow_light': '#ffe366',

        # buys
        # 'hedge': '#768a75',
        # 'hedge': '#41694d',
        # 'hedge': '#558964',
        # 'hedge_light': '#5e9870',

        '80s_neon_green': '#00b677',
        # 'buy_green': '#41694d',
        'buy_green': '#558964',
        'buy_green_light': '#558964',

        # sells
        # techincally "raspberry"
        # 'sell_red': '#990036',
        # 'sell_red': '#9A0036',

        # brighter then above
        # 'sell_red': '#8c0030',

        'sell_red': '#b6003f',
        # 'sell_red': '#d00048',
        'sell_red_light': '#f85462',
        'wine': '#69212d',

        # 'sell_red': '#f85462',
        # 'sell_red_light': '#ff4d5c',

    }[name]
