import os, time
import asyncio
import json
import websockets
from tempfile import NamedTemporaryFile
import urwid

from .config import Config
from . import ui
from .ui import Screen, Form, FormField, menu, menu_button, sub_menu, ColorText, ExternalEditor

def quit_client(screen):
    asyncio.ensure_future(screen.client_state.send('QUIT'), loop=screen.loop)
    raise urwid.ExitMainLoop()

class Splash(Screen):
    def __init__(self, exit=lambda _:True):
        bt = urwid.BigText('WELCOME TO TILDEMUSH', urwid.font.HalfBlock5x4Font())
        bt = urwid.Padding(bt, 'center', None)
        bt = urwid.Filler(bt, 'middle', None, 7)
        ftr = ColorText('~ press any key to jack in ~', align='center')
        self.base = urwid.Frame(body=bt, footer=ftr)
        super().__init__(self.base, exit=exit)

    def input(self, key):
        self.exit(True)


class MainMenu(Screen):
    def __init__(self, loop, client=None, exit=lambda _: True):
        self.loop = loop
        ftr = ColorText('press ESC to close windows', align='center')
        body = ui.solidfill('░', 'background')
        self.base = urwid.Frame(body=body, footer=ftr)
        super().__init__(self.base, client, exit)
        self.show_menu()

    def show_menu(self):
        self.open_box(
            menu('tildemush main menu', [
                menu_button('login', lambda _:self.show_login()),
                menu_button('create a new user account', lambda _:self.show_register()),
                sub_menu(self, 'settings', [
                    menu_button('forget login details', lambda _:True),
                    menu_button('set server domain', lambda _:True),
                    menu_button('set server port', lambda _:True),
                    menu_button('set server password', lambda _:True)]),
                menu_button('exit', quit_client)]))

    def input(self, key):
        return False

    def show_login(self):
        un = self.client.config.get('username')
        pw = self.client.config.get('password')
        if un and pw:
            asyncio.wait_for(
                    asyncio.ensure_future(self.handle_login({'username':un, 'password':pw}), loop=self.loop),
                    60.0, loop=self.loop)
        else:
            un_field = FormField(caption='username: ', name='username')
            pw_field = FormField(caption='password: ', name='password', mask='~')
            submit_btn = urwid.Button('login! >')
            login_form = Form([un_field, pw_field], submit_btn)

            def wait_for_login(_):
                asyncio.wait_for(
                    asyncio.ensure_future(self.handle_login(login_form.data), loop=self.loop),
                    60.0, loop=self.loop)

            urwid.connect_signal(submit_btn, 'click', wait_for_login)

            self.open_box(urwid.Filler(login_form))

    async def handle_login(self, login_data):
        await self.client.authenticate(login_data['username'], login_data['password'])

    def show_register(self):
        info = ColorText('register a new account! password must be at least 12 characters long.\n')
        un_field = FormField(caption='username: ', name='username')
        pw_field = FormField(caption='password: ', name='password', mask='~')
        pw_confirm_field = FormField(caption='confirm password: ', name='confirm_password', mask='~')
        submit_btn = urwid.Button('register! >')
        register_form = Form([un_field, pw_field, pw_confirm_field], submit_btn)

        def wait_for_register(_):
            asyncio.wait_for(
                asyncio.ensure_future(self.handle_register(register_form.data), loop=self.loop),
                60.0, loop=self.loop)

        urwid.connect_signal(submit_btn, 'click', wait_for_register)
        self.open_box(urwid.Filler(urwid.Pile([info, register_form])))

    async def handle_register(self, register_data):
        if not register_data['username']:
            self.message("please enter a username", "error")
            return

        if not register_data['password']\
           or not register_data['confirm_password']\
           or register_data['password'] != register_data['confirm_password']:
            self.message("password mismatch", "error")
            return

        await self.client.register(register_data['username'], register_data['password'])

