#!/usr/bin/env python3

import argparse
import os
import pathlib
import subprocess
import tarfile
import shutil

import requests
import sys

#
# GoVPP API generator generates Go bindings compatible with the local VPP
#

DefaultGoVppCommit = "16a47ef937b3a5ce6acf45885386062b323c8d25"


def version_geq(ver_a, ver_b):
    major_a, minor_a, patch_a = ver_a.split(".")
    major_b, minor_b, patch_b = ver_b.split(".")
    if major_a > major_b:
        return True
    elif major_a == major_b and minor_a > minor_b:
        return True
    elif major_a == major_b and minor_a == minor_b and patch_a >= patch_b:
        return True
    return False


def execute(cli, cwd=None):
    p = subprocess.Popen(
        cli.split(),
        cwd=cwd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True,
    )
    output, error = p.communicate()
    if p.returncode != 0:
        print("Command `%s` failed: %d %s" % (cli, p.returncode, error))
        sys.exit(1)
    return output, error


def get_go_version(go_root):
    # Returns version of the installed Go
    output, _ = execute("./go version", cwd=go_root + "/bin")
    return output.replace("go version go", "", 1).rstrip("\n")


# Returns version of the installed binary API generator
def get_binapi_gen_version(go_path):
    output, _ = execute("./binapi-generator -version", cwd=go_path + "/bin")
    return output.replace("govpp", "", 1).rstrip("\n")


# Verifies local Go installation and installs the latest
# one if missing
def install_golang(go_root):
    go_bin = go_root + "/bin/go"

    if os.path.exists(go_bin) and os.path.isfile(go_bin):
        print("Go " + get_go_version(go_root) + " is already installed")
        return

    filename = (
        requests.get("https://golang.org/VERSION?m=text").text + ".linux-amd64.tar.gz"
    )
    url = "https://dl.google.com/go/" + filename

    print("Go binary not found, installing the latest version...")
    print("Download url      = %s" % url)
    print("Install directory = %s" % go_root)
    text = input("[Y/n] ?")

    if text.strip().lower() != "y" and text.strip().lower() != "yes":
        print("Aborting...")
        exit(1)

    go_folders = ["src", "pkg", "bin"]

    for f in go_folders:
        if not os.path.exists(os.path.join(go_root, f)):
            os.makedirs(os.path.join(go_root, f))
    r = requests.get(url)
    with open("/tmp/" + filename, "wb") as f:
        f.write(r.content)

    go_tf = tarfile.open("/tmp/" + filename)
    # Strip /go dir from the go_root path as it will
    # be created while extracting the tar file
    go_root_head, _ = os.path.split(go_root)
    go_tf.extractall(path=go_root_head)
    go_tf.close()
    os.remove("/tmp/" + filename)

    print("Go " + get_go_version(go_root) + " was installed")


# Installs latest binary API generator
def install_binapi_gen(c, go_root, go_path):
    go_version = get_go_version(go_root)
    if version_geq(go_version, "1.18.0"):
        execute(
            "./go install git.fd.io/govpp.git/cmd/binapi-generator@" + c,
            cwd=go_root + "/bin",
        )
    else:
        os.environ["GO111MODULE"] = "on"
        execute(
            "./go get git.fd.io/govpp.git/cmd/binapi-generator@" + c,
            cwd=go_root + "/bin",
        )
    bg_ver = get_binapi_gen_version(go_path)
    print("Installed binary API generator " + bg_ver)


# Creates generated bindings using GoVPP binapigen to the target folder
def generate_api(output_dir, vpp_dir, api_list, import_prefix, no_source, go_path):
    json_dir = vpp_dir + "/build-root/install-vpp-native/vpp/share/vpp/api"

    if not os.path.exists(json_dir):
        print("Missing JSON api definitions")
        sys.exit(1)

    print("Generating API")
    cmd = ["./binapi-generator", "--input-dir=" + json_dir]
    if output_dir:
        cmd += ["--output-dir=" + output_dir]
    if len(api_list):
        print("Following API files were requested by 'GO_API_FILES': " + str(api_list))
        print("Note that dependency requirements may generate additional API files")
        cmd.append(api_list)
    if import_prefix:
        cmd.append("-import-prefix=" + import_prefix)
    if no_source:
        cmd.append("-no-source-path-info")

    _, out = execute(" ".join(cmd), cwd=go_path + "/bin")
    # Print nice output of the binapi generator
    for msg in out.split():
        if "=" in msg:
            print()
        print(msg, end=" ")

    print("\n")
    print("Go API bindings were generated to " + output_dir)


def get_go_variables():
    # go specific environment variables
    if "GOROOT" in os.environ:
        go_root = os.environ["GOROOT"]
    else:
        go_binary = shutil.which("go")
        if go_binary != "":
            go_binary_dir, _ = os.path.split(go_binary)
            go_root = os.path.join(go_binary_dir, "..")
        else:
            go_root = os.environ["HOME"] + "/.go"
    if "GOPATH" in os.environ:
        go_path = os.environ["GOPATH"]
    else:
        go_path = os.environ["HOME"] + "/go"

    return go_root, go_path


def main():
    # project root directory
    root = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
    vpp_dir = root.parent.parent.parent

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-govpp-commit",
        "--govpp-commit",
        help="GoVPP commit or branch ",
        default=DefaultGoVppCommit,
        type=str,
    )
    parser.add_argument(
        "-output-dir",
        "--output-dir",
        help="output target directory for generated bindings",
        type=str,
        default=os.path.join(vpp_dir, "vppbinapi"),
    )
    parser.add_argument(
        "-api-files",
        "--api-files",
        help="api files to generate (without commas)",
        nargs="+",
        type=str,
        default=[],
    )
    parser.add_argument(
        "-import-prefix",
        "--import-prefix",
        help="prefix imports in the generated go code",
        default="",
        type=str,
    )
    parser.add_argument(
        "-no-source-path-info",
        "--no-source-path-info",
        help="disable source path info in generated files",
        nargs="?",
        const=True,
        default=True,
    )
    args = parser.parse_args()

    go_root, go_path = get_go_variables()
    install_golang(go_root)

    if not (
        os.path.exists(go_root + "/bin/go") and os.path.isfile(go_root + "/bin/go")
    ):
        print(go_root + "/bin/go does not exist")
        sys.exit(1)

    install_binapi_gen(args.govpp_commit, go_root, go_path)
    generate_api(
        args.output_dir,
        str(vpp_dir),
        args.api_files,
        args.import_prefix,
        args.no_source_path_info,
        go_path,
    )


if __name__ == "__main__":
    main()