//
//  MyDocument.m
//  DisplayLinkAsyncMoviePlayer
//
//  Created by vade on 10/26/10.
//  Copyright __MyCompanyName__ 2010 . All rights reserved.
//

#import "aiConfig.h"
#import "MyDocument.h"
#import <OpenGL/CGLMacro.h>

#pragma mark -
#pragma mark Helper Functions

#define aisgl_min(x,y) (x<y?x:y)
#define aisgl_max(x,y) (y>x?y:x)

static void color4_to_float4(const struct aiColor4D *c, float f[4])
{
	f[0] = c->r;
	f[1] = c->g;
	f[2] = c->b;
	f[3] = c->a;
}

static void set_float4(float f[4], float a, float b, float c, float d)
{
	f[0] = a;
	f[1] = b;
	f[2] = c;
	f[3] = d;
}

#pragma mark -
#pragma mark CVDisplayLink Callback
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink,const CVTimeStamp *inNow,const CVTimeStamp *inOutputTime,CVOptionFlags flagsIn,CVOptionFlags *flagsOut,void *displayLinkContext)
{
	CVReturn error = [(MyDocument*) displayLinkContext displayLinkRenderCallback:inOutputTime];
	return error;
}

#pragma mark -

@implementation MyDocument
@synthesize _view;

- (id)init 
{
    self = [super init];
    if (self != nil)
    {
        // initialization code
    }
    return self;
}

- (NSString *)windowNibName 
{
    return @"MyDocument";
}

- (void)windowControllerDidLoadNib:(NSWindowController *)windowController 
{
    [super windowControllerDidLoadNib:windowController];
    
    NSOpenGLPixelFormatAttribute attributes[] = 
	{
		NSOpenGLPFADoubleBuffer,
		NSOpenGLPFAAccelerated,
        NSOpenGLPFADepthSize, 24,
        NSOpenGLPFAMultisample,
        NSOpenGLPFASampleBuffers, 2,
		(NSOpenGLPixelFormatAttribute) 0
    };
    
    _glPixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];

    if(!_glPixelFormat)
        NSLog(@"Error creating PF");
    
    _glContext = [[NSOpenGLContext alloc] initWithFormat:_glPixelFormat shareContext:nil];
    
    const GLint one = 1;
    
    [_glContext setValues:&one forParameter:NSOpenGLCPSwapInterval];
    [_glContext setView:_view];

    // Set up initial GL state.
    CGLContextObj cgl_ctx = (CGLContextObj)[_glContext CGLContextObj];
    
    glEnable(GL_MULTISAMPLE);
    
    glClearColor(0.3, 0.3, 0.3, 0.3);

    // enable color tracking
    //glEnable(GL_COLOR_MATERIAL);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glDepthMask(GL_TRUE);
    
    glEnable(GL_NORMALIZE);
    glEnable(GL_TEXTURE_2D);

    glShadeModel(GL_SMOOTH);
    
    glEnable(GL_LIGHTING);
    
    GLfloat global_ambient[] = { 0.5f, 0.5f, 0.5f, 1.0f };
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);
    
    GLfloat specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

    GLfloat diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);

    GLfloat ambient[] = {0.2, 0.2f, 0.2f, 0.2f};
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);

    GLfloat position[] = { 1.0f, 1.0f, 1.0f, 1.0f};
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    glEnable(GL_LIGHT0);
   
    // This is the only client state that always has to be set.
    glEnableClientState(GL_VERTEX_ARRAY); 

    // end GL State setup.
    
    // Display Link setup.
    CVReturn error = kCVReturnSuccess;
    
    error = CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
    if(error == kCVReturnError)
        NSLog(@"Error Creating DisplayLink");
    
    error = CVDisplayLinkSetOutputCallback(_displayLink,MyDisplayLinkCallback, self);
    if(error == kCVReturnError)
        NSLog(@"Error Setting DisplayLink Callback");
    
    error = CVDisplayLinkStart(_displayLink);
    if(error == kCVReturnError)
        NSLog(@"Error Starting DisplayLink");

    NSOpenPanel* openPanel = [NSOpenPanel openPanel];
    
    [openPanel beginSheetModalForWindow:[_view window] completionHandler:^(NSInteger result) 
    {
        if (result == NSOKButton)
        {
            [openPanel orderOut:self]; // close panel before we might present an error
        
            if([[NSFileManager defaultManager] fileExistsAtPath:[openPanel filename]])
            {
                // Load our new path.
                
                // only ever give us triangles.
                aiSetImportPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT );
                
                NSUInteger aiPostProccesFlags;
                
                switch (2)
                {
                    case 0:
                        aiPostProccesFlags = aiProcessPreset_TargetRealtime_Fast;
                        break;                    
                    case 1:
                        aiPostProccesFlags = aiProcessPreset_TargetRealtime_Quality;
                        break;
                    case 2:
                        aiPostProccesFlags = aiProcessPreset_TargetRealtime_MaxQuality;
                        break;
                    default:
                        aiPostProccesFlags = aiProcessPreset_TargetRealtime_MaxQuality;
                        break;
                }
                
                // aiProcess_FlipUVs is needed for VAO / VBOs,  not sure why.
                _scene = (aiScene*) aiImportFile([[openPanel filename] cStringUsingEncoding:[NSString defaultCStringEncoding]], aiPostProccesFlags | aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_PreTransformVertices | 0 );
                
                if (_scene)
                {       
                    textureDictionary = [[NSMutableDictionary alloc] initWithCapacity:5];
                    
                    // Why do I need to cast this !?
                    CGLContextObj cgl_ctx = (CGLContextObj)[_glContext CGLContextObj];
                    CGLLockContext(cgl_ctx);
                    
                    [self loadTexturesInContext:cgl_ctx withModelPath:[[openPanel filename] stringByStandardizingPath]];
                    
                    //NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[NSValue valueWithPointer:cgl_ctx], @"context", [self.inputModelPath stringByStandardizingPath], @"path", nil ];
                    //[self performSelectorInBackground:@selector(loadTexturesInBackground:) withObject:userInfo];
                    
                    [self getBoundingBoxWithMinVector:&scene_min maxVectr:&scene_max];
                    scene_center.x = (scene_min.x + scene_max.x) / 2.0f;
                    scene_center.y = (scene_min.y + scene_max.y) / 2.0f;
                    scene_center.z = (scene_min.z + scene_max.z) / 2.0f;
                    
                    // optional normalized scaling
                    normalizedScale = scene_max.x-scene_min.x;
                    normalizedScale = aisgl_max(scene_max.y - scene_min.y,normalizedScale);
                    normalizedScale = aisgl_max(scene_max.z - scene_min.z,normalizedScale);
                    normalizedScale = 1.f / normalizedScale;
                    
                    if(_scene->HasAnimations())
                        NSLog(@"scene has animations");
                                        
                    [self createGLResourcesInContext:cgl_ctx];
                    CGLUnlockContext(cgl_ctx);
                }
            }
        }            
    }]; // end block handler
}