class GameMain(urwid.Frame):
    def __init__(self, client_state, loop, ui_loop, config):
        self.client_state = client_state
        self.loop = loop
        self.ui_loop = ui_loop
        self.config = config
        self.game_state = {"USER":{
                    "description": "a shadow",
                    "display_name": "nothing"},
                    "room": {
                        "name": "limbo",
                        "description": "a liminal space. type /look to open your eyes.",
                        "contains":[]}
                    }
        self.scope = []
        self.hotkeys = self.load_hotkeys()

        self.game_tab = ui.GameView(self.game_state, self.config)
        self.witch_tab = ui.WitchView({}, self.scope, self.config)
        self.worldmap_tab = ui.WorldmapView(self.config)
        self.settings_tab = ui.SettingsView(self.config)

        # quit placeholder
        self.quit_prompt = urwid.Edit()
        self.quit_view = ColorText("quit")
        self.quit_tab = ui.GameTab(self.quit_view,
                ui.TabHeader("F9 QUIT", position='last'), self.quit_prompt)

        # set starting conditions
        self.tabs = {
                "f1": self.game_tab,
                "f2": self.witch_tab,
                "f3": self.worldmap_tab,
                "f4": self.settings_tab,
                "f9": self.quit_tab
                }
        self.tab_headers = urwid.Columns([])
        self.header = self.tab_headers
        self.refresh_tabs()
        self.prompt = self.game_tab.prompt
        self.statusbar = ColorText("{dark green}connection okay!", align='right')
        self.client_state.set_on_recv(self.on_server_message)
        super().__init__(header=self.header, body=self.game_tab, footer=self.statusbar)
        self.focus_prompt()

    async def on_server_message(self, server_msg):
        if server_msg == 'COMMAND OK':
            pass
        elif server_msg.startswith('STATE'):
            self.update_state(server_msg[6:])
        elif server_msg.startswith('OBJECT'):
            object_state = json.loads(server_msg[7:])
            if object_state.get('edit'):
                self.launch_witch(object_state)
            elif object_state.get('read'):
                self.launch_witch_readonly(object_state)
        elif server_msg.startswith('MAP'):
            self.worldmap_tab.update_map(server_msg[4:])
        else:
            self.game_tab.add_message(server_msg)

        self.focus_prompt()

    def launch_witch_readonly(self, data):
        self.witch_tab.editor.original_widget = urwid.BoxAdapter(
                urwid.Filler(urwid.Text(data['code'])),
                self.ui_loop.screen_size[1] // 2)
        self.witch_tab.refresh(data, self.scope)

        self.switch_tab(self.tabs.get('f2'))

    def launch_witch(self, data):
        tf = NamedTemporaryFile(delete=False, mode='w')
        tf.write(data["code"])
        tf.close()
        self.witch_tab.editor.original_widget = urwid.BoxAdapter(
                ExternalEditor(tf.name, self.ui_loop,
                    lambda path: self.close_witch(data, path)),
                self.ui_loop.screen_size[1] // 2
            )
        self.witch_tab.prompt = self.witch_tab.editor.original_widget
        self.witch_tab.refresh(data, self.scope)
        self.switch_tab(self.tabs.get("f2"))

    def close_witch(self, data, filepath):
        with open(filepath, "r") as f:
            code = f.read()
        revision_payload = dict(
            shortname=data["shortname"],
            code=code,
            current_rev=data["current_rev"])
        os.remove(filepath)

        self.witch_tab.editor.original_widget  = self.witch_tab.editor_filler
        self.switch_tab(self.tabs.get("f1"))

        payload = 'REVISION {}'.format(json.dumps(revision_payload))
        self.witch_tab.refresh({}, self.scope)
        asyncio.ensure_future(self.client_state.send(payload), loop=self.loop)

    def focus_prompt(self):
        self.focus_position = 'body'
        self.prompt = self.body.prompt

    def keypress(self, size, key):
        if key == 'enter' and self.prompt == self.game_tab.prompt:
            self.handle_game_input(self.prompt.get_edit_text())
        else:
            try:
                self.prompt.keypress((size[0],), key)
            except ValueError:
                pass
            self.handle_keypress(size, key)


    def handle_game_input(self, text):
        """Entry point for user commands in MAIN"""
        self.prompt.add_line(text)

        if not self.client_state.listening:
            asyncio.ensure_future(self.client_state.start_listen_loop(), loop=self.loop)

        if text.startswith('/quit'):
            quit_client(self)
        elif text.startswith('/'):
            text = text[1:]
        else:
            chat_color = self.config.get('chat_color', 'light magenta')
            if text:
                text = 'say {'+chat_color+'}'+text+'{/}'
            else:
                text = 'say {'+chat_color+'}...{/}'

        server_msg = 'COMMAND {}'.format(text)

        asyncio.ensure_future(self.client_state.send(server_msg), loop=self.loop)
        self.prompt.edit_text = ''

    def handle_keypress(self, size, key):
        # debugging output
        #self.footer = urwid.Text(key)

        if key in self.hotkeys.get("quit"):
            quit_client(self)
        elif key in self.tabs.keys():
            self.switch_tab(self.tabs.get(key))
        elif key in self.hotkeys.get("scrolling").keys():
            if self.body == self.game_tab:
                self.game_tab.game_area.keypress(size, key)
        elif key in self.hotkeys.get("movement").keys():
            asyncio.ensure_future(self.client_state.send(
                    "COMMAND {}".format(self.hotkeys.get("movement").get(key))
                ), loop=self.loop)
        elif key in self.hotkeys.get("rlwrap").keys() and isinstance(self.prompt, ui.GamePrompt):
            self.prompt.handle_rlwrap(self.hotkeys.get("rlwrap").get(key))

    def switch_tab(self, new_tab):
        self.body.unfocus()
        self.body = new_tab
        self.body.focus()
        self.focus_prompt()
        self.refresh_tabs()
        # this is obviously very bad style but I'm at a loss:
        if new_tab == self.worldmap_tab:
            asyncio.ensure_future(self.client_state.send('MAP'), loop=self.loop)

    def refresh_tabs(self):
        headers = []
        for tab in sorted(self.tabs.keys()):
            headers.append(self.tabs.get(tab).tab_header)
        self.tab_headers = urwid.Columns(headers)
        self.header = self.tab_headers

    def update_state(self, raw_state):
        self.game_state = json.loads(raw_state)
        self.update_scope()
        self.game_tab.refresh(self.game_state)
        self.witch_tab.refresh(self.game_state, self.scope)

    def update_scope(self):
        self.scope.clear()
        for o in self.game_state.get("room").get("contains"):
            self.scope.append(o.get("shortname"))
        for o in self.game_state.get("inventory"):
            self.scope.append(o.get("shortname"))

    def load_hotkeys(self):
        defaults = {
                "scrolling": {
                    "page up": "up",
                    "page down": "down",
                    },
                "quit": [
                    "f9"
                    ],
                "movement": {
                    "shift up": "go north",
                    "shift down": "go south",
                    "shift left": "go west",
                    "shift right": "go east",
                    "shift page up": "go above",
                    "shift page down": "go below",
                    },
                "rlwrap": {
                    "up": "up",
                    "down": "down",
                    "ctrl a": "start",
                    "ctrl e": "end",
                    "ctrl u": "delete backwards",
                    "ctrl k": "delete forwards"
                    }
                }

        hotkeys = {}
        for group in defaults:
            hotkeys.update({group: self.config.get(group, defaults.get(group))})
        return hotkeys