#!/usr/bin/env python3

# Usage: go mod graph | ./gomodtorbm

import errno
import os
import os.path
import re
import sys

STAGING_DIR = "staging"

MAIN_VERSION = "v3.1.41"

# Projects that already exist.
TERMINALS = {
    "golang.org/x/crypto",
    "golang.org/x/net",
    "golang.org/x/sys",
    "golang.org/x/text",
}

def strip_version_suffix(import_path):
	return re.sub(r'/+v[0-9]+$', "", import_path)

def parse_spec(spec):
    print("Parsing "+ spec)
    try:
        import_path, version = spec.split("@")
    except ValueError:
        import_path = spec
        version = MAIN_VERSION
    return strip_version_suffix(import_path), Version(version)

class Version:
    def __init__(self, version):
        self.version = version

    def tuple(self):
        m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)-(?:0\.)?([0-9]+)-([0-9a-fA-F]+)(\+incompatible)?$', self.version)
        if m:
            g = m.groups()
            return int(g[0]), int(g[1]), int(g[2]), g[3], g[4]
        m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)-(rc\.[0-9\.]+)-([0-9a-fA-F]+)$', self.version)
        if m:
            g = m.groups()
            return int(g[0]), int(g[1]), int(g[2]), g[3], g[4]
        m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)-(rc\.[0-9]+)$', self.version)
        if m:
            g = m.groups()
            return int(g[0]), int(g[1]), int(g[2]), g[3], ""
        m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]{4}\.[0-9]+\.[0-9]+)$', self.version)
        if m:
            g = m.groups()
            return int(g[0]), int(g[1]), int(g[2]), g[3], ""
        m = re.match(r'^v([0-9]+)\.([0-9]+)\.([0-9]+)(\+incompatible)?$', self.version)
        if m:
            g = m.groups()
            return int(g[0]), int(g[1]), int(g[2]), "", ""
        assert False, self.version

    def __str__(self):
        major, minor, micro, _, hash = self.tuple()
        if hash:
            if major != 0 or minor != 0 or micro != 0:
                return "{} # v{}.{}.{}".format(hash, major, minor, micro)
            else:
                return "{}".format(hash)
        else:
            return "v{}.{}.{}".format(major, minor, micro)

    # https://github.com/golang/proposal/blob/master/design/24301-versioned-go.md#update-timing--high-fidelity-builds
    # https://github.com/golang/proposal/blob/master/design/24301-versioned-go.md#compatibility
    def __lt__(self, other):
        return self.tuple() < other.tuple()

PROJECT_MAP = {}
# Create a unique Project for each unique import path, and set version to the
# maximum seen.
def lookup_project(spec):
    import_path, version = parse_spec(spec)
    try:
        project = PROJECT_MAP[import_path]
    except KeyError:
        project = Project(spec)
        PROJECT_MAP[import_path] = project
    print("Project version = ", project.version, "version = ", version)
    if project.version < version:
        project.version = version
    return project

class Project:
    def __init__(self, spec):
        self.import_path, self.version = parse_spec(spec)
        self.deps = set()

    def add_dep(self, dep):
        self.deps.add(dep)

    # Cheesy heuristics to choose an rbm package name given an import path.
    def project_name(self):
        m = re.match(r'^gopkg\.in/([\w_-]+)\.v[0-9]+$', self.import_path)
        if m:
            return "go" + m.group(1)
        m = re.match(r'^github\.com/(?:golang|hpcloud)/([\w_-]+)$', self.import_path)
        if m:
            return "go" + m.group(1)

        try:
            return {
                "golang.org/x/crypto": "goxcrypto",
                "golang.org/x/mod": "goxmod",
                "golang.org/x/net": "goxnet",
                "golang.org/x/sys": "goxsys",
                "golang.org/x/sync": "goxsync",
                "golang.org/x/term": "goxterm",
                "golang.org/x/text": "goxtext",
                "golang.org/x/tools": "goxtools",
                "golang.org/x/xerrors": "goxxerrors",
                "github.com/pkg/errors": "goerrors",
                "google.golang.org/protobuf": "protobuf",
            }[self.import_path]
        except KeyError:
            pass

        if self.import_path.startswith("github.com/pion"):
            return "-".join(self.import_path.split("/")[1:])
        elif self.import_path.startswith("github.com/"):
            return "-".join(self.import_path.split("/")[2:])

        assert False, self.import_path

    def output(self, f):
        print("""\
# vim: filetype=yaml sw=2
version: '[% c("abbrev") %]'
git_url: https://{import_path}
git_hash: {version}
filename: '[% project %]-[% c("version") %]-[% c("var/osname") %]-[% c("var/build_id") %].tar.gz'

build: '[% c("projects/go/var/build_go_lib") %]'

var:
  container:
    use_container: 1
  go_lib: {import_path}\
""".format(import_path=self.import_path, version=self.version), file=f)
        if self.deps:
            print("  go_lib_deps:", file=f)
        for dep in sorted(self.deps, key=lambda x: x.project_name()):
            print("    - {project_name}".format(project_name=dep.project_name()), file=f)
        print("""\

input_files:
  - project: container-image
  - name: go
    project: go\
""", file=f)
        for dep in sorted(self.deps, key=lambda x: x.project_name()):
            print("""\
  - name: {project_name}
    project: {project_name}\
""".format(project_name=dep.project_name()), file=f)

def makedirs(path):
    try:
        os.makedirs(path)
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise

for line in sys.stdin:
    mod_spec, req_spec = line.strip().split()
    mod_project = lookup_project(mod_spec)
    req_project = lookup_project(req_spec)
    mod_project.add_dep(req_project)

for project in PROJECT_MAP.values():
    if project.import_path in TERMINALS:
        continue
    project_path = os.path.join(STAGING_DIR, "projects", project.project_name())
    makedirs(project_path)
    with open(os.path.join(project_path, "config"), "w") as f:
        print(project_path)
        project.output(f)