# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2014-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""This module provides a class to handle shader program compilation."""
__authors__ = ["T. Vincent"]
__license__ = "MIT"
__date__ = "25/07/2016"
import logging
import numpy
from . import gl
from .Context import getGLContext
_logger = logging.getLogger(__name__)
[docs]class Program(object):
    """Wrap OpenGL shader program.
    The program is compiled lazily (i.e., at first program :meth:`use`).
    When the program is compiled, it stores attributes and uniforms locations.
    So, attributes and uniforms must be used after :meth:`use`.
    This object supports multiple OpenGL contexts.
    :param str vertexShader: The source of the vertex shader.
    :param str fragmentShader: The source of the fragment shader.
    :param str attrib0:
        Attribute's name to bind to position 0 (default: 'position').
        On certain platform, this attribute MUST be active and with an
        array attached to it in order for the rendering to occur....
    """
    def __init__(self, vertexShader, fragmentShader,
                 attrib0='position'):
        self._vertexShader = vertexShader
        self._fragmentShader = fragmentShader
        self._attrib0 = attrib0
        self._programs = {}
    @staticmethod
    def _compileGL(vertexShader, fragmentShader, attrib0):
        program = gl.glCreateProgram()
        gl.glBindAttribLocation(program, 0, attrib0.encode('ascii'))
        vertex = gl.glCreateShader(gl.GL_VERTEX_SHADER)
        gl.glShaderSource(vertex, vertexShader)
        gl.glCompileShader(vertex)
        if gl.glGetShaderiv(vertex, gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
            raise RuntimeError(gl.glGetShaderInfoLog(vertex))
        gl.glAttachShader(program, vertex)
        gl.glDeleteShader(vertex)
        fragment = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
        gl.glShaderSource(fragment, fragmentShader)
        gl.glCompileShader(fragment)
        if gl.glGetShaderiv(fragment,
                            gl.GL_COMPILE_STATUS) != gl.GL_TRUE:
            raise RuntimeError(gl.glGetShaderInfoLog(fragment))
        gl.glAttachShader(program, fragment)
        gl.glDeleteShader(fragment)
        gl.glLinkProgram(program)
        if gl.glGetProgramiv(program, gl.GL_LINK_STATUS) != gl.GL_TRUE:
            raise RuntimeError(gl.glGetProgramInfoLog(program))
        attributes = {}
        for index in range(gl.glGetProgramiv(program,
                                             gl.GL_ACTIVE_ATTRIBUTES)):
            name = gl.glGetActiveAttrib(program, index)[0]
            namestr = name.decode('ascii')
            attributes[namestr] = gl.glGetAttribLocation(program, name)
        uniforms = {}
        for index in range(gl.glGetProgramiv(program, gl.GL_ACTIVE_UNIFORMS)):
            name = gl.glGetActiveUniform(program, index)[0]
            namestr = name.decode('ascii')
            uniforms[namestr] = gl.glGetUniformLocation(program, name)
        return program, attributes, uniforms
    def _getProgramInfo(self):
        glcontext = getGLContext()
        if glcontext not in self._programs:
            raise RuntimeError(
                "Program was not compiled for current OpenGL context.")
        return self._programs[glcontext]
    @property
    def attributes(self):
        """Vertex attributes names and locations as a dict of {str: int}.
        WARNING:
        Read-only usage.
        To use only with a valid OpenGL context and after :meth:`use`
        has been called for this context.
        """
        return self._getProgramInfo()[1]
    @property
    def uniforms(self):
        """Program uniforms names and locations as a dict of {str: int}.
        WARNING:
        Read-only usage.
        To use only with a valid OpenGL context and after :meth:`use`
        has been called for this context.
        """
        return self._getProgramInfo()[2]
    @property
    def program(self):
        """OpenGL id of the program.
        WARNING:
        To use only with a valid OpenGL context and after :meth:`use`
        has been called for this context.
        """
        return self._getProgramInfo()[0]
    # def discard(self):
    #    pass  # Not implemented yet
[docs]    def use(self):
        """Make use of the program, compiling it if necessary"""
        glcontext = getGLContext()
        if glcontext not in self._programs:
            self._programs[glcontext] = self._compileGL(
                self._vertexShader,
                self._fragmentShader,
                self._attrib0)
        if _logger.getEffectiveLevel() <= logging.DEBUG:
            gl.glValidateProgram(self.program)
            if gl.glGetProgramiv(
                    self.program, gl.GL_VALIDATE_STATUS) != gl.GL_TRUE:
                _logger.debug('Cannot validate program: %s',
                              gl.glGetProgramInfoLog(self.program))
        gl.glUseProgram(self.program)