"""Migrates C++ code to Carbon."""

__copyright__ = """
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""

import argparse
import glob
import os
import subprocess
import sys
from typing import Optional

_CPP_REFACTORING = "./cpp_refactoring/cpp_refactoring"
_H_EXTS = {".h", ".hpp"}
_CPP_EXTS = {".c", ".cc", ".cpp", ".cxx"}


class _Workflow:
    _parsed_args: argparse.Namespace
    _data_dir: str
    _cpp_files: Optional[list[str]]

    def __init__(self) -> None:
        """Parses command-line arguments and flags."""
        parser = argparse.ArgumentParser(description=__doc__)
        parser.add_argument(
            "dir",
            type=str,
            help="A directory containing C++ files to migrate to Carbon.",
        )
        parsed_args = parser.parse_args()
        self._parsed_args = parsed_args

        self._data_dir = os.path.dirname(sys.argv[0])

        # Validate arguments.
        if not os.path.isdir(parsed_args.dir):
            sys.exit("%r must point to a directory." % parsed_args.dir)

    def run(self) -> None:
        """Runs the migration workflow."""
        try:
            self._gather_files()
            self._clang_tidy()
            self._cpp_refactoring()
            self._rename_files()
            self._print_header("Done!")
        except subprocess.CalledProcessError as e:
            # Discard the stack for subprocess errors.
            sys.exit(str(e))

    def _data_file(self, relative_path: str) -> str:
        """Returns the path to a data file."""
        return os.path.join(self._data_dir, relative_path)

    @staticmethod
    def _print_header(header: str) -> None:
        print("*" * 79)
        print("* %-75s *" % header)
        print("*" * 79)

    def _gather_files(self) -> None:
        """Returns the list of C++ files to convert."""
        self._print_header("Gathering C++ files...")
        all_files = glob.glob(
            os.path.join(self._parsed_args.dir, "**/*.*"), recursive=True
        )
        exts = _CPP_EXTS.union(_H_EXTS)
        cpp_files = [f for f in all_files if os.path.splitext(f)[1] in exts]
        if not cpp_files:
            sys.exit(
                "%r doesn't contain any C++ files to convert."
                % self._parsed_args.dir
            )
        self._cpp_files = sorted(cpp_files)
        print("%d files found." % len(self._cpp_files))

    def _clang_tidy(self) -> None:
        """Runs clang-tidy to fix C++ files in a directory."""
        self._print_header("Running clang-tidy...")
        with open(self._data_file("clang_tidy.yaml")) as f:
            config = f.read()
        subprocess.run(
            ["run-clang-tidy.py", "-fix", "-config", config],
            check=True,
        )

    def _cpp_refactoring(self) -> None:
        """Runs cpp_refactoring to migrate C++ files towards Carbon syntax."""
        self._print_header("Running cpp_refactoring...")
        cpp_refactoring = self._data_file(_CPP_REFACTORING)
        assert self._cpp_files is not None
        subprocess.run([cpp_refactoring] + self._cpp_files, check=True)

    def _rename_files(self) -> None:
        """Renames C++ files to the destination Carbon filenames."""
        api_renames = 0
        impl_renames = 0
        assert self._cpp_files is not None
        for f in self._cpp_files:
            parts = os.path.splitext(f)
            if parts[1] in _H_EXTS:
                os.rename(f, parts[0] + ".carbon")
                api_renames += 1
            else:
                os.rename(f, parts[0] + ".impl.carbon")
                impl_renames += 1
        print(
            "Renaming resulted in %d API files and %d impl files."
            % (api_renames, impl_renames)
        )


if __name__ == "__main__":
    _Workflow().run()
