#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<SDL.h>
#include<GL/glew.h>
#include<fmod.hpp>


// Never include Windows.h without it!
#define WIN32_LEAN_AND_MEAN
#include<windows.h>


// Function forward decls.
GLuint glTexImageTGAFile( const char* filename, int* outWidth, int* outHeight );


// Some macros that are convenient to have around at all times
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)<(b)?(a):(b))


// A simple C++ class that will automatically assert if OpenGL gets
// into an error state.
struct GLDetectError {
	GLDetectError() {
		GLenum error = glGetError();
		assert( GL_NO_ERROR == error );
	}
	~GLDetectError() {
		GLenum error = glGetError();
		assert( GL_NO_ERROR == error );
	}
};


// System-wide globals
bool shouldExit = false;
FMOD::System* fmod;
unsigned char kbPrevState[SDLK_LAST];
unsigned char kbState[SDLK_LAST];
int mousePos[2];
int mousePrevState;
int mouseState;
SDL_Joystick* joy1;
int joy1Axis[2];
unsigned char joy1PrevButton[4];
unsigned char joy1Button[4];


// Game data globals.  Replace these with your own game's data.
int boxX;
int boxY;
GLuint tex;
FMOD::Sound* bg1;
FMOD::Sound* bg2;
FMOD::Sound* fx;
FMOD::Channel* bgChannel = NULL;


// Game specific initialization.  Called once, before graphics, sound,
// etc. are all set up.
//
// If you want to customize the way graphics or sound are set up, you
// need to figure that out here.
void InitEarly( void )
{
	;
}


// Game specific initialization.  Called once, after graphics, sound,
// etc. are all set up.
//
// You probably want to load resources, save data, etc here.
void Init( void )
{
	// initialize 2D graphics
	glMatrixMode( GL_PROJECTION );
	gluOrtho2D( 0, 800, 600, 0 );
	glEnable( GL_TEXTURE_2D );

	// initialize textures
	int width, height;
	tex = glTexImageTGAFile( "red-square.tga", &width, &height );
	fprintf( stderr, "Loaded red-square.tga, %dx%d\n", width, height );

	// initialize sound files
	fmod->createStream( "mm2titl2.mid", FMOD_DEFAULT, 0, &bg1 );
	fmod->createStream( "01 - Overture.mp3", FMOD_DEFAULT, 0, &bg2 );
	fmod->createSound( "shotgun.mp3", FMOD_DEFAULT, 0, &fx );
	fmod->playSound( FMOD_CHANNEL_FREE, NULL, true, &bgChannel );
	bgChannel->setLoopCount( -1 );

	// Initialize box pos
	boxX = 0;
	boxY = 0;
}


// Game specific update. Called once per frame.
//
// You probably want to replace this with a call to the active state's
// update function.
void Update( void )
{
	if( kbState[ SDLK_ESCAPE ] || joy1Button[0] ) {
		shouldExit = true;
	}

	if( kbState[ SDLK_LEFT ] || joy1Axis[0] < -5000 ) {
		boxX = MAX( 0, boxX - 2 );
	}
	if( kbState[ SDLK_RIGHT ] || joy1Axis[0] > 5000 ) {
		boxX = MIN( 800, boxX + 2 );
	}
	if( kbState[ SDLK_UP ] || joy1Axis[1] < -5000 ) {
		boxY = MAX( 0, boxY - 2 );
	}
	if( kbState[ SDLK_DOWN ] || joy1Axis[1] > 5000 ) {
		boxY = MIN( 600, boxY + 2 );
	}

	if( kbState[ SDLK_1 ]) {
		fmod->playSound( FMOD_CHANNEL_REUSE, bg1, false, &bgChannel );
		bgChannel->setLoopCount( -1 );
	}
	if( kbState[ SDLK_2 ]) {
		fmod->playSound( FMOD_CHANNEL_REUSE, bg2, false, &bgChannel );
		bgChannel->setLoopCount( -1 );
	}
	if( kbState[ SDLK_3 ]) {
		bgChannel->setPaused( true );
	}
	if( kbState[ SDLK_0 ]) {
		fmod->playSound( FMOD_CHANNEL_FREE, fx, false, NULL );
	}

	if( mouseState & SDL_BUTTON(1) ) {
		boxX = mousePos[0];
		boxY = mousePos[1];
	}
}

void Render( void )
{
	GLDetectError err;

	glClearColor( 0, 0, 0, 1 );
	glClear( GL_COLOR_BUFFER_BIT );

	// draw a red square
	glBindTexture( GL_TEXTURE_2D, tex );
	glBegin( GL_QUADS );
	glColor3ub( 255, 255, 255 );
	glTexCoord2f( 0, 1 );
	glVertex2i( boxX - 25, boxY - 25 );
	glTexCoord2f( 1, 1 );
	glVertex2i( boxX + 25, boxY - 25 );
	glTexCoord2f( 1, 0 );
	glVertex2i( boxX + 25, boxY + 25 );
	glTexCoord2f( 0, 0 );
	glVertex2i( boxX - 25, boxY + 25 );
	glEnd();
}


#define BPP 4


