import os
import platform
import struct
from functools import reduce

import urwid as uw

from .model import FindModel
from .options import (MENUS, OPTIONS, CHECKBOX_OPTION, RADIO_BUTTON_OPTION,
                      PATH_INPUT_OPTION, TEXT_INPUT_OPTION, INT_INPUT_OPTION)

LEN_OF_MENUS = len(MENUS)

CLR_RADIO_CHOOSE = 'clr'
# Global status variable, changed by `exit_on_keys` or `FindView`
EXIT_WITH_SUCCESS = False

def bind_key(name, key):
    """
    Bind key to specific key name, without checking key conflict.
    :param name is the name of bined key, like 'EXIT_KEY'
    :param key is a string of key, like 'ctrl n'
    """
    return os.getenv(name, key)

# short keys
JUMP_TO_MENUS = bind_key('JUMP_TO_MENUS', 'ctrl n')# want to use ctrl+m, but it is equal to enter in terminal
JUMP_TO_OPTIONS = bind_key('JUMP_TO_OPTIONS', 'ctrl o')
JUMP_TO_COMMAND = bind_key('JUMP_TO_COMMAND', 'ctrl e')
TRIGGER_COMPLETITION = bind_key('TRIGGER_COMPLETITION', 'tab')

def bind_keys(name, keys):
    """
    Bind keys to specific key name, without checking key conflict.
    :param name is the name of bined key, like 'EXIT_KEY'
    :param keys is a tuple of keys, like ('q', 'Q', 'ctrl d')
    """
    type = os.getenv(name, keys)
    if isinstance(type, str):
        type = (type, )
    return type


EXIT_KEY = bind_keys('EXIT_KEY', ('q', 'Q', 'ctrl d'))
RUN_KEY = bind_keys('RUN_KEY', ('ctrl r', 'enter'))

def exit_on_keys(key):
    """
    Handle unhandled key input.

    If key given is q/Q/ctrl+d, exit UI simply;
    else if key given is ctrl+r, exit and prepare to run the command.

    Assuming you can connect with nearest extraterrestrial intelligent life by
    pressing ctrl+r in the terminal, You won't reserve it for trivival find(1) probably.
    Don't worry, You can set environment variables like RUN_KEY and EXIT_KEY to specify your key:

        RUN_KEY='ctrl d' find # will use ctrl+d to run the command
        EXIT_KEY='enter' find # will use enter to exit the UI(without running command)

    Some notices:
        1. to use ALT, you should type 'meta'
        2. to use SHIFT+OtherKey, you should type 'shift otherkey'
        3. no need to type '+' among each key

        So, to use 'ALT+R', you need to type 'shift meta r'
    """
    if key in EXIT_KEY:
        exit_loop(success=False)
    elif key in RUN_KEY:
        exit_loop(success=True)

def exit_loop(success):
    """exit UI loop and report success or not"""
    global EXIT_WITH_SUCCESS
    if success:
        EXIT_WITH_SUCCESS = True
    else:
        EXIT_WITH_SUCCESS = False
    raise uw.ExitMainLoop()

def get_terminal_size():
    """get width and height of console"""
    current_os = platform.system()
    tuple_xy = None
    if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'):
        def ioctl_GWINSZ(fd):
            try:
                import fcntl
                import termios
                cr = struct.unpack('hh',
                                fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
                return cr
            except:
                pass
        cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) # try 1
        if not cr:
            try:
                fd = os.open(os.ctermid(), os.O_RDONLY) # try 2
                cr = ioctl_GWINSZ(fd)
                os.close(fd)
            except:
                pass
        if not cr:
            try:
                cr = (os.environ['LINES'], os.environ['COLUMNS']) # try 3
            except:
                pass
        if not cr:
            tuple_xy = None # accept it
        else:
            tuple_xy = (int(cr[1]), int(cr[0]))

    if tuple_xy is None:
        tuple_xy = (80, 25)      # default value
    return tuple_xy


class FocusListWalker(uw.SimpleFocusListWalker):
    """
    Add focus callback to SimpleFocusListWalker.
    focus_handler will receive a focus_position argument each time the focus changed.
    """
    def __init__(self, contents, focus_handler):
        super(FocusListWalker, self).__init__(contents)
        self.focus_handler = focus_handler

    def set_focus(self, pos):
        super(FocusListWalker, self).set_focus(pos)
        self.focus_handler(pos)


