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

#  Copyright © 2009-2017  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/>.

# pylint: disable=W0614,C0326
# 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

#px/from libc.math cimport M_PI, cos, sin
from math import radians, cos, sin

#pxm>IF '[[GLDEBUG]]' == 'gldebug'
#pxd+from [[_debug_VARIANT]] cimport *
#pxm>IF '[[GLDEBUG]]' != 'gldebug'
#pxd+from gl_[[GLVARIANT]] cimport *
#pxm>IF_END

#px:
from pybiklib.debug import DEBUG_LOGGL
try:
    if DEBUG_LOGGL:
        import OpenGL
        OpenGL.FULL_LOGGING = True
    from OpenGL.GLU import * # XXX: without the next line fails with python 3.5
    from OpenGL.GL import *     # pylint: disable=W0614,W0401
except ImportError as e:
    print('The pure Python mode needs PyOpenGL (for Python 3):', e)
    raise SystemExit(1)
import ctypes
from ctypes import sizeof
from OpenGL.raw.GL.VERSION.GL_2_0 import glVertexAttribPointer
#px.


#px/cdef enum: #
if True:
    DEBUG_MSGEXT = 8
#px+cdef long debug
debug = 0
    
def set_debug_flags(module):
    global debug
    if module.DEBUG_MSGEXT: debug |= DEBUG_MSGEXT
    
#px-
NULL = None

#pxd>cdef enum: #
#px-
if True:
    #pxd:
    MAX_TRANSFORMATIONS = 60
    MAX_BLOCKS = 1312
    MAX_FACES = 20
    ATTRIB_LOCATION = 0
    PICKATTRIB_LOCATION = 5
    ATTRIB_CNT = 7
    #px.

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

#pxm-FUNC PD nogil
def matrix_set_identity(matrix:'mat4 &'):
    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+int cnt_debug
    
    #px+GLuint object_location
    #px+GLuint glbuffer
    
    #px+int vertexdatalen
    #px+char *vertexdata
    #px/long attrib_pointers[ATTRIB_CNT]
    attrib_pointers = [None] * ATTRIB_CNT
    
    # animated slice
    #px/mat4 rotation_matrix
    rotation_matrix = mat4()
    
#px+cdef Cube cube


#pxm-FUNC PD
def init_gldraw():
    if debug & DEBUG_MSGEXT:
        print('init module:', __name__)
        print('  from package:', __package__)
        print('  compiled:', __compiled)
        #px+print('  GL-type: [[GLVARIANT]]')
    cube.number_blocks = 0
    cube.cnt_pick = 0
    cube.idx_debug = 0
    cube.cnt_debug = 0
    cube.vertexdatalen = 0
    cube.vertexdata = NULL
    
#pxm-FUNC PD nogil
def sync_block_transformations(blocks:'short *'):
    #px+cdef unsigned int i
    for i in range(cube.number_blocks):
        cube.blocks[i].transformation = cube.transformations[blocks[i]]
        cube.blocks[i].in_motion = False
    
#pxm-FUNC PD nogil
def sync_animation_start(blocks_count:int, blocks:'short *'):
    matrix_set_identity(cube.rotation_matrix)
    #px+cdef int i
    for i in range(blocks_count):
        cube.blocks[blocks[i]].in_motion = True
    
#pxm-FUNC PD nogil
def sync_animation_next(angle:float, rotation_x:float, rotation_y:float, rotation_z:float):
    #px/angle = angle / 180. * M_PI
    angle = radians(angle)
    #px+cdef float x, y, z
    x = rotation_x
    y = rotation_y
    z = rotation_z
    #px+cdef float c, s
    c = cos(angle)
    s = sin(angle)
    
    cube.rotation_matrix[0][0] = x*x*(1-c) + c
    cube.rotation_matrix[0][1] = x*y*(1-c) + z*s
    cube.rotation_matrix[0][2] = x*z*(1-c) - y*s
    cube.rotation_matrix[1][0] = y*x*(1-c) - z*s
    cube.rotation_matrix[1][1] = y*y*(1-c) + c
    cube.rotation_matrix[1][2] = y*z*(1-c) + x*s
    cube.rotation_matrix[2][0] = x*z*(1-c) + y*s
    cube.rotation_matrix[2][1] = y*z*(1-c) - x*s
    cube.rotation_matrix[2][2] = z*z*(1-c) + c
    
#pxm-FUNC P nogil
def matrix_mult(dest:'mat4 &', src1:'mat4 &', src2:'mat4 &'):
    #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_
            
#pxm-FUNC PD nogil
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:
            matrix_mult(object_matrix, cube.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)
        
#pxm-FUNC PD nogil
def gl_pick_cube():
    glDrawArrays(GL_TRIANGLES, 0, cube.cnt_pick)
    
#pxm-FUNC PD nogil
def gl_init_buffers():
    #px/glGenBuffers(1, &cube.glbuffer)
    cube.glbuffer = glGenBuffers(1)
    
