import os

from threading import Lock
from .utils import compat_os_name, get_windows_version


class MultilinePrinterBase():
    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.end()

    def print_at_line(self, text, pos):
        pass

    def end(self):
        pass


class MultilinePrinter(MultilinePrinterBase):

    def __init__(self, stream, lines):
        """
        @param stream stream to write to
        @lines number of lines to be written
        """
        self.stream = stream

        is_win10 = compat_os_name == 'nt' and get_windows_version() >= (10, )
        self.CARRIAGE_RETURN = '\r'
        if os.getenv('TERM') and self._isatty() or is_win10:
            # reason not to use curses https://github.com/yt-dlp/yt-dlp/pull/1036#discussion_r713851492
            # escape sequences for Win10 https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
            self.UP = '\x1b[A'
            self.DOWN = '\n'
            self.ERASE_LINE = '\x1b[K'
            self._HAVE_FULLCAP = self._isatty() or is_win10
        else:
            self.UP = self.DOWN = self.ERASE_LINE = None
            self._HAVE_FULLCAP = False

        # lines are numbered from top to bottom, counting from 0 to self.maximum
        self.maximum = lines - 1
        self.lastline = 0
        self.lastlength = 0

        self.movelock = Lock()

    @property
    def have_fullcap(self):
        """
        True if the TTY is allowing to control cursor,
        so that multiline progress works
        """
        return self._HAVE_FULLCAP

    def _isatty(self):
        try:
            return self.stream.isatty()
        except BaseException:
            return False

    def _move_cursor(self, dest):
        current = min(self.lastline, self.maximum)
        self.stream.write(self.CARRIAGE_RETURN)
        if current == dest:
            # current and dest are at same position, no need to move cursor
            return
        elif current > dest:
            # when maximum == 2,
            # 0. dest
            # 1.
            # 2. current
            self.stream.write(self.UP * (current - dest))
        elif current < dest:
            # when maximum == 2,
            # 0. current
            # 1.
            # 2. dest
            self.stream.write(self.DOWN * (dest - current))
        self.lastline = dest

    def print_at_line(self, text, pos):
        with self.movelock:
            if self.have_fullcap:
                self._move_cursor(pos)
                self.stream.write(self.ERASE_LINE)
                self.stream.write(text)
            else:
                if self.maximum != 0:
                    # let user know about which line is updating the status
                    text = f'{pos + 1}: {text}'
                textlen = len(text)
                if self.lastline == pos:
                    # move cursor at the start of progress when writing to same line
                    self.stream.write(self.CARRIAGE_RETURN)
                    if self.lastlength > textlen:
                        text += ' ' * (self.lastlength - textlen)
                    self.lastlength = textlen
                else:
                    # otherwise, break the line
                    self.stream.write('\n')
                    self.lastlength = 0
                self.stream.write(text)
                self.lastline = pos

    def end(self):
        with self.movelock:
            # move cursor to the end of the last line, and write line break
            # so that other to_screen calls can precede
            self._move_cursor(self.maximum)
            self.stream.write('\n')


class QuietMultilinePrinter(MultilinePrinterBase):
    def __init__(self):
        self.have_fullcap = True


class BreaklineStatusPrinter(MultilinePrinterBase):

    def __init__(self, stream, lines):
        """
        @param stream stream to write to
        """
        self.stream = stream
        self.maximum = lines
        self.have_fullcap = True

    def print_at_line(self, text, pos):
        if self.maximum != 0:
            # let user know about which line is updating the status
            text = f'{pos + 1}: {text}'
        self.stream.write(text + '\n')