#!/usr/bin/env python3

from collections import defaultdict
from pathlib import Path
import networkx as nx
import sys
import urllib.request
import gzip
import shutil
import subprocess


def getmissing(qtyperpart, tokeep, relationships, relationships2, top):
    topmissing = list()
    for (
        k,
        v,
    ) in sorted(
        qtyperpart.items(), key=lambda item: item[1], reverse=True
    )[:top]:
        if not any([(Path(p) / f"{k}.dat" in tokeep) for p in ["parts", "p"]]):
            if k not in relationships and k not in relationships2:
                topmissing.append(k)
            else:
                found = False
                for s in relationships[k] | relationships2[k]:
                    if any([(Path(p) / f"{s}.dat" in tokeep) for p in ["parts", "p"]]):
                        found = True
                        break
                if not found:
                    topmissing.append(k)
    return topmissing


def main():
    for fname in ["inventory_parts.csv", "part_relationships.csv"]:
        if Path(fname).is_file():
            continue
        with urllib.request.urlopen(
            f"https://cdn.rebrickable.com/media/downloads/{fname}.gz"
        ) as response:
            print(f"downloading {fname}...", file=sys.stderr)
            with gzip.GzipFile(fileobj=response) as gzf, open(fname, "wb") as f:
                shutil.copyfileobj(gzf, f)

    qtyperpart = defaultdict(int)
    with open("inventory_parts.csv") as f:
        for i, line in enumerate(f):
            if i == 0:
                continue
            inventory_id, part_num, _, qty, _, _ = line.split(",")
            qtyperpart[part_num] += int(qty)
    relationships = defaultdict(set)
    relationships2 = defaultdict(set)
    with open("part_relationships.csv") as f:
        for line in f:
            rel, child, parent = line.strip().split(",")
            relationships[parent].add(child)
            relationships2[child].add(parent)

    num2name = dict()
    curdir = Path(".")
    tokeep = set()
    DG = nx.DiGraph()
    for glob in [
        "./models/*.dat",
        "./p/*.dat",
        "./p/8/*.dat",
        "./p/48/*.dat",
        "./parts/*.dat",
        "./parts/s/*.dat",
    ]:
        for p in curdir.glob(glob):
            if not p.is_file():
                continue
            ccby4 = False
            with p.open() as f:
                for i, line in enumerate(f):
                    if i == 0:
                        assert line.startswith("0 ")
                        line = line.removeprefix("0 ")
                        num2name[p] = line
                        continue
                    if i == 1:
                        assert line.startswith("0 Name: "), line
                        line = line.removeprefix("0 Name: ").strip()
                        line = line.replace("\\", "/")
                        assert p in [Path(d) / line for d in ["parts", "p", "models"]]
                        continue
                    if (
                        line
                        == "0 !LICENSE Licensed under CC BY 4.0 : see CAreadme.txt\n"
                    ):
                        ccby4 = True
                        continue
                    if line.startswith("1 "):
                        _, _, _, _, _, _, _, _, _, _, _, _, _, _, fname = line.split()
                        fname = Path(fname.replace("\\", "/"))
                        for e in (
                            fname,
                            (p.parent / fname),
                            (Path("p") / fname),
                            (Path("parts") / fname),
                        ):
                            if e.is_file():
                                fname = e
                                break
                        if not fname.is_file():
                            print(f"W: {p} references missing {fname}", file=sys.stderr)
                            continue
                        DG.add_edge(p, fname)
            if ccby4:
                tokeep.add(p)
    top = 100

    topmissing = getmissing(qtyperpart, tokeep, relationships, relationships2, top)
    print(
        f"top direct missing ({len(topmissing)}/{top}): {', '.join(topmissing)}",
        file=sys.stderr,
    )

    # remove all parts that depend on non-free parts (directly or indirectly)
    toremove = set()
    ancestors_by_node = dict()
    for node in DG:
        pred = nx.ancestors(DG, node)
        ancestors_by_node[node] = len(pred)
        if node not in tokeep:
            toremove |= set(pred)
    # print the top removal reasons (parts with most ancestors)
    removalreasons = list()
    for k, v in sorted(
        ancestors_by_node.items(), key=lambda item: item[1], reverse=True
    )[:top]:
        if k not in tokeep:
            removalreasons.append((k, v))
    print(f"top removal reasons:", file=sys.stderr)
    for k, v in removalreasons:
        print(f"  {str(k):17} {v}", file=sys.stderr)
    tokeep -= toremove
    print(
        f"remaining {len(tokeep)} out of {len(num2name)} ({len(tokeep)/len(num2name)*100:.2f}%)",
        file=sys.stderr,
    )
    topmissing = getmissing(qtyperpart, tokeep, relationships, relationships2, top)
    print(
        f"top missing ({len(topmissing)}/{top}): {', '.join(topmissing)}",
        file=sys.stderr,
    )

    uversion = sys.argv[1]
    if uversion == "--dry-run":
        sys.exit(0)
    verbatimfiles = [
        "p",
        "parts",
        "models",
        "include",
        "CAlicense4.txt",
        "CAlicense.txt",
        "CAreadme.txt",
        "LDCfgalt.ldr",
        "LDConfig.ldr",
        "LDConfig_TLG.ldr",
        "mklist.c",
        "Readme.txt",
    ]
    for l in [curdir.glob("models/*.txt"), curdir.glob("include/*.h"), tokeep]:
        verbatimfiles.extend([str(p) for p in l])
    subprocess.run(
        [
            "tar",
            "--verbatim-files-from",
            "--null",
            "--no-recursion",
            "--files-from=-",
            "--transform",
            f"s#^#./ldraw-parts-free-{uversion}/#",
            "--xz",
            "--create",
            "--file",
            f"../ldraw-parts-free_{uversion}.orig.tar.xz",
        ],
        input="\x00".join(sorted(verbatimfiles)),
        text=True,
        check=True,
        env={"XZ_OPT": "-v9e"},
    )


if __name__ == "__main__":
    main()
