from flappy import _core, _gl as gl
from flappy.events import Event
from flappy.display import DirectRenderer, Stage, BitmapData
from flappy.geom import Rectangle
from flappy.display3d import Texture
from flappy.display3d.vertexbuffer3d import VertexBuffer3D, VertexBuffer3DFormat
 
 
class Scene3DClearMask(object):
    COLOR       = gl.COLOR_BUFFER_BIT
    DEPTH       = gl.DEPTH_BUFFER_BIT
    STENCIL     = gl.STENCIL_BUFFER_BIT
    ALL         = COLOR | DEPTH | STENCIL
 
 
class Context3DTriangleFace(object):
    BACK            = 'back'
    FRONT           = 'front'
    FRONT_AND_BACK  = 'frontAndBack'
    NONE            = 'none'
 
 
class Scene3D(DirectRenderer):
 
    def __init__(self, antialias=0, enable_depth_and_stencil=False, 
                                                            name='Scene3D'):
        DirectRenderer.__init__(self, render_func=self._render, name=name)
        self._antialias = antialias
        self._depth_n_stencil = enable_depth_and_stencil
        self._current_prog = None
        self._frame_buffer = None
        self._fb_depth_n_stencil = False
        self._render_to_texture = False
 
        self._queue = []
 
        self.scrollRect = Rectangle(0.0, 0.0, 0.0, 0.0)
 
    def clear(self, red=1.0, green=0.0, blue=0.0, alpha=1.0, 
                            depth=1.0, stencil=0, mask=Scene3DClearMask.ALL):
    #TODO: clear depth and stenicil
        self._do(gl.clearColor, red, green, blue, alpha)
        self._do(gl.clear, mask)
 
    def setProgram(self, program):
        self._current_prog = program
        self._do(gl.useProgram, program.gl_program)
        gl.useProgram(program.gl_program)
 
    def setVertexBufferAt(self, locname, vbuffer, offset=0, 
                                        format=VertexBuffer3DFormat.FLOAT_1):
        loc = gl.getAttribLocation(self._current_prog.gl_program, locname)
        gl.bindBuffer(gl.ARRAY_BUFFER, vbuffer.gl_buffer)
 
        tp = gl.FLOAT
        numbytes = 4
        dimension = 4
        try:
            dimension = {
                VertexBuffer3DFormat.BYTES_4: 4,
                VertexBuffer3DFormat.FLOAT_1: 1,
                VertexBuffer3DFormat.FLOAT_2: 2,
                VertexBuffer3DFormat.FLOAT_3: 3,
                VertexBuffer3DFormat.FLOAT_4: 4,
            }[format]
        except KeyError:
            raise ValueError('Buffer format "%s" is not supported' % \
                                                                str(format))
 
        self._do(gl.bindBuffer, gl.ARRAY_BUFFER, vbuffer.gl_buffer)
        self._do(gl.enableVertexAttribArray, loc)
        self._do(gl.vertexAttribPointer, loc, dimension, tp, False, 
                    vbuffer.data32_per_vertex * numbytes, offset * numbytes)
 
    def setProgramConstantFromMatrix(self, locname, mat, transpose=False):
        loc = gl.getUniformLocation(self._current_prog.gl_program, locname)
        self._do(gl.uniformMatrix, loc, transpose, mat)
 
    def setProgramConstant(self, locname, data):
        loc = gl.getUniformLocation(self._current_prog.gl_program, locname)
 
        if isinstance(data, int):
            self._do(gl.uniform1i, loc, data)
        elif isinstance(data, float):
            self._do(gl.uniform1f, loc, data)
        else:
            assert len(data) <= 4
            meth_tp = 'f' if isinstance(data[0], float) else 'i'
            meth = getattr(gl, 'uniform%d%s' % (len(data), meth_tp))
            self._do(meth, loc, *data)
 
    def setTextureAt(self, locname, texture, texture_index=0):
        loc = gl.getUniformLocation(self._current_prog.gl_program, locname)
 
        assert 0 <= texture_index < 8, \
                    'Texture index "%s" is not supported' % str(texture_index)
        self._do(gl.activeTexture, gl.TEXTURE0 + texture_index)
 
        self._bind_texture(texture)
 
        self._do(gl.uniform1i, loc, texture_index)
        self._do(gl.texParameter, gl.TEXTURE_2D,
                        gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
        self._do(gl.texParameter, gl.TEXTURE_2D,
                        gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
        self._do(gl.texParameter, gl.TEXTURE_2D,
                        gl.TEXTURE_MIN_FILTER, gl.LINEAR)
        self._do(gl.texParameter, gl.TEXTURE_2D,
                        gl.TEXTURE_MAG_FILTER, gl.LINEAR)
 
    def drawTriangles(self, ibuffer, first_index=0, num_triangles=-1):
        num_indeces = 0
 
        if num_triangles == -1:
            num_indeces = ibuffer.num_indeces
        else:
            num_indeces = num_triangles * 3
 
        self._do(gl.bindBuffer, gl.ELEMENT_ARRAY_BUFFER, ibuffer.gl_buffer)
        self._do(gl.drawElements, gl.TRIANGLES, num_indeces,
                                            gl.UNSIGNED_SHORT, first_index)
 
    def setRenderToTexture(self, texture, enable_depth_and_stencil=False, 
                                            antialias=0, surface_selector=0):
        if self._frame_buffer is None:
            self._frame_buffer = gl.createFramebuffer()
 
        self._render_to_texture = True
        self._fb_depth_n_stencil = enable_depth_and_stencil
 
        self._do(self._bind_texture, texture)
        self._do(gl.texParameter, gl.TEXTURE_2D,
                                    gl.TEXTURE_WRAP_S, gl.REPEAT)
        self._do(gl.texParameter, gl.TEXTURE_2D,
                                    gl.TEXTURE_WRAP_T, gl.REPEAT)
        self._do(gl.texParameter, gl.TEXTURE_2D,
                                    gl.TEXTURE_MIN_FILTER, gl.LINEAR)
        self._do(gl.texParameter, gl.TEXTURE_2D,
                                    gl.TEXTURE_MAG_FILTER, gl.LINEAR)
 
        self._do(gl.bindFramebuffer, gl.FRAMEBUFFER, self._frame_buffer)
        self._set_depth_and_stencil(enable_depth_and_stencil)
        self._do(gl.framebufferTexture2D, gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0, 
                                        gl.TEXTURE_2D, texture.gl_texture, 0)
        self._do(gl.viewport, 0, 0, texture.width, texture.height)
        self._do(gl.scissor, 0, 0, texture.width, texture.height)
 
    def setRenderToBackBuffer(self):
        self._render_to_texture = False
        self._do(gl.bindFramebuffer, gl.FRAMEBUFFER, 0)
        self._set_depth_and_stencil(self._depth_n_stencil)
        sr = self.scrollRect
        self._do(gl.viewport, sr.x, sr.y, sr.width, sr.height)
        self._do(gl.scissor, sr.x, sr.y, sr.width, sr.height)
 
    def setWidth(self, width):
        super(Scene3D, self).setWidth(width)
        self.scrollRect.width = width
        self.setScrollRect(self.scrollRect)
 
    def setHeight(self, height):
        super(Scene3D, self).setHeight(height)
        self.scrollRect.height = height
        self.setScrollRect(self.scrollRect)
 
    def _bind_texture(self, texture):
        if isinstance(texture, BitmapData):
            self._do(_core.bindBitmapDataTexture, texture)
        elif isinstance(texture, Texture):
            self._do(gl.bindTexture, gl.TEXTURE_2D, texture.gl_texture)
        else:
            raise ValueError('Unsupported texture class "%s"' % \
                                                texture.__class__.__name__)
 
    def _set_depth_and_stencil(self, value):
        if value:
            self._do(gl.enable, gl.DEPTH_TEST)
            self._do(gl.enable, gl.DEPTH_STENCIL)
        else:
            self._do(gl.disable, gl.DEPTH_TEST)
            self._do(gl.disable, gl.DEPTH_STENCIL)                 
 
    def _do(self, *args):
        self._queue.append(args)
 
    def _render(self, rect):
        gl.enable(gl.SCISSOR_TEST)
        gl.enable(gl.CULL_FACE)
 
        if (not self._render_to_texture and self._depth_n_stencil) or \
            (self._render_to_texture and self._fb_depth_n_stencil):
            gl.enable(gl.DEPTH_TEST)
            gl.enable(gl.DEPTH_STENCIL)
 
        gl.viewport(rect.x, rect.y, rect.width, rect.height)
        gl.scissor(rect.x, rect.y, rect.width, rect.height)
 
        for com in self._queue:
            com[0](*com[1:])
 
        self._queue = []
        gl.disable(gl.SCISSOR_TEST)
        gl.disable(gl.CULL_FACE)
        gl.disable(gl.DEPTH_TEST)
 
        gl.useProgram(0)
        gl.bindBuffer(gl.ARRAY_BUFFER, 0)
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
        gl.bindFramebuffer(gl.FRAMEBUFFER, 0)
        gl.viewport(0.0, 0.0, self.stage.stageWidth, self.stage.stageHeight)