/*
Copyright (c) 2000-2001 Olivier Dagenais

This software is provided 'as-is', without any express or implied warranty. In
no event will the authors be held liable for any damages arising from the use
of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim
that you wrote the original software. If you use this software in a product,
an acknowledgment in the product documentation would be appreciated but is not
required.

2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.
*/
#include "GopherCore.h"
#include "GopherTypes.h"

bool execute ( void * functionPointer, unsigned long argCount, PARAM * arguments )
{
	bool retval = false;
	switch ( argCount )
	{
		case 0:
			retval = ( (lpFunc0) functionPointer ) ( );
			break;
		case 1:
			retval = ( (lpFunc1) functionPointer ) ( arguments[0] );
			break;
		case 2:
			retval = ( (lpFunc2) functionPointer ) ( arguments[0], arguments[1] );
			break;
		case 3:
			retval = ( (lpFunc3) functionPointer ) ( arguments[0], arguments[1], arguments[2] );
			break;
		case 4:
			retval = ( (lpFunc4) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3] );
			break;
		case 5:
			retval = ( (lpFunc5) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4] );
			break;
		case 6:
			retval = ( (lpFunc6) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5] );
			break;
		case 7:
			retval = ( (lpFunc7) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6] );
			break;
		case 8:
			retval = ( (lpFunc8) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7] );
			break;
		case 9:
			retval = ( (lpFunc9) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8] );
			break;
		case 10:
			retval = ( (lpFunc10) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9] );
			break;
		case 11:
			retval = ( (lpFunc11) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], arguments[10] );
			break;
		case 12:
			retval = ( (lpFunc12) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], arguments[10], arguments[11] );
			break;
		case 13:
			retval = ( (lpFunc13) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], arguments[10], arguments[11], arguments[12] );
			break;
		case 14:
			retval = ( (lpFunc14) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], arguments[10], arguments[11], arguments[12], arguments[13] );
			break;
		case 15:
			retval = ( (lpFunc15) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], arguments[10], arguments[11], arguments[12], arguments[13], arguments[14] );
			break;
		case 16:
			retval = ( (lpFunc16) functionPointer ) ( arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9], arguments[10], arguments[11], arguments[12], arguments[13], arguments[14], arguments[15] );
			break;
	}
	return retval;
}



int initializeLibraries ( DemoStruct & theDemo )
{
	unsigned long c, d;
	LibraryStruct * currentLibrary;
	void * libraryHandle;

	for ( c = 0; c < theDemo.numLibraries; c++ )
	{
		currentLibrary = &(theDemo.libraries[c]);
		if ( ( libraryHandle = SDL_LoadObject ( currentLibrary->libraryName ) ) == NULL )
		{
			fprintf ( stderr, "Couldn't load library '%s'!\n", currentLibrary->libraryName );
			return ( -1 );
		}
		currentLibrary->libraryHandle = libraryHandle;

		if ( ( currentLibrary->pointerToInitFunction = SDL_LoadFunction ( libraryHandle, "Gopher_Init" ) ) == NULL )
		{
			fprintf ( stderr, "Couldn't load function 'Gopher_Init' from library '%s'!\n", currentLibrary->libraryName );
			return ( -1 );
		}

		if ( ( currentLibrary->pointerToQuitFunction = SDL_LoadFunction ( libraryHandle, "Gopher_Quit" ) ) == NULL )
		{
			fprintf ( stderr, "Couldn't load function 'Gopher_Quit' from library '%s'!\n", currentLibrary->libraryName );
			return ( -1 );
		}

		if ( ( currentLibrary->pointerToBeginNewFrameFunction = SDL_LoadFunction ( libraryHandle, "Gopher_BeginNewFrame" ) ) == NULL )
		{
			fprintf ( stderr, "Couldn't load function 'Gopher_BeginNewFrame' from library '%s'!\n", currentLibrary->libraryName );
			return ( -1 );
		}

		for ( d = 0; d < currentLibrary->numFunctions; d++ )
		{
			FunctionStruct * currentFunction = &(currentLibrary->functions[d]);
			if ( ( currentFunction->pointerToFunction = SDL_LoadFunction ( libraryHandle, currentFunction->functionName ) ) == NULL )
			{
				fprintf ( stderr, "Couldn't load function '%s' from library '%s'!\n", currentFunction->functionName, currentLibrary->libraryName );
				return ( -1 );
			}
		}
	}

	return ( 0 );
}


