#-*- coding:utf-8 -*-
# cython: profile=False

#  Copyright © 2009-2015  B. Clausius <barcc@gmx.de>
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.


# although this file is compiled with Python3 syntax, Cython needs at least division from __future__
from __future__ import print_function, division

# This line makes cython happy
global __name__, __package__    # pylint: disable=W0604
#px/__compiled = True
__compiled = False

__all__ = ['ATTRIB_LOCATION', 'PICKATTRIB_LOCATION',
           'matrix_set_identity', 'mat4',
           'gl_draw_cube', 'gl_pick_cube', 'gl_init_buffers', 'gl_delete_buffers',
           'gl_draw_cube_debug', 'gl_draw_select_debug', 'gl_init_object_location']

#px/from libc.math cimport M_PI, cos, sin
from math import radians, cos, sin
#pxd/from gl cimport *
from OpenGL.GL import *     # pylint: disable=W0614,W0401
#px:
import ctypes
from ctypes import sizeof
from OpenGL.raw.GL.VERSION.GL_2_0 import glVertexAttribPointer
#px.

from .debug import debug, DEBUG


if DEBUG:
    debug('Importing module:', __name__)
    debug('  from package:', __package__)
    debug('  compiled:', __compiled)


#pxd/cdef enum: #
if True:
    MAX_TRANSFORMATIONS = 24
    MAX_BLOCKS = 1000
    #pxd:
    ATTRIB_LOCATION = 0
    PICKATTRIB_LOCATION = 5
    #px.

#pxd>ctypedef float vec4[4]
#pxd>ctypedef vec4 mat4[4]
#px-
mat4 = lambda: [[0.]*4 for _i in range(4)]

#pxd/cdef matrix_set_identity(mat4& matrix):
def matrix_set_identity(matrix):
    matrix[0][0] = 1.; matrix[0][1] = 0.; matrix[0][2] = 0.; matrix[0][3] = 0.
    matrix[1][0] = 0.; matrix[1][1] = 1.; matrix[1][2] = 0.; matrix[1][3] = 0.
    matrix[2][0] = 0.; matrix[2][1] = 0.; matrix[2][2] = 1.; matrix[2][3] = 0.
    matrix[3][0] = 0.; matrix[3][1] = 0.; matrix[3][2] = 0.; matrix[3][3] = 1.
    
#px/cdef struct Block:
class Block:     # pylint: disable=R0903
#px-
    def __init__(self):
        #px/vec4 *transformation
        self.transformation = None
        
        #px/bint in_motion
        self.in_motion = None
        
        #px/int idx_triangles
        self.idx_triangles = None
        #px/int cnt_triangles
        self.cnt_triangles = None
        
#px/cdef struct Cube:
class cube:     # pylint: disable=W0232, R0903
    #px/mat4 transformations[MAX_TRANSFORMATIONS]
    transformations = [[[None]*4, [None]*4, [None]*4, [None]*4] for __t in range(MAX_TRANSFORMATIONS)]
        
    #px+unsigned int number_blocks
    #px/Block blocks[MAX_BLOCKS]
    blocks = [Block() for __block in range(MAX_BLOCKS)]
    
    #px+int cnt_pick
    #px+int idx_debug
    
    #px+GLuint object_location
    #px+GLuint glbuffer
    
#px+cdef Cube cube

#px/cdef struct Animation_Struct:
class animation:
    #px+float angle, angle_max
    #px+float rotation_x, rotation_y, rotation_z
    #px/mat4 rotation_matrix
    rotation_matrix = mat4()
#px+cdef Animation_Struct animation


def init_module():
    #px+cdef int i
    
    cube.number_blocks = 0
    #cube.blocks
    
    cube.cnt_pick = 0
    cube.idx_debug = 0
    
    animation.angle = animation.angle_max = 0

def set_transformations(blocks):
    #px+cdef unsigned int i, b
    for i, b in enumerate(blocks):
        cube.blocks[i].transformation = cube.transformations[b]
        cube.blocks[i].in_motion = False
    
#px/cpdef gl_set_atlas_texture(int x, int w, int h, bytes data):
def gl_set_atlas_texture(x, w, h, data):
    #px/glTexSubImage2D(GL_TEXTURE_2D, 0, x, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, <char*>data)
    glTexSubImage2D(GL_TEXTURE_2D, 0, x, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data)
    
