#!/usr/bin/env python3
import sys

import blenderfarm

class Action:
    def __init__(self):
        self.name = ''
        self.description = ''
        self.options = []
        self.prefix = ''

    def __lt__(self, other):
        return self.name < other.name

    def parse_arguments(self, args, raise_errors=False):
        options = {}
        for option_index in range(len(self.options)):
            option_format = Action.get_option_format(self.options[option_index])
            option_name = option_format['name']
            option_value = option_format['default']
            if len(args) >= option_index + 1:
                option_value = args[option_index]
            elif not option_format['optional'] and raise_errors:
                print('! option "' + option_name + '" (for action "' + self.name + '") is not optional')
                exit(1)
            options[option_name] = option_value
        if len(args) > len(self.options):
            options['__'] = args[len(self.options):]
        else:
            options['__'] = []

        return options

    @staticmethod
    def get_option_help(option):
        option_format = Action.get_option_format(option)
        name = option_format['name']
        if option_format['optional']:
            return '[' + name + ']'
        return '<' + name + '>'

    @staticmethod
    def get_option_format(option):
        option_format = {
            'name': None,
            'optional': False,
            'default': None
        }
        if option.startswith('?'):
            option = option[1:]
            option_format['optional'] = True
        if '=' in option:
            option_format['default'] = option.split('=')[1]
            option = option.split('=')[0]

        option_format['name'] = option

        return option_format

    def get_string_options_list(self):
        options = []

        for option in self.options:
            options.append(Action.get_option_help(option))

        return ' '.join(options)

    def get_help_line(self):
        return self.prefix + ' ' + self.name.ljust(12) + ' ' + self.get_string_options_list().ljust(24) + ' ' + self.description

    def get_help_usage(self):
        return (self.prefix + ' ' + self.name + ' ' + self.get_string_options_list()).ljust(48) + self.description

    def help(self, options):
        _ = options
        
        print(self.get_help_usage())

    def invoke(self, options):    # pylint: disable=no-self-use
        _ = options
        
        print('override me')

class ActionList(Action):
    def __init__(self):
        super().__init__()
        self.options = ['action']

        self.actions = []

    def get_action(self, name):
        name = name.lower().strip()

        for action in self.actions:
            if action.name == name:
                return action

        return None

    def help(self, options):
        action_name = options['action']

        if action_name:
            action = self.get_action(action_name)

            if action:
                action.prefix = self.prefix + ' ' + self.name

                action.help(action.parse_arguments(options['__']))

                return

        print(self.get_help_usage())

        print()

        for action in self.actions:
            action.prefix = self.prefix + ' ' + self.name
            print(action.get_help_usage())

    def invoke(self, options):
        action_name = options['action']
        arguments = options['__']

        action = self.get_action(action_name)

        if not action:
            self.help(options)
            print()
            print('! no such sub-action "' + action_name + '"')
            return

        action.prefix = self.prefix + ' ' + self.name
        action.invoke(action.parse_arguments(arguments, True))


class HelpAction(Action):
    def __init__(self):
        super().__init__()
        self.name = 'help'
        self.description = 'prints out help'
        self.options = ['?module']

    def invoke(self, options):
        print()
        if options['module'] != None:
            action_name = options['module']
            action = get_action(action_name)

            if not action:
                print_help()
                print()
                print('! no such action "' + action_name + '"')
                return

            action.prefix = self.prefix
            action.help(action.parse_arguments(options['__']))
        else:
            print_help()

        print()

class VersionAction(Action):
    def __init__(self):
        super().__init__()
        self.name = 'version'
        self.description = 'prints out the blenderfarm version'

    def help(self, options):
        print(self.get_help_usage())

        print()

        print('only the version is printed, to make machine-parsing possible.')

    def invoke(self, options):
        print(blenderfarm.__version__)