void freeLibraries ( DemoStruct & theDemo )
{
	unsigned long c;
	LibraryStruct * currentLibrary;

	for ( c = 0; c < theDemo.numLibraries; c++ )
	{
		currentLibrary = &(theDemo.libraries[c]);
		// TODO:  set all function pointers to NULL
		SDL_UnloadObject ( (currentLibrary->libraryHandle) );
	}	
}


//	Match function pointers to all event instances
int matchFunctionPointers ( DemoStruct & theDemo )
{
	unsigned long t, e, l, f;
	bool found;

	for ( t = 0; t < theDemo.numTracks; t++ )
	{
		TrackStruct * currentTrack = &(theDemo.tracks[t]);
		for ( e = 0; e < currentTrack->numEvents; e++ )
		{
			EventStruct * currentEvent = &(currentTrack->events[e]);
			found = false;
			// Find Library called "currentEvent->libraryName"
			for ( l = 0; l < theDemo.numLibraries; l++ )
			{
				LibraryStruct * currentLibrary = &(theDemo.libraries[l]);
				if ( strcmp ( currentEvent->libraryName, currentLibrary->libraryName ) == 0 )
				{
					// Find Function called "currentEvent->functionName
					for ( f = 0; f < currentLibrary->numFunctions; f++ )
					{
						FunctionStruct * currentFunction = &(currentLibrary->functions[f]);
						if ( strcmp ( currentEvent->functionName, currentFunction->functionName ) == 0 )
						{
							currentEvent->functionPointer = currentFunction->pointerToFunction;
							found = true;
							break;
						}
					}
					if ( found )
					{
						break;
					}
				}
			}
			if ( !found )
			{
				fprintf ( stderr, "Couldn't match event %d's function '%s' for library '%s' to available libraries in track %d",
					              e, currentEvent->functionName, currentEvent->libraryName, t );
				return ( -1 );
			}
		}
	}

	return ( 0 );
}



int createSurfaces ( DemoStruct & theDemo, SDL_Config_Struct & config, SDL_Surface * screen )
{
	unsigned long s;
	theDemo.inputSurfaces = new SDL_Surface *[theDemo.numInputSurfaces];
	for ( s = 0; s < theDemo.numInputSurfaces; s++ )
	{
		if ( ( theDemo.inputSurfaces[s] = SDL_CreateRGBSurface ( SDL_SWSURFACE,
										  config.width, config.height, screen->format->BitsPerPixel,
										  screen->format->Rmask, screen->format->Gmask,
										  screen->format->Bmask, screen->format->Amask ) ) == NULL )
		{
			fprintf ( stderr, "Couldn't create some input surface!" );
			return ( -1 );
		}
	}

	theDemo.outputSurfaces = new SDL_Surface *[theDemo.numOutputSurfaces];
	for ( s = 0; s < theDemo.numOutputSurfaces; s++ )
	{
		if ( ( theDemo.outputSurfaces[s] = SDL_CreateRGBSurface ( SDL_SWSURFACE,
										  config.width, config.height, screen->format->BitsPerPixel,
										  screen->format->Rmask, screen->format->Gmask,
										  screen->format->Bmask, screen->format->Amask ) ) == NULL )
		{
			fprintf ( stderr, "Couldn't create some output surface!" );
			return ( -1 );
		}
	}

	return ( 0 );
}


void destroySurfaces ( DemoStruct & theDemo )
{
	unsigned long s;
	for ( s = 0; s < theDemo.numInputSurfaces; s++ )
	{
		SDL_FreeSurface ( theDemo.inputSurfaces[s] );
	}

	delete [] theDemo.inputSurfaces;


	for ( s = 0; s < theDemo.numOutputSurfaces; s++ )
	{
		SDL_FreeSurface ( theDemo.outputSurfaces[s] );
	}

	delete [] theDemo.outputSurfaces;
}


int gopherInitialize ( DemoStruct & theDemo )
{
	unsigned long l;
	bool retval;
	for ( l = 0; l < theDemo.numLibraries; l++ )
	{
		// call Gopher_Init in each library
		retval = ( ( lpfGopher_Init ) theDemo.libraries[l].pointerToInitFunction ) ( theDemo.outputSurfaces, theDemo.inputSurfaces );
		if ( !retval )
		{
			return ( -1 );
		}
	}
	return ( 0 );
}