- (void) close
{
    CVDisplayLinkStop(_displayLink);
    CVDisplayLinkRelease(_displayLink);
    
    if(_scene)
    {
        aiReleaseImport(_scene);
        _scene = NULL;
        
        CGLContextObj cgl_ctx = (CGLContextObj)[_glContext CGLContextObj];
        glDeleteTextures([textureDictionary count], textureIds);
        
        [textureDictionary release];
        textureDictionary = nil;
        
        free(textureIds);
        textureIds = NULL;
        
        [self deleteGLResourcesInContext:cgl_ctx];
    }    
    
    [_glContext release];
    _glContext = nil;
    
    [_glPixelFormat release];
    _glPixelFormat = nil;
    
    [super close];
}

- (CVReturn)displayLinkRenderCallback:(const CVTimeStamp *)timeStamp
{
    CVReturn rv = kCVReturnError;
    NSAutoreleasePool *pool;
	
    pool = [[NSAutoreleasePool alloc] init];
    {
        [self render];
		rv = kCVReturnSuccess;
    }
    [pool release];
    return rv;
}

- (void) render
{   
    CGLContextObj cgl_ctx = (CGLContextObj)[_glContext CGLContextObj];
    CGLLockContext(cgl_ctx);
        
    [_glContext update];
    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    
    glViewport(0, 0, _view.frame.size.width, _view.frame.size.height);

    GLfloat aspect = _view.frame.size.height/_view.frame.size.width; 
    glOrtho(-1, 1, - (aspect), aspect, -10, 10);
        
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glTranslated(0.0, 0.0, 1.0);
    
    // Draw our GL model.    
    if(_scene)
    {
        glScaled(normalizedScale , normalizedScale, normalizedScale);
        // center the model
        glTranslated( -scene_center.x, -scene_center.y, -scene_center.z);    
        
        glScaled(2.0, 2.0, 2.0);
        
        static float i = 0;
        i+=0.5;
        glRotated(i, 0, 1, 0);
        
        [self drawMeshesInContext:cgl_ctx];
    }

    CGLUnlockContext(cgl_ctx);
    
    CGLFlushDrawable(cgl_ctx);
}

