#!/usr/bin/python3
# -*- coding: utf-8 -*-

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


import sys, os
import re
import pickle
from array import array

from .debug import DEBUG_DRAW, debug
from .config import MODELS_DIR
from .modelcommon import filebyteorder, epsilon, get_texcoords_range, Vector, epsdigits

try:
    _
except NameError:
    _ = lambda t: t
    

def get_texcoords(vector, normal, minx, maxx, miny, maxy):
    x, y = vector.rotationyx_normal(normal)
    return (x - minx) / (maxx - minx), (y - miny) / (maxy - miny)
    
        
class EmptyModel:
    symbols = ''
    symbolsI = ''
    faces = ''
    default_rotation = (0., 0.)
    
    size = sizes = ()
    blocks = []
    cell_count = 0
    cells_visible_faces = []
    
    def __init__(self, *unused_args):
        pass
        
    def __str__(self):
        return 'no puzzle'
        
    def gl_vertex_data(self, *unused_args):
        return (0, b'', (0, 0, 0, 0, 0, 0), ([], 0, 0), []), [()]
        
        
class Model:
    cache_index = {}
    cache_data = {}
    cache_vectors = []
    
    def __init__(self, mtype, size):
        items = self.cache_index['type'][mtype].items()
        self.type = mtype
        for key, value in items:
            setattr(self, key, value)
        self.mformat = _(self.mformat)
        # size for UI and config, sizes for plugins
        self.size = self.cache_index['normsize'][mtype].get(size, self.defaultsize)
        self.sizes, filename = self.cache_index['sizes'][mtype][self.size]
        
        self.load_data(filename)
        
        self.cell_count = len(self.cell_indices)
        self.rotation_indices = {s:i for i, s in enumerate(self.rotation_symbols)}
        self._inplace_rotations = None
        
    @property
    def inplace_rotations(self):
        if self._inplace_rotations is None:
            # slow and only used in edit mode
            self._inplace_rotations = []
            for cellidx in range(self.cell_count):
                inplace_rotations = []
                for rotsym, blocknum_blocknum2 in self.rotated_position.items():
                    for blocknum, blocknum2 in enumerate(blocknum_blocknum2):
                        if blocknum == blocknum2 == cellidx and rotsym:
                            inplace_rotations.append(rotsym)
                self._inplace_rotations.append(sorted(inplace_rotations, key=len)[:2])
        return self._inplace_rotations
        
    def __str__(self):
        return self.mformat.format(*self.sizes)
        
    @classmethod
    def load_index(cls):
        indexfilename = os.path.join(MODELS_DIR, 'index')
        with open(indexfilename, 'rb') as indexfile:
            cls.cache_index = pickle.load(indexfile)
            
    @staticmethod
    def read_vectors(datafile):
        def isplit3(vals):
            it = iter(vals)
            for x in it:
                try:
                    yield Vector((x, next(it), next(it)))
                except StopIteration:
                    raise ValueError('len must be a multiple of 3')
        vlen = array('I')
        vlen.fromfile(datafile, 1)
        if sys.byteorder != filebyteorder:
            vlen.byteswap()
        vlen = vlen[0]
        vectors = array('f')
        vectors.fromfile(datafile, vlen)
        if sys.byteorder != filebyteorder:
            vectors.byteswap()
        return list(isplit3(vectors))
        
    def load_data(self, filename):
        try:
            data = self.cache_data[self.type][self.sizes]
        except KeyError:
            filename = os.path.join(MODELS_DIR, filename)
            debug('loading model data:', filename)
            with open(filename, 'rb') as datafile:
                data = pickle.load(datafile)
                vectors = self.read_vectors(datafile)
            self.__class__.cache_data = data
            self.__class__.cache_vectors = vectors
            data = data[self.type][self.sizes]
        self.vectors = self.cache_vectors
        for key, value in data.items():
            setattr(self, key, value)
            
    @classmethod
    def from_string(cls, modelstr):
        if modelstr == '*':
            return '*', '*', (), None
        re_model = r'''^(\w+)
                        (?: \s+(\w+)
                            (?: \s*(\W)
                                \s*(\w+)
                                (?: \s*\3
                                    \s*(\w+)
                            )?)?
                            (?:\s+with
                                \s+(.+)
                        )?)?\s*$'''
        match = re.match(re_model, modelstr, re.X)
        if match is None:
            raise ValueError('Invalid model: ' + modelstr)
        #TODO: len(sizes) > 3
        mtype, width, height, depth, exp = match.group(1, 2, 4, 5, 6)
        if mtype not in cls.cache_index['types']:
            raise ValueError('Unknown model type %r' % mtype)
        def convert_if_int(value):
            if value is None:
                return value
            try:
                return int(value)
            except ValueError:
                return value
        sizes = tuple(convert_if_int(s) for s in (width, height, depth))
        return modelstr, mtype, sizes, exp
        
    def norm_symbol(self, sym):
        try:
            return self.normal_rotation_symbols[sym]
        except KeyError:
            new_sym = ''
            for c in sym:
                try:
                    new_sym = self.normal_rotation_symbols[new_sym+c]
                except KeyError:
                    raise ValueError('invalid symbol:', sym)
            return new_sym
            
    def rotations_symbolic_to_index(self, rotations):
        for sym in rotations:
            yield self.rotation_indices[sym]
        
    def blocksym_to_blockpos(self, blocksym):
        for i, faces in enumerate(self.cells_visible_faces):
            #FIXME: different blocks can have same visible faces (eg. edges for 4-Cubes)
            if sorted(faces) == sorted(list(blocksym)):
                return i
        raise ValueError('Invalid block symbol:', blocksym)
        
    def blockpos_to_blocksym(self, blockpos, rotsym):
        #TODO: use cells_visible_faces, see blocksym_to_blockpos
        def axisidx_to_sym(axis, idx):
            if idx <= self.sizes[axis] // 2:
                sym = self.symbols[axis]
            else:
                idx = self.sizes[axis]-1 - idx
                sym = self.symbolsI[axis]
            if idx == 0:
                # skip idx for corners
                return sym, self.face_symbolic_to_face_color(sym, rotsym)
            elif idx == 1 and self.sizes[axis] == 3:
                # for size == 3 there is only one edge
                return '', ''
            else:
                # symbol with index to distinguish edge, but no color because the face is not visible
                return sym + str(idx+1), '?'
        x, y, z = self.cell_indices[blockpos]
        symx, colorsymx = axisidx_to_sym(0, x)
        symy, colorsymy = axisidx_to_sym(1, y)
        symz, colorsymz = axisidx_to_sym(2, z)
        return symx + symy + symz, colorsymx + colorsymy + colorsymz
        
    def face_symbolic_to_face_color(self, face, rot):
        for k, v in self.face_permutations[rot].items():
            if v == face:
                return k
        assert False, (face, rot)
        
    def sym_to_move(self, sym, mslice=-1):
        try:
            maxis = self.symbols.index(sym)
            return maxis, mslice, False
        except ValueError:
            maxis = self.symbolsI.index(sym)
            return maxis, mslice, True
        
    def normalize_complete_moves(self, moves):
        syms = ''.join((self.symbols if not mdir else self.symbolsI)[maxis] for maxis, unused_mslice, mdir in moves)
        syms = self.norm_symbol(syms)
        for sym in syms:
            yield self.sym_to_move(sym)
        
    def rotate_symbolic(self, axis, rdir, block, sym):
        rsym = (self.symbols if not rdir else self.symbolsI)[axis]
        block = self.rotated_position[rsym][block]
        sym = self.norm_symbol(sym + rsym)
        return block, sym
        
    def rotate_move(self, complete_move, move):
        caxis, unused_cslice, cdir = complete_move
        maxis, mslice, mdir = move
        caxissym = (self.symbols if cdir else self.symbolsI)[caxis]
        maxissym = (self.symbols if not mdir else self.symbolsI)[maxis]
        raxissym = self.face_permutations[caxissym][maxissym]
        raxis, unused_rslice, rdir = self.sym_to_move(raxissym)
        if mdir != rdir:
            mslice = self.sizes[raxis] - 1 - mslice
        return raxis, mslice, rdir
        
    def _get_selected_moves(self, cellidx, vector):
        for iaxis, axis in enumerate(self.axes):
            vda = vector.dot(axis)
            if -epsilon < vda < epsilon:
                continue
            mslice = self.cell_indices[cellidx][iaxis]
            if mslice is not None:
                yield iaxis, mslice, vda
            
    def get_selected_moves2(self, cellidx, vert1, vert2):
        res = sorted(self._get_selected_moves(cellidx, vert2 - vert1), key=lambda m: m[2])
        a1, s1, d1 = res[0]
        if len(res) == 1:
            return True, (a1, s1, d1>0)
        a2, s2, d2 = res[-1]
        return False, (a1, s1, d1>0,  a2, s2, d2>0)
        
    def get_selected_move_center(self, cellidx, vector):
        moves = ((ma, ms, md) for ma, ms, md in self._get_selected_moves(cellidx, vector) if ms in [0,self.sizes[ma]-1])
        ma, ms, md = max(moves, key=lambda m: m[2])
        return ma, ms, md>0
        
    @staticmethod
    def triangulate(indices, reverse=False):
        indices = iter(indices)
        first = next(indices)
        prev = next(indices)
        for index in indices:
            yield first
            if reverse:
                yield index
                yield prev
            else:
                yield prev
                yield index
            prev = index
            
    @staticmethod
    def triangulate_barycentric(count, indices, reverse=False):
        if count == 3:
            index1, index2, index3 = indices
            yield index1, (1., 0., 0.)
            index2 = index2, (0., 1., 0.)
            index3 = index3, (0., 0., 1.)
            if reverse:
                yield index3; yield index2
            else:
                yield index2; yield index3
        elif count == 4:
            # +---+  4---3  +-1-+ 
            # | / |  | / |  3 2 1 
            # +---+  1---2  +-3-+ 
            index1, index2, index3, index4 = indices
            index1 = index1, (1., 1., 0.)
            index2 = index2, (0., 1., 0.)
            index3 = index3, (0., 1., 1.)
            index4 = index4, (0., 1., 0.)
            if reverse:
                yield index1; yield index3; yield index2; yield index1; yield index4; yield index3
            else:
                yield index1; yield index2; yield index3; yield index1; yield index3; yield index4
        elif count == 5:
            #   +---+    4---3    +---+ 
            #  /|  /|   /|  /|   2|  /| 
            # + | / |  5 | / |  + 3 3 2 
            #  \|/  |   \|/  |   1|/  | 
            #   +---+    1---2    +-1-+ 
            index1, index2, index3, index4, index5 = indices
            index1 = index1, (0., 1., 1.)
            index2 = index2, (0., 0., 1.)
            index3 = index3, (1., 0., 1.)
            index4 = index4, (1., 0., 1.)
            index5 = index5, (0., 0., 1.)
            if reverse:
                yield index1; yield index3; yield index2; yield index1; yield index4; yield index3
                yield index1; yield index5; yield index4
            else:
                yield index1; yield index2; yield index3; yield index1; yield index3; yield index4
                yield index1; yield index4; yield index5
        elif count == 6:
            #   +---+      1---6      +---+   
            #  /|\  |\    /|\  |\    1|\  |1  
            # + | \ | +  2 | \ | 5  + 3 \ 3 + 
            #  \|  \|/    \|  \|/    2|  \|2  
            #   +---+      3---4      +---+   
            index1, index2, index3, index4, index5, index6 = indices
            index1a = index1, (0., 1., 1.)
            index1b = index1, (0., 1., 1.)
            index2 = index2, (0., 0., 1.)
            index3 = index3, (1., 0., 1.)
            index4 = index4, (1., 0., 1.)
            index5 = index5, (0., 0., 1.)
            index6 = index6, (0., 1., 1.)
            if reverse:
                yield index1a; yield index3; yield index2; yield index1b; yield index4; yield index3
                yield index1b; yield index6; yield index4; yield index4; yield index6; yield index5
            else:
                yield index1a; yield index2; yield index3; yield index1a; yield index3; yield index4
                yield index1a; yield index4; yield index6; yield index4; yield index5; yield index6
        else:
            assert False, count
            
    def gl_label_vertices(self, polygons, faces, mirror_distance):
        for *cell_vertices, faceno in polygons:
            fd = faces[faceno]
            cell_vertices = [self.vectors[i] for i in cell_vertices]
            normal = self.vectors[self.normals[faceno]]
            color = fd.color
            if fd.imagemode:
                texrange = self.texranges_mosaic[faceno]
            else:
                texrange = get_texcoords_range(cell_vertices, normal)
            texpos = (get_texcoords(v, normal, *texrange) for v in cell_vertices)
            x1,y1,x2,y2 = fd.texrect
            texpos = [(tpx*(x2-x1) + x1, tpy*(y2-y1) + y1) for tpx, tpy in texpos]
            
            for (vertex, tp), baryc in self.triangulate_barycentric(
                                                    len(cell_vertices),
                                                    zip(cell_vertices, texpos)):
                yield vertex, normal, color, tp, baryc
            if mirror_distance is not None:
                offset = normal * mirror_distance
                mirror_normal = -normal
                for (vertex, tp), baryc in self.triangulate_barycentric(
                                                    len(cell_vertices),
                                                    zip(cell_vertices, texpos),
                                                    reverse=True):
                    yield vertex + offset, mirror_normal, color, tp, baryc
                    
    def gl_black_vertices(self, polygons):
        def vngroup(vns):
            vns = iter(vns)
            for v in vns:
                yield v, next(vns)
        for vertices_normals in polygons:
            for ivertex, inormal in self.triangulate(vngroup(vertices_normals)):
                yield self.vectors[ivertex], self.vectors[inormal]
                
    def pickarrowdir(self, maxis, mdir, normal):
        if mdir:
            return Vector(self.axes[maxis]).cross(normal).normalised()
        else:
            return normal.cross(Vector(self.axes[maxis])).normalised()
            
    def _gl_pick_triangles_pickdata_helper(self, cellidx, symbol, normal, vert1, vert2, pickdata):
        def roundeps(val):
            return round(val, epsdigits)
        one, masd = self.get_selected_moves2(cellidx, vert1, vert2)
        if one:
            maxis, mslice, mdir = masd
            arrowdir = self.pickarrowdir(maxis, mdir, normal)
            pickdata.append((maxis, mslice, mdir, cellidx, symbol, arrowdir))
        else:
            maxis1, mslice1, mdir1,  maxis2, mslice2, mdir2 = masd
            sdist1 = roundeps(abs(vert1.dot(self.axes[maxis1]) - self.slice_centers[maxis1][mslice1]))
            sdist2 = roundeps(abs(vert1.dot(self.axes[maxis2]) - self.slice_centers[maxis2][mslice2]))
            if sdist1 <= sdist2:
                arrowdir = self.pickarrowdir(maxis1, mdir1, normal)
                pickdata.append((maxis1, mslice1, mdir1, cellidx, symbol, arrowdir))
            else:
                arrowdir = self.pickarrowdir(maxis2, mdir2, normal)
                pickdata.append((maxis2, mslice2, mdir2, cellidx, symbol, arrowdir))
            sdist1 = roundeps(abs(vert2.dot(self.axes[maxis1]) - self.slice_centers[maxis1][mslice1]))
            sdist2 = roundeps(abs(vert2.dot(self.axes[maxis2]) - self.slice_centers[maxis2][mslice2]))
            if sdist1 < sdist2:
                arrowdir = self.pickarrowdir(maxis1, mdir1, normal)
                pickdata.append((maxis1, mslice1, mdir1, cellidx, symbol, arrowdir))
            else:
                arrowdir = self.pickarrowdir(maxis2, mdir2, normal)
                pickdata.append((maxis2, mslice2, mdir2, cellidx, symbol, arrowdir))
        return one
            
    def _gl_pick_triangulate(self, color1, vertsl, normal, mirror_distance):
        for i, verts in enumerate(vertsl):
            for vert in self.triangulate(verts):
                yield color1 + i, vert
        if mirror_distance is not None:
            mirror_offset = normal * mirror_distance
            vertsl = [[v + mirror_offset for v in verts] for verts in vertsl]
            for i, verts in enumerate(vertsl):
                for vert in self.triangulate(verts, reverse=True):
                    yield color1 + i, vert
                    
    def gl_pick_triangles_center(self, cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata):
        # selection_mode: simple, no adjacent visible faces
        maxis, mslice, mdir = self.get_selected_move_center(cellidx, normal)
        color = len(pickdata)
        pickdata.append((maxis, mslice, mdir, cellidx, symbol, None))
        verts = [self.vectors[i] for i in edges_iverts]
        for vert in self.triangulate(verts):
            yield color, vert
        if mirror_distance is not None:
            mirror_offset = normal * mirror_distance
            color = len(pickdata)
            pickdata.append((maxis, mslice, not mdir, cellidx, symbol, None))
            for vert in self.triangulate(verts, reverse=True):
                yield color, vert + mirror_offset
                
    def gl_pick_triangles_edge3(self, cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata):
        # selection_mode: simple, face is on an edge block of a tetrahedron
        # the first and second vertex are on the edge of the tetrahedron
        vert1, vert2, vert3 = (self.vectors[i] for i in edges_iverts)
        color1 = len(pickdata)
        one = self._gl_pick_triangles_pickdata_helper(cellidx, symbol, normal, vert1, vert2, pickdata)
        if one:
            vertsl = [[vert1, vert2, vert3]]
        else:
            v12 = (vert1 + vert2) / 2
            vertsl = [[v12, vert2, vert3], [vert1, v12, vert3]]
        yield from self._gl_pick_triangulate(color1, vertsl, normal, mirror_distance)
            
    def gl_pick_triangles_edge6(self, cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata):
        # selection_mode: simple, face is on an edge block and has 6 vertices, rotates 2 slices
        # the first and second vertex are on the edge
        vert1, vert2, vert3, vert4, vert5, vert6 = (self.vectors[i] for i in edges_iverts)
        v12 = (vert1 + vert2) / 2
        v45 = (vert4 + vert5) / 2
        color = len(pickdata)
        one = self._gl_pick_triangles_pickdata_helper(cellidx, symbol, normal, vert1, vert2, pickdata)
        if one:
            vertsl = [[vert1, vert2, vert3, vert4, vert5, vert6]]
        else:
            vertsl = [[v12, vert2, vert3, vert4, v45], [vert1, v12, v45, vert5, vert6]]
        yield from self._gl_pick_triangulate(color, vertsl, normal, mirror_distance)
                
    def gl_pick_triangles_edge4(self, cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata):
        # selection_mode: simple, face is on an edge block, 4 vertices, rotate 1 or 2 slices,
        # the first and second vertex are on the edge
        vert1, vert2, vert3, vert4 = (self.vectors[i] for i in edges_iverts)
        color = len(pickdata)
        one = self._gl_pick_triangles_pickdata_helper(cellidx, symbol, normal, vert1, vert2, pickdata)
        if one:
            vertsl = [[vert1, vert2, vert3, vert4]]
        else:
            #assert ncolors == 2
            ec12 = (vert1 + vert2) / 2
            ec34 = (vert3 + vert4) / 2
            vertsl = [[ec12, vert2, vert3, ec34], [vert1, ec12, ec34, vert4]]
        yield from self._gl_pick_triangulate(color, vertsl, normal, mirror_distance)
                
    def gl_pick_triangles_corner34(self, cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata):
        # selection_mode: simple, face is on a corner block, 3 or 4 vertices,
        # the second vert is on the corner, the first and third are on the edges
        if len(edges_iverts) == 3:
            vert1, vert2, vert3 = (self.vectors[iv] for iv in edges_iverts)
            vert4 = (vert3 + vert1) / 2
        else:
            vert1, vert2, vert3, vert4 = (self.vectors[iv] for iv in edges_iverts)
        # first triangle
        color = len(pickdata)
        one = self._gl_pick_triangles_pickdata_helper(cellidx, symbol, normal, vert1, vert2, pickdata)
        if one:
            vertsl = [[vert1, vert2, vert4]]
        else:
            ec12 = (vert1 + vert2) / 2
            vertsl = [[ec12, vert2, vert4], [vert1, ec12, vert4]]
        # second triangle
        one = self._gl_pick_triangles_pickdata_helper(cellidx, symbol, normal, vert2, vert3, pickdata)
        if one:
            vertsl.append([vert2, vert3, vert4])
        else:
            ec23 = (vert2 + vert3) / 2
            vertsl.extend([[ec23, vert3, vert4], [vert2, ec23, vert4]])
        yield from self._gl_pick_triangulate(color, vertsl, normal, mirror_distance)
                    
    def gl_pick_triangles_corner5(self, cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata):
        # selection_mode: simple, face is on a corner block, 5 vertices,
        # the second vert is on the corner, the first and third are on the edges
        vert1, vert2, vert3, vert4, vert5 = (self.vectors[iv] for iv in edges_iverts)
        vc13 = (vert1 + vert3) / 2
        ec45 = (vert4 + vert5) / 2
        color1 = len(pickdata)
        one = self._gl_pick_triangles_pickdata_helper(cellidx, symbol, normal, vert1, vert2, pickdata)
        if one:
            vertsl = [[vert1, vert2, ec45, vert5]]
        else:
            vertsl = [[vert1, vert2, vc13], [vert1, vc13, ec45, vert5]]
        one = self._gl_pick_triangles_pickdata_helper(cellidx, symbol, normal, vert2, vert3, pickdata)
        if one:
            vertsl.append([vert2, vert3, vert4, ec45])
        else:
            vertsl.extend([[vc13, vert3, vert4, ec45], [vert2, vert3, vc13]])
        yield from self._gl_pick_triangulate(color1, vertsl, normal, mirror_distance)
        
    def gl_pick_triangles_any(self, cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata):
        # either selection_mode is quadrant or weird model (size==1 in at least one dimension)
        verts = [self.vectors[i] for i in edges_iverts]
        vc = sum(verts, Vector()) / len(verts)
        vert1 = verts[-1]
        for vert2 in verts:
            color1 = len(pickdata)
            one = self._gl_pick_triangles_pickdata_helper(cellidx, symbol, normal, vert1, vert2, pickdata)
            if one:
                vertsl = [[vc, vert1, vert2]]
            else:
                #assert ncolors == 2
                ec = (vert1 + vert2) / 2
                vertsl = [[vc, vert1, ec], [vc, ec, vert2]]
            yield from self._gl_pick_triangulate(color1, vertsl, normal, mirror_distance)
            vert1 = vert2
                
    def gl_pick_triangles(self, polygons, selection_mode, mirror_distance, pickdata):
        for cellidx, (face, picktype), edges_iverts in polygons:
            symbol = self.faces[face]
            normal = self.vectors[self.normals[face]]
            
            if selection_mode == 0 or picktype == -1:
                yield from self.gl_pick_triangles_any(cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata)
            elif selection_mode == 1:
                if picktype == 0:
                    yield from self.gl_pick_triangles_center(cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata)
                elif picktype == 1:
                    yield from self.gl_pick_triangles_edge3(cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata)
                elif picktype == 2:
                    yield from self.gl_pick_triangles_edge4(cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata)
                elif picktype == 3:
                    yield from self.gl_pick_triangles_corner34(cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata)
                elif picktype == 5:
                    yield from self.gl_pick_triangles_corner5(cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata)
                elif picktype == 4:
                    yield from self.gl_pick_triangles_edge6(cellidx, edges_iverts, symbol, normal, mirror_distance, pickdata)
                else:
                    assert False, (selection_mode, picktype)
            else:
                assert False, (selection_mode, picktype)
                    
    def gl_vertex_data(self, selection_mode, theme, mirror_distance):
        vertices = []
        normals = []
        colors = []
        texpos = []
        baryc = []
        # list of [count vertices, face index]
        cnts_block = []
        faces = [theme.faces[fk] for fk in self.facekeys]
        
        for label_polygons, black_polygons in self.block_polygons:
            cnt = 0
            # label vertices
            for v, n, c, t, b in self.gl_label_vertices(label_polygons, faces, mirror_distance):
                vertices += v
                normals += n
                colors += c
                texpos += t
                baryc += b
                cnt += 1
            # bevel, hidden labels
            for v, n in self.gl_black_vertices(black_polygons):
                vertices += v
                normals += n
                #TODO: colors and texpos unused
                colors += (0,0,0)
                texpos += (0,0)
                baryc += (0,0,0)
                cnt += 1
            cnts_block.append(cnt)
        
        idx_debug = len(vertices) // 3
        if DEBUG_DRAW:
            #FIXME: what if len(self.sizes) < 3 ?
            x = float(-self.sizes[0])
            y = float(-self.sizes[1])
            z = float(-self.sizes[2])
            # axes
            vertices += [x-1, y-1, z,  -x, y-1, z]
            colors += [255, 0, 0,  255, 0, 0]
            vertices += [x-1, y-1, z,  x-1, -y, z]
            colors += [0, 255, 0,  0, 255, 0]
            vertices += [x-1, y-1, z,  x-1, y-1, -z]
            colors += [0, 0, 255,  0, 0, 255]
            # selection (modelview)
            vertices += [0, 0, 0,  1, 1, 1]
            colors += [255, 255, 255,  255, 255, 255]
            # selection (viewport)
            vertices += [0, 0, 0,  1, 1, 0]
            colors += [255, 255, 0,  255, 0, 0]
            
            assert len(cnts_block) == self.cell_count
            assert sum(cnts_block) == idx_debug
            
        pickvertices, pickcolors, pickdata = self.gl_pick_data(selection_mode, mirror_distance)
        cnt_pick = len(pickvertices) // 3
        
        vertices = array('f', vertices).tobytes()
        normals = array('f', normals).tobytes()
        colors = array('B', colors).tobytes()
        texpos = array('f', texpos).tobytes()
        barycentric = array('f', baryc).tobytes()
        pickvertices = array('f', pickvertices).tobytes()
        pickcolors = array('B', pickcolors).tobytes()
        
        vertexdata = vertices + normals + colors + texpos + barycentric + pickvertices + pickcolors
        debug('GL data: %d bytes' % len(vertexdata))
        
        ptrnormals = len(vertices)
        ptrcolors = ptrnormals + len(normals)
        ptrtexpos = ptrcolors + len(colors)
        ptrbarycentric = ptrtexpos + len(texpos)
        ptrpickvertices = ptrbarycentric + len(barycentric)
        ptrpickcolors = ptrpickvertices + len(pickvertices)
        
        return (    self.cell_count,
                    vertexdata,
                    (ptrnormals, ptrcolors, ptrtexpos, ptrbarycentric, ptrpickvertices, ptrpickcolors),
                    (cnts_block, idx_debug, cnt_pick),
                    self.rotation_matrices,
               ), pickdata
                
    def gl_pick_data(self, selection_mode, mirror_distance):
        # Pick TRIANGLES vertices
        vertices = []
        colors = []
        pickdata = [()]  # list of (maxis, mslice, mdir, face, center, cellidx, symbol, arrowdir)
        for col, v in self.gl_pick_triangles(self.pick_polygons, selection_mode, mirror_distance, pickdata):
            vertices += v
            color = [(col>>4) & 0xf0, (col) & 0xf0, (col<<4) & 0xf0]
            colors += color
        assert len(vertices) == len(colors)
        return vertices, colors, pickdata
        
        
empty_model = EmptyModel()
    
    
