#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import logging
import os
import re
import subprocess
import sys
import tempfile
import typing

import githelper


# We recommend a maximum line length of 80, but do allow up to 100 characters
# if deemed necessary by the developer. Lines that exceed that limit will
# be wrapped after 80 characters automatically.
LINE_LENGTH_THRESHOLD = 100
BREAK_BEFORE = 80


def get_clang_format_config_with_columnlimit(rootdir, limit):
    """Create a temporary config with ColumnLimit set to 80."""
    cpp_file = os.path.join(rootdir, "src/main.cpp")
    proc = subprocess.run(
        ["clang-format", "--dump-config", cpp_file],
        capture_output=True,
        text=True,
    )
    proc.check_returncode()

    return re.sub(
        r"(ColumnLimit:\s*)\d+", r"\g<1>{}".format(limit), proc.stdout
    )


def run_clang_format_on_lines(rootdir, file_to_format, stylepath=None):
    logger = logging.getLogger(__name__)

    line_arguments = [
        "--lines={}:{}".format(start, end)
        for start, end in file_to_format.lines
    ]
    assert line_arguments

    logger.info(
        "Reformatting %s [%s]...",
        file_to_format.filename,
        ", ".join("{}-{}".format(*x) for x in file_to_format.lines),
    )

    filename = os.path.join(rootdir, file_to_format.filename)
    cmd = [
        "clang-format",
        "--style=file",
        # The --assume-filename argument sets the path for the .clang-format
        # config file implicitly by assuming a different location of the file
        # to format
        "--assume-filename={}".format(
            os.path.join(
                stylepath if stylepath else rootdir, file_to_format.filename
            )
        ),
        *line_arguments,
    ]

    with open(filename) as fp:
        logger.debug("Executing: %r", cmd)
        proc = subprocess.run(cmd, stdin=fp, capture_output=True, text=True)
    try:
        proc.check_returncode()
    except subprocess.CalledProcessError:
        logger.error(
            "Error while executing command %s: %s",
            cmd,
            proc.stderr,
        )
        raise

    if proc.stderr:
        logger.error(proc.stderr)
    with open(filename, mode="w") as fp:
        fp.write(proc.stdout)


def main(argv: typing.Optional[typing.List[str]] = None) -> int:
    logging.basicConfig(
        format="[%(levelname)s] %(message)s", level=logging.INFO
    )

    logger = logging.getLogger(__name__)

    parser = argparse.ArgumentParser()
    parser.add_argument("--from-ref", help="use changes changes since commit")
    parser.add_argument("--to-ref", help="use changes until commit")
    parser.add_argument("files", nargs="*", help="only check these files")
    args = parser.parse_args(argv)

    if not args.from_ref:
        args.from_ref = os.getenv("PRE_COMMIT_FROM_REF") or os.getenv(
            "PRE_COMMIT_SOURCE"
        )

    if not args.to_ref:
        args.to_ref = os.getenv("PRE_COMMIT_TO_REF") or os.getenv(
            "PRE_COMMIT_ORIGIN"
        )

    # Filter filenames
    rootdir = githelper.get_toplevel_path()

    # First pass: Format added/changed lines using clang-format
    logger.info("First pass: Reformatting added/changed lines...")
    files_with_added_lines = githelper.get_changed_lines_grouped(
        from_ref=args.from_ref,
        to_ref=args.to_ref,
        filter_lines=lambda line: line.added,
        include_files=args.files,
    )
    for changed_file in files_with_added_lines:
        run_clang_format_on_lines(rootdir, changed_file)

    # Second pass: Wrap long added/changed lines in C++ using clang-format
    logger.info("Second pass: Breaking long added/changed lines...")
    files_with_long_added_lines = githelper.get_changed_lines_grouped(
        from_ref=args.from_ref,
        to_ref=args.to_ref,
        filter_lines=lambda line: line.added
        and len(line.text) > LINE_LENGTH_THRESHOLD,
        include_files=[
            file for file in args.files if re.search(r"\.(?:cpp|h)$", file)
        ],
    )
    config = get_clang_format_config_with_columnlimit(rootdir, BREAK_BEFORE)
    with tempfile.TemporaryDirectory(prefix="clang-format") as tempdir:
        # Create temporary config with ColumnLimit enabled
        configfile = os.path.join(tempdir, ".clang-format")
        with open(configfile, mode="w") as configfp:
            configfp.write(config)

        for changed_file in files_with_long_added_lines:
            run_clang_format_on_lines(rootdir, changed_file, stylepath=tempdir)
    return 0


if __name__ == "__main__":
    sys.exit(main())
