#!/usr/bin/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 MacroCheckerFile
from spec_tools.main import checkerMain
from spec_tools.shared import (AUTO_FIX_STRING, EXTENSION_CATEGORY, MessageId,
                               MessageType)

###
# "Configuration" constants

FREEFORM_CATEGORY = 'freeform'

# defines mentioned in spec but not needed in registry
EXTRA_DEFINES = ('VKAPI_ATTR', 'VKAPI_CALL', 'VKAPI_PTR', 'VK_NO_STDINT_H', 'VK_NO_STDDEF_H')

# Extra freeform refpages in addition to EXTRA_DEFINES
EXTRA_REFPAGES = ('WSIheaders', 'provisional-headers')

# 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
DEFAULT_DISABLED_MESSAGES = set((
    MessageId.LEGACY,
    MessageId.REFPAGE_MISSING,
    MessageId.MISSING_MACRO,
    MessageId.EXTENSION,
    # TODO *text macro checking actually needs fixing for Vulkan
    MessageId.MISUSED_TEXT,
    MessageId.MISSING_TEXT
))

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


class VulkanEntityDatabase(EntityDatabase):
    """Vulkan-specific subclass of EntityDatabase."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._conditionally_recognized = set(('fname', 'sname'))

    def makeRegistry(self):
        registryFile = str(ROOT / 'xml/vk.xml')
        registry = Registry()
        registry.loadFile(registryFile)
        return registry

    def getNamePrefix(self):
        return "vk"

    def getPlatformRequires(self):
        return 'vk_platform'

    def getSystemTypes(self):
        return SYSTEM_TYPES

    def populateMacros(self):
        self.addMacros('t', ['link', 'name'], ['funcpointers', 'flags'])

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

    def shouldBeRecognized(self, macro, entity_name):
        """Determine, based on the macro and the name provided, if we should expect to recognize the entity."""
        if super().shouldBeRecognized(macro, entity_name):
            return True

        # The *name: macros in Vulkan should also be recognized if the entity name matches the pattern.
        if macro in self._conditionally_recognized and self.likelyRecognizedEntity(entity_name):
            return True
        return False


class VulkanMacroCheckerFile(MacroCheckerFile):
    """Vulkan-specific subclass of MacroCheckerFile."""

    def handleWrongMacro(self, msg, data):
        """Report an appropriate message when we found that the macro used is incorrect.

        May be overridden depending on each API's behavior regarding macro misuse:
        e.g. in some cases, it may be considered a MessageId.LEGACY warning rather than
        a MessageId.WRONG_MACRO or MessageId.EXTENSION.
        """
        message_type = MessageType.WARNING
        message_id = MessageId.WRONG_MACRO
        group = 'macro'

        if data.category == EXTENSION_CATEGORY:
            # Ah, this is an extension
            msg.append(
                'This is apparently an extension name, which should be marked up as a link.')
            message_id = MessageId.EXTENSION
            group = None  # replace the whole thing
        else:
            # Non-extension, we found the macro though.
            if data.macro[0] == self.macro[0] and data.macro[1:] == 'link' and self.macro[1:] == 'name':
                # First letter matches, old is 'name', new is 'link':
                # This is legacy markup
                msg.append(
                    'This is legacy markup that has not been updated yet.')
                message_id = MessageId.LEGACY
            else:
                # Not legacy, just wrong.
                message_type = MessageType.ERROR

        msg.append(AUTO_FIX_STRING)
        self.diag(message_type, message_id, msg,
                  group=group, replacement=self.makeMacroMarkup(data=data), fix=self.makeFix(data=data))


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


if __name__ == '__main__':
    default_enabled_messages = set(MessageId).difference(
        DEFAULT_DISABLED_MESSAGES)

    all_docs = [str(fn)
                for fn in sorted((ROOT / 'chapters/').glob('**/*.txt'))]
    all_docs.extend([str(fn)
                     for fn in sorted((ROOT / 'appendices/').glob('**/*.txt'))])

    checkerMain(default_enabled_messages, makeMacroChecker,
                all_docs)
