# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import json
import os
import posixpath
from os import PathLike

# `typing.Literal` not available until Python 3.8;
# `typing_extensions` not generally available here
from typing import Iterable, Set

FIRST_LINE = "// This file was generated by {}. DO NOT EDIT.".format(
    # `posixpath` for forward slashes, for presentation purposes
    posixpath.relpath(__file__, os.getenv("TOPSRCDIR", "/"))
)


def generate_allowed_items(
    which: str,  # should be: Literal["files", "names"],
    paths: Iterable[PathLike],
) -> str:
    def remove_trailing_comment(s: str) -> str:
        return s[0 : s.find("#")]

    def read_items_from_path(path: PathLike) -> Set[str]:
        out = set()
        with open(path) as file:
            for line in file.readlines():
                line = remove_trailing_comment(line).strip()
                if not line:
                    continue  # comment or empty line; discard
                out.add(line)
        return out

    allowed = set().union(*(read_items_from_path(path) for path in paths))
    # BUG: `json.dumps` may not correctly handle use of the quote character in
    #   thread names
    allowed_list_s = ",\n  ".join(json.dumps(elem) for elem in sorted(allowed))

    return f"""\
static const char *allow_thread_{which}[] = {{
  {allowed_list_s}
}};"""


def generate_allows(
    *, allowed_names: Iterable[PathLike], allowed_files: Iterable[PathLike]
) -> str:
    """
    This function reads in the specified sets of files -- ordinarily,
    ["ThreadAllows.txt"] and ["ThreadFileAllows.txt"] -- and generates the text
    of a header file containing two arrays with their contents, for inclusion by
    the thread-name checker.

    The checker will reject the creation of any thread via NS_NewNamedThread
    unless either:
      - the thread's name is a literal string which is found in the set of
        allowed thread names; or
      - the thread's creation occurs within a file which is found in the set of
        unchecked files.

    The latter condition exists mostly for the definition of NS_NewNamedThread,
    but there also exist a few cases where the thread name is dynamically
    computed (and so can't be checked).
    """
    output_string = (
        FIRST_LINE
        + "\n\n"
        + generate_allowed_items("files", allowed_files)
        + "\n\n"
        + generate_allowed_items("names", allowed_names)
        + "\n"
    )
    return output_string


# Entry point used by build/clang-plugin/moz.build (q.v.).
def generate_file(output, allowed_names, allowed_files):
    output.write(
        generate_allows(allowed_names=[allowed_names], allowed_files=[allowed_files])
    )
