#!/usr/bin/env python
import argparse
import logging
import logging.config
import os.path
import sys

import asyncio
from syncrypt.app import SyncryptApp
from syncrypt.app.cli import CLIAuthenticationProvider
from syncrypt.config import AppConfig
from syncrypt.models import Vault
from syncrypt.utils.logging import setup_logging

LOGLEVELS = ['CRITICAL', 'ERROR', 'WARN', 'INFO', 'DEBUG']

class SyncryptCmd():
    command = None
    description = None

    def configure_parser(self, parser):
        parser.add_argument('-l', metavar='LOGLEVEL', type=str, default='INFO',
                dest='loglevel', choices=LOGLEVELS,
                help='log level: ' + ', '.join(LOGLEVELS)+ ' (default: INFO)')

    def __init__(self):
        self.parser = argparse.ArgumentParser(
                description=self.description,
                prog='%s %s' % ('syncrypt', self.command)
            )
        self.configure_parser(self.parser)
        self.config = None
        self.app_config = AppConfig()
        self.app_config.read_config_file()

    def parse(self, args):
        self.config = self.parser.parse_args(args)

    def setup(self):
        setup_logging(self.config.loglevel)
        self.loop = asyncio.get_event_loop()
        self.app = SyncryptApp(self.app_config, auth_provider=CLIAuthenticationProvider())

    def shutdown(self):
        self.loop.close()

    def run(self):
        raise NotImplementedError()

class SingleVaultCmd(SyncryptCmd):
    '''command that supports only a single vault directory'''

    def configure_parser(self, parser):
        super(SingleVaultCmd, self).configure_parser(parser)
        parser.add_argument('-d', metavar='DIRECTORY', type=str,
                default='.', dest='directory', help='directory (default: .)')

    def setup(self):
        self.app_config.add_vault_dir(self.config.directory)
        super(SingleVaultCmd, self).setup()

class MultipleVaultCmd(SyncryptCmd):
    '''command that supports multiple vault directories'''

    def configure_parser(self, parser):
        super(MultipleVaultCmd, self).configure_parser(parser)
        parser.add_argument('-d', metavar='DIRECTORY', type=str,
                action='append', dest='directory', help='directory (default: .)')

    def setup(self):
        # For now, each command will work on a default AppConfig object. When
        # you want app-level configuration, use ``syncrypt_daemon``.
        DIRECTORIES = (['.'] if self.config.directory is None else self.config.directory)
        for directory in DIRECTORIES:
            self.app_config.add_vault_dir(directory)

        super(MultipleVaultCmd, self).setup()

class Clone(SyncryptCmd):
    command = 'clone'
    description = 'clone a remote vault to a local directory'

    def configure_parser(self, parser):
        super(Clone, self).configure_parser(parser)
        parser.add_argument('vault_id', help='vault id')
        parser.add_argument('directory', nargs='?', help='local directory')

    def run(self):
        self.loop.run_until_complete(self.app.clone(self.config.vault_id,
                self.config.directory or self.config.vault_id))

class Pull(MultipleVaultCmd):
    command = 'pull'
    description = 'pull all files from the latest revision'

    def run(self):
        self.loop.run_until_complete(self.app.pull())

class AddUser(SingleVaultCmd):
    command = 'add-user'
    description = 'add another user to this vault'

    def configure_parser(self, parser):
        super(AddUser, self).configure_parser(parser)
        parser.add_argument('email', help='the user\'s email')

    def run(self):
        self.loop.run_until_complete(self.app.add_user(self.config.email))

class Vaults(SyncryptCmd):
    command = 'vaults'
    description = 'list vaults'

    def run(self):
        self.loop.run_until_complete(self.app.list_vaults())

