#! /usr/bin/env python3

"""Write autoconf and CMake configuration for our C++ feature checks.

Produces configuration files for inclusion in the autoconf script and the
CMake configuration, respectively.  The config will ask the build systems to
try and compile each of the snippets of check code in `config-tests/`, and
where that succeeds, define the C++ macro of the same name as the base name
of the check snippet.
"""

from argparse import (
    ArgumentParser,
    Namespace,
)
import os.path
from pathlib import Path
from textwrap import dedent


# Name of the autoconf configuration script that we produce.  (Lives in the
# main source directory.)
AUTOCONF_CONFIG = 'pqxx_cxx_feature_checks.ac'

# Name of the CMake config that we produce.  (Lives in the cmake directory.)
CMAKE_CONFIG = 'pqxx_cxx_feature_checks.cmake'


def parse_args() -> Namespace:
    """Parse command line."""
    parser = ArgumentParser(__doc__)
    parser.add_argument(
        '--source', '-s', metavar='DIR', type=Path, default=Path(),
        help="Location of the source tree.  Defaults to current directory.")
    return parser.parse_args()


def list_snippets(source: Path) -> list[str]:
    """Return list of C++ snippets."""
    return sorted(map(str, (source / 'config-tests').glob('*.cxx')))


# Comment header for either autoconf or CMake config we generate.
HEAD = dedent("""\
    # Configuration for feature checks. Generated by %(script)s.
    """)


def compose_header() -> str:
    """Produce a config header."""
    return HEAD % {'script': os.path.basename(__file__)}


FOOT = "# End of config.\n"


# Template for an autoconf feature check.
AUTOCONF_TEMPLATE = dedent("""\
    AC_MSG_CHECKING([%(macro)s])
    %(macro)s=yes
    AC_COMPILE_IFELSE(
        [read_test(%(macro)s.cxx)],
        AC_DEFINE(
            [%(macro)s],
            1,
            [Define if this feature is available.]),
        %(macro)s=no)
    AC_MSG_RESULT($%(macro)s)
""")


def generate_autoconf(source: Path, snippets: list[str]) -> None:
    """Generate autoconf configuration."""
    dest = source / AUTOCONF_CONFIG
    with dest.open('w', encoding='ascii') as stream:
        stream.write(compose_header())
        for snippet in snippets:
            stream.write(AUTOCONF_TEMPLATE % {'macro': Path(snippet).stem})
        stream.write(FOOT)


# Template for a CMake feature check.
#
# Makes use of a custom function, try_compile.
CMAKE_TEMPLATE = dedent("""\
    try_compile(
        %(macro)s ${PROJECT_BINARY_DIR}
        SOURCES ${PROJECT_SOURCE_DIR}/config-tests/%(macro)s.cxx
    )
""")


def generate_cmake(source: Path, snippets: list[str]) -> None:
    """Generate CMake configuration."""
    dest = source / 'cmake' / CMAKE_CONFIG
    with dest.open('w', encoding='ascii') as stream:
        stream.write(compose_header())
        for snippet in snippets:
            stream.write(CMAKE_TEMPLATE % {'macro': Path(snippet).stem})
        stream.write(FOOT)


def main(args: Namespace) -> None:
    """Main entry point."""
    snippets = list_snippets(args.source)
    generate_autoconf(args.source, snippets)
    generate_cmake(args.source, snippets)


if __name__ == '__main__':
    main(parse_args())
