#-*- coding: UTF-8 -*-

"""
PyAssimp

This is the main-module of PyAssimp.
"""

import structs
import ctypes
import os
import helper
from errors import AssimpError


#get the assimp path
LIBRARY = os.path.join(os.path.dirname(__file__), "assimp.so")



class AssimpLib(object):
    """
    Assimp-Singleton
    """
    load, release = helper.search_library()



class AssimpBase(object):
    """
    Base class for all Assimp-classes.
    """
    
    def _load_array(self, data, count, cons):
        """
        Loads a whole array out of data, and constructs a new object. If data
        is NULL, an empty list will be returned.
        
        data - pointer to array
        count - size of the array
        cons - constructor
        
        result array data
        """
        if data:
            return [cons(data[i]) for i in range(count)]
        else:
            return []


class Scene(AssimpBase):
    """
    The root structure of the imported data.
    Everything that was imported from the given file can be accessed from here.
    """
    
    #possible flags
    FLAGS = {1 : "AI_SCENE_FLAGS_ANIM_SKELETON_ONLY"}
    
    
    def __init__(self, model):
        """
        Converts the model-data to a real scene
        
        model - the raw model-data
        """
        #process data
        self._load(model)
    
    
    def _load(self, model):
        """
        Converts model from raw-data to fancy data!
        
        model - pointer to data
        """
        #store scene flags
        self.flags = model.flags
        
        #load mesh-data
        self.meshes = self._load_array(model.mMeshes,
                                       model.mNumMeshes,
                                       lambda x: Mesh(x.contents))
    
    
    def list_flags(self):
        """
        Returns a list of all used flags.
        
        result list of flags
        """
        return [name for (key, value) in Scene.FLAGS.iteritems()
                     if (key & self.flags)>0]


class Face(AssimpBase):
    """
    A single face in a mesh, referring to multiple vertices. 
    If the number of indices is 3, the face is a triangle, 
    for more than 3  it is a polygon.
    
    Point and line primitives are rarely used and are NOT supported. However,
    a load could pass them as degenerated triangles.
    """
    
    def __init__(self, face):
        """
        Loads a face from raw-data.
        """
        self.indices = [face.mIndices[i] for i in range(face.mNumIndices)]
    
    
    def __repr__(self):
        return str(self.indices)


class Mesh(AssimpBase):
    """
    A mesh represents a geometry or model with a single material. 
    It usually consists of a number of vertices and a series of primitives/faces 
    referencing the vertices. In addition there might be a series of bones, each 
    of them addressing a number of vertices with a certain weight. Vertex data 
    is presented in channels with each channel containing a single per-vertex 
    information such as a set of texture coords or a normal vector.
    If a data pointer is non-null, the corresponding data stream is present.
    
    A Mesh uses only a single material which is referenced by a material ID.
    """
    
    def __init__(self, mesh):
        """
        Loads mesh from raw-data.
        """
        #process data
        self._load(mesh)
    
    
    def _load(self, mesh):
        """
        Loads mesh-data from raw data
        
        mesh - raw mesh-data
        """
        #load vertices
        self.vertices = self._load_array(mesh.mVertices,
                                         mesh.mNumVertices,
                                         helper.vec2tuple)
        
        #load normals
        self.normals = self._load_array(mesh.mNormals,
                                        mesh.mNumVertices,
                                        helper.vec2tuple)
        
        #load tangents
        self.tangents = self._load_array(mesh.mTangents,
                                         mesh.mNumVertices,
                                         helper.vec2tuple)
        
        #load bitangents
        self.bitangents = self._load_array(mesh.mBitangents,
                                           mesh.mNumVertices,
                                           helper.vec2tuple)
        
        #vertex color sets
        self.colors = self._load_colors(mesh)
        
        #number of coordinates per uv-channel
        self.uvsize = self._load_uv_component_count(mesh)
        
        #number of uv channels
        self.texcoords = self._load_texture_coords(mesh)
        
        #the used material
        self.material_index = int(mesh.mMaterialIndex)
        
        #faces
        self.faces = self._load_faces(mesh)
    
    
    def _load_faces(self, mesh):
        """
        Loads all faces.
        
        mesh - mesh-data
        
        result faces
        """
        return [Face(mesh.mFaces[i]) for i in range(mesh.mNumFaces)]
    
    
    def _load_uv_component_count(self, mesh):
        """
        Loads the number of components for a given UV channel.
        
        mesh - mesh-data
        
        result (count channel 1, count channel 2, ...)
        """
        return tuple(mesh.mNumUVComponents[i]
                     for i in range(structs.MESH.AI_MAX_NUMBER_OF_TEXTURECOORDS))
    
    
    def _load_texture_coords(self, mesh):
        """
        Loads texture coordinates.
        
        mesh - mesh-data
        
        result texture coordinates
        """
        result = []
        
        for i in range(structs.MESH.AI_MAX_NUMBER_OF_TEXTURECOORDS):
            result.append(self._load_array(mesh.mTextureCoords[i],
                                           mesh.mNumVertices,
                                           helper.vec2tuple))
                
        return result
    
    
    def _load_colors(self, mesh):
        """
        Loads color sets.
        
        mesh - mesh with color sets
        
        result all color sets
        """
        result = []
        
        #for all possible sets
        for i in range(structs.MESH.AI_MAX_NUMBER_OF_COLOR_SETS):
            #try this set
            x = mesh.mColors[i]
            
            if x:
                channel = []
                
                #read data for al vertices!
                for j in range(mesh.mNumVertices):
                    c = x[j]
                    channel.append((c.r, c.g, c.b, c.a))
                
                result.append(channel)
                
        
        return result



#the loader as singleton
_assimp_lib = AssimpLib()


def load(filename, processing=0):
    """
    Loads the model with some specific processing parameters.
    
    filename - file to load model from
    processing - processing parameters
    
    result Scene-object with model-data
    
    throws AssimpError - could not open file
    """
    #read pure data
    model = _assimp_lib.load(filename, processing)
    if not model:
        #Uhhh, something went wrong!
        raise AssimpError, ("could not import file: %s" % filename)
    
    try:
        #create scene
        return Scene(model.contents)
    finally:
        #forget raw data
        _assimp_lib.release(model)