#pragma mark -
#pragma mark Loading 

// Inspired by LoadAsset() & CreateAssetData() from AssimpView D3D project
- (void) createGLResourcesInContext:(CGLContextObj)cgl_ctx
{
    // create new mesh helpers for each mesh, will populate their data later.
    modelMeshes = [[NSMutableArray alloc] initWithCapacity:_scene->mNumMeshes];
    
    // create OpenGL buffers and populate them based on each meshes pertinant info.
    for (unsigned int i = 0; i < _scene->mNumMeshes; ++i)
    {
        NSLog(@"%u", i);
        
        // current mesh we are introspecting
        const aiMesh* mesh = _scene->mMeshes[i];
        
        // the current meshHelper we will be populating data into.
        MeshHelper* meshHelper = [[MeshHelper alloc] init];

        // Handle material info
        
        aiMaterial* mtl = _scene->mMaterials[mesh->mMaterialIndex];
        
        // Textures
        int texIndex = 0;
        aiString texPath;
        
        if(AI_SUCCESS == mtl->GetTexture(aiTextureType_DIFFUSE, texIndex, &texPath))
        {
            NSString* textureKey = [NSString stringWithCString:texPath.data encoding:[NSString defaultCStringEncoding]];
            //bind texture
            NSNumber* textureNumber = (NSNumber*)[textureDictionary valueForKey:textureKey];
            
            //NSLog(@"applyMaterialInContext: have texture %i", [textureNumber unsignedIntValue]); 
            meshHelper.textureID = [textureNumber unsignedIntValue];		
        }
        else
            meshHelper.textureID = 0;
        
        // Colors
        
        aiColor4D dcolor = aiColor4D(0.8f, 0.8f, 0.8f, 1.0f);
        if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_DIFFUSE, &dcolor))
            [meshHelper setDiffuseColor:&dcolor];
        
        aiColor4D scolor = aiColor4D(0.0f, 0.0f, 0.0f, 1.0f);
        if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_SPECULAR, &scolor))
            [meshHelper setSpecularColor:&scolor];
        
        aiColor4D acolor = aiColor4D(0.2f, 0.2f, 0.2f, 1.0f);
        if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_AMBIENT, &acolor))
            [meshHelper setAmbientColor:&acolor];
        
        aiColor4D ecolor = aiColor4D(0.0f, 0.0f, 0.0f, 1.0f);
        if(AI_SUCCESS == aiGetMaterialColor(mtl, AI_MATKEY_COLOR_EMISSIVE, &ecolor))
            [meshHelper setEmissiveColor:&ecolor];
        
        // Culling
        unsigned int max = 1;
        int two_sided;
        if((AI_SUCCESS == aiGetMaterialIntegerArray(mtl, AI_MATKEY_TWOSIDED, &two_sided, &max)) && two_sided)
            [meshHelper setTwoSided:YES];
        else
            [meshHelper setTwoSided:NO];
        
        // Create a VBO for our vertices
        
        GLuint vhandle;
        glGenBuffers(1, &vhandle);
        
        glBindBuffer(GL_ARRAY_BUFFER, vhandle);
        glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * mesh->mNumVertices, NULL, GL_STATIC_DRAW);
        
        // populate vertices
        Vertex* verts = (Vertex*)glMapBuffer(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);
        
        for (unsigned int x = 0; x < mesh->mNumVertices; ++x)
        {
            verts->vPosition = mesh->mVertices[x];
            
            if (NULL == mesh->mNormals)
                verts->vNormal = aiVector3D(0.0f,0.0f,0.0f);
            else
                verts->vNormal = mesh->mNormals[x];
            
            if (NULL == mesh->mTangents)
            {
                verts->vTangent = aiVector3D(0.0f,0.0f,0.0f);
                verts->vBitangent = aiVector3D(0.0f,0.0f,0.0f);
            }
            else
            {
                verts->vTangent = mesh->mTangents[x];
                verts->vBitangent = mesh->mBitangents[x];
            }
            
            if (mesh->HasVertexColors(0))
            {
                verts->dColorDiffuse = mesh->mColors[0][x];
            }
            else
                verts->dColorDiffuse = aiColor4D(1.0, 1.0, 1.0, 1.0);
            
            // This varies slightly form Assimp View, we support the 3rd texture component.
            if (mesh->HasTextureCoords(0))
                verts->vTextureUV = mesh->mTextureCoords[0][x];
            else
                verts->vTextureUV = aiVector3D(0.5f,0.5f, 0.0f);
            
            if (mesh->HasTextureCoords(1))
                verts->vTextureUV2 = mesh->mTextureCoords[1][x];
            else 
                verts->vTextureUV2 = aiVector3D(0.5f,0.5f, 0.0f);
            
            // TODO: handle Bone indices and weights
            /*          if( mesh->HasBones())
             {
             unsigned char boneIndices[4] = { 0, 0, 0, 0 };
             unsigned char boneWeights[4] = { 0, 0, 0, 0 };
             ai_assert( weightsPerVertex[x].size() <= 4);
             
             for( unsigned int a = 0; a < weightsPerVertex[x].size(); a++)
             {
             boneIndices[a] = weightsPerVertex[x][a].mVertexId;
             boneWeights[a] = (unsigned char) (weightsPerVertex[x][a].mWeight * 255.0f);
             }
             
             memcpy( verts->mBoneIndices, boneIndices, sizeof( boneIndices));
             memcpy( verts->mBoneWeights, boneWeights, sizeof( boneWeights));
             }
             else
             */ 
            {
                memset( verts->mBoneIndices, 0, sizeof( verts->mBoneIndices));
                memset( verts->mBoneWeights, 0, sizeof( verts->mBoneWeights));
            }
            
            ++verts;
        }
        
        glUnmapBufferARB(GL_ARRAY_BUFFER_ARB); //invalidates verts
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        
        // set the mesh vertex buffer handle to our new vertex buffer.
        meshHelper.vertexBuffer = vhandle;
        
        // Create Index Buffer
        
        // populate the index buffer.
        NSUInteger nidx;
        switch (mesh->mPrimitiveTypes)
        {
            case aiPrimitiveType_POINT:
                nidx = 1;break;
            case aiPrimitiveType_LINE:
                nidx = 2;break;
            case aiPrimitiveType_TRIANGLE:
                nidx = 3;break;
            default: assert(false);
        }   
        
        // create the index buffer
        GLuint ihandle;
        glGenBuffers(1, &ihandle);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ihandle);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * mesh->mNumFaces * nidx, NULL, GL_STATIC_DRAW);
        
        unsigned int* indices = (unsigned int*)glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY_ARB);
        
        // now fill the index buffer
        for (unsigned int x = 0; x < mesh->mNumFaces; ++x)
        {
            for (unsigned int a = 0; a < nidx; ++a)
            {
                //                 if(mesh->mFaces[x].mNumIndices != 3)
                //                     NSLog(@"whoa dont have 3 indices...");
                
                *indices++ = mesh->mFaces[x].mIndices[a];
            }
        }
        
        glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        
        // set the mesh index buffer handle to our new index buffer.
        meshHelper.indexBuffer = ihandle;
        meshHelper.numIndices = mesh->mNumFaces * nidx;
        
        // create the normal buffer. Assimp View creates a second normal buffer. Unsure why. Using only the interleaved normals for now.
        // This is here for reference.
        
        /*          GLuint nhandle;
         glGenBuffers(1, &nhandle);
         glBindBuffer(GL_ARRAY_BUFFER, nhandle);
         glBufferData(GL_ARRAY_BUFFER, sizeof(aiVector3D)* mesh->mNumVertices, NULL, GL_STATIC_DRAW);
         
         // populate normals
         aiVector3D* normals = (aiVector3D*)glMapBuffer(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);
         
         for (unsigned int x = 0; x < mesh->mNumVertices; ++x)
         {
         aiVector3D vNormal = mesh->mNormals[x];
         *normals = vNormal;
         ++normals;
         }
         
         glUnmapBufferARB(GL_ARRAY_BUFFER_ARB); //invalidates verts
         glBindBuffer(GL_ARRAY_BUFFER, 0);
         
         meshHelper.normalBuffer = nhandle;
         */
        
        // Create VAO and populate it
        
        GLuint vaoHandle; 
        glGenVertexArraysAPPLE(1, &vaoHandle);
        
        glBindVertexArrayAPPLE(vaoHandle);
        
        
        glBindBuffer(GL_ARRAY_BUFFER, meshHelper.vertexBuffer);
        
        glEnableClientState(GL_NORMAL_ARRAY);
        glNormalPointer(GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(12));
        
        glEnableClientState(GL_COLOR_ARRAY);
        glColorPointer(4, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(24));
        
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer(3, GL_FLOAT, sizeof(Vertex), BUFFER_OFFSET(64));
        //TODO: handle second texture
        
        // VertexPointer ought to come last, apparently this is some optimization, since if its set once, first, it gets fiddled with every time something else is update.
        glEnableClientState(GL_VERTEX_ARRAY);
        glVertexPointer(3, GL_FLOAT, sizeof(Vertex), 0);
        
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshHelper.indexBuffer);
        
        glBindVertexArrayAPPLE(0);
        
        // save the VAO handle into our mesh helper
        meshHelper.vao = vaoHandle;
        
        // Create the display list
        
        GLuint list = glGenLists(1);
        
        glNewList(list, GL_COMPILE);
        
        float dc[4];
        float sc[4];
        float ac[4];
        float emc[4];        
        
        // Material colors and properties
        color4_to_float4([meshHelper diffuseColor], dc);
        glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, dc);
        
        color4_to_float4([meshHelper specularColor], sc);
        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, sc);
        
        color4_to_float4([meshHelper ambientColor], ac);
        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ac);
        
        color4_to_float4(meshHelper.emissiveColor, emc);
        glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emc);
        
        glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

        // Culling
        if(meshHelper.twoSided)
            glEnable(GL_CULL_FACE);
        else 
            glDisable(GL_CULL_FACE);
        
        
        // Texture Binding
        glBindTexture(GL_TEXTURE_2D, meshHelper.textureID);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 
        
        // This binds the whole VAO, inheriting all the buffer and client state. Weeee
        glBindVertexArrayAPPLE(meshHelper.vao);        
        glDrawElements(GL_TRIANGLES, meshHelper.numIndices, GL_UNSIGNED_INT, 0);
        
        glEndList();
        
        meshHelper.displayList = list;
        
        // Whew, done. Save all of this shit.
        [modelMeshes addObject:meshHelper];
        
        [meshHelper release];
    }
}

