#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Shader programs and infrastructure for wire frame items


"""
import logging

from OpenGL.GL import (
    shaders,
    glUniform1fv,
    glGetUniformLocation,
    GL_VERTEX_SHADER,
    GL_FRAGMENT_SHADER,
)

from qplotutils import CONFIG
from qplotutils.wireframe.base_types import DefaultGlOptions

_log = logging.getLogger(__name__)

__author__ = "Philipp Baust"
__copyright__ = "Copyright 2019, Philipp Baust"
__credits__ = []
__license__ = "MIT"
__version__ = "0.0.1"
__maintainer__ = "Philipp Baust"
__email__ = "philipp.baust@gmail.com"
__status__ = "Development"


class ShaderProgram(object):
    def __init__(
        self,
        name=None,
        vertexShaderSrc=None,
        fragmentShaderSrc=None,
        uniforms=None,
        glOptions=DefaultGlOptions.OPAQUE,
    ):
        self._name = name
        self._vertex_shader_src = vertexShaderSrc
        self._fragment_shader_src = fragmentShaderSrc
        self._program = 0
        self.uniformData = {}
        self.__glOptions = glOptions

        ## parse extra options from the shader definition
        if uniforms is not None:
            for k, v in uniforms.items():
                self.uniformData[k] = v

    @property
    def glOptions(self):
        return self.__glOptions

    def compile(self):
        try:
            if self._vertex_shader_src and self._fragment_shader_src:
                vertex_shader = shaders.compileShader(
                    self._vertex_shader_src, GL_VERTEX_SHADER
                )
                fragment_shader = shaders.compileShader(
                    self._fragment_shader_src, GL_FRAGMENT_SHADER
                )
                self._program = shaders.compileProgram(vertex_shader, fragment_shader)
        except Exception as ex:
            _log.error("Failed to compile vertex/fragment shader.", ex)
            self._program = 0

            if CONFIG.debug:
                raise

    @property
    def name(self):
        return self._name

    @property
    def program(self):
        return self._program

    def __enter__(self):
        shaders.glUseProgram(self.program)  # lgtm [py/call/wrong-arguments]

        try:
            ## load uniform values into program
            for uniformName, data in self.uniformData.items():
                loc = self.uniform(uniformName)
                if loc == -1:
                    raise Exception(
                        'Could not find uniform variable "%s"' % uniformName
                    )

                glUniform1fv(loc, len(data), data)
        except:
            shaders.glUseProgram(0)
            raise

    def __exit__(self, exc_type, exc_val, exc_tb):
        shaders.glUseProgram(0)

    def uniform(self, name):
        """Return the location integer for a uniform variable in this program"""
        return glGetUniformLocation(self.program, name.encode("utf_8"))


class ShaderRegistry(object):
    """ Global states and settings, shared as a borg object. """

    __shared_state = None

    __default_shader = {
        "balloon": ShaderProgram(
            "balloon",
            """
            varying vec3 normal;
            void main() {
                // compute here for use in fragment shader
                normal = normalize(gl_NormalMatrix * gl_Normal);
                gl_FrontColor = gl_Color;
                gl_BackColor = gl_Color;
                gl_Position = ftransform();
            }
            """,
            """
            varying vec3 normal;
            void main() {
                vec4 color = gl_Color;
                color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0);
                gl_FragColor = color;
            }
            """,
            glOptions=DefaultGlOptions.TRANSLUCENT
        ),
        "shaded": ShaderProgram(
            "shaded",
            """
            varying vec3 normal;
            void main() {
                // compute here for use in fragment shader
                normal = normalize(gl_NormalMatrix * gl_Normal);
                gl_FrontColor = gl_Color;
                gl_BackColor = gl_Color;
                gl_Position = ftransform();
            }               
            """,
            """
            varying vec3 normal;
            void main() {
                vec4 color = gl_Color;
                float s = pow(normal.x*normal.x + normal.y*normal.y, 2.0);
                color.x = color.x + s * (1.0-color.x);
                color.y = color.y + s * (1.0-color.y);
                color.z = color.z + s * (1.0-color.z);
                gl_FragColor = color;
            }  
            """,
        ),
        "heightColor": ShaderProgram(
            "heightColor",
            """
            varying vec4 pos;
            void main() {
                gl_FrontColor = gl_Color;
                gl_BackColor = gl_Color;
                pos = gl_Vertex;
                gl_Position = ftransform();
            }
            """,
            """
            uniform float colorMap[9];
            varying vec4 pos;
            //out vec4 gl_FragColor;   // only needed for later glsl versions
            //in vec4 gl_Color;
            void main() {
                vec4 color = gl_Color;
                color.x = colorMap[0] * (pos.z + colorMap[1]);
                if (colorMap[2] != 1.0)
                color.x = pow(color.x, colorMap[2]);
                color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x);
                
                color.y = colorMap[3] * (pos.z + colorMap[4]);