//
//  ReachGraphicsDevice.m
//  XNI
//
//  Created by Matej Jan on 27.7.10.
//  Copyright 2010 Retronator. All rights reserved.
//

#import "ReachGraphicsDevice.h"

#import "Retronator.Xni.Framework.Graphics.h"

#import "VertexBuffer+Internal.h"
#import "IndexBuffer+Internal.h"

@interface ReachGraphicsDevice()

- (void) drawUserIndexedPrimitivesOfType:(PrimitiveType)primitiveType 
							  vertexData:(void*)vertexData
							vertexOffset:(int)vertexOffset
							 numVertices:(int)numVertices
						       indexData:(void*)indexData
							 indexOffset:(int)indexOffset
						  primitiveCount:(int)primitiveCount
					   vertexDeclaration:(VertexDeclaration*) vertexDeclaration
						indexElementSize:(IndexElementSize)indexElementSize;

- (void) enableVertexBuffers;
- (void) disableVertexBuffers;

- (void) enableDeclaration:(VertexDeclaration*)vertexDeclaration forUserData:(void*)data;

- (void) enableDeclaration:(VertexDeclaration*)vertexDeclaration onStream:(int)stream useBuffers:(BOOL)useBuffers pointer:(void*)pointer;
- (void) disableDeclaration:(VertexDeclaration*)vertexDeclaration;

@end


@implementation ReachGraphicsDevice

- (id)initWithGame:(Game *)theGame {
    self = [super initWithGame:theGame];
    if (self) {
        // Disable lights.
        for (int i=0; i<8; i++) {
            lightsActive[i] = false;
            glDisable(GL_LIGHT0 + i);
        }
    }
    return self;
}

- (EAGLContext*) createContext { 
	return [[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1] autorelease]; 
}

- (void) drawPrimitivesOfType:(PrimitiveType)primitiveType 
				  startVertex:(int)startVertex 
			   primitiveCount:(int)primitiveCount
{
	[self enableVertexBuffers];
	
    int count = [GraphicsDevice getNumberOfVerticesForPrimitiveType:primitiveType primitiveCount:primitiveCount]; 
    glDrawArrays(primitiveType, 0, count);
	
	[self disableVertexBuffers];
}

- (void) drawIndexedPrimitivesOfType:(PrimitiveType)primitiveType 
						  baseVertex:(int)baseVertex 
					  minVertexIndex:(int)minVertexIndex
						 numVertices:(int)numVertices
						  startIndex:(int)startIndex
					  primitiveCount:(int)primitiveCount
{
	[self enableVertexBuffers];
	
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indices.bufferID);
	
    int count = [GraphicsDevice getNumberOfVerticesForPrimitiveType:primitiveType primitiveCount:primitiveCount]; 
	
	GLenum type = 0;
	switch (indices.indexElementSize) {
		case IndexElementSizeSixteenBits:
			type = DataTypeUnsignedShort;
			break;
	}
	
	void *startIndexPointer = (void*)(startIndex * indices.indexElementSize);
    glDrawElements(primitiveType, count, type, startIndexPointer);
	
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	
	[self disableVertexBuffers];
}

- (void) drawUserPrimitivesOfType:(PrimitiveType)primitiveType
					   vertexData:(VertexArray*)vertexData
					 vertexOffset:(int)vertexOffset 
				   primitiveCount:(int)primitiveCount
{
	[self drawUserPrimitivesOfType:primitiveType
						vertexData:vertexData.array
					  vertexOffset:vertexOffset
					primitiveCount:primitiveCount
				 vertexDeclaration:vertexData.vertexDeclaration];
}


- (void) drawUserPrimitivesOfType:(PrimitiveType)primitiveType
					   vertexData:(void*)vertexData 
					 vertexOffset:(int)vertexOffset 
				   primitiveCount:(int)primitiveCount
				vertexDeclaration:(VertexDeclaration*) vertexDeclaration
{
	[self enableDeclaration:vertexDeclaration forUserData:(vertexData + vertexOffset * vertexDeclaration.vertexStride)];
    
    int count = [GraphicsDevice getNumberOfVerticesForPrimitiveType:primitiveType primitiveCount:primitiveCount]; 
    glDrawArrays(primitiveType, 0, count);
    
    [self disableDeclaration:vertexDeclaration]; 
}

- (void) drawUserIndexedPrimitivesOfType:(PrimitiveType)primitiveType 
							  vertexData:(VertexArray*)vertexData
							vertexOffset:(int)vertexOffset
							 numVertices:(int)numVertices
							   indexData:(IndexArray*)indexData
							 indexOffset:(int)indexOffset
						  primitiveCount:(int)primitiveCount
{
	[self drawUserIndexedPrimitivesOfType:primitiveType
							   vertexData:vertexData.array
							 vertexOffset:vertexOffset 
							  numVertices:numVertices
								indexData:indexData.array 
							  indexOffset:indexOffset 
						   primitiveCount:primitiveCount
						vertexDeclaration:vertexData.vertexDeclaration
						 indexElementSize:indexData.indexElementSize];
}

