#!/usr/bin/python3
#
# A tool to manage a workspace of git repositories.
#
# Copyright (c) 2017-2019 Xevo Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#

import argparse
import logging
import os
import shutil
import sys

from wst import (
    WSError,
    log
)
import wst.cmd
import wst.cmd.build
import wst.cmd.clean
import wst.cmd.config
import wst.cmd.default
import wst.cmd.env
import wst.cmd.init
import wst.cmd.list
import wst.cmd.remove
import wst.cmd.rename
from wst.conf import (
    find_root,
    get_default_ws_link,
    get_ws_dir,
    sync_config
)
from wst.version import version

_LOG_FORMAT = '%(message)s'


# Keys that taint a build.
_TAINT_KEYS = {'type'}

# This dictionary gives the list of available subcmds. Each subcmd must supply
# a populate hook, which populates its arguments in the argument parser, and a
# do hook, which actually executes the subcmd given parsed arguments.
_SUBCMDS = {
    'init': {
        'friendly': 'Create a new workspace',
        'cmd': wst.cmd.init.Init
    },
    'list': {
        'friendly': 'List projects',
        'cmd': wst.cmd.list.List
    },
    'rename': {
        'friendly': 'Rename a workspace',
        'cmd': wst.cmd.rename.Rename
    },
    'remove': {
        'friendly': 'Remove a workspace',
        'cmd': wst.cmd.remove.Remove
    },
    'default': {
        'friendly': 'Set the default workspace',
        'cmd': wst.cmd.default.Default
    },
    'config': {
        'friendly': 'Configure a workspace',
        'cmd': wst.cmd.config.Config
    },
    'clean': {
        'friendly': 'Clean project or workspace',
        'cmd': wst.cmd.clean.Clean
    },
    'build': {
        'friendly': 'Build project or workspace',
        'cmd': wst.cmd.build.Build
    },
    'env': {
        'friendly': 'Run command in the workspace environment',
        'cmd': wst.cmd.env.Env
    }
}


def parse_args():
    '''Parses the command-line arguments, returning the arguments, the argument
    parser, and the workspace directory to use.'''
    parser = argparse.ArgumentParser(description='Manage workspaces')
    parser.set_defaults(subcmd=None)
    parser.add_argument(
        '-c', '--current-dir',
        action='store',
        required=False,
        dest='start_dir',
        default=os.getcwd(),
        help=('Run ws as if from the given directory. Defaults to the current '
              'working directory.'))
    parser.add_argument(
        '-w', '--workspace',
        action='store',
        dest='ws',
        required=False,
        help='Name of the workspace inside the root on which to act')
    parser.add_argument(
        '-n', '--dry-run',
        action='store_true',
        default=False,
        required=False,
        help="Don't do anything; just print what would happen")
    parser.add_argument(
        '-d', '--debug',
        action='store_true',
        default=False,
        required=False,
        help='Debug output')
    parser.add_argument(
        '-v', '--verbose',
        action='store_true',
        default=False,
        required=False,
        help='Verbose output')
    parser.add_argument(
        '--version',
        action='version',
        version=version())

    subparsers = parser.add_subparsers(help='subcommands')
    for cmd, d in _SUBCMDS.items():
        subparser = subparsers.add_parser(cmd, help=d['friendly'])
        d['cmd'].args(subparser)
        subparser.set_defaults(subcmd=cmd)

    args = parser.parse_args()

    args.root = find_root(args.start_dir)
    if args.root is None:
        if args.subcmd != 'init':
            raise WSError("can't find .ws directory; please run %s init"
                          % sys.argv[0])

    if args.subcmd not in ('init', 'default', 'list', 'rename', 'remove'):
        if args.ws is None:
            args.ws = get_default_ws_link(args.root)
        ws_dir = get_ws_dir(args.root, args.ws)
        if not os.path.exists(ws_dir):
            raise WSError('workspace %s at %s does not exist.'
                          % (args.ws, ws_dir))
        if not os.path.exists(ws_dir):
            raise WSError('workspace at %s is not a directory.' % ws_dir)
    else:
        if args.ws is not None:
            raise WSError('cannot specify a top-level workspace with the %s '
                          'subcmd' % args.subcmd)
        ws_dir = None

    if args.debug:
        level = logging.DEBUG
    elif args.verbose or args.dry_run:
        level = logging.INFO
    else:
        level = logging.WARNING

    # Must remove old handlers first.
    for handler in logging.root.handlers:
        logging.root.removeHandler(handler)
    logging.basicConfig(format=_LOG_FORMAT, level=level)

    if args.dry_run:
        wst.set_dry_run()

    return args, parser, ws_dir


def check_tool(tool):
    '''Check if the given tool exists in the PATH. If not, print an error and
    exit.'''
    if shutil.which(tool) is None:
        print('Cannot find %s; please install it.' % tool, file=sys.stderr)
        return 1


def main():
    '''Entrypoint.'''
    logging.basicConfig(format=_LOG_FORMAT, level=logging.WARNING)

    args, parser, ws_dir = parse_args()
    if args.subcmd is None:
        parser.print_help()
        return 1

    # Sanity check for required tools.
    check_tool('git')
    check_tool('gcc')

    cls = _SUBCMDS[args.subcmd]['cmd']
    try:
        cls.do(ws_dir, args)
    except:  # noqa: E722
        raise
    finally:
        # Init writes out the config, so we don't need to sync it.
        if args.subcmd != 'init':
            sync_config(ws_dir)


if __name__ == '__main__':
    try:
        status = main()
    except WSError as e:
        log(e, logging.ERROR)
        status = 1
    sys.exit(status)