#px/cpdef set_animation_start(blocks, float angle_max, float axisx, float axisy, float axisz):
def set_animation_start(blocks, angle_max, axisx, axisy, axisz):
    animation.angle = 0.0
    animation.angle_max = angle_max
    animation.rotation_x, animation.rotation_y, animation.rotation_z = axisx, axisy, axisz
    matrix_set_identity(animation.rotation_matrix)
    #px+cdef int block_id
    #px+cdef bint selected
    for block_id, unused_block in blocks:
        cube.blocks[block_id].in_motion = True
    
#px/cdef void _update_animation_rotation_matrix():
def _update_animation_rotation_matrix():
    #px/cdef float angle = animation.angle / 180. * M_PI
    angle = radians(animation.angle)
    #px+cdef float x, y, z
    x = animation.rotation_x
    y = animation.rotation_y
    z = animation.rotation_z
    #px+cdef float c, s
    c = cos(angle)
    s = sin(angle)
    
    animation.rotation_matrix[0][0] = x*x*(1-c) + c
    animation.rotation_matrix[0][1] = x*y*(1-c) + z*s
    animation.rotation_matrix[0][2] = x*z*(1-c) - y*s
    animation.rotation_matrix[1][0] = y*x*(1-c) - z*s
    animation.rotation_matrix[1][1] = y*y*(1-c) + c
    animation.rotation_matrix[1][2] = y*z*(1-c) + x*s
    animation.rotation_matrix[2][0] = x*z*(1-c) + y*s
    animation.rotation_matrix[2][1] = y*z*(1-c) - x*s
    animation.rotation_matrix[2][2] = z*z*(1-c) + c
    
#px/cpdef set_animation_next(float increment):
def set_animation_next(increment):
    animation.angle -= increment
    _update_animation_rotation_matrix()
    return abs(animation.angle) < animation.angle_max

#px/cdef void _mult_matrix(mat4 &dest, mat4 &src1, mat4 &src2):
def _mult_matrix(dest, src1, src2):
    #px+cdef int i, j, k
    #px+cdef float sum_
    for j in range(4):
        for i in range(4):
            sum_ = 0.
            for k in range(4):
                sum_ += src1[k][i] * src2[j][k]
            dest[j][i] = sum_
            
#pxd/cdef void gl_draw_cube():
def gl_draw_cube():
    #px+cdef unsigned int i
    #px/cdef mat4 object_matrix
    object_matrix = mat4()
    for i in range(cube.number_blocks):
        if cube.blocks[i].in_motion:
            _mult_matrix(object_matrix, animation.rotation_matrix, cube.blocks[i].transformation)
            #px/glUniformMatrix4fv(cube.object_location, 1, GL_FALSE, &object_matrix[0][0])
            glUniformMatrix4fv(cube.object_location, 1, GL_FALSE, object_matrix)
        else:
            #px/glUniformMatrix4fv(cube.object_location, 1, GL_FALSE, &cube.blocks[i].transformation[0][0])
            glUniformMatrix4fv(cube.object_location, 1, GL_FALSE, cube.blocks[i].transformation)
        glDrawArrays(GL_TRIANGLES, cube.blocks[i].idx_triangles, cube.blocks[i].cnt_triangles)
        
#pxd/cdef void gl_pick_cube():
def gl_pick_cube():
    glDrawArrays(GL_TRIANGLES, 0, cube.cnt_pick)
    
#pxd/cdef void gl_init_buffers():
def gl_init_buffers():
    #px/glGenBuffers(1, &cube.glbuffer)
    cube.glbuffer = glGenBuffers(1)
    
#pxd/cdef void gl_delete_buffers():
def gl_delete_buffers():
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    #px/glDeleteBuffers(1, &cube.glbuffer)
    glDeleteBuffers(1, [cube.glbuffer])
    cube.glbuffer = 0
    
#px/cdef void _gl_set_pointer(GLuint index, GLint size, GLenum type, GLboolean normalized, long pointer):
def _gl_set_pointer(index, size, type, normalized, pointer):
    #px/glVertexAttribPointer(index, size, type, normalized, 0, <void*>pointer)
    glVertexAttribPointer(index, size, type, normalized, 0, ctypes.cast(pointer, ctypes.c_void_p))
    glEnableVertexAttribArray(index)
    