- (void) drawUserIndexedPrimitivesOfType:(PrimitiveType)primitiveType 
							  vertexData:(void*)vertexData
							vertexOffset:(int)vertexOffset
							 numVertices:(int)numVertices
						  shortIndexData:(void*)indexData
							 indexOffset:(int)indexOffset
						  primitiveCount:(int)primitiveCount
					   vertexDeclaration:(VertexDeclaration*) vertexDeclaration 
{
	[self drawUserIndexedPrimitivesOfType:primitiveType
							   vertexData:vertexData
							 vertexOffset:vertexOffset 
							  numVertices:numVertices
								indexData:indexData 
							  indexOffset:indexOffset 
						   primitiveCount:primitiveCount
						vertexDeclaration:vertexDeclaration
						 indexElementSize:IndexElementSizeSixteenBits];	
}

- (void) drawUserIndexedPrimitivesOfType:(PrimitiveType)primitiveType 
							  vertexData:(void*)vertexData
							vertexOffset:(int)vertexOffset
							 numVertices:(int)numVertices
							   indexData:(void*)indexData
							 indexOffset:(int)indexOffset
						  primitiveCount:(int)primitiveCount
					   vertexDeclaration:(VertexDeclaration*) vertexDeclaration
						indexElementSize:(IndexElementSize)indexElementSize
{	
	[self enableDeclaration:vertexDeclaration forUserData:(vertexData + vertexOffset * vertexDeclaration.vertexStride)];
    
    int count = [GraphicsDevice getNumberOfVerticesForPrimitiveType:primitiveType primitiveCount:primitiveCount]; 

	GLenum type = 0;
	switch (indexElementSize) {
		case IndexElementSizeSixteenBits:
			type = DataTypeUnsignedShort;
			break;
	}
	
	void *startIndex = indexData + indexOffset * indexElementSize;
    glDrawElements(primitiveType, count, type, startIndex);
    
    [self disableDeclaration:vertexDeclaration]; 
	
}

- (void)setLight:(uint)lightname to:(BOOL)value {
    int index = lightname - GL_LIGHT0;
    
    if (value != lightsActive[index]) {
        lightsActive[index] = value;
        if (value) {
            glEnable(lightname);
        } else {
            glDisable(lightname);
        }
    }
}

// Private methods

- (void) enableVertexBuffers {
	// We need to enable the declarations set on the vertex buffers.
	for (int i=0;i<[vertices count];i++) {
		VertexBufferBinding *binding = [vertices objectAtIndex:i];
		[self enableDeclaration:binding.vertexBuffer.vertexDeclaration
					   onStream:i useBuffers:YES pointer:(void*)binding.vertexOffset];
	}
}

- (void) enableDeclaration:(VertexDeclaration*)vertexDeclaration forUserData:(void*)data {
	[self enableDeclaration:vertexDeclaration onStream:0 useBuffers:NO pointer:data];
}

- (void) enableDeclaration:(VertexDeclaration *)vertexDeclaration onStream:(int)stream useBuffers:(BOOL)useBuffers pointer:(void*)pointer {	
    NSArray *vertexElements = vertexDeclaration.vertexElements;
    
    int stride = vertexDeclaration.vertexStride;
    
    for (VertexElement *vertexElement in vertexElements) {       
        if (useBuffers) {
            // Bind the buffer the vertex element is using.
			VertexBufferBinding* binding = [vertices objectAtIndex:stream];
            glBindBuffer(GL_ARRAY_BUFFER, binding.vertexBuffer.bufferID);
        }
        
        // Enable the state that the vertex element represents.
        glEnableClientState(vertexElement.vertexElementUsage);
        
        // Create the pointer to the vertex element data.
        switch (vertexElement.vertexElementUsage) {
            case VertexElementUsagePosition:
                glVertexPointer([vertexElement getValueDimensions], [vertexElement getValueDataType], 
                                stride, pointer + vertexElement.offset);
                break;
            case VertexElementUsageNormal:
                glNormalPointer([vertexElement getValueDataType], 
                                stride, pointer + vertexElement.offset);
                break;
            case VertexElementUsageTextureCoordinate:
                glTexCoordPointer([vertexElement getValueDimensions], [vertexElement getValueDataType], 
                                  stride, pointer + vertexElement.offset);
                break;
            case VertexElementUsageColor:
                glColorPointer([vertexElement getValueDimensions], [vertexElement getValueDataType], 
                               stride, pointer + vertexElement.offset);
                break;                
            case VertexElementUsagePointSize:
                glPointSizePointerOES([vertexElement getValueDataType], 
                                      stride, pointer + vertexElement.offset);
                break;
            default:
                [NSException raise:@"NotImplementedException" 
                            format:@"The vertex element usage %i is not yet implemented.", vertexElement.vertexElementUsage];
                break;
        }
    }
}

- (void) disableVertexBuffers {
	// We need to disable the declarations set on the vertex buffers.
	for (int i=0;i<[vertices count];i++) {
		VertexBufferBinding *binding = [vertices objectAtIndex:i];
		[self disableDeclaration:binding.vertexBuffer.vertexDeclaration];
	}
	
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

- (void) disableDeclaration:(VertexDeclaration*)vertexDeclaration {
    NSArray *vertexElements = vertexDeclaration.vertexElements;
    for (VertexElement *vertexElement in vertexElements) {
        // Enable the state that the vertex element represents.
        glDisableClientState(vertexElement.vertexElementUsage);
    }
}

@end