- (void) deleteGLResourcesInContext:(CGLContextObj)cgl_ctx
{    
    for(MeshHelper* helper in modelMeshes)
    {
        const GLuint indexBuffer = helper.indexBuffer;
        const GLuint vertexBuffer = helper.vertexBuffer;
        const GLuint normalBuffer = helper.normalBuffer;
        const GLuint vaoHandle = helper.vao;
        const GLuint dlist = helper.displayList;
        
        glDeleteBuffers(1, &vertexBuffer);
        glDeleteBuffers(1, &indexBuffer);
        glDeleteBuffers(1, &normalBuffer);
        glDeleteVertexArraysAPPLE(1, &vaoHandle);
        
        glDeleteLists(1, dlist);
        
        helper.indexBuffer = 0;
        helper.vertexBuffer = 0;
        helper.normalBuffer = 0;
        helper.vao = 0;
        helper.displayList = 0;
    }
    
    [modelMeshes release];
    modelMeshes = nil;
}

- (void) drawMeshesInContext:(CGLContextObj)cgl_ctx
{
    for(MeshHelper* helper in modelMeshes)
    {
        // Set up meterial state.
        glCallList(helper.displayList);  
    }
}


- (void) loadTexturesInContext:(CGLContextObj)cgl_ctx withModelPath:(NSString*) modelPath
{    
    if (_scene->HasTextures())
    {
        NSLog(@"Support for meshes with embedded textures is not implemented");
        return;
    }
    
    /* getTexture Filenames and Numb of Textures */
	for (unsigned int m = 0; m < _scene->mNumMaterials; m++)
	{		
		int texIndex = 0;
		aiReturn texFound = AI_SUCCESS;
        
		aiString path;	// filename
        
        // TODO: handle other aiTextureTypes
		while (texFound == AI_SUCCESS)
		{
			texFound = _scene->mMaterials[m]->GetTexture(aiTextureType_DIFFUSE, texIndex, &path);
            
            NSString* texturePath = [NSString stringWithCString:path.data encoding:[NSString defaultCStringEncoding]];
            
            // add our path to the texture and the index to our texture dictionary.
            [textureDictionary setValue:[NSNumber numberWithUnsignedInt:texIndex] forKey:texturePath];
            
			texIndex++;
		}		
	}
    
    textureIds = (GLuint*) malloc(sizeof(GLuint) * [textureDictionary count]); //new GLuint[ [textureDictionary count] ];
    glGenTextures([textureDictionary count], textureIds);
    
    NSLog(@"textureDictionary: %@", textureDictionary);
    
    // create our textures, populate them, and alter our textureID value for the specific textureID we create.
    
    // so we can modify while we enumerate... 
    NSDictionary *textureCopy = [textureDictionary copy];
    
    // GCD attempt.
    //dispatch_sync(_queue, ^{
    
    int i = 0;
    
    for(NSString* texturePath in textureCopy)
    {        
        NSString* fullTexturePath = [[[modelPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:[texturePath stringByStandardizingPath]] stringByStandardizingPath];
        NSLog(@"texturePath: %@", fullTexturePath);
        
        NSImage* textureImage = [[NSImage alloc] initWithContentsOfFile:fullTexturePath];
        
        if(textureImage)
        {
            //NSLog(@"Have Texture Image");
            NSBitmapImageRep* bitmap = [NSBitmapImageRep alloc];
            
            [textureImage lockFocus];
            [bitmap initWithFocusedViewRect:NSMakeRect(0, 0, textureImage.size.width, textureImage.size.height)];
            [textureImage unlockFocus];
            
            glActiveTexture(GL_TEXTURE0);
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D, textureIds[i]);
            //glPixelStorei(GL_UNPACK_ROW_LENGTH, [bitmap pixelsWide]);
            //glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
            
            // generate mip maps
            glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
            
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); 
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
            
            // draw into our bitmap
            int samplesPerPixel = [bitmap samplesPerPixel];
            
            if(![bitmap isPlanar] && (samplesPerPixel == 3 || samplesPerPixel == 4))
            {
                glTexImage2D(GL_TEXTURE_2D,
                             0,
                             //samplesPerPixel == 4 ? GL_COMPRESSED_RGBA_S3TC_DXT3_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT, 
                             samplesPerPixel == 4 ? GL_RGBA8 : GL_RGB8,
                             [bitmap pixelsWide],
                             [bitmap pixelsHigh],
                             0,
                             samplesPerPixel == 4 ? GL_RGBA : GL_RGB,
                             GL_UNSIGNED_BYTE,
                             [bitmap bitmapData]);
                
            } 
            
            
            // update our dictionary to contain the proper textureID value (from out array of generated IDs)
            [textureDictionary setValue:[NSNumber numberWithUnsignedInt:textureIds[i]] forKey:texturePath];
            
            [bitmap release];
        }
        else
        {
            [textureDictionary removeObjectForKey:texturePath];
            NSLog(@"Could not Load Texture: %@, removing reference to it.", fullTexturePath);
        }
        
        [textureImage release];
        i++;
    }       
    //});
    
    [textureCopy release];
    
}