class Keys(SingleVaultCmd):
    command = 'keys'
    description = 'list keys'

    def configure_parser(self, parser):
        super(Keys, self).configure_parser(parser)
        parser.add_argument('-u', '--user', action='store', dest='user',
                help='user email to list keys for')
        parser.add_argument('--upload', action='store_true',
                help='upload your public keys to the server')
        parser.add_argument('--art', action='store_true', dest='art',
                help='show ascii art for each key')

    def run(self):
        if self.config.upload:
            self.loop.run_until_complete(self.app.upload_identity())
        else:
            self.loop.run_until_complete(
                    self.app.list_keys(self.config.user, with_art=self.config.art)
                )

class Push(MultipleVaultCmd):
    command = 'push'
    description = 'push local changes to the server'

    def configure_parser(self, parser):
        super(Push, self).configure_parser(parser)

    def run(self):
        self.loop.run_until_complete(self.app.push())

class Watch(MultipleVaultCmd):
    command = 'watch'
    description = 'watch directory for changes'

    def run(self):
        self.loop.run_until_complete(self.app.start())
        self.loop.run_forever()

class Init(SingleVaultCmd):
    command = 'init'
    description = 'register the directory as a Syncrypt vault'

    def run(self):
        self.loop.run_until_complete(self.app.init())

class Info(MultipleVaultCmd):
    command = 'info'
    description = 'show vault information'

    def run(self):
        self.loop.run_until_complete(self.app.info())

class ConfigSet(SingleVaultCmd):
    command = 'set'
    description = 'set a vault config parameter'

    def configure_parser(self, parser):
        super(ConfigSet, self).configure_parser(parser)
        parser.add_argument('setting', help='the thing to set')
        parser.add_argument('value', help='the value to set it to')

    def run(self):
        self.loop.run_until_complete(
            self.app.set(self.config.setting, self.config.value)
        )

class ConfigUnset(SingleVaultCmd):
    command = 'unset'
    description = 'unset a vault config parameter'

    def configure_parser(self, parser):
        super(ConfigUnset, self).configure_parser(parser)
        parser.add_argument('setting', help='the thing to unset')

    def run(self):
        self.loop.run_until_complete(self.app.unset(self.config.setting))

class Log(MultipleVaultCmd):
    command = 'log'
    description = 'show recent changes (file uploads, deletions, etc)'

    def configure_parser(self, parser):
        super(Log, self).configure_parser(parser)
        parser.add_argument('-v', '--verbose', action='store_true',
                help='print more information')

    def run(self):
        self.loop.run_until_complete(self.app.print_log(self.config.verbose))

class Login(SyncryptCmd):
    command = 'login'
    description = 'login to server and store auth token'

    def run(self):
        self.loop.run_until_complete(self.app.login())

class Export(SingleVaultCmd):
    command = 'export'
    description = 'export vault config and keys to backup or share'

    def configure_parser(self, parser):
        super(Export, self).configure_parser(parser)
        parser.add_argument('-o', '--output', dest='filename', help='export filename')

    def run(self):
        self.loop.run_until_complete(self.app.export(self.config.filename))


COMMANDS = [Init(), Vaults(), Clone(), Pull(), Push(), Watch(), AddUser(),
        Info(), Log(), ConfigSet(), ConfigUnset(), Export(), Keys(), Login()]
COMMAND_NAMES = [c.command for c in COMMANDS]

global_parser = argparse.ArgumentParser(
    usage='''syncrypt <command> [<args>]

available commands:
''' + '\n'.join([
    '  {0.command:11s} {0.description}'.format(c) for c in COMMANDS
    ]))

global_parser.add_argument('command', help='Subcommand to run', choices=COMMAND_NAMES)

args = global_parser.parse_args(sys.argv[1:2])

if __name__ == '__main__':
    # setlocale() is called here so that strftime will use the correct user
    # locale when formatting datetime objects.
    import locale
    locale.setlocale(locale.LC_TIME, '')

    for c in COMMANDS:
        if c.command == args.command:
            c.parse(sys.argv[2:])
            c.setup()
            c.run()
            c.shutdown()

