"""Extension module support methods

This module provides the tools required to check whether
an extension is available
"""

from OpenGL.latebind import LateBind
from OpenGL._bytes import bytes, unicode, as_8_bit
import OpenGL as root
import sys
import logging
import re

_log = logging.getLogger('OpenGL.extensions')
VERSION_PREFIX = as_8_bit('GL_VERSION_GL_')
CURRENT_GL_VERSION = None
AVAILABLE_GL_EXTENSIONS = []
AVAILABLE_GLU_EXTENSIONS = []

# version tuple -> list of implicitly included extensions...
VERSION_EXTENSIONS = [
    (
        (3, 0),
        [
            as_8_bit('GL_ARB_vertex_array_object'),
            as_8_bit('GL_ARB_texture_buffer_object'),
            as_8_bit('GL_ARB_framebuffer_object'),
            as_8_bit('GL_ARB_map_buffer_range'),
        ],
    ),
    (
        (3, 1),
        [
            as_8_bit('GL_ARB_copy_buffer'),
            as_8_bit('GL_ARB_uniform_buffer_object'),
        ],
    ),
    (
        (3, 2),
        [
            as_8_bit('GL_ARB_draw_elements_base_vertex'),
            as_8_bit('GL_ARB_provoking_vertex'),
            as_8_bit('GL_ARB_sync'),
            as_8_bit('GL_ARB_texture_multisample'),
        ],
    ),
    (
        (3, 3),
        [
            as_8_bit('GL_ARB_texture_multisample'),
            as_8_bit('GL_ARB_blend_func_extended'),
            as_8_bit('GL_ARB_sampler_objects'),
            as_8_bit('GL_ARB_explicit_attrib_location'),
            as_8_bit('GL_ARB_occlusion_query2'),
            as_8_bit('GL_ARB_shader_bit_encoding'),
            as_8_bit('GL_ARB_texture_rgb10_a2ui'),
            as_8_bit('GL_ARB_texture_swizzle'),
            as_8_bit('GL_ARB_timer_query'),
            as_8_bit('GL_ARB_vertex_type_2_10_10_10_rev'),
        ],
    ),
    (
        (4, 0),
        [
            as_8_bit('GL_ARB_texture_query_lod'),
            as_8_bit('GL_ARB_draw_indirect'),
            as_8_bit('GL_ARB_gpu_shader5'),
            as_8_bit('GL_ARB_gpu_shader_fp64'),
            as_8_bit('GL_ARB_shader_subroutine'),
            as_8_bit('GL_ARB_tessellation_shader'),
            as_8_bit('GL_ARB_texture_buffer_object_rgb32'),
            as_8_bit('GL_ARB_texture_cube_map_array'),
            as_8_bit('GL_ARB_texture_gather'),
            as_8_bit('GL_ARB_transform_feedback2'),
            as_8_bit('GL_ARB_transform_feedback3'),
        ],
    ),
    (
        (4, 1),
        [
            as_8_bit('GL_ARB_ES2_compatibility'),
            as_8_bit('GL_ARB_get_program_binary'),
            as_8_bit('GL_ARB_separate_shader_objects'),
            as_8_bit('GL_ARB_shader_precision'),
            as_8_bit('GL_ARB_vertex_attrib_64bit'),
            as_8_bit('GL_ARB_viewport_array'),
        ],
    ),
    (
        (4, 2),
        [
            as_8_bit('GL_ARB_base_instance'),
            as_8_bit('GL_ARB_shading_language_420pack'),
            as_8_bit('GL_ARB_transform_feedback_instanced'),
            as_8_bit('GL_ARB_compressed_texture_pixel_storage'),
            as_8_bit('GL_ARB_conservative_depth'),
            as_8_bit('GL_ARB_internalformat_query'),
            as_8_bit('GL_ARB_map_buffer_alignment'),
            as_8_bit('GL_ARB_shader_atomic_counters'),
            as_8_bit('GL_ARB_shader_image_load_store'),
            as_8_bit('GL_ARB_shading_language_packing'),
            as_8_bit('GL_ARB_texture_storage'),
        ],
    ),
]


class ExtensionQuerier(object):
    prefix = None
    version_prefix = None
    assumed_version = [1, 0]

    version = extensions = None
    version_string = extensions_string = None

    registered = []

    def __init__(self):
        self.registered.append(self)

    @classmethod
    def hasExtension(self, specifier):
        for registered in self.registered:
            result = registered(specifier)
            if result:
                return result
        return False

    def __call__(self, specifier):
        specifier = as_8_bit(specifier).replace(as_8_bit('.'), as_8_bit('_'))
        if not specifier.startswith(as_8_bit(self.prefix)):
            return None

        if specifier.startswith(as_8_bit(self.version_prefix)):
            specifier = [
                int(x)
                for x in specifier[len(self.version_prefix) :].split(as_8_bit('_'))
            ]
            if specifier[:2] <= self.assumed_version:
                return True
            version = self.getVersion()
            if not version:
                return version
            return specifier <= version
        else:
            extensions = self.getExtensions()
            return extensions and specifier in extensions

    def getVersion(self):
        if not self.version:
            self.version = self.pullVersion()
        return self.version

    def getExtensions(self):
        if not self.extensions:
            self.extensions = self.pullExtensions()
        return self.extensions


