"""
Define the base window object and the constants/"globals" used
by the file of this module.

A window is a little part of the screen, for example the input window,
the text window, the roster window, etc.
A Tab (see the poezio.tabs module) is composed of multiple Windows
"""

from __future__ import annotations

import curses
import logging
import string

from contextlib import contextmanager
from typing import Optional, cast, Any, Generator

from poezio.theming import to_curses_attr, read_tuple, TextStyle

from poezio.ui.consts import FORMAT_CHAR

log = logging.getLogger(__name__)


class Win:
    __slots__ = ('_win', 'height', 'width', 'y', 'x')

    width: int
    height: int
    x: int
    y: int

    def __init__(self) -> None:
        if TAB_WIN is None:
            raise ValueError
        self._win: curses.window = TAB_WIN
        self.height, self.width = 0, 0

    def _resize(self, height: int, width: int, y: int, x: int) -> None:
        if height == 0 or width == 0:
            self.height, self.width = height, width
            return
        self.height, self.width, self.x, self.y = height, width, x, y
        try:
            if TAB_WIN is None:
                log.debug('error in resize %s: TAB_WIN is None', self)
            else:
                self._win = TAB_WIN.derwin(height, width, y, x)
        except Exception:
            log.debug('DEBUG: mvwin returned ERR. Please investigate')

    def resize(self, height: int, width: int, y: int, x: int) -> None:
        """
        Override if something has to be done on resize
        """
        self._resize(height, width, y, x)

    def _refresh(self) -> None:
        self._win.noutrefresh()

    def addnstr(self, *args: Any) -> None:
        """
        Safe call to addnstr
        """
        try:
            self._win.addnstr(*args)
        except curses.error:
            # this actually mostly returns ERR, but works.
            # more specifically, when the added string reaches the end
            # of the screen.
            # We actually want addchnstr to avoid curses trying to move the
            # cursor outside of the screen, but alas it is not implemented
            # in python curses.
            pass

    @contextmanager
    def colored_text(self, color: TextStyle | None = None,
                     attr: int | None = None) -> Generator[None, None, None]:
        """Context manager which sets up an attr/color when inside"""
        if color is None and attr is None:
            yield None
            return
        mode: int
        if color is not None:
            mode = to_curses_attr(color)
            if attr is not None:
                mode = mode | attr
        else:
            # attr cannot be none here
            mode = cast(int, attr)
        self._win.attron(mode)
        yield None
        self._win.attroff(mode)

    def addstr(self, *args: Any) -> None:
        """
        Safe call to addstr
        """
        try:
            self._win.addstr(*args)
        except Exception:
            log.debug('Unable to addstr to win: %s', args)

    def move(self, y: int, x: int) -> None:
        try:
            self._win.move(y, x)
        except Exception:
            log.debug('Unable to move win to x:%s y:%s', x, y)

    def addstr_colored(self, text: str, y: Optional[int] = None, x: Optional[int] = None) -> None:
        """
        Write a string on the window, setting the
        attributes as they are in the string.
        For example:
        \x19bhello → hello in bold
        \x191}Bonj\x192}our → 'Bonj' in red and 'our' in green
        next_attr_char is the \x19 delimiter
        attr_char is the char following it, it can be
        one of 'u', 'b', 'i', 'c[0-9]'
        """
        if y is not None and x is not None:
            self.move(y, x)
        next_attr_char = text.find(FORMAT_CHAR)
        attr_italic = curses.A_ITALIC if hasattr(
            curses, 'A_ITALIC') else curses.A_REVERSE
        while next_attr_char != -1 and text:
            if next_attr_char + 1 < len(text):
                attr_char = text[next_attr_char + 1].lower()
            else:
                attr_char = str()
            if next_attr_char != 0:
                self.addstr(text[:next_attr_char])
            if attr_char == 'o':
                self._win.attrset(0)
            elif attr_char == 'u':
                self._win.attron(curses.A_UNDERLINE)
            elif attr_char == 'b':
                self._win.attron(curses.A_BOLD)
            elif attr_char == 'i':
                self._win.attron(attr_italic)
            if (attr_char in string.digits
                    or attr_char == '-') and attr_char != '':
                color_str = text[next_attr_char +
                                 1:text.find('}', next_attr_char)]
                if ',' in color_str:
                    tup, char = read_tuple(color_str)
                    self._win.attron(to_curses_attr(tup))
                    if char:
                        if char == 'o':
                            self._win.attrset(0)
                        elif char == 'u':
                            self._win.attron(curses.A_UNDERLINE)
                        elif char == 'b':
                            self._win.attron(curses.A_BOLD)
                        elif char == 'i':
                            self._win.attron(attr_italic)
                    else:
                        # this will reset previous bold/uderline sequences if any was used
                        self._win.attroff(curses.A_UNDERLINE)
                        self._win.attroff(curses.A_BOLD)
                elif color_str:
                    self._win.attron(to_curses_attr((int(color_str), -1)))
                text = text[next_attr_char + len(color_str) + 2:]
            else:
                text = text[next_attr_char + 2:]
            next_attr_char = text.find(FORMAT_CHAR)
        self.addstr(text)

    def finish_line(self, color: TextStyle | None = None) -> None:
        """
        Write colored spaces until the end of line
        """
        (y, x) = self._win.getyx()
        size = self.width - x
        if color:
            self.addnstr(' ' * size, size, to_curses_attr(color))
        else:
            self.addnstr(' ' * size, size)


TAB_WIN: Optional[curses.window] = None
