"""Command line user interface"""

import cmd
import re
import threading

import authority_string_transformer

INTRO = 'Type the command "help" for help document.'
PROMPT = '> '


class CmdUI(cmd.Cmd):  # pylint: disable=R0904
    """Command line user interface.

    It's a simple UI for doing operation on server, supplied commands:
        - Add/delete/reset a user
        - List (online) users
        - Save/load the user list to/from a file.
        - Exit.
        - Prints the help document.

    And it is also the main output interface of the whole program.

    Attributes:
        _users_text_manager: An instance of UsersTextManager.
        _tcp_server: An instance of TcpServer.
        _shrvim_server: An instance of ShrVimServer.
        _exit_flag: Whether this UI should stop or not.
        _thread: Instance of Thread.
        _init_cmds: Initialize commands.
    """
    def __init__(self,
                 init_cmds, users_text_manager, tcp_server, shrvim_server):
        """Constructor.

        Args:
            init_cmds: Lists of commands to run after startup.
            users_text_manager: An instance of UsersTextManager.
            tcp_server: An instance of TCPServer.
            shrvim_server: An instance of ShrVimServer.
        """
        super(CmdUI, self).__init__()
        self.prompt = PROMPT
        self._users_text_manager = users_text_manager
        self._tcp_server = tcp_server
        self._shrvim_server = shrvim_server
        self._stop_flag = False
        self._thread = None
        self._init_cmds = init_cmds

    def do_add(self, text):
        """Adds a user, [usage] add <identity> <nickname> <authority>"""
        try:
            identity, nickname, authority_str = _split_text(text, 3)
            if identity in self._users_text_manager.get_users_info():
                self.write('The identity %r is already in used.\n' % identity)
                return
            authority = authority_string_transformer.to_number(authority_str)
            self._users_text_manager.add_user(identity, nickname, authority)
            user_info = self._users_text_manager.get_users_info()[identity]
            self.write('Added %s => %s\n' % (identity, str(user_info)))
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] add <identity> <nickname> <authority>\n')
        except authority_string_transformer.Error as e:
            self.write('Fail: %r\n' % e)

    def do_delete(self, text):
        """Deletes a user, [usage] delete <identity>"""
        try:
            identity = _split_text(text, 1)[0]
            if identity not in self._users_text_manager.get_users_info():
                self.write('The identity %r is not in used.\n' % identity)
                return
            self._users_text_manager.delete_user(identity)
            self.write('Done\n')
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] delete <identity>\n')

    def do_deleteall(self, text):
        """Deletes all users, [usage] deleteall"""
        try:
            _split_text(text, 0)
            for identity in self._users_text_manager.get_users_info():
                self._users_text_manager.delete_user(identity)
            self.write('Done\n')
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] deleteall\n')

    def do_reset(self, text):
        """Resets a user, [usage] reset <identity>"""
        try:
            iden = _split_text(text, 1)[0]
            if iden not in self._users_text_manager.get_users_info():
                self.write('The User with identity %r is not exist.\n' % iden)
                return
            self._users_text_manager.reset_user(iden)
            user_info = self._users_text_manager.get_users_info()[iden]
            self.write('Reseted %s ==> %s\n' % (iden, str(user_info)))
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] reset <identity>\n')

    def do_list(self, text):
        """Lists users, [usage] list"""
        try:
            _split_text(text, 0)
            infos = self._users_text_manager.get_users_info().items()
            for iden, user in sorted(infos, key=lambda x: x[0]):
                self.write('%-10s => %s' % (iden, str(user)))
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] list\n')

    def do_online(self, text):
        """Lists online users, [usage] online"""
        try:
            _split_text(text, 0)
            infos = self._users_text_manager.get_users_info(
                must_online=True).items()
            for iden, user in sorted(infos, key=lambda x: x[0]):
                self.write('%-10s => %s' % (iden, str(user)))
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] online\n')

    def do_load(self, text):
        """Loads users from a file, [usage] load <filename>"""
        try:
            filename = _split_text(text, 1)[0]
            with open(filename, 'r') as f:
                for line in f.readlines():
                    line = line if not line.endswith('\n') else line[ : -1]
                    if not line:
                        continue
                    self.do_add(line)
            self.write('Done')
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] load <filename>\n')
        except IOError as e:
            self.write('Error occured when opening the file: %r' % e)

    def do_save(self, text):
        """Saves users list to file, [usage] save <filename>"""
        try:
            filename = _split_text(text, 1)[0]
            with open(filename, 'w') as f:
                users_info = self._users_text_manager.get_users_info().items()
                for iden, user in sorted(users_info, key=lambda x: x[0]):
                    auth_str = authority_string_transformer.to_string(
                        user.authority)
                    f.write('%s %s %s\n' % (iden, user.nick_name, auth_str))
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] save <filename>\n')
        except IOError as e:
            self.write('Cannot open file? %s' % str(e))

    def do_port(self, text):
        """Print the server's port."""
        try:
            _split_text(text, 0)
            self.write('Server port = %r\n' % self._tcp_server.port)
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] port\n')

    def do_exit(self, text):
        """Exits the program."""
        try:
            _split_text(text, 0)
            self._shrvim_server.stop()
        except _SplitTextError:
            self.write('Format error!\n'
                       '[usage] exit\n')

    def do_echo(self, text):  # pylint: disable=R0201
        """Echo."""
        print(text)

    def do_eval(self, text):
        """For debug, evaluate an peice of python code and prints the result."""
        try:
            result = eval(text)  # pylint: disable=W0123
            self.write('The result is %r\n' % result)
        except Exception as e:  # pylint: disable=W0703
            self.write('Exception occured: %r' % e)

    def do_help(self, text):
        """Prints the help document, [usage] help"""
        try:
            _split_text(text, 0)
            commands = [m[3 : ] for m in dir(self) if m.startswith('do_')]
            self.write('Commands: \n' + ' '.join(commands))
        except _SplitTextError:
            self.write('Format error!\n' +
                       '[usage] help\n')

    def do_EOF(self, text):  # pylint: disable=C0103
        """Same as exit"""
        self.do_exit(text)

    def emptyline(self):
        """Do nothing."""
        pass

    def postcmd(self, unused_stop, unused_text):
        """Checks whether it should be stop or not."""
        return self._stop_flag

    def write(self, text):
        """Writes text by this UI.

        It will call the "do_echo" command.

        Args:
            text: String to be printed.
        """
        for line in text.splitlines():
            self.onecmd('echo ' + line)

    def preloop(self):
        for c in self._init_cmds:
            self.onecmd(c)

    def start(self):
        """Starts this CmdUI."""
        self._thread = threading.Thread(target=self.cmdloop,
                                        args=(INTRO,))
        self._thread.start()

    def stop(self):
        """Stops the command line UI."""
        self._stop_flag = True
        self.onecmd('echo bye~\n')

    def join(self):
        """Joins this thread."""
        self._thread.join()

    def flush(self):
        """Flush the screen."""
        pass


class _SplitTextError(Exception):
    """Error raised by the function _split_text()."""
    pass

def _split_text(text, num):
    """Split the text into tuple.

    Args:
        text: The string to be splitted.
        num: Number of elements in the result tuple.

    Return:
        A <num>-tuple.
    """
    words = [word for word in re.split(r'[ \t]', text) if word]
    if len(words) != num:
        raise _SplitTextError()
    return tuple(words)