from ... import ShaderMaterial
from ... import ReadFile

from mathutils import Vector
from mathutils import Matrix
import time
import math
import bge
import bgl

class Water(ShaderMaterial):
    '''The original work for this material comes from Martinsh:
    > http://devlog-martinsh.blogspot.ca/'''

    FragmentShader = ReadFile('./water.fs', __file__)
    VertexShader = ReadFile('./water.vs', __file__)

    # Parameters
    backgroundColor = Vector((.1, .2, .8, .0))
    waterCamera = property(
        lambda self: self.owner.children.get('WaterRenderCamera'))
    activeCamera = property(
        lambda self: self.owner.scene.active_camera)
    height = property(
        lambda self: bge.render.getWindowHeight())
    width = property(
        lambda self: bge.render.getWindowWidth())
    reflectionSize = 512
    refractionSize = 512
    timeModulo = 1 << 16
    offset = -0.2

    def init(self):

        # Setting up the reflection texture
        self.reflection = bge.texture.Texture(self.owner, 0, 0)
        self.reflection.source = bge.texture.ImageRender(
            self.owner.scene, self.waterCamera)
        self.reflection.source.capsize = (
            self.width, self.height)
            # self.reflectionSize, self.reflectionSize)
            # self.reflectionSize // 2, self.reflectionSize // 2)
        self.reflection.source.background = list(
            255 * self.backgroundColor)
        self.reflection.refresh(True)

        # Setting up the refraction texture
        self.refraction = bge.texture.Texture(self.owner, 0, 1)
        self.refraction.source = bge.texture.ImageRender(
            self.owner.scene, self.waterCamera)
        self.refraction.source.capsize = (
            self.width, self.height)
            # self.refractionSize, self.refractionSize)
            # self.refractionSize // 2, self.refractionSize // 2)
        self.refraction.source.background = list(
            255 * self.backgroundColor)
        self.refraction.refresh(True)

    def pre_draw_setup(self, shader):
        '''I have no idea why, but this has to run on pre_draw_setup...'''
        shader.setAttrib(bge.logic.SHD_TANGENT)
        shader.setUniformDef('ModelMatrix', bge.logic.MODELMATRIX)
        shader.setUniformDef('cameraPos', bge.logic.CAM_POS)
        shader.setSampler('reflectionSampler', 0)
        shader.setSampler('refractionSampler', 1)
        shader.setSampler('normalSampler', 2)
        shader.setSampler('depthSampler', 3)
        shader.setUniform1f('timer',
            (3 * time.time()) % self.timeModulo)

        # User uniforms
        shader.setUniform1f('scale', self.owner.get('water.scale', 1.))
        shader.setUniform1f('windSpeed', self.owner.get('water.wind.speed', .2))

        self.watertexture()

    def watertexture(self):
        waterCamera = self.waterCamera
        activeCamera = self.activeCamera

        # Setting up the water camera parameters
        waterCamera.lens = activeCamera.lens
        waterCamera.projection_matrix = activeCamera.projection_matrix
        self.owner.visible = False

        # plane normals Z = Front
        normal = self.owner.getAxisVect((0., 0., 1.))

        # closest distance from center to plane
        distance = -self.owner.position.project(normal).magnitude

        # VdotN to get front-face/back-face
        V = (activeCamera.position - self.owner.position
            ).normalized().dot(normal)

        # Invert normals when back-face (?)
        if V < 0: normal = -normal

        # Processing the reflection and the refraction
        self.processReflection(activeCamera, waterCamera, normal, distance)
        self.processRefraction(activeCamera, waterCamera, normal, distance)
        self.owner.visible = True

    def processReflection(self, activeCamera, waterCamera, normal, distance):
        '''This is the refactored way of processing reflection'''
        M1 = Matrix(self.owner.orientation)
        M2 = Matrix(self.owner.orientation)
        M2.invert()

        R = Matrix.Rotation(math.radians(180), 3, 'Y')
        U = Matrix.Scale(-1, 3, Vector((1, 0, 0)))

        position = (activeCamera.position - self.owner.position) * M1
        position = self.owner.position + position * R * U * M2

        orientation = Matrix(activeCamera.orientation)
        orientation.transpose()
        orientation = orientation * M1 * R * U * M2
        orientation.transpose()

        # Orienting and positioning the water camera
        waterCamera.orientation = orientation
        waterCamera.position = position

        # Culling front faces as the camera is scaled to -1 (?)
        bgl.glCullFace(bgl.GL_FRONT)

        # Changing clipping plane
        plane = bgl.Buffer(bgl.GL_DOUBLE, [4], (
            -normal[0], -normal[1], -normal[2], -distance + self.offset))
        bgl.glClipPlane(bgl.GL_CLIP_PLANE0, plane)
        bgl.glEnable(bgl.GL_CLIP_PLANE0)

        self.reflection.refresh(True)

        # Reverting parameters
        bgl.glCullFace(bgl.GL_BACK)
        bgl.glDisable(bgl.GL_CLIP_PLANE0)

    def processRefraction(self, activeCamera, waterCamera, normal, distance):
        '''This is the refactored way of processing refraction'''
        orientation = Matrix(activeCamera.orientation)
        position = activeCamera.position

        # Orienting and positioning the water camera
        waterCamera.orientation = orientation
        waterCamera.position = position

        # Changing clipping plane
        plane = bgl.Buffer(bgl.GL_DOUBLE, [4], (
            normal[0], normal[1], normal[2], -distance + self.offset))
        bgl.glClipPlane(bgl.GL_CLIP_PLANE1, plane)
        bgl.glEnable(bgl.GL_CLIP_PLANE1)

        self.refraction.refresh(True)

        bgl.glDisable(bgl.GL_CLIP_PLANE1)