void gopherTerminate ( DemoStruct & theDemo )
{
	unsigned long l;
	for ( l = 0; l < theDemo.numLibraries; l++ )
	{
		// call Gopher_Quit in each library
		( ( lpfGopher_Quit ) theDemo.libraries[l].pointerToQuitFunction ) (  );
	}
	return;
}


bool checkUserInput ( )
{
	bool done = false;
	SDL_Event event;
	while ( SDL_PollEvent(&event) )
	{
		if ( event.type == SDL_QUIT )
		{
			done = true;
		}
		if ( event.type == SDL_KEYDOWN )
		{
			if ( event.key.keysym.sym == SDLK_ESCAPE )
			{
				done = true;
			}
		}
	}
	return done;
}

// Should I return some value?
void interpolate ( Uint32 currentTime, EventStruct * currentEvent, PARAM * currentParameters )
{
	unsigned long c;
	signed long diff, mult, div, eventTime;
	eventTime = (currentEvent->endTime - currentEvent->startTime);
	for ( c = 0; c < currentEvent->numParams; c++ )
	{
		diff = currentTime - currentEvent->startTime;
		mult = diff * (currentEvent->endParameters[c] - currentEvent->startParameters[c]);
		div = mult / eventTime;
		currentParameters[c] = currentEvent->startParameters[c] + div;
	}
}

int gopherPlay ( DemoStruct & theDemo, SDL_Surface * screen, Uint32 startOffset)
{
	bool done, retval = true;
	unsigned int library;
	signed int track, tracks;
	unsigned long * eventIndex;
	TrackStruct * currentTrack;
	EventStruct * currentEvent;
	LibraryStruct * currentLibrary;
	PARAM currentParameters[16];

	Uint32 startTime, currentTime;
	startTime = SDL_GetTicks ( ) - startOffset;

	tracks = theDemo.numTracks;
	eventIndex = new unsigned long[tracks];
	for ( track = 0; track < tracks; track++ )
	{
		eventIndex[track] = 0;
	}

	done = false;
	while ( !done )
	{
		currentTime = SDL_GetTicks ( ) - startTime;
	    if ( currentTime > theDemo.runningTime )
		{
			break;
		}
		// Tell all the libraries we are starting a new frame
		// (right now we only use the zero'th output surface
		for ( library = 0; library < theDemo.numLibraries; library++ )
		{
			currentLibrary = &(theDemo.libraries[library]);
			( ( lpfGopher_BeginNewFrame ) currentLibrary->pointerToBeginNewFrameFunction ) ( 0 );
		}
		
		// play from right to left
		for ( track = tracks - 1; track >= 0; track-- )
		{
			currentTrack = &(theDemo.tracks[track]);
			startPlayingTrackEvent:
			// make sure we didn't run out of events for this track
			if ( eventIndex[track] < currentTrack->numEvents )
			{
				currentEvent = &(currentTrack->events[eventIndex[track]]);
				unsigned long start, end;
				start = currentEvent->startTime;
				end = currentEvent->endTime;
				if ( currentTime >= start )
				{
					if ( currentTime > end )
					{
						// This event has ceased happening, move to the next one
						eventIndex[track]++;
						// Now we have to try playing an event in this track again...
						goto startPlayingTrackEvent;
					}

					// interpolate parameters
					interpolate ( currentTime, currentEvent, currentParameters );

					// actually execute
					retval = execute ( currentEvent->functionPointer, currentEvent->numParams, currentParameters );
					if ( !retval )
					{
						break;
					}
				}
			}
		}

		// Now blit our frame
		if ( theDemo.useOpenGL )
		{
			SDL_GL_SwapBuffers ( );
		}
		else {
			SDL_BlitSurface ( theDemo.outputSurfaces[0], NULL, screen, NULL );
			SDL_Flip ( screen );
		}

		// Check to see if the user hit ESC or clicked on close or something...
		done = checkUserInput ( );
	}

	delete [] eventIndex;

	if ( retval )
	{
		return ( 0 );
	}
	else
	{
		return ( -1 );
	}
}