# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.

from typing import Optional, TYPE_CHECKING

from UM.Application import Application
from UM.Resources import Resources

from UM.View.RenderPass import RenderPass
from UM.View.GL.OpenGL import OpenGL
from UM.View.RenderBatch import RenderBatch


from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator

if TYPE_CHECKING:
    from UM.View.GL.ShaderProgram import ShaderProgram

MYPY = False
if MYPY:
    from UM.Scene.Camera import Camera


# Make color brighter by normalizing it (maximum factor 2.5 brighter)
# color_list is a list of 4 elements: [r, g, b, a], each element is a float 0..1
def prettier_color(color_list):
    maximum = max(color_list[:3])
    if maximum > 0:
        factor = min(1 / maximum, 2.5)
    else:
        factor = 1.0
    return [min(i * factor, 1.0) for i in color_list]


##  A render pass subclass that renders slicable objects with default parameters.
#   It uses the active camera by default, but it can be overridden to use a different camera.
#
#   This is useful to get a preview image of a scene taken from a different location as the active camera.
class PreviewPass(RenderPass):
    def __init__(self, width: int, height: int) -> None:
        super().__init__("preview", width, height, 0)

        self._camera = None  # type: Optional[Camera]

        self._renderer = Application.getInstance().getRenderer()

        self._shader = None #type: Optional[ShaderProgram]
        self._non_printing_shader = None #type: Optional[ShaderProgram]
        self._support_mesh_shader = None #type: Optional[ShaderProgram]
        self._scene = Application.getInstance().getController().getScene()

    #   Set the camera to be used by this render pass
    #   if it's None, the active camera is used
    def setCamera(self, camera: Optional["Camera"]):
        self._camera = camera

    def render(self) -> None:
        if not self._shader:
            self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
            if self._shader:
                self._shader.setUniformValue("u_overhangAngle", 1.0)
                self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0])
                self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0])
                self._shader.setUniformValue("u_shininess", 20.0)

        if not self._non_printing_shader:
            if self._non_printing_shader:
                self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
                self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5])
                self._non_printing_shader.setUniformValue("u_opacity", 0.6)

        if not self._support_mesh_shader:
            self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
            if self._support_mesh_shader:
                self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
                self._support_mesh_shader.setUniformValue("u_width", 5.0)

        self._gl.glClearColor(0.0, 0.0, 0.0, 0.0)
        self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)

        # Create batches to be rendered
        batch = RenderBatch(self._shader)
        batch_support_mesh = RenderBatch(self._support_mesh_shader)

        # Fill up the batch with objects that can be sliced.
        for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
            if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
                per_mesh_stack = node.callDecoration("getStack")
                if node.callDecoration("isNonThumbnailVisibleMesh"):
                    # Non printing mesh
                    continue
                elif per_mesh_stack is not None and per_mesh_stack.getProperty("support_mesh", "value"):
                    # Support mesh
                    uniforms = {}
                    shade_factor = 0.6
                    diffuse_color = node.getDiffuseColor()
                    diffuse_color2 = [
                        diffuse_color[0] * shade_factor,
                        diffuse_color[1] * shade_factor,
                        diffuse_color[2] * shade_factor,
                        1.0]
                    uniforms["diffuse_color"] = prettier_color(diffuse_color)
                    uniforms["diffuse_color_2"] = diffuse_color2
                    batch_support_mesh.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms)
                else:
                    # Normal scene node
                    uniforms = {}
                    uniforms["diffuse_color"] = prettier_color(node.getDiffuseColor())
                    batch.addItem(node.getWorldTransformation(), node.getMeshData(), uniforms = uniforms)

        self.bind()

        if self._camera is None:
            render_camera = Application.getInstance().getController().getScene().getActiveCamera()
        else:
            render_camera = self._camera

        batch.render(render_camera)
        batch_support_mesh.render(render_camera)

        self.release()
