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

import asyncio
from syncrypt import __project__, __version__
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

COPYRIGHT = '''Syncrypt v{version}
(c) 2016 Syncrypt UG | https://syncrypt.space
Licensed under the GPLv3 | Source: https://github.com/syncrypt/client'''\
    .format(version=__version__)

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.run_until_complete(self.app.close())
        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(
    formatter_class=argparse.RawDescriptionHelpFormatter,
    usage='''syncrypt <command> [<args>]

''' + COPYRIGHT  + '''

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, nargs='?')
global_parser.add_argument('-v', '--version', action='store_true', help='Show version information')

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, '')

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

    if args.version:
        print(COPYRIGHT)
    elif args.command:
        for c in COMMANDS:
            if c.command == args.command:
                c.parse(sys.argv[2:])
                c.setup()
                c.run()
                c.shutdown()
    else:
        global_parser.print_help()

