#!/usr/bin/env python3
# pylint: disable=invalid-name,missing-module-docstring,missing-function-docstring
#
# Copyright 2021 Richard Hughes <richard@hughsie.com>
# Copyright 2021 Mario Limonciello <superm1@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-or-later

import glob
import sys
import os
from typing import List


def __get_includes(fn: str) -> List[str]:
    includes: List[str] = []
    with open(fn) as f:
        for line in f.read().split("\n"):
            if line.find("#include") == -1:
                continue
            if line.find("waive-pre-commit") > 0:
                continue
            for char in ["<", ">", '"']:
                line = line.replace(char, "")
            for char in ["\t"]:
                line = line.replace(char, " ")
            includes.append(line.split(" ")[-1])
    return sorted(includes)


def test_files() -> int:
    rc: int = 0

    lib_headers1 = glob.glob("libfwupd/*.h")
    lib_headers1.remove("libfwupd/fwupd.h")

    lib_headers2 = glob.glob("libfwupdplugin/*.h")
    lib_headers2.remove("libfwupdplugin/fwupdplugin.h")

    toplevel_headers = ["libfwupd/fwupd.h", "libfwupdplugin/fwupdplugin.h"]
    toplevel_headers_nopath = [os.path.basename(fn) for fn in toplevel_headers]
    lib_headers = lib_headers1 + lib_headers2
    lib_headers_nopath = [os.path.basename(fn) for fn in lib_headers]

    # test all C and H files
    for fn in (
        glob.glob("libfwupd/*.[c|h]")
        + glob.glob("libfwupdplugin/*.[c|h]")
        + glob.glob("plugins/*/*.[c|h]")
        + glob.glob("src/*.[c|h]")
    ):
        # we do not care
        if fn in [
            "libfwupd/fwupd-context-test.c",
            "libfwupd/fwupd-thread-test.c",
            "libfwupdplugin/fu-fuzzer-main.c",
        ]:
            continue

        includes = __get_includes(fn)
        if (
            fn.startswith("plugins")
            and not fn.endswith("self-test.c")
            and not fn.endswith("tool.c")
        ):
            for include in includes:
                # check for using private header use in plugins
                if include.endswith("private.h"):
                    print(f"{fn} uses private header {include}")
                    rc = 1
                    continue

                # check for referring to anything but top level header
                if include in lib_headers or include in lib_headers_nopath:
                    print(
                        f"{fn} contains {include}, should only use top level includes"
                    )
                    rc = 1

        # check for double top level headers
        for toplevel_header in toplevel_headers:
            toplevel_fn = os.path.basename(toplevel_header)
            toplevel_includes = __get_includes(toplevel_header)
            toplevel_includes_nopath = [
                os.path.basename(fn) for fn in toplevel_includes
            ]

            # we do not need both toplevel headers
            if set(toplevel_headers_nopath).issubset(set(includes)):
                print(f"{fn} contains both {', '.join(toplevel_headers_nopath)}")

            # toplevel not listed
            if toplevel_fn not in includes:
                continue

            # includes toplevel and *also* something listed in the toplevel
            for include in includes:
                if include in toplevel_includes or include in toplevel_includes_nopath:
                    print(f"{fn} contains {toplevel_fn} but also includes {include}")
                    rc = 1

        # check for missing config.h
        if fn.endswith(".c") and "config.h" not in includes:
            print(f"{fn} does not include config.h")
            rc = 1

        # check for headers including themselves
        if fn.endswith(".h") and os.path.basename(fn) in includes:
            print(f"{fn} includes itself")
            rc = 1

        # check for duplicate includes
        if sorted(set(includes)) != includes:
            print(f"{fn} contains duplicate includes")
            rc = 1

        # check for one header implying the other
        implied_headers = {
            "fu-common.h": ["xmlb.h"],
            "fwupdplugin.h": [
                "gio/gio.h",
                "glib.h",
                "glib-object.h",
                "xmlb.h",
                "fwupd.h",
            ]
            + lib_headers1,
            "gio/gio.h": ["glib.h", "glib-object.h"],
            "glib-object.h": ["glib.h"],
            "xmlb.h": ["gio/gio.h"],
        }
        for key, values in implied_headers.items():
            for value in values:
                if key in includes and value in includes:
                    print(f"{fn} contains {value} which is implied by {key}")
                    rc = 1

    return rc


if __name__ == "__main__":
    # all done!
    sys.exit(test_files())