- (void) getBoundingBoxWithMinVector:(struct aiVector3D*) min maxVectr:(struct aiVector3D*) max
{
	struct aiMatrix4x4 trafo;
	aiIdentityMatrix4(&trafo);
    
	min->x = min->y = min->z =  1e10f;
	max->x = max->y = max->z = -1e10f;
    
    [self getBoundingBoxForNode:_scene->mRootNode minVector:min maxVector:max matrix:&trafo];
}

- (void) getBoundingBoxForNode:(const struct aiNode*)nd  minVector:(struct aiVector3D*) min maxVector:(struct aiVector3D*) max matrix:(struct aiMatrix4x4*) trafo
{
	struct aiMatrix4x4 prev;
	unsigned int n = 0, t;
    
	prev = *trafo;
	aiMultiplyMatrix4(trafo,&nd->mTransformation);
    
	for (; n < nd->mNumMeshes; ++n)
    {
		const struct aiMesh* mesh = _scene->mMeshes[nd->mMeshes[n]];
		for (t = 0; t < mesh->mNumVertices; ++t)
        {
        	struct aiVector3D tmp = mesh->mVertices[t];
			aiTransformVecByMatrix4(&tmp,trafo);
            
			min->x = aisgl_min(min->x,tmp.x);
			min->y = aisgl_min(min->y,tmp.y);
			min->z = aisgl_min(min->z,tmp.z);
            
			max->x = aisgl_max(max->x,tmp.x);
			max->y = aisgl_max(max->y,tmp.y);
			max->z = aisgl_max(max->z,tmp.z);
		}
	}
    
	for (n = 0; n < nd->mNumChildren; ++n) 
    {
		[self getBoundingBoxForNode:nd->mChildren[n] minVector:min maxVector:max matrix:trafo];
	}
    
	*trafo = prev;
}


@end
