#!/usr/bin/env python3

import ast
import sys
import os
import re
import subprocess
from typing import List, Optional, Pattern

RELEASE_PATTERN = re.compile(r"release_[0-9]+(_docs)*")
TRAINER_INIT_FILE = "ml-agents/mlagents/trainers/__init__.py"

# Filename -> regex list to allow specific lines.
# To allow everything in the file, use None for the value
ALLOW_LIST = {
    # Previous release table
    "README.md": re.compile(r"\*\*Release [0-9]+\*\*"),
    "docs/Versioning.md": None,
    "com.unity.ml-agents/CHANGELOG.md": None,
    "utils/make_readme_table.py": None,
    "utils/validate_doc_versions.py": None,
}


def test_pattern():
    # Just some sanity check that the regex works as expected.
    assert RELEASE_PATTERN.search(
        "https://github.com/Unity-Technologies/ml-agents/blob/release_3_docs/Food.md"
    )
    assert RELEASE_PATTERN.search(
        "https://github.com/Unity-Technologies/ml-agents/blob/release_3/Foo.md"
    )
    assert RELEASE_PATTERN.search(
        "git clone --branch release_3 https://github.com/Unity-Technologies/ml-agents.git"
    )
    assert RELEASE_PATTERN.search(
        "https://github.com/Unity-Technologies/ml-agents/blob/release_123_docs/Foo.md"
    )
    assert RELEASE_PATTERN.search(
        "https://github.com/Unity-Technologies/ml-agents/blob/release_123/Foo.md"
    )
    assert not RELEASE_PATTERN.search(
        "https://github.com/Unity-Technologies/ml-agents/blob/latest_release/docs/Foo.md"
    )
    print("tests OK!")


def git_ls_files() -> List[str]:
    """
    Run "git ls-files" and return a list with one entry per line.
    This returns the list of all files tracked by git.
    """
    return subprocess.check_output(["git", "ls-files"], universal_newlines=True).split(
        "\n"
    )


def get_release_tag() -> Optional[str]:
    """
    Returns the release tag for the mlagents python package.
    This will be None on the master branch.
    :return:
    """
    with open(TRAINER_INIT_FILE) as f:
        for line in f:
            if "__release_tag__" in line:
                lhs, equals_string, rhs = line.strip().partition(" = ")
                # Evaluate the right hand side of the expression
                return ast.literal_eval(rhs)
    # If we couldn't find the release tag, raise an exception
    # (since we can't return None here)
    raise RuntimeError("Can't determine release tag")


def check_file(filename: str, global_allow_pattern: Pattern) -> List[str]:
    """
    Validate a single file and return any offending lines.
    """
    bad_lines = []
    with open(filename, "r") as f:
        for line in f:
            if not RELEASE_PATTERN.search(line):
                continue

            if global_allow_pattern.search(line):
                continue

            if filename in ALLOW_LIST:
                if ALLOW_LIST[filename] is None or ALLOW_LIST[filename].search(line):
                    continue

            bad_lines.append(f"{filename}: {line.strip()}")
    return bad_lines


def check_all_files(allow_pattern: Pattern) -> List[str]:
    """
    Validate all files tracked by git.
    :param allow_pattern:
    """
    bad_lines = []
    file_types = {".py", ".md", ".cs"}
    for file_name in git_ls_files():
        if "localized" in file_name or os.path.splitext(file_name)[1] not in file_types:
            continue
        bad_lines += check_file(file_name, allow_pattern)
    return bad_lines


def main():
    release_tag = get_release_tag()
    if not release_tag:
        print("Release tag is None, exiting")
        sys.exit(0)

    print(f"Release tag: {release_tag}")
    allow_pattern = re.compile(f"{release_tag}(_docs)*")
    bad_lines = check_all_files(allow_pattern)
    if bad_lines:
        print(
            f"Found lines referring to previous release. Either update the files, or add an exclusion to {__file__}"
        )
        for line in bad_lines:
            print(line)

    sys.exit(1 if bad_lines else 0)


if __name__ == "__main__":
    if "--test" in sys.argv:
        test_pattern()
    main()