#px/cpdef gl_set_data(int nblocks, bytes vertexdata, vertexpointers, vertexinfo, transformations):
def gl_set_data(nblocks, vertexdata, vertexpointers, vertexinfo, transformations):
    assert nblocks <= MAX_BLOCKS, '{} blocks, hardcoded limit is {}'.format(
                                  nblocks, MAX_BLOCKS)
    cube.number_blocks = nblocks
    
    #### Create the raw GL data ####
    #px+cdef long normalpointer, colorpointer, texpospointer, barycpointer
    #px+cdef unsigned int idx_debug, cnt_pick
    normalpointer, colorpointer, texpospointer, barycpointer, pickvertpointer, pickcolorpointer = vertexpointers
    cnts_block, idx_debug, cnt_pick = vertexinfo
    
    #px+cdef unsigned int idx_block, idx, i, j
    idx_block = 0
    
    for idx in range(cube.number_blocks):
        cube.blocks[idx].idx_triangles = idx_block
        cube.blocks[idx].cnt_triangles = cnts_block[idx]
        idx_block += cnts_block[idx]
        
    cube.cnt_pick = cnt_pick
    cube.idx_debug = idx_debug
    
    glBindBuffer(GL_ARRAY_BUFFER, cube.glbuffer)
    #px/glBufferData(GL_ARRAY_BUFFER, len(vertexdata), <char*>vertexdata, GL_STATIC_DRAW)
    glBufferData(GL_ARRAY_BUFFER, len(vertexdata), vertexdata, GL_STATIC_DRAW)
    
    _gl_set_pointer(ATTRIB_LOCATION,   3, GL_FLOAT, GL_FALSE, 0)
    _gl_set_pointer(ATTRIB_LOCATION+1, 3, GL_FLOAT, GL_FALSE, normalpointer)
    _gl_set_pointer(ATTRIB_LOCATION+2, 3, GL_UNSIGNED_BYTE, GL_TRUE, colorpointer)
    _gl_set_pointer(ATTRIB_LOCATION+3, 2, GL_FLOAT, GL_FALSE, texpospointer)
    _gl_set_pointer(ATTRIB_LOCATION+4, 3, GL_FLOAT, GL_FALSE, barycpointer)
    _gl_set_pointer(PICKATTRIB_LOCATION, 3, GL_FLOAT, GL_FALSE, pickvertpointer)
    _gl_set_pointer(PICKATTRIB_LOCATION+1, 3, GL_UNSIGNED_BYTE, GL_TRUE, pickcolorpointer)
    
    assert len(transformations) <= MAX_TRANSFORMATIONS, '{} transformations, hardcoded limit is {}'.format(
                                                        len(transformations), MAX_TRANSFORMATIONS)
    for idx in range(len(transformations)):
        for i in range(4):
            for j in range(4):
                cube.transformations[idx][i][j] = float(transformations[idx][i][j])
                
#pxd/cdef void gl_draw_cube_debug():
def gl_draw_cube_debug():
    #px/cdef mat4 object_matrix
    object_matrix = mat4()
    matrix_set_identity(object_matrix)
    #px/glUniformMatrix4fv(cube.object_location, 1, GL_FALSE, &object_matrix[0][0])
    glUniformMatrix4fv(cube.object_location, 1, GL_FALSE, object_matrix)
    glDrawArrays(GL_LINES, cube.idx_debug, 6)
    
#pxd/cdef void gl_draw_select_debug(GLfloat *selectdata, GLsizeiptr size, GLuint prog_hud):
def gl_draw_select_debug(selectdata, size, prog_hud):
    #px+cdef int i, j
    #px+cdef GLintptr offset
    offset = (cube.idx_debug+6) * 3 * sizeof(GLfloat)
    #px/glBufferSubData(GL_ARRAY_BUFFER, offset, size, &selectdata[0])
    glBufferSubData(GL_ARRAY_BUFFER, offset, len(selectdata) * sizeof(GLfloat), ArrayDatatype.asArray(selectdata, GL_FLOAT))
    
    glDisable(GL_DEPTH_TEST)
    glDrawArrays(GL_LINES, cube.idx_debug+6, 2)
    glUseProgram(prog_hud)
    glDrawArrays(GL_POINTS, cube.idx_debug+6+2, 2)
    glEnable(GL_DEPTH_TEST)
    
#pxd/cdef void gl_init_object_location(GLuint location):
def gl_init_object_location(location):
    cube.object_location = location
    

