#!/usr/bin/env python3

import os
import glob
import argparse
import subprocess
import configparser

from scripts.common import git
from scripts.common import Colors


SCRIPTDIR = os.path.normpath(os.path.dirname(__file__))
SUBPROJECTS_DIR = os.path.normpath(os.path.join(SCRIPTDIR, "subprojects"))


def repo_has_branch(repo_dir, branch):
    if not branch:
        return False
    try:
        git("describe", branch, repository_path=repo_dir)
    except subprocess.CalledProcessError:
        return False
    return True

def parse_wrapfile(wrapf):
    cgp = configparser.ConfigParser()
    cgp.read(wrapf)
    if 'wrap-git' not in cgp:
        return None
    section = cgp['wrap-git']
    return section['directory'], section['revision']

def get_wrap_subprojects(srcdir, gst_branch):
    '''
    Parses wrap files in the subprojects directory for the specified source
    tree and gets the revisions for all common repos.
    '''
    for wrapf in glob.glob(os.path.join(srcdir, 'subprojects', '*.wrap')):
        entries = parse_wrapfile(wrapf)
        if not entries:
            continue

        repo_name, repo_branch = entries
        parent_repo_dir = os.path.join(SUBPROJECTS_DIR, repo_name)
        if not os.path.exists(os.path.join(parent_repo_dir, '.git')):
            continue
        # If a branch of the same name exists in the gst subproject, use it
        if repo_name.startswith('gst') and repo_has_branch(parent_repo_dir, gst_branch):
            repo_branch = gst_branch

        yield repo_name, repo_branch, parent_repo_dir

def checkout_worktree(repo_name, repo_dir, worktree_dir, branch, force=False):
    print('Checking out worktree for project {!r} into {!r} '
          '(branch {})'.format(repo_name, worktree_dir, branch))
    try:
        args = ["worktree", "add"]
        if force:
            args += ["-f", "-f"]
        args += [worktree_dir, branch]
        git(*args, repository_path=repo_dir)
    except subprocess.CalledProcessError as e:
        out = getattr(e, "output", b"").decode()
        print("\nCould not checkout worktree %s, please fix and try again."
              " Error:\n\n%s %s" % (repo_dir, out, e))

        return False

    commit_message = git("show", "--shortstat", repository_path=repo_dir).split("\n")
    print(u"  -> %s%s%s - %s" % (Colors.HEADER, repo_dir, Colors.ENDC,
                                    commit_message[4].strip()))
    return True

def checkout_subprojects(worktree_dir, branch):
    worktree_subdir = os.path.join(worktree_dir, "subprojects")

    for repo_name, repo_branch, parent_repo_dir in get_wrap_subprojects(worktree_dir, branch):
        workdir = os.path.normpath(os.path.join(worktree_subdir, repo_name))
        if not checkout_worktree(repo_name, parent_repo_dir, workdir, repo_branch, force=True):
            return False

    return True

def remove_worktree(worktree_dir):
    worktree_subdir = os.path.join(worktree_dir, "subprojects")

    for repo_name, _, parent_repo_dir in get_wrap_subprojects(worktree_dir, None):
        workdir = os.path.normpath(os.path.join(worktree_subdir, repo_name))
        if not os.path.exists(workdir):
            continue

        subprojdir = os.path.normpath(os.path.join(SUBPROJECTS_DIR, repo_name))
        if not os.path.exists(subprojdir):
            continue

        print('Removing worktree {!r}'.format(workdir))
        try:
            git('worktree', 'remove', '-f', workdir, repository_path=subprojdir)
        except subprocess.CalledProcessError as e:
            out = getattr(e, "output", b"").decode()
            print('Ignoring error while removing worktree {!r}:\n\n{}'.format(workdir, out))

    try:
        git('worktree', 'remove', '-f', worktree_dir, repository_path=SCRIPTDIR)
    except subprocess.CalledProcessError:
        print('Failed to remove worktree {!r}'.format(worktree_dir))
        return False
    return True


if __name__ == "__main__":
    parser = argparse.ArgumentParser(prog="gst-worktree")
    parser.add_argument("--no-color", default=False, action='store_true',
                        help="Do not output ANSI colors")

    subparsers = parser.add_subparsers(help='The sub-command to run', dest='command')

    parser_add = subparsers.add_parser('add',
                                       help='Create a worktree for gst-build and all subprojects')
    parser_add.add_argument('worktree_dir', type=str,
                            help='Directory where to create the new worktree')
    parser_add.add_argument('branch', type=str, default=None,
                            help='Branch to checkout')

    parser_rm = subparsers.add_parser('rm',
                                      help='Remove a gst-build worktree and the subproject worktrees inside it')
    parser_rm.add_argument('worktree_dir', type=str,
                           help='Worktree directory to remove')

    options = parser.parse_args()

    if options.no_color or not Colors.can_enable():
        Colors.disable()

    if not options.command:
        parser.print_usage()
        exit(1)

    worktree_dir = os.path.abspath(options.worktree_dir)

    if options.command == 'add':
        if not checkout_worktree('gst-build', SCRIPTDIR, worktree_dir, options.branch):
            exit(1)
        if not checkout_subprojects(worktree_dir, options.branch):
            exit(1)
    elif options.command == 'rm':
        if not os.path.exists(worktree_dir):
            print('Cannot remove worktree directory {!r}, it does not exist'.format(worktree_dir))
            exit(1)
        if not remove_worktree(worktree_dir):
            exit(1)
    else:
        # Unreachable code
        raise AssertionError