#!/usr/bin/env python3
#
# Copyright (c) 2018-2019 Collabora, Ltd.
#
# SPDX-License-Identifier: Apache-2.0
#
# Author(s):    Ryan Pavlik <ryan.pavlik@collabora.com>
#
# Purpose:      This file performs some basic checks of the custom macros
#               used in the AsciiDoctor source for the spec, especially
#               related to the validity of the entities linked-to.

from pathlib import Path

from reg import Registry
from spec_tools.entity_db import EntityDatabase
from spec_tools.macro_checker import MacroChecker
from spec_tools.macro_checker_file import BlockType, MacroCheckerFile
from spec_tools.main import checkerMain
from spec_tools.shared import MessageId

###
# "Configuration" constants

EXTRA_DEFINES = ('XRAPI_ATTR', 'XRAPI_CALL', 'XRAPI_PTR', 'XR_NO_STDINT_H')

# These are marked with the code: macro
SYSTEM_TYPES = set(('void', 'char', 'float', 'size_t', 'uintptr_t',
                    'int8_t', 'uint8_t',
                    'int32_t', 'uint32_t',
                    'int64_t', 'uint64_t'))

ROOT = Path(__file__).resolve().parent.parent.parent
DEFAULT_DISABLED_MESSAGES = set((MessageId.REFPAGE_MISSING,))

CWD = Path('.').resolve()


class XREntityDatabase(EntityDatabase):
    """OpenXR-specific subclass of EntityDatabase."""

    def makeRegistry(self):
        root = Path(__file__).resolve().parent.parent.parent
        registryFile = str(root / 'specification/registry/xr.xml')
        registry = Registry()
        registry.loadFile(registryFile)
        return registry

    def getNamePrefix(self):
        return "xr"

    def getPlatformRequires(self):
        return "openxr_platform_defines"

    def getSystemTypes(self):
        return SYSTEM_TYPES

    def populateMacros(self):
        # TODO: Where should flags actually go?
        # #Not mentioned in the style guide.
        # TODO: What about flag wildcards? There are a few such uses...

        self.addMacro('basetype', ('basetypes',), link=True)

    def populateEntities(self):
        # These are not mentioned in the XML
        for name in EXTRA_DEFINES:
            self.addEntity(name, 'dlink', category='configdefines',
                           generates=False)

    def handleType(self, name, info, requires):
        """Extend superclass implementation for OpenXR bitmasks."""
        cat = info.elem.get('category')

        if cat == 'bitmask':
            # OpenXR uses elink, not tlink, for flags.
            self.addEntity(
                name,
                'elink',
                elem=info.elem,
                category='flags')
        else:
            super().handleType(name, info, requires)


class XRMacroCheckerFile(MacroCheckerFile):
    """OpenXR-specific subclass of MacroCheckerFile."""

    def processBlockOpen(self, block_type, context=None, delimiter=None):
        """Do any block-type-specific processing and push the new block.

        Extends the superclass to warn if a ref-page-like block is opened
        without a ref page tag.

        Must call self.pushBlock().

        Called by self.processBlockDelimiter().
        """
        # OpenXR only uses '--' blocks for ref pages.
        if block_type == BlockType.REF_PAGE_LIKE and \
                not self.prev_line_ref_page_tag:

            self.error(MessageId.REFPAGE_BLOCK,
                       ["Found a line containing only -- outside of a reference page block, not preceded by a reference page tag,",
                        "Pretending there was one and opening refpage block for unknown entity anyway, for more readable messages."],
                       group=None)

            self.current_ref_page = '?missing-refpage-tag?'
            self.prev_line_ref_page_tag = None
            self.in_ref_page = True
            # Manually pushing the block here, to force a refpage block.
            # Not going to superclass method in this case.
            self.pushBlock(block_type, refpage=self.current_ref_page,
                           context=context, delimiter=delimiter)
            return

        # Everything else about block opening is standard.
        super().processBlockOpen(block_type, context=context,
                                 delimiter=delimiter)

    def handleIncludeMissingRefPage(self, entity, generated_type):
        """Report a message about an include outside of a ref-page block."""
        # TODO is it reasonable that we just stuck all the Flags includes in
        # the appendix?
        if entity.endswith('Flags'):
            # OK, it's a flag, no worries.
            return

        super().handleIncludeMissingRefPage(entity, generated_type)

    def computeExpectedRefPageFromInclude(self, entity):
        """Compute the expected ref page entity based on an include entity name."""
        # TODO use registry associations between FlagBits and Flags to do this
        # better?
        if entity.endswith('FlagBits'):
            return entity.replace('FlagBits', 'Flags')
        return entity


def makeMacroChecker(enabled_messages):
    """Create a correctly-configured MacroChecker instance."""
    entity_db = XREntityDatabase()
    return MacroChecker(enabled_messages, entity_db, XRMacroCheckerFile, ROOT)


if __name__ == '__main__':
    available_messages = set(MessageId)
    # LEGACY messages are Vulkan-only.
    available_messages.remove(MessageId.LEGACY)

    default_enabled_messages = available_messages.difference(
        DEFAULT_DISABLED_MESSAGES)

    all_docs = sorted((str(fn)
                       for fn in (ROOT / 'specification/sources/').glob('**/*.adoc')
                       if "styleguide" not in str(fn)))

    checkerMain(default_enabled_messages, makeMacroChecker,
                all_docs, available_messages=available_messages)