class _GLQuerier(ExtensionQuerier):
    prefix = as_8_bit('GL_')
    version_prefix = as_8_bit('GL_VERSION_GL_')
    assumed_version = [1, 1]

    # If you call OpenGL's glGetString for a context which is
    # an ES context, you get an ES-style string, here we're going
    # to parse out the ES value, but this is one of those "the issue is the usage"
    # problems. You shouldn't do this, but if you do, this will make
    # the issue clearer
    version_matcher = re.compile(
        r'^(?P<api_marker>OpenGL (ES )?)?(?P<version>\d+([.]\d+)+)'
    )
    is_opengl_es = False

    def pullVersion(self):
        """Retrieve 2-int declaration of major/minor GL version

        returns [int(major),int(minor)] or False if not loaded
        """
        from OpenGL import platform

        if not platform.PLATFORM.CurrentContextIsValid():
            return False
        from OpenGL.raw.GL.VERSION.GL_1_1 import glGetString
        from OpenGL.raw.GL.VERSION.GL_1_1 import GL_VERSION

        new = glGetString(GL_VERSION)

        self.version_string = new.decode('utf-8', errors='ignore')
        match = self.version_matcher.match(self.version_string)
        if match:
            self.version_string = match.group('version')
            api_marker = match.group('api_marker')
            if api_marker:
                if api_marker == 'OpenGL ES':
                    _log.warning(
                        "Importing OpenGL in an OpenGL ES context is not advisable, generally"
                    )
                    self.is_opengl_es = True
        if new:
            return [int(x) for x in self.version_string.split('.')]
        else:
            return False  # not yet loaded/supported

    def pullExtensions(self):
        from OpenGL import platform

        if not platform.PLATFORM.CurrentContextIsValid():
            return False
        from OpenGL.raw.GL._types import GLint
        from OpenGL.raw.GL.VERSION.GL_1_1 import glGetString, glGetError
        from OpenGL.raw.GL.VERSION.GL_1_1 import GL_EXTENSIONS
        from OpenGL import error

        try:
            extensions = glGetString(GL_EXTENSIONS)
            if glGetError():
                raise error.GLError()
            if extensions:
                extensions = extensions.split()
            else:
                return False
        except (AttributeError, error.GLError):
            # OpenGL 3.0 deprecates glGetString( GL_EXTENSIONS )
            from OpenGL.raw.GL.VERSION.GL_3_0 import GL_NUM_EXTENSIONS, glGetStringi
            from OpenGL.raw.GL.VERSION.GL_1_1 import glGetIntegerv

            count = GLint()
            glGetIntegerv(GL_NUM_EXTENSIONS, count)
            extensions = []
            for i in range(count.value):
                extension = glGetStringi(GL_EXTENSIONS, i)
                extensions.append(extension)
        # Add included-by-reference extensions...
        version = self.getVersion()
        if not version:
            # should not be possible?
            return version
        check = tuple(version[:2])
        for v, v_exts in VERSION_EXTENSIONS:
            if v <= check:
                for v_ext in v_exts:
                    if v_ext not in extensions:
                        extensions.append(as_8_bit(v_ext))
            else:
                break
        return extensions


GLQuerier = _GLQuerier()


class _GLUQuerier(ExtensionQuerier):
    prefix = as_8_bit('GLU_')
    version_prefix = as_8_bit('GLU_VERSION_GL_')

    def pullVersion(self):
        from OpenGL.GLU import gluGetString, GLU_VERSION

        return [
            int(x)
            for x in gluGetString(GLU_VERSION).split(as_8_bit('_'))
            if x.isdigit()
        ]

    def pullExtensions(self):
        from OpenGL.GLU import gluGetString, GLU_EXTENSIONS

        return gluGetString(GLU_EXTENSIONS).split()


GLUQuerier = _GLUQuerier()


def hasExtension(specifier):
    return ExtensionQuerier.hasExtension(specifier)


hasGLExtension = hasGLUExtension = hasExtension


class _Alternate(LateBind):
    def __init__(self, name, *alternates):
        """Initialize set of alternative implementations of the same function"""
        self.__name__ = name
        self._alternatives = alternates
        if root.MODULE_ANNOTATIONS:
            frame = sys._getframe().f_back
            if frame and frame.f_back and '__name__' in frame.f_back.f_globals:
                self.__module__ = frame.f_back.f_globals['__name__']

    def __bool__(self):
        from OpenGL import error

        try:
            return bool(self.getFinalCall())
        except error.NullFunctionError as err:
            return False

    __nonzero__ = __bool__  # Python 2.6 compatibility

    def finalise(self):
        """Call, doing a late lookup and bind to find an implementation"""
        for alternate in self._alternatives:
            if alternate:
                #                _log.info(
                #                    """Chose alternate: %s from %s""",
                #                    alternate.__name__,
                #                    ", ".join([x.__name__ for x in self._alternatives])
                #                )
                return alternate
        from OpenGL import error

        raise error.NullFunctionError(
            """Attempt to call an undefined alternate function (%s), check for bool(%s) before calling"""
            % (
                ', '.join([x.__name__ for x in self._alternatives]),
                self.__name__,
            )
        )


def alternate(name, *functions):
    """Construct a callable that functions as the first implementation found of given set of alternatives

    if name is a function then its name will be used....
    """
    if not isinstance(name, (bytes, unicode)):
        functions = (name,) + functions
        name = name.__name__
    return type(name, (_Alternate,), {})(name, *functions)
