#!/usr/bin/env python3

"""Script that makes sure that hardcoded version numbers are consistent.

It looks at files in the Firedrake repository and makes sure that everywhere a
version number is hardcoded that it matches. This allows for single-source-of-truth
of hardcoded values (via CI).

"""

import pathlib
import re


REPO_ROOT = pathlib.Path(__file__).parent.parent


class InvalidConfigurationException(Exception):
    pass


def main():
    check_min_python_version()
    check_petsc_version()
    check_slepc_version()
    check_petsc_version_spec()


def check_min_python_version() -> None:
    min_python_version = check_file_contains_pattern(
        REPO_ROOT / "pyproject.toml",
        "requires-python = \">=(.*)\""
    )
    check_file_contains_pattern(
        REPO_ROOT / "docs" / "source" / "install.rst",
        f"Python \\({min_python_version} or greater\\)"
    )
    check_file_contains_pattern(
        REPO_ROOT / "scripts" / "firedrake-configure",
        f"MINIMUM_PYTHON_VERSION = \"{min_python_version}\""
    )


def check_petsc_version() -> None:
    petsc_version = check_file_contains_pattern(
        REPO_ROOT / "scripts/firedrake-configure",
        "SUPPORTED_PETSC_VERSION = \"v(.*)\"",
    )
    check_file_contains_pattern(
        REPO_ROOT / "pyproject.toml",
        f"petsc4py=={petsc_version}",
        2,
    )


def check_slepc_version() -> None:
    check_file_contains_pattern(
        REPO_ROOT / "pyproject.toml",
        "slepc4py==(.*)",
        3,
    )


def check_petsc_version_spec() -> None:
    petsc_version_spec = check_file_contains_pattern(
        REPO_ROOT / "firedrake/__init__.py",
        "PETSC_SUPPORTED_VERSIONS = \"(.*)\"",
    )
    check_file_contains_pattern(
        REPO_ROOT / "setup.py",
        f"petsctools.init\\(version_spec=\"{petsc_version_spec}\"\\)",
    )


def check_file_contains_pattern(
    filename: pathlib.Path | str,
    pattern: str,
    num_expected_matches: int = 1,
) -> str:
    """Check that the regex pattern exists in the file.

    Parameters
    ----------
    filename :
        The filename.
    pattern :
        The regular expression pattern to look for.
    num_expected_matches :
        The number of expected matches.

    Returns
    -------
    str :
        The matched value. This follows the semantics of `re.findall`.

    """
    with open(filename) as f:
        text = f.read()
    matches = re.findall(pattern, text)
    if len(matches) != num_expected_matches:
        raise InvalidConfigurationException(
            f"Expected to find {num_expected_matches} matches for '{pattern}' in "
            f"{filename} but found {len(matches)}"
        )

    # all the matches should be the same so just return one of them
    match, = set(matches)
    return match


if __name__ == "__main__":
    main()