class FindView():
    def __init__(self, model):
        """
        The UI of FindView like this:

            menus-----------help---------
            options----------------------
            options----------------------
            notice_board-----------------
            exec_cmd_input---------------
            path_input-------------------
            cmd_input----------ok--reset-

        """
        help = uw.Text(
            "Click menu and edit options.\n"
            "Fill 'Execute Command' with the command you want to execute.\n"
            "Fill 'Path' with the path you want to run with.\n"
            "Press Ctrl+e to jump to command input\n"
            "Press Ctrl+n to jump to menus, and Ctrl+o to jump to Options\n"
            "Press q or Q or Ctrl+d to quit.\n"
            "Press reset to reset command with options/exec/path\n"
            "Press ctrl+r or click OK to run the command\n"
        )

        self.menus = self.create_menubar(MENUS)
        middle_of_menu = len(MENUS) // 2 - 1

        self.__options_panels = {}
        self.options_panel = uw.Padding(self.create_options(MENUS[middle_of_menu]))

        self.current_selected_menu_idx = middle_of_menu
        self.menus.focus_position = middle_of_menu

        self.notice_board = uw.Padding(uw.ListBox(uw.SimpleListWalker([])))

        self.actions_input = uw.Edit("Execute Command:")
        self.path_input = uw.Edit("Path:")
        uw.connect_signal(self.actions_input, 'change', self.actions_changed)
        uw.connect_signal(self.path_input, 'change', self.path_changed)

        self.command_input = uw.Edit("Final Command: ")
        self.command_input.set_edit_text('find ')
        uw.connect_signal(self.command_input, 'change', self.command_changed)
        # click it to run the command
        ok_button = uw.Button("OK")
        uw.connect_signal(ok_button, 'click', self.ok_clicked)
        self.ok_button = uw.AttrMap(ok_button,
                                          None, focus_map='reversed')
        # click it to reset output command
        reset_button = uw.Button("Reset")
        uw.connect_signal(reset_button, 'click', self.reset_clicked)
        self.reset_button = uw.AttrMap(reset_button,
                                          None, focus_map='reversed')

        self.menus_area = uw.Columns([
                    ('weight', 0.5, self.menus),
                    ('weight', 0.2, uw.Filler(uw.Text(''))),
                    uw.Filler(help, valign='top')
        ])
        self.command_area = uw.Columns([
                    self.command_input,
                    ('weight', 0.1, self.ok_button),
                    ('weight', 0.1, self.reset_button)
        ])

        self.frame = uw.Frame(
            body=uw.Pile([
                ('weight', 0.6, self.menus_area),
                ('weight', 1, self.options_panel),
                ('weight', 0.7, self.notice_board),
                ('pack', self.actions_input),
                ('pack', self.path_input),
                ('pack', self.command_area)
            ])
        )

        # Non-GUI part
        self.bind_model(model)
        self.component_waited_completed = None

    def bind_model(self, model):
        """Bind a model bidirectionally to store and deal with data."""
        self.model = model

    def filter_short_keys(self, keys, raw_keys):
        """
        This method will be used as input_filter in MainLoop.
        We will define short keys here, to catch them before widgets.
        Return keys so that the widgets can receive the input.
        """
        def handle_up(keys, k, i):
            if self.__is_on_options_panel():
                if self.__is_on_option_n(0):
                    self.focus_menus()
                    del keys[i] # no need to up again

        def handle_trigger_completion_action(keys, k, i):
            if self.__is_on_options_panel():
                opt = self.options_panel.original_widget.focus.original_widget.contents[1][0]
                pos = self.options_panel.original_widget.focus_position
                name = MENUS[self.current_selected_menu_idx]
                if OPTIONS[name][pos].type == PATH_INPUT_OPTION:
                    self.complete(opt, self.model.complete_path)
            elif self.__is_on_command_input():
                self.complete(self.command_input, self.model.complete_any)
            elif self.__is_on_path_input():
                self.complete(self.path_input, self.model.complete_path)
            del keys[i]

        def handle_jump_to_command(keys, k, i):
            self.focus_inputs()
            del keys[i]

        def handle_jump_to_menus(keys, k, i):
            self.focus_menus()
            del keys[i]

        def handle_jump_to_options(keys, k, i):
            self.focus_options()
            del keys[i]

        def handle_remain_keys(*args): # lambda : pass is not allowed in python
            pass

        keys_handler_dict = {
            JUMP_TO_COMMAND: handle_jump_to_command,
            JUMP_TO_MENUS: handle_jump_to_menus,
            JUMP_TO_OPTIONS: handle_jump_to_options,
            'up': handle_up,
            TRIGGER_COMPLETITION: handle_trigger_completion_action,
        }
        for i, k in enumerate(keys):
            keys_handler_dict.get(k, handle_remain_keys)(keys, k, i)

        return keys

    def run(self):
        uw.MainLoop(self.frame, palette=[('reversed', 'standout', '')],
                    input_filter=self.filter_short_keys,
                    unhandled_input=exit_on_keys).run()

    def create_menubar(self, menus):
        body = []
        for i, menu in enumerate(menus):
            button = uw.Button(menu)
            uw.connect_signal(button, 'click', self.menu_chosen, user_args=[i])
            body.append(uw.AttrMap(button, None, focus_map='reversed'))
        # put all menus vertically, male clicking on them easier
        return uw.ListBox(FocusListWalker(body, self.__set_options))

    def __set_options(self, n):
        if 0 <= n < len(MENUS):
            self.current_selected_menu_idx = n
            self.options_panel.original_widget = self.create_options(MENUS[n])

    def create_options(self, choice):
        """