class ServerAction(Action):
    def __init__(self):
        super().__init__()
        self.name = 'server'
        self.description = 'starts the blenderfarm server'
        self.options = ['?host=0.0.0.0', '?port=44363']

    def help(self, options):
        print(self.description)

        print()

        print('accepts two arguments: [host] and [port].')
        print('If omitted, the defaults of "0.0.0.0" and "44363" are used.')

    def invoke(self, options):
        host = options['host']
        port = options['port']
        try:
            port = int(port)
        except ValueError:
            print('! invalid port number "' + port + '"')
            return
        server = blenderfarm.server.Server(host=host, port=port)
        print('starting blenderfarm server at ' + host + ' (' + str(port) + ')...')
        server.start()

class AdminUserAddAction(Action):
    def __init__(self):
        super().__init__()
        self.name = 'add'
        self.description = 'adds a new user'
        self.options = ['username']

    def help(self, options):
        print(self.description)
        print()
        print('the username must not exist yet')

    def invoke(self, options):
        username = options['username']

        users_db = blenderfarm.db.Users()

        user = users_db.get_user(username)

        if user:
            print('that username already exists:')
        else:
            user = users_db.add(username)
            print('user creation successful:')

        print(user.get_username_key())


class AdminUserRemoveAction(Action):
    def __init__(self):
        super().__init__()
        self.name = 'remove'
        self.description = 'removes a user'
        self.options = ['username']

    def invoke(self, options):
        username = options['username']

        users_db = blenderfarm.db.Users()

        if not users_db.get_user(username):
            print('! that username does not exist')
            return

        users_db.remove(username)

        print('user deletion successful; "' + username + '" no longer exists')


class AdminUserRekeyAction(Action):
    def __init__(self):
        super().__init__()
        self.name = 'rekey'
        self.description = 'deletes the old user key and creates a new one'
        self.options = ['username']

    def invoke(self, options):
        username = options['username']

        users_db = blenderfarm.db.Users()

        user = users_db.get_user(username)

        if not user:
            print('! that username does not exist')
            return

        users_db.rekey(username)

        print('user rekey successful')
        print(user.get_username_key())


class AdminUserListAction(Action):
    def __init__(self):
        super().__init__()
        self.name = 'list'
        self.description = 'lists users and keys'
        self.options = ['?username']

    def help(self, options):
        print(self.get_help_usage())
        print()
        print('lists existing users on this blenderfarm server')
        print('if the optional parameter [username] is present, only lists that user')

    def invoke(self, options):
        users_db = blenderfarm.db.Users()

        username = options['username']

        if username:
            user = users_db.get_user(username)

            if user:
                print('...')
                print(user.get_username_key())
                print('...')
                return

            print('! that username does not exist')
            return

        for user in users_db.get_users():
            print(user.get_username_key())


class AdminUserAction(ActionList):
    def __init__(self):
        super().__init__()
        self.name = 'user'
        self.description = 'blenderfarm server user administration commands'

        self.actions = [
            AdminUserAddAction(),
            AdminUserRemoveAction(),
            AdminUserRekeyAction(),
            AdminUserListAction()
        ]

class AdminAction(ActionList):
    def __init__(self):
        super().__init__()
        self.name = 'admin'
        self.description = 'blenderfarm server administration commands'

        self.actions = [
            AdminUserAction()
        ]
ACTIONS = [
    HelpAction(),
    VersionAction(),

    ServerAction(),
    AdminUserAction(),
    AdminAction()
]

ACTIONS.sort()

def print_version():
    print('blenderfarm version ' + blenderfarm.__version__)

def print_help():
    print('actions:')

    print()

    for action in ACTIONS:
        action.prefix = sys.argv[0]
        print(action.get_help_line())

def print_usage():
    print('usage: ' + sys.argv[0] + ' <action> [options...]')

    print()

    print_help()

def get_action(name):
    name = name.lower().strip()

    for action in ACTIONS:
        if action.name == name:
            return action

    return None

def run():
    if len(sys.argv) < 2:
        print_usage()
        exit(1)

    action_name = sys.argv[1]
    action = get_action(action_name)

    if not action:
        print_usage()
        print()
        print('! no such action "' + action_name + '"')
        exit(1)

    action.prefix = sys.argv[0]

    action.invoke(action.parse_arguments(sys.argv[2:]))
if __name__ == '__main__':
    run()