/*===============================================================================================
 Native C code for accessing FMOD libraries.
 peter "pdx" drescher
 www.twittering.com

===============================================================================================*/

#include <jni.h>
#include <android/log.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include "../../fmodexapi/fmodeventapi/api/inc/fmod_event.h"
#include "../../fmodexapi/api/inc/fmod_errors.h"
#include "LSAv2.h"

FMOD_EVENTSYSTEM *sEventSystem  = 0;
FMOD_MUSICSYSTEM *sMusicSystem  = 0;
FMOD_EVENT *sEvent = 0;
FMOD_MUSICPROMPT *seaLevelMusic   = 0;
int sSegmentStart = 0;
int sKeyPlaying = 0;
int prevByteCounter, currByteCounter;
int pinkDice[3]; // red, green, blue
bool firstNoteAfterKeyChange = true;
bool seaAudioActive = false;
enum {KEY_OF_Gmin  = 0,
	  KEY_OF_Bbmin = 1,
	  KEY_OF_Csmin = 2,
	  KEY_OF_Emin  = 3};


#define CHECK_RESULT(x) \
{ \
	FMOD_RESULT _result = x; \
	if (_result != FMOD_OK) \
	{ \
		__android_log_print(ANDROID_LOG_ERROR, "fmod", "FMOD error! (%d) %s\n%s:%d", _result, FMOD_ErrorString(_result), __FILE__, __LINE__); \
		exit(-1); \
	} \
}
FMOD_RESULT F_CALLBACK sSegmentCallback(FMOD_MUSIC_CALLBACKTYPE type, void *param1, void *param2, void *userdata)
{
	FMOD_RESULT result = FMOD_OK;
	FMOD_BOOL active;

	switch (type) {
		case FMOD_MUSIC_CALLBACKTYPE_SEGMENT_START:
		{
			sSegmentStart = (uintptr_t)param1;
			firstNoteAfterKeyChange = true;
			if (sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_G1  ||
				sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_G2  ||
				sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_G3)  {
				sKeyPlaying = KEY_OF_Gmin;
			} else
			if (sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_BB1 ||
				sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_BB2 ||
				sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_BB3) {
				sKeyPlaying = KEY_OF_Bbmin;
			} else
			if (sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_C_1 ||
				sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_C_2 ||
				sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_C_3) {
				sKeyPlaying = KEY_OF_Csmin;
			} else
			if (sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_E1  ||
				sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_E2  ||
				sSegmentStart == MUSICSEGMENT_LSAV2_SEALEVEL_THEME_E3)  {
				sKeyPlaying = KEY_OF_Emin;
			}
			break;
		}
	}
	return FMOD_OK;
}

void Java_com_twittering_landseaair_FMODaudio_cInitSeaAudio(JNIEnv *env, jobject thiz, jstring mediaPath)
{
	FMOD_RESULT result = FMOD_OK;
	time_t loadTime;
	sEventSystem = (FMOD_EVENTSYSTEM*) getEventSystem();
	sMusicSystem = (FMOD_MUSICSYSTEM*) getMusicSystem();
    const char *_mediaPath = (*env)->GetStringUTFChars (env, mediaPath, 0);

	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "set media path= %s", _mediaPath);
	result = FMOD_EventSystem_SetMediaPath(sEventSystem, _mediaPath);
	CHECK_RESULT(result);
    (*env)->ReleaseStringUTFChars (env, mediaPath, _mediaPath);

	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "load eventsystem");
	result = FMOD_EventSystem_Load(sEventSystem, "LSAv2.fev", 0, 0);
	CHECK_RESULT(result);

	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "get initial events");
	result = FMOD_EventSystem_GetEvent(sEventSystem, "LSAv2/sealevel/plankton", FMOD_EVENT_DEFAULT, &sEvent);
	CHECK_RESULT(result);

	loadTime = time (NULL);
	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "load samples");
	result = FMOD_MusicSystem_LoadSoundData(sMusicSystem, FMOD_EVENT_RESOURCE_SAMPLES, FMOD_EVENT_DEFAULT);
	CHECK_RESULT(result);
	loadTime -= time(NULL);
	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "load time = %ld", -loadTime);

	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "prepare cues");
	result = FMOD_MusicSystem_PrepareCue(sMusicSystem, MUSICCUE_LSAV2_SEALEVEL,  &seaLevelMusic);
	CHECK_RESULT(result);

	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "setCallback");
	result = FMOD_MusicSystem_SetCallback(sMusicSystem, sSegmentCallback, 0);
	CHECK_RESULT(result);

	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "begin sea level music");
	result = FMOD_MusicPrompt_Begin(seaLevelMusic);
	CHECK_RESULT(result);
	sKeyPlaying = KEY_OF_Gmin;
	seaAudioActive = true;
}

void Java_com_twittering_landseaair_FMODaudio_cStopSeaAudio(JNIEnv *env, jobject thiz)
{
	FMOD_RESULT result = FMOD_OK;
	result = FMOD_MusicSystem_Reset(sMusicSystem);
	CHECK_RESULT(result);
	result = FMOD_EventSystem_Unload(sEventSystem);
	CHECK_RESULT(result);
	seaAudioActive = false;
}

