#!/usr/bin/env python3

import argparse
import toml.decoder
import sys
import os.path
import os
import list_crates
from subprocess import run

TOPDIR = os.path.split(os.path.dirname(sys.argv[0]))[0]
os.chdir(TOPDIR)

# some tests don't compile on every combination 😐
# also test is way slower than check
KEYWORD = "check"

supplementary_targets = dict()


def combination(*args):
    res = None
    for featureset in args:
        powerset = [[]]
        for feature in featureset:
            powerset.extend([combination + [feature] for combination in powerset])
        # remove empty set
        powerset.pop(0)
        if res is None:
            res = powerset
        else:
            new_res = []
            for prev_feat in res:
                for new_feat in powerset:
                    new_res.append(prev_feat + new_feat)
            res = new_res
    return res


supplementary_targets["tor-rtcompat"] = combination(
    ["async-std", "tokio", "native-tls", "rustls"]
)
supplementary_targets["arti-client"] = combination(
    ["async-std", "tokio", "native-tls", "rustls"]
)
supplementary_targets["arti"] = combination(
    ["async-std", "tokio"], ["native-tls", "rustls"]
)


def take(dic, key):
    if key in dic:
        res = dic.get(key)
        del dic[key]
        return res
    return None


def test_crate_config(crate, features, allow_empty=False):
    if features is None:
        return
    if len(features) == 0 and not allow_empty:
        return
    features = ",".join(features)
    args = [
        "cargo",
        KEYWORD,
        "-p",
        crate,
        "--no-default-features",
        "--features",
        features,
    ]
    print("running:", " ".join(args), file=sys.stderr)
    p = run(args)
    if p.returncode != 0:
        raise Exception(
            "Failed to test '" + crate + "' with features '" + features + "'"
        )


def test_crate(crate):
    if crate.name in ["fs-mistrust", "tor-config"]:
        # these tests do not pass as of now. Skipping them.
        return

    toml_path = os.path.join(crate.subdir, "Cargo.toml")
    t = toml.decoder.load(toml_path)
    features = t.get("features") or {}

    # remove testing features, it makes little sens to test them
    take(features, "testing")

    default = sorted(take(features, "default") or [])
    full = sorted(take(features, "full") or [])
    all_features = sorted([feat for feat in features.keys()])

    # no features; don't test if it would already be tested by normal tests
    if len(features) != 0:
        # arti does not work: it requires an executor
        if crate.name not in ["arti"]:
            test_crate_config(crate.name, [], True)
    # default
    test_crate_config(crate.name, default)
    # full
    test_crate_config(crate.name, full)
    # all
    test_crate_config(crate.name, all_features)

    for combination in supplementary_targets.get(crate.name, []):
        test_crate_config(crate.name, combination)
    # TODO test random combination?


# https://stackoverflow.com/questions/24483182/python-split-list-into-n-chunks
def chunks(lst, n):
    """
    Yield n number of chunks from lst.

    This is a striped chunk, meaning that rather than making contigious chunks, each chunk is made
    up of elements that are n spaces apart in the input list. This has the advantage of making
    sure that the lists are as close to evenly sized as possible, even if the input list length is
    not divisible by n.
    """
    for i in range(0, n):
        yield lst[i::n]


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--parallel",
        type=str,
        default="1,0",
        help="Split the checking into multiple chunks and run only a single one of the chunks. "
        "Takes two comma-separated integers, the first is the number of chunks, "
        "the second is which chunk to run (zero-indexed)."
        "This assumes that the set of crates that exist is the same between all invocations.",
    )
    args = parser.parse_args()
    parallel_chunks, parallel_n = map(int, args.parallel.split(","))
    assert parallel_n < parallel_chunks

    crates = sorted(list_crates.list_crates(), key=lambda crate: crate.name)
    for crate in list(chunks(crates, parallel_chunks))[parallel_n]:
        test_crate(crate)


if __name__ == "__main__":
    main()
