# Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

"""
Dynamic IO / Interactive terminal
"""

import sys
import time

import colorama
from cement.core.exc import CaughtSignal
from cement.utils.misc import minimal_logger

from ebcli.core import io, ebglobals

LOG = minimal_logger(__name__)

terminal = None
counter = 0
total = 1

# Special characters
UP_ARROW = 'Up' if sys.platform.startswith('win') else u'\u25b2'
DOWN_ARROW = 'Dn' if sys.platform.startswith('win') else u'\u25bc'
LEFT_ARROW = 'Left' if sys.platform.startswith('win') else u'\u25c4'
RIGHT_ARROW = 'Right' if sys.platform.startswith('win') else u'\u25ba'


def get_terminal():
    if terminal is None:
        init_terminal()
    return terminal


def init_terminal():
    global terminal
    if terminal:
        return
    else:
        if sys.platform.startswith('win'):
            terminal = WindowsTerminal()

        else:
            from blessed import Terminal
            terminal = Terminal()

        io.echo(terminal.clear())


def reset_terminal():
    global counter, total
    total = counter
    counter = 0


def height():
    init_terminal()
    return terminal.height


def width():
    init_terminal()
    return terminal.width


def underline():
    init_terminal()
    if isinstance(terminal, WindowsTerminal):
        return ''
    else:
        return terminal.underline


def reverse_():
    init_terminal()
    if isinstance(terminal, WindowsTerminal):
        return colorama.Back.WHITE + colorama.Fore.BLACK
    else:
        return terminal.reverse


def underlined(string):
    init_terminal()
    return terminal.underline(string)


def reverse_colors(string):
    init_terminal()
    return terminal.reverse(string)


def echo_line(*strings):
    global counter
    # if total and counter < total:
    echo_on_line(counter, *strings)
    counter += 1


def clear_eos():
    init_terminal()
    if term_is_live():
        return terminal.clear_eos()
    else:
        return ''


def hide_cursor():
    init_terminal()
    io.echo(terminal.hide_cursor(), end='')


def return_cursor_to_normal():
    init_terminal()
    if term_is_live():
        move_cursor(total-1, 1)
    io.echo(terminal.normal_cursor())


def term_is_live():
    if not sys.stdout.isatty():  # pipe
        return False
    if ebglobals.app.pargs.debug:  # Debug mode. Don't overwrite anything
        return False
    return True


def echo_on_line(line_num, *strings):
    init_terminal()
    if term_is_live():
        with terminal.location(x=0, y=line_num):
            io.echo(terminal.clear_eol())
        with terminal.location(x=0, y=line_num):
            io.echo(*strings)
    else:
        io.echo(*strings)


def get_key(timeout=None):
    init_terminal()
    with terminal.cbreak():
        return terminal.inkey(timeout=timeout)


def move_cursor(line_num, column_num):
    io.echo(terminal.move(line_num, column_num), end='')


class WindowsTerminal(object):
    def __init__(self):
        colorama.init()
        self.win32 = colorama.win32
        self.normal = colorama.Style.RESET_ALL
        self.bold = colorama.Style.BRIGHT

    @property
    def height(self):
        return self.get_terminal_size()[0]-1

    @property
    def width(self):
        return self.get_terminal_size()[1]-1

    def clear(self):
        return '\033[2J\033[1;1f'

    def get_terminal_size(self):
        import shutil
        try:
            size = shutil.get_terminal_size()
            return size.lines, size.columns
        except AttributeError:
            screen = self._get_screen_info()
            window = screen.srWindow
            h = window.Bottom - window.Top
            w = window.Right - window.Left

            return h or 80, w or 25

    def _get_screen_info(self):
        return self.win32.GetConsoleScreenBufferInfo()

    def _get_cursor_pos(self):
        csbi = self._get_screen_info()
        position = csbi.dwCursorPosition
        return position.X, position.Y

    def _get_view_borders(self):
        csbi = self._get_screen_info()
        window = csbi.srWindow
        return window.Top, window.Left, window.Bottom, window.Right

    def underline(self, string):
        LOG.debug('Windows does not support underline. Doing nothing')
        return string

    def reverse(self, string):
        """
        Windows doesn't support reverse. But since you cant change
        terminal colors on windows, we can safely assume that
        white background and black text will be a reverse
        Its not quite reverse on powershell, but it works
        """
        return io.on_color('white', io.color('black', string))

    def clear_eos(self):
        # print spaces till the end of the screen
        pos = self._get_cursor_pos()
        borders = self._get_view_borders()

        string = ''
        current_line = pos[1]
        height = borders[2] - 1

        while current_line < height:
            string += self.clear_eol() + '\n'
            current_line += 1

        return string

    def clear_eol(self):
        # print spaces till the end of the line
        size = self.get_terminal_size()
        return ' ' * (size[1] - 1)

    def hide_cursor(self):
        return ''

    def normal_cursor(self):
        return ''

    def location(self, x=0, y=0):
        """
        In order to use 'with' you need an object
         with an __enter__ and __exit__ method
        """
        class TermLocation(object):
            def __init__(self, term):
                self.term = term
                self.saved_position = None

            def __enter__(self):
                self.saved_position = self.term._get_cursor_pos()
                print(self.term.move(y, x), end='')

            def __exit__(self, exc_type, exc_val, exc_tb):
                # Return cursor state
                print(self.term.move(self.saved_position[1], self.saved_position[0]), end='')

        return TermLocation(self)

    def move(self, y, x):
        return '\x1b[{y};{x}H'.format(y=y+1, x=x+1)

    def cbreak(self):

        class CBreak(object):
            def __enter__(self):
                pass

            def __exit__(self, exc_type, exc_val, exc_tb):
                pass

        return CBreak()

    def inkey(self, timeout=None):
        # Since get_key is a single method, we will just do the cbreak
        # and input stuff in a single method.
        # We should ever need inkey without cbreak

        if sys.version_info[0] >= 3:
            from msvcrt import kbhit, getwch as _getch
        else:
            from msvcrt import kbhit, getch as _getch

        def readInput():
            start_time = time.time()
            while True:
                if kbhit():
                    return _getch()
                if (time.time() - start_time) > timeout:
                    return None

        key = readInput()
        if not key:
            return None

        if key == '\x03':
            # Ctrl C
            raise CaughtSignal(2, None)

        elif key == '\x1c':
            # Ctrl \
            sys.exit(1)

        elif key == '\xe0':
            # Its an arrow key, get next symbol
            next_key = _getch()
            if next_key == 'H':
                return Val(name='KEY_UP', code=259)
            elif next_key == 'K':
                return Val(name='KEY_LEFT', code=260)
            elif next_key == 'P':
                return Val(name='KEY_DOWN', code=258)
            elif next_key == 'M':
                return Val(name='KEY_RIGHT', code=261)

        elif key == '\x1b':
            return Val(name='KEY_ESCAPE', code=361)
        elif key == '\x0d':
            return Val(name='KEY_ENTER', code=362)

        else:
            return Val(key=key)


class Val(object):
    # mimics terminals inkey val object
    def __init__(self, key=None, name=None, code=None):
        self.key = key
        self.name = name
        self.code = code

    def __eq__(self, other):
        if isinstance(other, Val):
            if other.name == self.name and \
                    other.code == self.code and \
                    str(other) == str(self):
                return True
        elif isinstance(other, str):
            return str(self) == other
        else:
            return False

    def __str__(self):
        return self.key or ''

    @property
    def is_sequence(self):
        return self.key is None
