//
//  GraphicsView.mm
//  iphone-gl-app
//
//  Created by John Ryland on 7/06/09.
//  Copyright InvertedLogic 2009. All rights reserved.
//

#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
#import "GraphicsView.h"
#import "Context.h"
#include "Debug.h"


#define kAccelerometerFrequency			50 //Hz
//#define EGL_SUPPORT
#define USE_DEPTH_BUFFER				0
#define COLOR_DEPTH						32


@implementation GraphicsView


- (id) initWithFrame:(CGRect)frame
{
	if ((self = [super initWithFrame:frame])) {
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;

        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];

#ifdef EGL_SUPPORT
		eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
		if (eglDisplay == EGL_NO_DISPLAY)
			printf("Error getting OpenGL ES display\n");

		if (!eglInitialize(eglDisplay, 0, 0))
			printf("Error initializing OpenGL ES\n");


		EGLint numConfigs;
		EGLint attribList[] = // attribute list
		{
			EGL_BUFFER_SIZE, COLOR_DEPTH, // color depth
			EGL_DEPTH_SIZE, 15, // z-buffer
			EGL_NONE
		};
		if (!eglChooseConfig(eglDisplay, attribList, &eglConfig, 1, &numConfigs))
			printf("Error choosing OpenGL ES config\n");
		
		EGLint win[2] = { w, h };
		eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, &win, 0);
		if (eglSurface == EGL_NO_SURFACE)
			printf("Error creating OpenGL ES surface\n");
		
		eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, 0);
		if (eglContext == 0)
			printf("Error creating OpenGL ES context\n");

		eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
#else
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
		
        if (!context || ![EAGLContext setCurrentContext:context] || ![self createFramebuffer]) {			
            [self release];
            return nil;
        }
#endif

		//Configure and start accelerometer
		[[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0 / kAccelerometerFrequency)];
		[[UIAccelerometer sharedAccelerometer] setDelegate:self];
		
		cpp_context = new Context();
		
        animationInterval = 1.0 / 30.0;		
		[self setupView];
		[self drawView];
		[self startAnimation];
    }
	return self;
}


- (void) dealloc
{
    [self stopAnimation];
	[self destroyFramebuffer];	
	delete cpp_context;
    
	// SoundEngine_Teardown();

#ifdef EGL_SUPPORT
	eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
	eglDestroySurface(eglDisplay, eglSurface);
	eglDestroyContext(eglDisplay, eglContext);
	eglTerminate(eglDisplay);
#else
    if ([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
	[context release];
	context = nil;
#endif

    [super dealloc];	
}


- (void) setupView
{
	glViewport(0, 0, backingWidth, backingHeight);

/*
	// Note that each of the Sound Engine functions defined in SoundEngine.h return an OSStatus value.
	// Although the code in this application does not check for errors, you'll want to add error checking code 
	// in your own application, particularly during development.
	//Setup sound engine. Run  it at 44Khz to match the sound files
	SoundEngine_Initialize(44100);
	// Assume the listener is in the center at the start. The sound will pan as the position of the rocket changes.
	SoundEngine_SetListenerPosition(0.0, 0.0, kListenerDistance);
	// Load each of the four sounds used in the game.
	SoundEngine_LoadEffect([[bundle pathForResource:@"Start" ofType:@"caf"] UTF8String], &_sounds[kSound_Start]);
	SoundEngine_LoadEffect([[bundle pathForResource:@"Success" ofType:@"caf"] UTF8String], &_sounds[kSound_Success]);
	SoundEngine_LoadEffect([[bundle pathForResource:@"Failure" ofType:@"caf"] UTF8String], &_sounds[kSound_Failure]);
	SoundEngine_LoadLoopingEffect([[bundle pathForResource:@"Thrust" ofType:@"caf"] UTF8String], NULL, NULL, &_sounds[kSound_Thrust]);
*/
	
/*
	// Sets up pointers and enables states needed for using vertex arrays and textures
	glVertexPointer(2, GL_FLOAT, 0, spriteVertices);
	glEnableClientState(GL_VERTEX_ARRAY);
	glTexCoordPointer(2, GL_SHORT, 0, spriteTexcoords);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	spriteData = (unsigned int *)malloc(4*65536);
	for (int i = 0; i < 256; i++)
		for (int j = 0; j < 256; j++)
			spriteData[i+j*256] = i+i*256+i*65536 + j*256+j;
	spriteTexture = loadTextureFromData(256, 256, (unsigned char*)spriteData, true);
	spriteTexture = loadTextureFromData(256, 256, (unsigned char*)spriteData, false);
	spriteTexture = loadTextureFromFile("Sprite.png");
*/
}


/*
- (void) swapBuffers
{
	EAGLContext *oldContext = [EAGLContext currentContext];
	if (oldContext != context)
		[EAGLContext setCurrentContext:context];
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
	[context presentRenderbuffer:GL_RENDERBUFFER_OES];	
	if (oldContext != context)
		[EAGLContext setCurrentContext:oldContext];
}
*/


- (void) drawView
{
#ifdef EGL_SUPPORT
	eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); // ?
#else
    [EAGLContext setCurrentContext:context];
#endif

    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	cpp_context->draw();
    glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);

#ifdef EGL_SUPPORT
	eglSwapBuffers(eglDisplay, eglSurface);
#else
	// [glView swapBuffers];
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
	[context presentRenderbuffer:GL_RENDERBUFFER_OES];
#endif
}


