"""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
_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( self.prefix ):
            return None 
        
        if specifier.startswith( 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]
    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
        if new:
            return [
                int(x) for x in new.split(as_8_bit(' '),1)[0].split( as_8_bit('.') )
            ]
        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) as err:
            # 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('_')
            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 )