float getSPitch()
{
	//                            0    1   2    3   4   5   6   7  8  9  10 11 12 13  14  15
	const float GminScale[]  = {-12, -10, -9,  -7, -5, -3, -2,  0, 2, 3, 5, 7, 9, 10, 12, 14};
	const float BbminScale[] = {-12, -11, -9,  -7, -6, -4, -2,  0, 1, 3, 5, 6, 8, 10, 12, 13};
	const float CsminScale[] = {-13, -11, -9,  -8, -6, -4, -3, -1, 1, 3, 4, 6, 8,  9, 11, 13};
	const float EminScale[]  = {-13, -12, -10, -8, -6, -5, -3, -1, 0, 2, 4, 6, 7,  9, 11, 12};
	float pitch;
	bool inPrevKey;
	int arrayPos = 0;
	int bitsChanged = 0;
	int c;

	// pick a position in the array, according to a pink noise (1/f) algorithm using
	// the Martin Gardner method, as described in "White, Brown, and Fractal Music"
	// http://www.twittering.com/twittering/code/AudioUIdemo/docs/WhiteBrownFractalMusic.pdf
	if (currByteCounter == 0)
	{
		for (c = 0; c < 3; c++)
		{
			pinkDice[c] = (rand() % 6);
			arrayPos += pinkDice[c];
		}
	}
	else
	{
		bitsChanged = prevByteCounter ^ currByteCounter;
		for (c = 0; c < 3; c++)
		{
			if (bitsChanged & (1 << c))
			{
				pinkDice[c] = (rand() % 6);
				arrayPos += pinkDice[c];
			}
			else
			{
				arrayPos += pinkDice[c];
			}
		}
	}
	prevByteCounter = currByteCounter;
	currByteCounter++;

//  When the key changes, the first note played can sound "wrong" (even though it's from the "right" scale), if it's not also in the previous key
//  in that case, if the first note after a key change is not in the previous key, reset it
		switch (sKeyPlaying) {
		case KEY_OF_Gmin:
			if (firstNoteAfterKeyChange){
				firstNoteAfterKeyChange = false;
				switch (arrayPos) {
				case 2:  arrayPos = 1;  break;
				case 3:  arrayPos = 4;  break;
				case 6:  arrayPos = 5;  break;
				case 9:  arrayPos = 8;  break;
				case 10: arrayPos = 11; break;
				case 13: arrayPos = 12; break;
				case 15: arrayPos = 14; break;
				}
			}
			pitch = GminScale[arrayPos];
			break;
		case KEY_OF_Bbmin:
			if (firstNoteAfterKeyChange){
				firstNoteAfterKeyChange = false;
				switch (arrayPos) {
				case 1:   arrayPos = 0;  break;
				case 4:   arrayPos = 3;  break;
				case 5:   arrayPos = 6;  break;
				case 8:   arrayPos = 7;  break;
				case 11:  arrayPos = 10; break;
				case 12:  arrayPos = 13; break;
				case 15:  arrayPos = 14; break;
				}
			}
			pitch = BbminScale[arrayPos];
			break;
		case KEY_OF_Csmin:
			if (firstNoteAfterKeyChange){
				firstNoteAfterKeyChange = false;
				switch (arrayPos) {
				case 0:  arrayPos = 1;  break;
				case 3:  arrayPos = 2;  break;
				case 5:  arrayPos = 6;  break;
				case 6:  arrayPos = 5;  break;
				case 7:  arrayPos = 8;  break;
				case 10: arrayPos = 9;  break;
				case 13: arrayPos = 12; break;
				case 14: arrayPos = 15; break;
				}
			}
			pitch = CsminScale[arrayPos];
			break;
		case KEY_OF_Emin:
			if (firstNoteAfterKeyChange){
				firstNoteAfterKeyChange = false;
				switch (arrayPos) {
				case 1:  arrayPos = 0;  break;
				case 2:  arrayPos = 3;  break;
				case 5:  arrayPos = 4;  break;
				case 8:  arrayPos = 7;  break;
				case 9:  arrayPos = 10; break;
				case 12: arrayPos = 11; break;
				case 15: arrayPos = 14; break;
				}
			}
			pitch = EminScale[arrayPos];
			break;
		}

	return pitch;
}

void Java_com_twittering_landseaair_FMODaudio_cPlayPlanktonSound(JNIEnv *env, jobject thiz)
{
	FMOD_RESULT result = FMOD_OK;
	FMOD_EVENT_PITCHUNITS units = FMOD_EVENT_PITCHUNITS_SEMITONES;
	if (seaAudioActive) {
		result = FMOD_EventSystem_GetEvent(sEventSystem, "LSAv2/sealevel/plankton", FMOD_EVENT_DEFAULT, &sEvent);
		CHECK_RESULT(result);
		result = FMOD_Event_SetPitch(sEvent, getSPitch(), units);
		CHECK_RESULT(result);
		result = FMOD_Event_Start(sEvent);
		CHECK_RESULT(result);
	}
}

jint Java_com_twittering_landseaair_FMODaudio_cCheckSeaKey(JNIEnv *env, jobject thiz)
{
	return sKeyPlaying;
}

