#!/usr/bin/env python3
"""TBS."""

import argparse
import sys
import os
import re
import shlex
import subprocess


def get_remote_url():
    """Return remote url."""
    # check if git version > 2
    out = check(run_command('git --version'))
    version = re.search(r'git version (\d+)\.(\d+)\.(\d+)', out)
    if version and int(version.group(1)) >= 2:
        return check(run_command('git remote get-url origin'))
    else:
        out = check(run_command('git remote show origin'))
        search = re.search(r'Fetch\s+URL:\s+(\S+)', out)
        if search:
            return search.group(1)
        else:
            print "Error: cannot get remote url"
            exit(1)


def chop_comment(line):
    """Chop comment from line, quotation and escape taken into account."""
    c_backslash = '\\'
    c_dquote = '"'
    c_comment = '#'
    # a little state machine with two state varaibles:
    in_quote = False  # whether we are in a quoted string right now
    backslash_escape = False  # true if we just saw a backslash

    for i, ch in enumerate(line):
        if not in_quote and ch == c_comment:
            # not in a quote, saw a '#', it's a comment.  Chop it and return!
            return line[:i]
        elif backslash_escape:
            # we must have just seen a backslash; reset that flag and continue
            backslash_escape = False
        elif in_quote and ch == c_backslash:
            # we are in a quote and we see a backslash; escape next char
            backslash_escape = True
        elif ch == c_dquote:
            in_quote = not in_quote

    return line


def check((exitcode, out, err)):
    """Check exitcode and print statement and exit if needed."""
    if exitcode == 0:
        return out

    print "Error:", exitcode
    print "StdOut:", out
    print "StdErr:", err

    exit(exitcode)


def run_command(cmd, dir=None):
    """Execute the external command and get its exitcode, stdout and stderr."""
    args = shlex.split(cmd)

    proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dir)
    out, err = proc.communicate()
    exitcode = proc.returncode

    return exitcode, out, err


def get_version(package, versions_file):
    """Return version for package."""
    version = None
    if os.path.isfile(versions_file):
        with open(versions_file) as f:
            for line in f:
                line = line.strip()
                line = chop_comment(line)

                search = re.search(r'set\s*\(\s*([-\w]+)_version\s+([\d\.]+)\s*\)', line)
                if search and search.group(1) == package:
                    version = search.group(2)
                    break
        f.close()

    return version


def get_tdaq_version(version):
    """Convert version from 1.2.3 format to 01-02-03 format."""
    if version is not None:
        search = re.search(r'(\d{1,2})\.(\d{1,2})(?:\.(\d{1,2})(?:\.(\d{1,2}))?)?', version)
        if search:
            bugfix = int(search.group(3)) if search.group(3) is not None else 0
            version = '{0:02d}-{1:02d}-{2:02d}'.format(int(search.group(1)), int(search.group(2)), bugfix)
            if search.group(4) is not None:
                version = '{0}-{1:02d}'.format(version, int(search.group(4)))

    return version


def is_tag(tag_or_branch):
    """Return True if tag is actually a tag and not a branch or None."""
    if tag_or_branch is None:
        return False
    exitcode, out, err = run_command('git rev-parse --verify tags/' + tag_or_branch)
    return exitcode == 0


def checkout_tag(dir, tag_or_branch, package):
    """Checkout a particular tag for a package."""
    version = get_version(package, 'versions.cmake')
    package_dir = os.path.join(dir, package)
    quiet = '--quiet'

    if version is None:
        version = get_version(package, 'cmake_tdaq/cmake/modules/FELIX-versions.cmake')

    version = get_tdaq_version(version)

    # print 'checkout_tag', dir, package, version

    # in case of 'external' or 'tag' is specified
    if dir != '.' or is_tag(tag_or_branch):
        # check out specific version
        if version is None:
            print 'Error: version mandatory for ', package_dir
            exit(1)
        package_version = package + '-' + version
        print package_dir, package_version
        check(run_command('git checkout ' + quiet + ' ' + package_version, package_dir))

    else:
        if tag_or_branch is not None:
            # switch to branch if exists
            run_command('git checkout ' + quiet + ' ' + tag_or_branch, package_dir)
            current_branch = check(run_command('git rev-parse --abbrev-ref HEAD', package_dir)).strip()
            print package_dir, 'switched:', current_branch
        else:
            # if not leave branch untouched OR
            # if detached head, we switch to the default branch (origin/HEAD -> master)
            out = check(run_command('git log -n 1 --pretty=%d HEAD', package_dir))
            # DETACHED = (HEAD, tag: hdlc_coder-00-01-00, origin/master, origin/HEAD, master)
            # Branched = (HEAD -> master, tag: cmake_tdaq-00-01-00, origin/master, origin/HEAD)
            if re.search(r'\(HEAD, tag:', out):
                cmd = 'git branch -r'
                out = check(run_command(cmd, package_dir))
                search = re.search(r'origin/HEAD\s+->\s+\S+/(\S+)', out)
                if search:
                    head_branch = search.group(1)
                    check(run_command('git checkout ' + quiet + ' ' + head_branch, package_dir))
                    print package_dir, 'default:', head_branch
                else:
                    print 'Error command:', cmd, 'failed in directory', package_dir, 'with output:', out
                    exit(1)
            else:
                current_branch = check(run_command('git rev-parse --abbrev-ref HEAD', package_dir)).strip()
                print package_dir, 'kept:', current_branch


def get_dependencies(prefix, group, project, tag, package, checked_packages):
    """TBD."""
    if package in checked_packages:
        return

    print 'get_dependencies', prefix, group, project, tag, package, checked_packages

    dependencies_file = os.path.join(package, 'dependencies.txt')
    if not os.path.isfile(dependencies_file):
        return

    with open(dependencies_file) as f:
        for line in f:
            line = line.strip()
            line = chop_comment(line)

            search = re.search(r'([-\w]+)(?:\/([-\w]+))?', line)
            if search:
                if search.group(2) is None:
                    dir = '.'
                    package = search.group(1)
                    repo = package + '.git'
                else:
                    dir = search.group(1)
                    package = search.group(2)
                    repo = dir + '-' + package + '.git'

#                print dir, name, repo

                package_dir = os.path.join(dir, package)
                if os.path.isdir(package_dir):
                    # print 'git pull ' + package_dir
                    check(run_command('git fetch --quiet', package_dir))
                else:
                    # print 'git clone ' + prefix + '/' + group + '/' + repo + ' ' + package_dir
                    check(run_command('git clone --quiet ' + prefix + '/' + group + '/' + repo + ' ' + package_dir))

                if package not in checked_packages:
                    checkout_tag(dir, tag, package)
                    checked_packages.add(package)

                if dir == '.':
                    get_dependencies(prefix, group, project, tag, package, checked_packages)

    f.close()


def main():
    """TBD."""
    parser = argparse.ArgumentParser(
        description='Get Dependencies for the project')
    parser.add_argument('tag', help="tag/branch to use to get dependencies", default=None, nargs='?')
    args = parser.parse_args(sys.argv[1:])
    tag = args.tag

    url = get_remote_url()

    prefix_group_project = re.search(r'(\S+\/\/\S+)\/(\S+)\/(\S+)', url)
    if prefix_group_project:
        prefix = prefix_group_project.group(1)
        group = prefix_group_project.group(2)
        project = prefix_group_project.group(3)
    else:
        print "Error: could not parse", url
        exit(1)

    package = '.'
    checked_packages = set()
    get_dependencies(prefix, group, project, tag, package, checked_packages)


if __name__ == '__main__':
    main()
