#! /usr/bin/env python

import argparse
import sys

from commands import changes, settings, upstream
from commands.utils import git
from commands.utils.messages import error


def main():

    parser = argparse.ArgumentParser(
        prog='git changes',
        version='git-changes 0.7.0',
        description=changes.__doc__,
        epilog='for more detail, use: git help changes'
    )

    # --------------------------------------------
    # associate sub-command
    # --------------------------------------------
    subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')
    associate_parser = subparsers.add_parser(
        'associate',
        help='associate a branch',
        description='associate a branch',
        usage='git changes associate [-h] [-u] [-V] [COMMIT-ISH [-q]]'
    )
    associate_parser.set_defaults(func=changes.associate)

    # committish
    associate_parser.add_argument(
        'committish',
        help='associate the current branch with a commit-ish or returns the assoication if not included',
        nargs='?',
        metavar='COMMIT-ISH'
    )

    # -q|--quiet
    associate_parser.add_argument(
        '-q',
        '--quiet',
        help='suppress all non-error output',
        action='store_true',
        default=argparse.SUPPRESS
    )

    # -u|--upstream
    associate_parser.add_argument(
        '-u',
        '--upstream',
        help='associate the current branch with its upstream branch',
        action='store_true'
    )

    # -V|--verbose
    associate_parser.add_argument(
        '-V',
        '--verbose',
        help='print the default association when an explicit one does not exist',
        action='store_true'
    )

    # --------------------------------------------
    # unassociate sub-command
    # --------------------------------------------
    unassociate_parser = subparsers.add_parser(
        'unassociate',
        help="remove a branch's association",
        description="remove a branch's association",
    )
    unassociate_parser.set_defaults(func=changes.unassociate)

    # clean up: -a|--all or -p|--prune
    cleanup_group = unassociate_parser.add_mutually_exclusive_group()
    cleanup_group.add_argument(
        '-a',
        '--all',
        help='unassociate all branches',
        action='store_const',
        const='all',
        dest='cleanup'
    )
    cleanup_group.add_argument(
        '-p',
        '--prune',
        help='remove associations for branches that no longer exist',
        action='store_const',
        const='prune',
        dest='cleanup'
    )

    # output: -q|--quiet or -d|--dry-run
    output_group = unassociate_parser.add_mutually_exclusive_group()
    output_group.add_argument(
        '-q',
        '--quiet',
        help='suppress all non-error output',
        action='store_true'
    )
    output_group.add_argument(
        '-d',
        '--dry-run',
        help='show the association(s) that would be remove but do nothing',
        action='store_true'
    )

    # --------------------------------------------
    # view sub-command
    # --------------------------------------------
    view_parser = subparsers.add_parser(
        'view',
        help='view changes (default when omitted)',
        usage="""git changes view [-h] [-r] [-c | -s | -d]
                        [--color [{always,never,auto}] | --no-color]
                        [COMMIT-ISH] [-- FILE [FILE ...]]""",
        description='view changes'
    )
    view_parser.set_defaults(func=changes.changes)

    # committish
    committish_group = view_parser.add_mutually_exclusive_group()
    committish_group.add_argument(
        'committish',
        help='show the commits between HEAD and a commit-ish',
        nargs='?',
        metavar='COMMIT-ISH'
    )

    # -r|--remote
    committish_group.add_argument(
        '-r',
        '--remote',
        help='show the commits between the local and remote head',
        action='store_true',
        default=argparse.SUPPRESS
    )

    # details
    details_group = view_parser.add_mutually_exclusive_group()
    details_group.add_argument(
        '-c',
        '--count',
        help='show as a count of changes',
        action='store_const',
        const='count',
        dest='details'
    )
    details_group.add_argument(
        '-s',
        '--stat',
        help='show as a diffstat',
        action='store_const',
        const='stat',
        dest='details'
    )
    details_group.add_argument(
        '-d',
        '--diff',
        help='show as a full diff',
        action='store_const',
        const='diff',
        dest='details'
    )

    # color
    color_group = view_parser.add_mutually_exclusive_group()
    color_group.add_argument(
        '--color',
        help='color output',
        const='always',
        dest='color_when',
        nargs='?',
        choices=('always', 'never', 'auto')
    )
    color_group.add_argument(
        '--no-color',
        help='never color output',
        action='store_const',
        const='never',
        dest='color_when'
    )

    # -- <files> ...
    # NOTE: this is so the files argument is listed in the argparse output. All file arguments are handled manually.
    view_parser.add_argument('files', help='view changes to specific files', nargs='?', metavar='FILE')

    # default to view mode
    if len(sys.argv) == 1 or sys.argv[1] not in ('view', 'associate', 'unassociate') and not any(
            [opt in sys.argv for opt in ('-h', '--help', '-v', '--version')]):
        sys.argv.insert(1, 'view')

    # check for file arguments
    args = sys.argv[1:]
    file_args = []
    if '--' in args:
        delimiter_index = args.index('--') if '--' in args else len(args)
        file_args = args[delimiter_index + 1:]
        args = args[:delimiter_index]

    args = vars(parser.parse_args(args))
    subcommand = args.pop('subcommand')
    if subcommand == 'associate' and not args['committish'] and not args['upstream']:
        # suppressing output on retrieval makes no sense
        if 'quiet' in args:
            associate_parser.print_usage()
            error('argument -q/--quiet: not allowed without positional argument committish or option -r/--remote', prefix='git changes: error:')
        _error_if_files_supplied(associate_parser, file_args)
        args['func'] = changes.get_association
        del args['committish']
        del args['upstream']
    elif subcommand == 'associate' and args['upstream']:
        _error_if_files_supplied(associate_parser, file_args)
        args['func'] = changes.associate_upstream
        del args['committish']
        del args['verbose']
        del args['upstream']
    elif subcommand == 'associate':
        _error_if_files_supplied(associate_parser, file_args)
        del args['verbose']
        del args['upstream']
    elif subcommand == 'unassociate':
        _error_if_files_supplied(unassociate_parser, file_args)
    elif subcommand == 'view' and 'remote' in args:
        # -r|--remote doesn't work with dest='committish' when committish is positional
        del args['remote']
        remote_branch = upstream.upstream(include_remote=upstream.IncludeRemote.NONE_LOCAL)
        if not remote_branch:
            error('{0!r} has no remote branch'.format(git.current_branch()))
        args['committish'] = remote_branch
        args['files'] = file_args
    elif subcommand == 'view' and not args['committish']:
        if git.is_empty_repository():
            sys.exit(0)  # nothing to do
        committish = changes.get_association()
        if not committish:
            committish = settings.get('git-changes.default-commit-ish', default='refs/heads/master')
        args['committish'] = committish
        args['files'] = file_args
    elif subcommand == 'view':
        args['files'] = file_args

    func = args.pop('func')
    result = func(**args)
    if result:
        print result


def _error_if_files_supplied(parser, file_args):
    if file_args:
        parser.print_usage()
        error('argument FILES: only supported for view sub-command', prefix='git changes: error:')


if __name__ == '__main__':
    main()