// Load a file into an OpenGL texture, and return that texture.
GLuint glTexImageTGAFile( const char* filename, int* outWidth, int* outHeight )
{
	// open the file
	FILE* file = fopen( filename, "rb" );
	if( file == NULL ) {
		fprintf( stderr, "Could not open file %s for reading.\n", filename );
		return 0;
	}

	// skip first two bytes of data we don't need
	fseek( file, 2, SEEK_CUR );

	// read in the image type.  For our purposes the image type should
	// be either a 2 or a 3.
	unsigned char imageTypeCode;
	fread( &imageTypeCode, 1, 1, file );
	if( imageTypeCode != 2 && imageTypeCode != 3 ) {
		fclose( file );
		fprintf( stderr, "File %s is an unsupported TGA type: %d\n", filename, imageTypeCode );
		return 0;
	}

	// skip 9 bytes of data we don't need
	fseek( file, 9, SEEK_CUR );

	// read image dimensions
	int imageWidth = 0;
	int imageHeight = 0;
	int bitCount = 0;
	fread( &imageWidth, sizeof( short ), 1, file );
	fread( &imageHeight, sizeof( short ), 1, file );
	fread( &bitCount, sizeof( unsigned char ), 1, file );
	fseek( file, 1, SEEK_CUR );

	// allocate memory for image data and read it in
	unsigned char* bytes = (unsigned char*)calloc( imageWidth * imageHeight * BPP, 1 );

	// read in data
	if( bitCount == 32 ) {
		for( int it = 0; it != imageWidth * imageHeight; ++it ) {
			bytes[ it * BPP + 0 ] = fgetc( file );
			bytes[ it * BPP + 1 ] = fgetc( file );
			bytes[ it * BPP + 2 ] = fgetc( file );
			bytes[ it * BPP + 3 ] = fgetc( file );
		}
	} else {
		for( int it = 0; it != imageWidth * imageHeight; ++it ) {
			bytes[ it * BPP + 0 ] = fgetc( file );
			bytes[ it * BPP + 1 ] = fgetc( file );
			bytes[ it * BPP + 2 ] = fgetc( file );
			bytes[ it * BPP + 3 ] = 255;
		}
	}

	fclose( file );

	// load into OpenGL
	GLuint tex;
	glGenTextures( 1, &tex );
	glBindTexture( GL_TEXTURE_2D, tex );
	// gluBuild2DMipmaps does not support alpha channels :(
	// gluBuild2DMipmaps( GL_TEXTURE_2D, GL_RGBA, imageWidth, imageHeight,
	// 				   GL_RGBA, GL_UNSIGNED_BYTE, bytes );
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, imageWidth, imageHeight, 0,
				  GL_BGRA, GL_UNSIGNED_BYTE, bytes );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

	free( bytes );

	if( outWidth ) {
		*outWidth = imageWidth;
	}
	if( outHeight ) {
		*outHeight = imageHeight;
	}
	return tex;
}


// Main entry point
int main(int argc, char *argv[])
{
	// Initialize SDL
	if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK ) < 0 || !SDL_GetVideoInfo() ) {
		fprintf( stderr, "Could not initialize SDL\n" );
		return 1;
	}
	//SDL_ShowCursor( SDL_DISABLE );
	SDL_SetVideoMode( 800, 600, 32, SDL_OPENGL );
	SDL_WM_SetCaption( "TestSDL Game", NULL );
  
	// Initialize OpenGL
	SDL_GL_SetAttribute( SDL_GL_BUFFER_SIZE, 32 );
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
	SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, 0 );
	GLenum error = glewInit();
	if( error != GLEW_OK ) {
		fprintf( stderr, "Could not start up OpenGL.  Error=%s\n", glewGetErrorString( error ));
		return 1;
	}
	if( !GLEW_VERSION_1_5 ) {
		fprintf( stderr, "This game requires at least OpenGL 1.5, but you do not have that.\n" );
		return 1;
	}
	glViewport( 0, 0, 800, 600 );

	// Initialize FMod
	FMOD::System_Create(&fmod);
	fmod->init( 100, FMOD_INIT_NORMAL, 0 );
	fmod->setDSPBufferSize(1024, 10);

	// Initialize SDL's Joystick
	SDL_JoystickEventState( SDL_ENABLE );
	joy1 = SDL_JoystickOpen( 0 );
	if( !joy1 ) {
		fprintf( stderr, "Could not open first joystick, found only %d joysticks.\n", SDL_NumJoysticks() );
	}

	// Other Initialization goes here
	Init();

	int ticksPerFrame = 1000 / 10;
	int prevTick = SDL_GetTicks();

	while( !shouldExit ) {
		SDL_Event event;
		while( SDL_PollEvent( &event )) {
			switch( event.type ) {
				case SDL_QUIT:
					return 0;
			}
		}

		// throttle framerate
		int tick = SDL_GetTicks();
        do {
            SDL_Delay( MAX( 0, ticksPerFrame - (tick - prevTick) ));
			tick = SDL_GetTicks();
        } while( ticksPerFrame - (tick - prevTick) > 0 );
        prevTick = tick;
		
		// update keyboard input state
		int numKeys;
		unsigned char* keys = SDL_GetKeyState( &numKeys );
		memcpy( kbPrevState, kbState, sizeof( kbPrevState ));
		memset( kbState, 0, sizeof( kbState ));
		memcpy( kbState, keys, MIN( sizeof( kbState ), numKeys ));

		// update mouse input state
		mousePrevState = mouseState;
		mouseState = SDL_GetMouseState( &mousePos[0], &mousePos[1] );

		// update joystick input state
		memcpy( joy1PrevButton, joy1Button, sizeof( joy1PrevButton ));
		if( joy1 ) {
			for( int it = 0; it != 2; ++it ) {
				joy1Axis[it] = SDL_JoystickGetAxis( joy1, it );
			}

			for( int it = 0; it != 4; ++it) {
			    joy1Button[it] = SDL_JoystickGetButton( joy1, it );
			}
		}

		// Frame logic
		Update();
		Render();
		
		SDL_GL_SwapBuffers();
		fmod->update();
	}

	return 0;
}