#pxm-FUNC PD nogil
def gl_delete_buffers():
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    #px/glDeleteBuffers(1, &cube.glbuffer)
    glDeleteBuffers(1, [cube.glbuffer])
    cube.glbuffer = 0
    
#pxm-FUNC P nogil
def _gl_enable_pointer(index:'GLuint', size:'GLint', type:'GLenum', normalized:'GLboolean', pointer:'long'):
    #px/glVertexAttribPointer(index, size, type, normalized, 0, <void*>pointer)
    glVertexAttribPointer(index, size, type, normalized, 0, ctypes.cast(pointer, ctypes.c_void_p))
    glEnableVertexAttribArray(index)
    
#pxm-FUNC P nogil
def _gl_disable_pointer(index:'GLuint'):
    #px/glVertexAttribPointer(index, 4, GL_FLOAT, GL_FALSE, 0, NULL)
    glVertexAttribPointer(index, 4, GL_FLOAT, GL_FALSE, 0, ctypes.cast(0, ctypes.c_void_p))
    glDisableVertexAttribArray(index)
    
#pxm-FUNC PD nogil
def sync_blocks(nblocks:int, cnts_block:'short *', idx_debug:int, cnt_debug:int, cnt_pick:int):
    #px+cdef unsigned int idx_block, idx
    cube.number_blocks = nblocks
    
    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
    cube.cnt_debug = cnt_debug
    
#pxm-FUNC PD nogil
def sync_vertexdata(vertexdatalen:int, vertexdata:'char *', vertexpointers:'long *'):
    cube.vertexdatalen = vertexdatalen
    cube.vertexdata = vertexdata
    cube.attrib_pointers[0] = 0
    cube.attrib_pointers[1] = vertexpointers[0]
    cube.attrib_pointers[2] = vertexpointers[1]
    cube.attrib_pointers[3] = vertexpointers[2]
    cube.attrib_pointers[4] = vertexpointers[3]
    cube.attrib_pointers[5] = vertexpointers[4]
    cube.attrib_pointers[6] = vertexpointers[5]
    
#pxm-FUNC PD nogil
def sync_transformations(transformations_count:int, transformations:'mat4 *'):
    #px+cdef int t
    #px+cdef unsigned int i, j
    for t in range(transformations_count):
        for i in range(4):
            for j in range(4):
                cube.transformations[t][i][j] = float(transformations[t][i][j])
                
#pxm-FUNC PD nogil
def gl_enable_data():
    glBindBuffer(GL_ARRAY_BUFFER, cube.glbuffer)
    glBufferData(GL_ARRAY_BUFFER, cube.vertexdatalen, cube.vertexdata, GL_STATIC_DRAW)
        
    _gl_enable_pointer(ATTRIB_LOCATION,   3, GL_FLOAT, GL_FALSE, cube.attrib_pointers[0])
    _gl_enable_pointer(ATTRIB_LOCATION+1, 3, GL_FLOAT, GL_FALSE, cube.attrib_pointers[1])
    _gl_enable_pointer(ATTRIB_LOCATION+2, 3, GL_UNSIGNED_BYTE, GL_TRUE, cube.attrib_pointers[2])
    _gl_enable_pointer(ATTRIB_LOCATION+3, 2, GL_FLOAT, GL_FALSE, cube.attrib_pointers[3])
    _gl_enable_pointer(ATTRIB_LOCATION+4, 3, GL_FLOAT, GL_FALSE, cube.attrib_pointers[4])
    _gl_enable_pointer(PICKATTRIB_LOCATION, 3, GL_FLOAT, GL_FALSE, cube.attrib_pointers[5])
    _gl_enable_pointer(PICKATTRIB_LOCATION+1, 3, GL_UNSIGNED_BYTE, GL_TRUE, cube.attrib_pointers[6])
    
#pxm-FUNC PD nogil
def gl_disable_data():
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    _gl_disable_pointer(ATTRIB_LOCATION)
    _gl_disable_pointer(ATTRIB_LOCATION+1)
    _gl_disable_pointer(ATTRIB_LOCATION+2)
    _gl_disable_pointer(ATTRIB_LOCATION+3)
    _gl_disable_pointer(ATTRIB_LOCATION+4)
    _gl_disable_pointer(PICKATTRIB_LOCATION)
    _gl_disable_pointer(PICKATTRIB_LOCATION+1)
    
#pxm-FUNC PD nogil
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, cube.cnt_debug)
    
#pxm-FUNC PD nogil
def gl_draw_select_debug(selectdata:'GLfloat *', size:'GLsizeiptr', prog_hud:'GLuint'):
    #px+cdef int i, j
    #px+cdef GLintptr offset
    if cube.cnt_debug == 0:
        #XXX: The data used here has fixed size, but may not be initialized yet
        return
    offset = (cube.idx_debug+cube.cnt_debug) * 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+cube.cnt_debug, 2)
    glUseProgram(prog_hud)
    glDrawArrays(GL_POINTS, cube.idx_debug+cube.cnt_debug+2, 2)
    
#pxm-FUNC PD nogil
def gl_init_object_location(location:'GLuint'):
    cube.object_location = location
    

