#!/usr/bin/env python
# -*- coding: utf8 -*-
"""
This script check that only whitelisted headers are included (transitivly) by
including chemfiles.h or chemfiles.hpp.
"""
from __future__ import print_function
import os
import sys
import re
import codecs

ROOT = os.path.join(os.path.dirname(__file__), "..", "..")
GENERATED_HEADERS = ["chemfiles/config.h", "chemfiles/exports.h"]
ERRORS = 0

WHITELIST = [
    # standard C99 headers
    "stdbool.h",
    "stdint.h",
    # standard C++11 headers
    "iterator",
    "functional",
    "cstdint",
    "array",
    "utility",
    "cassert",
    "string",
    "memory",
    "exception",
    "limits",
    "algorithm",
    "stdexcept",
    "vector",
    "cmath",
    "type_traits",
    "unordered_map",
    "cfloat",
    "new",
    "cstddef",
    "map",
    # external headers
    "chemfiles/external/span.hpp",
    "chemfiles/external/optional.hpp",
    # chemfiles helper headers
    "chemfiles/exports.h",
    "chemfiles/config.h",
    "chemfiles/sorted_set.hpp",
    "chemfiles/unreachable.hpp",
    # chemfiles main headers
    "chemfiles/misc.hpp",
    "chemfiles/types.hpp",
    "chemfiles/Atom.hpp",
    "chemfiles/Frame.hpp",
    "chemfiles/Error.hpp",
    "chemfiles/Residue.hpp",
    "chemfiles/Property.hpp",
    "chemfiles/Topology.hpp",
    "chemfiles/UnitCell.hpp",
    "chemfiles/Trajectory.hpp",
    "chemfiles/Selection.hpp",
    "chemfiles/Connectivity.hpp",
    "chemfiles/FormatMetadata.hpp",
    # chemfiles capi headers
    "chemfiles/capi/atom.h",
    "chemfiles/capi/selection.h",
    "chemfiles/capi/trajectory.h",
    "chemfiles/capi/residue.h",
    "chemfiles/capi/property.h",
    "chemfiles/capi/cell.h",
    "chemfiles/capi/frame.h",
    "chemfiles/capi/types.h",
    "chemfiles/capi/topology.h",
    "chemfiles/capi/misc.h",
]


def error(message):
    global ERRORS
    ERRORS += 1
    print(message)


def included_headers(path):
    includes = set()
    with codecs.open(path, encoding="utf8") as fd:
        for line in fd:
            if "#include" in line:
                matched = re.match('#include\\s*["<](.*)[">]', line)
                if not matched:
                    error("bad include in {}: {}".format(path, line))
                header = matched.groups()[0]
                includes.add(header)
                if header.startswith("chemfiles"):
                    if header not in GENERATED_HEADERS:
                        path = os.path.join(ROOT, "include", header)
                        includes.update(included_headers(path))
    return includes


def check_allowded(headers):
    for header in headers:
        if header not in WHITELIST:
            error("private header {} is publicly reachable".format(header))


if __name__ == "__main__":
    headers = included_headers(os.path.join(ROOT, "include", "chemfiles.h"))
    check_allowded(headers)

    headers = included_headers(os.path.join(ROOT, "include", "chemfiles.hpp"))
    check_allowded(headers)

    if ERRORS != 0:
        sys.exit(1)
