"""This task makes include guards follow the style stipulated by the Google
style guide.
"""

import re
from enum import Enum
from pathlib import Path

from wpiformat.config import Config
from wpiformat.task import PipelineTask


class State(Enum):
    FINDING_IFNDEF = 1
    FINDING_ENDIF = 2
    DONE = 3


class IncludeGuard(PipelineTask):
    @staticmethod
    def should_process_file(config_file: Config, filename: Path):
        return config_file.is_header_file(filename)

    def run_pipeline(
        self, config_file: Config, filename: Path, lines: str
    ) -> tuple[str, bool]:
        linesep = super().get_linesep(lines)
        lines_list = lines.split(linesep)
        output_list = lines_list

        state = State.FINDING_IFNDEF
        ifndef_regex = re.compile(r"#ifndef \w+", re.ASCII)
        define_regex = re.compile(r"#define \w+", re.ASCII)

        if_preproc_count = 0
        for i in range(len(lines_list)):
            if state == State.FINDING_IFNDEF:
                if lines_list[i].lstrip().startswith("#ifndef ") and lines_list[
                    i + 1
                ].lstrip().startswith("#define "):
                    state = State.FINDING_ENDIF

                    guard = self.make_include_guard(config_file, filename)
                    output_list[i] = ifndef_regex.sub("#ifndef " + guard, lines_list[i])
                    output_list[i + 1] = define_regex.sub(
                        "#define " + guard, lines_list[i + 1]
                    )
                    if_preproc_count += 1
                elif lines_list[i].lstrip().startswith("#pragma once"):
                    state = State.DONE
            elif state == State.FINDING_ENDIF:
                if "#if" in lines_list[i]:
                    if_preproc_count += 1
                elif "#endif" in lines_list[i]:
                    if_preproc_count -= 1

                if if_preproc_count == 0:
                    state = State.DONE
                    output_list[i] = "#endif  // " + guard
                else:
                    output_list[i] = lines_list[i]
            else:
                output_list[i] = lines_list[i]

        # If include guard not found
        if state == State.FINDING_IFNDEF:
            print(f"error: {filename}: doesn't contain include guard or '#pragma once'")
            return lines, False

        output = linesep.join(output_list).rstrip() + linesep
        return output, True

    def make_include_guard(self, config_file: Config, filename: Path) -> str:
        """Returns properly formatted include guard based on repository root and
        filename.

        Keyword arguments:
        config_file -- Config object
        filename -- filename
        """
        repo_root = super().get_repo_root()

        if repo_root_override := config_file.group("repoRootNameOverride"):
            guard_path = repo_root_override[0] + "/"
        else:
            guard_path = repo_root.name + "/"

        # Use include root that results in shortest include guard
        guard_root = filename.relative_to(repo_root).as_posix()
        for include_root in sorted(
            config_file.group("includeGuardRoots"), key=len, reverse=True
        ):
            if guard_root.startswith(include_root):
                guard_path += guard_root[len(include_root) :]
                break

        return re.sub(r"[^a-zA-Z0-9]", "_", guard_path).upper().lstrip("_") + "_"
