#!/usr/bin/env python3

import argparse
import configparser
import netifaces
import os
import re
import subprocess
import sys


def main():
    is_windows = os.name == "nt"

    config_parser = configparser.ConfigParser()
    if os.path.exists(".config"):
        config_parser.read(".config")
    if "docker-run" not in config_parser:
        config_parser["docker-run"] = {}
    config = config_parser["docker-run"]

    parser = argparse.ArgumentParser(description="Run docker build.")
    parser.add_argument("--root", action="store_true", help="Be root in the container")
    parser.add_argument("--home", action="store_true", help="Mount the home directory")
    parser.add_argument("-ti", action="store_true", help="Use -ti docker run option")
    parser.add_argument("--env", action='append', default=[], help="A variable environment to pass or set")
    parser.add_argument("--network", help="Use a specific docker network")
    parser.add_argument("--share", action='append', default=[], help="A folder to share as a volume")
    parser.add_argument(
        "--add-host", action='append', default=[], help="Add a custom host-to-IP mapping (host:ip)")
    parser.add_argument(
        "--mount", action='append', default=[], help="Attach a filesystem mount to the container")
    parser.add_argument(
        "-v", "--volume", action='append', default=[], help="Bind mount a volume (default [])")
    parser.add_argument(
        "--image", default=config.get("image", "camptocamp/geomapfish-build-dev"),
        help="The docker image to use"
    )
    parser.add_argument(
        "--version", default=config.get("version", "2.4"),
        help="The docker image version to use"
    )
    parser.add_argument("cmd", metavar="CMD", help="The command to run")
    parser.add_argument("args", metavar="...", nargs=argparse.REMAINDER, help="The command arguments")
    options = parser.parse_args()

    docker_cmd = ['docker', 'run']

    if options.ti:
        docker_cmd.append("-ti")

    for share in options.share:
        docker_cmd.append("--volume={}:{}".format(share, share))

    for host in options.add_host:
        docker_cmd.append("--add-host={}".format(host))

    dir_path = os.path.dirname(os.path.realpath(__file__))
    build_volume_name = dir_path.replace(":", "-").replace("\\", "-") \
        if is_windows else dir_path[1:].replace("/", "-")

    try:
        git_branch = \
            os.environ['TRAVIS_BRANCH'] if 'TRAVIS_BRANCH' in os.environ else \
            subprocess.check_output([
                "git", "rev-parse", "--abbrev-ref", "HEAD"
            ], stderr=subprocess.DEVNULL).decode("utf-8").strip()
    except subprocess.CalledProcessError:
        git_branch = "unknown"

    try:
        git_hash = subprocess.check_output([
            "git", "rev-parse", "HEAD"
        ]).decode("utf-8").strip()
    except subprocess.CalledProcessError:
        git_hash = "unknown"

    try:
        git_tag = subprocess.check_output([
            'git', 'tag', '--list', '--points-at=HEAD'
        ], stderr=subprocess.DEVNULL).decode("utf-8").strip()
        if git_tag == '':
            if re.match(r'^[0-9]+.[0-9]+$', git_branch):
                last_git_tag = subprocess.check_output([
                    'git', 'describe', '--abbrev=0', '--tags'
                ], stderr=subprocess.DEVNULL)
                minor = subprocess.check_output([
                    '.venv/bin/python', 'travis/get-minor', '--no-save'
                ], stderr=subprocess.DEVNULL)
                version = "{}.{}".format(last_git_tag, minor)
            else:
                version = git_branch
        else:
            version = git_tag
    except subprocess.CalledProcessError:
        version = "unknown"

    try:
        login = os.getlogin()
    except FileNotFoundError:
        # workaround on strange error
        import pwd
        login = pwd.getpwuid(os.getuid())[0]
    except OSError:
        # workaround on strange error
        import pwd
        login = pwd.getpwuid(os.getuid())[0]

    docker_cmd.extend([
        "--rm",
        "--volume={socket}:{socket}".format(
            socket="//./pipe/docker_engine" if is_windows else "/var/run/docker.sock"
        ),
        "--volume={}:/build".format(build_volume_name),
        "--volume={pwd}:/src".format(pwd=os.getcwd()),
        "--env=USER_NAME={}".format(login),
        "--env=USER_ID={}".format(os.getuid() if os.name == "posix" else 1000),
        "--env=GROUP_ID={}".format(os.getgid() if os.name == "posix" else 1000),
        "--env=GIT_BRANCH={}".format(git_branch),
        "--env=GIT_HASH={}".format(git_hash),
        "--env=VERSION={}".format(version),
    ])

    if options.home:
        internal_home = "/home/{login}".format(login=login) if is_windows else os.environ["HOME"]
        external_home = os.environ["USERPROFILE"] if is_windows else os.environ["HOME"]

        if options.root:
            docker_cmd.append("--volume={}:/root".format(external_home))
        docker_cmd.append("--volume={}:{}".format(internal_home, external_home))
    else:
        docker_cmd.append("--volume={}:/home".format(build_volume_name + "-home"))

    if "SSH_AUTH_SOCK" in os.environ:
        docker_cmd.extend([
            "--volume={ssh}:{ssh}".format(ssh=os.environ["SSH_AUTH_SOCK"]),
            "--env=SSH_AUTH_SOCK",
        ])

    for env in ["CI"] + options.env:
        docker_cmd.append("--env={}".format(env))

    for mount in options.mount:
        docker_cmd.append("--mount={}".format(mount))

    for volume in options.volume:
        docker_cmd.append("--volume={}".format(volume))

    if is_windows:
        import winreg

        def get_connection_name_from_guid(iface_guid):
            reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
            reg_key = winreg.OpenKey(
                reg, r'SYSTEM\CurrentControlSet\Control\Network\{4d36e972-e325-11ce-bfc1-08002be10318}'
            )
            try:
                reg_subkey = winreg.OpenKey(reg_key, iface_guid + r'\Connection')
                return winreg.QueryValueEx(reg_subkey, 'Name')[0]
            except FileNotFoundError:
                return None

        for x in netifaces.interfaces():
            if get_connection_name_from_guid(x) == 'Ethernet':
                docker_adrs = netifaces.ifaddresses(x)[2][0]['addr']
                break
    elif 'docker0' in netifaces.interfaces():
        docker_adrs = netifaces.ifaddresses('docker0')[2][0]['addr']
    else:
        # For Jenkins slave in Docker
        docker_adrs = netifaces.gateways()[netifaces.AF_INET][0][0]
    # We needs the user HOME directory to correctly create the user in Docker
    docker_cmd.append("--env=HOME_DIR={}".format(
        "/home/" + login if is_windows else os.environ["HOME"]))
    docker_cmd.append("--env=DOCKER_HOST_={}".format(docker_adrs))
    docker_cmd.append("--env=BUILD_VOLUME_NAME={}".format(build_volume_name))
    docker_cmd.append("--env=PROJECT_DIRECTORY={}".format(os.getcwd()))
    ifaddresses = [netifaces.ifaddresses(iface) for iface in netifaces.interfaces()]
    # IP v4
    adrs = [e[2][0]['addr'] for e in ifaddresses if 2 in e]
    # IP v6
    adrs += [e[10][0]['addr'].split('%')[0] for e in ifaddresses if 10 in e]
    docker_cmd.append("--env=ADDRESSES={}".format(' '.join(adrs)))
    docker_cmd.append("--env=EXTERNAL_PYTHON_VERSION_MAJOR={}".format(sys.version_info.major))
    docker_cmd.append("--env=EXTERNAL_PYTHON_VERSION_MINOR={}".format(sys.version_info.minor))

    if options.network is not None:
        docker_cmd.append("--network={}".format(options.network))

    internal_network = options.network is None and config.get("no_external_network", "false") != "false"
    if internal_network:
        subprocess.call(['docker', 'network', 'create', '--internal', 'internal'])
        docker_cmd.append("--network=internal")

    if options.root:
        docker_cmd.extend(["--entrypoint", options.cmd])

    docker_cmd.append("{}:{}".format(options.image, options.version))
    if not options.root:
        docker_cmd.append(options.cmd)
    docker_cmd.extend(options.args)

    try:
        subprocess.check_call(docker_cmd)
    except subprocess.CalledProcessError:
        exit(2)
    finally:
        if internal_network:
            subprocess.call(['docker', 'network', 'rm', 'internal'])


if __name__ == "__main__":
    main()