- (void) layoutSubviews
{
    [EAGLContext setCurrentContext:context];
    [self destroyFramebuffer];
    [self createFramebuffer];
    [self drawView];
}


#define textureCase			false
#define offscreenCase		false
#define onscreenCase		true


- (BOOL) createFramebuffer
{
	int width = self.frame.size.width;
	int height = self.frame.size.height;

    glGenFramebuffersOES(1, &viewFramebuffer);
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);

	// Texture case
	if (textureCase) {
		GLuint texture;
		glGenTextures(1, &texture);
		glBindTexture(GL_TEXTURE_2D, texture);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
		glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D, texture, 0);
	}

	// Offscreen case
	if (offscreenCase) {
		glGenRenderbuffersOES(1, &viewRenderbuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
		glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_RGBA8_OES, width, height);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
	}

	// Onscreen case
	if (onscreenCase) {
		glGenRenderbuffersOES(1, &viewRenderbuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
		[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
		glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
		glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
	}

    if (USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
    }

    if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }

	if (textureCase)
		glBindFramebufferOES(GL_FRAMEBUFFER_OES, 0);

    return YES;
}


- (void) destroyFramebuffer
{
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;

	if (!textureCase) {
		glDeleteRenderbuffersOES(1, &viewRenderbuffer);
		viewRenderbuffer = 0;
	}

    if (depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}


- (void) startAnimation
{
    animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}


- (void) stopAnimation
{
	[animationTimer invalidate];
    animationTimer = nil;
}


- (void) setAnimationTimer:(NSTimer *)newTimer
{
    [animationTimer invalidate];
    animationTimer = newTimer;
}


- (void) setAnimationInterval:(NSTimeInterval)interval
{
    animationInterval = interval;
    if (animationTimer) {
        [self stopAnimation];
        [self startAnimation];
    }
}


// You must implement this method
+ (Class) layerClass
{
    return [CAEAGLLayer class];
}


- (void) accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration
{
	cpp_context->updateAcceleration(acceleration.x, acceleration.y);
}


- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	if ([[event allTouches] count] == 1) {
	    CGPoint pos = [[[[event allTouches] allObjects] objectAtIndex:0] locationInView:self];
		cpp_context->mouseDown(pos.x, pos.y);
    }
}


- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	if ([[event allTouches] count] == 1) {
	    CGPoint pos = [[[[event allTouches] allObjects] objectAtIndex:0] locationInView:self];
		cpp_context->mouseMove(pos.x, pos.y);
    }
}


- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	if ([[event allTouches] count] == 1) {
	    CGPoint pos = [[[[event allTouches] allObjects] objectAtIndex:0] locationInView:self];
		cpp_context->mouseUp(pos.x, pos.y);
	}
}


/*

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{	
	// If we're if waveform mode and not currently in a pinch event, and we've got two touches, start a pinch event
	if ((!pinchEvent) && ([[event allTouches] count] == 2) && (self.displayMode == aurioTouchDisplayModeOscilloscopeWaveform))
	{
		pinchEvent = event;
		NSArray *t = [[event allTouches] allObjects];
		lastPinchDist = fabs([[t objectAtIndex:0] locationInView:view].x - [[t objectAtIndex:1] locationInView:view].x);
		
		sampleSizeText.text = [NSString stringWithFormat:@"%i ms", drawBufferLen / (int)(hwSampleRate / 1000.)];
		[view addSubview:sampleSizeOverlay];
	}
	
}

- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	
	// If we are in a pinch event...
	if ((event == pinchEvent) && ([[event allTouches] count] == 2))
	{
		CGFloat thisPinchDist, pinchDiff;
		NSArray *t = [[event allTouches] allObjects];
		thisPinchDist = fabs([[t objectAtIndex:0] locationInView:view].x - [[t objectAtIndex:1] locationInView:view].x);
		
		// Find out how far we traveled since the last event
		pinchDiff = thisPinchDist - lastPinchDist;
		// Adjust our draw buffer length accordingly,
		drawBufferLen -= 12 * (int)pinchDiff;
		drawBufferLen = CLAMP(kMinDrawSamples, drawBufferLen, kMaxDrawSamples);
		
		// and display the size of our oscilloscope window in our overlay view
		sampleSizeText.text = [NSString stringWithFormat:@"%i ms", drawBufferLen / (int)(hwSampleRate / 1000.)];
		
		lastPinchDist = thisPinchDist;
	}
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	
	if (event == pinchEvent)
	{
		// If our pinch/zoom has ended, nil out the pinchEvent and remove the overlay view
		[sampleSizeOverlay removeFromSuperview];
		pinchEvent = nil;
		return;
	}
	
	// any tap in sonogram view will exit back to the waveform
	if (self.displayMode == aurioTouchDisplayModeSpectrum)
	{
		AudioServicesPlaySystemSound(doubleTapSound);
		self.displayMode = aurioTouchDisplayModeOscilloscopeWaveform;
		return;
	}
	
	UITouch *touch = [touches anyObject];
	if (CGRectContainsPoint(CGRectMake(0., 5., 52., 99.), [touch locationInView:view])) // The Sonogram button was touched
	{
		AudioServicesPlaySystemSound(doubleTapSound);
		if ((self.displayMode == aurioTouchDisplayModeOscilloscopeWaveform) || (self.displayMode == aurioTouchDisplayModeOscilloscopeFFT))
		{
			if (!initted_spectrum) [self setupViewForSpectrum];
			[self clearTextures];
			self.displayMode = aurioTouchDisplayModeSpectrum;
		}
	}
	else if (CGRectContainsPoint(CGRectMake(0., 104., 52., 99.), [touch locationInView:view])) // The Mute button was touched
	{
		AudioServicesPlaySystemSound(buttonPressSound);
		self.mute = !(self.mute);
		return;
	}
	else if (CGRectContainsPoint(CGRectMake(0., 203, 52., 99.), [touch locationInView:view])) // The FFT button was touched
	{
		
		AudioServicesPlaySystemSound(buttonPressSound);
		self.displayMode = (self.displayMode == aurioTouchDisplayModeOscilloscopeWaveform) ?  aurioTouchDisplayModeOscilloscopeFFT :
		aurioTouchDisplayModeOscilloscopeWaveform;
		return;
	}
}

*/


@end
