/*===============================================================================================
 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 "LSA2.h"

FMOD_EVENTSYSTEM *aEventSystem  = 0;
FMOD_MUSICSYSTEM *aMusicSystem  = 0;
FMOD_EVENT *aEvent = 0;

FMOD_MUSICPROMPT *aPnoC   = 0;
FMOD_MUSICPROMPT *aPnoE   = 0;
FMOD_MUSICPROMPT *aPnoAb  = 0;

FMOD_MUSICPROMPT *aEPnoC  = 0;
FMOD_MUSICPROMPT *aEPnoE  = 0;
FMOD_MUSICPROMPT *aEPnoAb = 0;

FMOD_MUSICPROMPT *aBassC  = 0;
FMOD_MUSICPROMPT *aBassE  = 0;
FMOD_MUSICPROMPT *aBassAb = 0;

FMOD_MUSICPROMPT *aPerc   = 0;

int aSegmentStart, seg = 0;
int beatNumber = 0;
int keyPlaying, bonusStartKey =  0;
enum {KEY_OF_C = 0,
	  KEY_OF_E = 1,
	  KEY_OF_Ab = 2};
bool changeTimeOfDay = false;
bool dayTime = true;
bool playingBonus = false;
bool airAudioActive = false;
int noteDx = 0;
int numNotes = 0;
int currMelody, prevMelody = 0;
int bonusMelody[50] = {0};

#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); \
	} \
}

void startPiano()
{
	FMOD_RESULT result = FMOD_OK;
	FMOD_BOOL active;

	switch (keyPlaying)
	{
		case KEY_OF_C:
			result = FMOD_MusicPrompt_Begin(aPnoC);
			CHECK_RESULT(result);
			break;
		case KEY_OF_E:
			result = FMOD_MusicPrompt_Begin(aPnoE);
			CHECK_RESULT(result);
			break;
		case KEY_OF_Ab:
			result = FMOD_MusicPrompt_Begin(aPnoAb);
			CHECK_RESULT(result);
			break;
	}
}

void startEPiano()
{
	FMOD_RESULT result = FMOD_OK;
	FMOD_BOOL active;

	switch (keyPlaying)
	{
		case KEY_OF_C:
			result = FMOD_MusicPrompt_Begin(aEPnoC);
			CHECK_RESULT(result);
			break;
		case KEY_OF_E:
			result = FMOD_MusicPrompt_Begin(aEPnoE);
			CHECK_RESULT(result);
			break;
		case KEY_OF_Ab:
			result = FMOD_MusicPrompt_Begin(aEPnoAb);
			CHECK_RESULT(result);
			break;
	}
}

void checkTimeOfDay()
{
	if (changeTimeOfDay) {
		if (dayTime) {
			startPiano();
		} else { //nighttime
			startEPiano();
		}
		changeTimeOfDay = false;
	}
}

float getPitch()
{
	// the tweet sfx is C5, so lay out the major and minor scales for C, E, and Ab as distances from C in semitones
	const float CMajScale[]  = {-12,  -10,  -8,  -7,  -5,  -3,  -1,   0,  2,  4,  5,  7,  9,  11,  12,  14,  16};
	const float CminScale[]  = {-12,  -10,  -9,  -7,  -5,  -3,  -2,   0,  2,  3,  5,  7,  9,  10,  12,  14,  16};
	const float EMajScale[]  = {-13,  -11,  -9,  -8,  -6,  -4,  -3,  -1,  1,  3,  4,  6,  8,   9,  11,  13,  15, 16};
	const float EminScale[]  = {-13,  -11, -10,  -8,  -6,  -5,  -3,  -1,  1,  2,  4,  6,  7,   9,  11,  13,  14, 16};
	const float AbMajScale[] = {-12,  -11,  -9,  -7,  -5,  -4,  -2,   0,  1,  3,  5,  7,  8,  10,  12,  13,  15};
	const float AbminScale[] = {-13,  -11,  -9,  -8,  -6,  -4,  -2,  -1,  1,  3,  4,  6,  8,  10,  11,  13,  15};

	// bonusMelody[10] are in KEY_OF_C, so play notes from the C Maj/min scales if that's the current key
	// if the bonusMelody starts playing when the current key is E or Ab, offset the pitch to begin at the appropriate place in that key
	// however, if the key changes while the melody is playing, transpose but don't offset, so as to prevent bad voice leading
	float pitch;
	int pitchOffset;

	switch (keyPlaying)
	{
		case KEY_OF_C:
			if (dayTime) {
				pitch = CMajScale[bonusMelody[noteDx]];
			} else {
				pitch = CminScale[bonusMelody[noteDx]];
			}
			break;
		case KEY_OF_E:
			if (bonusStartKey == keyPlaying) {
				pitchOffset = 3;
			} else {
				pitchOffset = 0;
			}

			if (dayTime) {
				pitch = EMajScale[bonusMelody[noteDx]+pitchOffset];
			} else {
				pitch = EminScale[bonusMelody[noteDx]+pitchOffset];
			}
			break;
		case KEY_OF_Ab:
			if (bonusStartKey == keyPlaying) {
				pitchOffset = 2;
			} else {
				pitchOffset = 0;
			}

			if (dayTime) {
				pitch = AbMajScale[bonusMelody[noteDx]-pitchOffset];
			} else {
				pitch = AbminScale[bonusMelody[noteDx]-pitchOffset];
			}
			break;
	}
	return pitch;
}

void bonusSound()
{
	FMOD_RESULT result = FMOD_OK;
	FMOD_EVENT_PITCHUNITS units = FMOD_EVENT_PITCHUNITS_SEMITONES;

	if (noteDx < numNotes) {
		if (bonusMelody[noteDx] == 99) { // don't play note, i.e. rest
			//__android_log_print(ANDROID_LOG_ERROR, "fmod", "bonusSound play rest");
		} else {
			result = FMOD_EventSystem_GetEvent(aEventSystem, "LSA2/LSAsfx/tweet", FMOD_EVENT_DEFAULT, &aEvent);
			CHECK_RESULT(result);
			result = FMOD_Event_SetPitch(aEvent, getPitch(), units);
			CHECK_RESULT(result);
			result = FMOD_Event_Start(aEvent);
			CHECK_RESULT(result);
		}
		noteDx++;
	} else {
		noteDx = 0;
		playingBonus = false;
	}
}

FMOD_RESULT F_CALLBACK aSegmentCallback(FMOD_MUSIC_CALLBACKTYPE type, void *param1, void *param2, void *userdata)
{
	FMOD_RESULT result = FMOD_OK;
	FMOD_BOOL active;

	switch (type)
	{
		case FMOD_MUSIC_CALLBACKTYPE_BEAT:
			beatNumber = (uintptr_t)param2;
			// set the key currently being played on the 3rd beat of any bass transition segment
			// then check if the daytime (piano) or nighttime (e.piano) instrument needs to start playing
			// (this is also done on the third beat of a transition segment, to keep in sync with the bass segments)
			if (beatNumber == 3) {
				if (aSegmentStart == MUSICSEGMENT_LSA2_BASSAB_THEME_ABTOC ||
					aSegmentStart == MUSICSEGMENT_LSA2_BASSC_THEME_CTOC   ||
					aSegmentStart == MUSICSEGMENT_LSA2_BASSE_THEME_ETOC) {
					keyPlaying = KEY_OF_C;
					checkTimeOfDay();
				} else
				if (aSegmentStart == MUSICSEGMENT_LSA2_BASSAB_THEME_ABTOE ||
					aSegmentStart == MUSICSEGMENT_LSA2_BASSC_THEME_CTOE   ||
					aSegmentStart == MUSICSEGMENT_LSA2_BASSE_THEME_ETOE) {
					keyPlaying = KEY_OF_E;
					checkTimeOfDay();
				} else
				if (aSegmentStart == MUSICSEGMENT_LSA2_BASSAB_THEME_ABTOAB ||
					aSegmentStart == MUSICSEGMENT_LSA2_BASSC_THEME_CTOAB   ||
					aSegmentStart == MUSICSEGMENT_LSA2_BASSE_THEME_ETOAB) {
					keyPlaying = KEY_OF_Ab;
					checkTimeOfDay();
				}
			}
			break;

		case FMOD_MUSIC_CALLBACKTYPE_SEGMENT_START:
			// set which bass segment is playing, so that it can be checked during the beat callback
			seg = (uintptr_t)param1;
			if (seg == MUSICSEGMENT_LSA2_BASSAB_THEME_ABTOC  ||
				seg == MUSICSEGMENT_LSA2_BASSC_THEME_CTOC    ||
				seg == MUSICSEGMENT_LSA2_BASSE_THEME_ETOC    ||
				seg == MUSICSEGMENT_LSA2_BASSAB_THEME_ABTOE  ||
				seg == MUSICSEGMENT_LSA2_BASSC_THEME_CTOE    ||
				seg == MUSICSEGMENT_LSA2_BASSE_THEME_ETOE    ||
				seg == MUSICSEGMENT_LSA2_BASSAB_THEME_ABTOAB ||
				seg == MUSICSEGMENT_LSA2_BASSC_THEME_CTOAB   ||
				seg == MUSICSEGMENT_LSA2_BASSE_THEME_ETOAB   ||
				seg == MUSICSEGMENT_LSA2_BASSAB_THEME_AB1    ||
				seg == MUSICSEGMENT_LSA2_BASSC_THEME_C1      ||
				seg == MUSICSEGMENT_LSA2_BASSE_THEME_E1) {
				aSegmentStart = seg;
			} // else ignore piano, e.piano, and percussion segments
		break;
	}
	return FMOD_OK;
}

void Java_com_twittering_landseaair_FMODaudio_cInitAirAudio(JNIEnv *env, jobject thiz, jstring mediaPath)
{
	FMOD_RESULT result = FMOD_OK;
	time_t loadTime;

	aEventSystem = (FMOD_EVENTSYSTEM*) getEventSystem();
	aMusicSystem = (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(aEventSystem, _mediaPath);
	CHECK_RESULT(result);
    (*env)->ReleaseStringUTFChars (env, mediaPath, _mediaPath);

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

	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "get initial events");
	result = FMOD_EventSystem_GetEvent(aEventSystem, "LSA2/LSAsfx/tweet", FMOD_EVENT_DEFAULT, &aEvent);
	CHECK_RESULT(result);
	result = FMOD_EventSystem_GetEvent(aEventSystem, "LSA2/LSAsfx/flutterMaj", FMOD_EVENT_DEFAULT, &aEvent);
	CHECK_RESULT(result);
	result = FMOD_EventSystem_GetEvent(aEventSystem, "LSA2/LSAsfx/fluttermin", FMOD_EVENT_DEFAULT, &aEvent);
	CHECK_RESULT(result);

	loadTime = time (NULL);
	//__android_log_print(ANDROID_LOG_ERROR, "fmod", "load samples");
	result = FMOD_MusicSystem_LoadSoundData(aMusicSystem, 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(aMusicSystem, MUSICCUE_LSA2_PERC,   &aPerc);
	CHECK_RESULT(result);
	result = FMOD_MusicSystem_PrepareCue(aMusicSystem, MUSICCUE_LSA2_PNOC,   &aPnoC);
	CHECK_RESULT(result);
	result = FMOD_MusicSystem_PrepareCue(aMusicSystem, MUSICCUE_LSA2_PNOE,   &aPnoE);
	CHECK_RESULT(result);
	result = FMOD_MusicSystem_PrepareCue(aMusicSystem, MUSICCUE_LSA2_PNOAB,  &aPnoAb);
	CHECK_RESULT(result);
	result = FMOD_MusicSystem_PrepareCue(aMusicSystem, MUSICCUE_LSA2_EPIANOC,  &aEPnoC);
	CHECK_RESULT(result);
	result = FMOD_MusicSystem_PrepareCue(aMusicSystem, MUSICCUE_LSA2_EPIANOE,  &aEPnoE);
	CHECK_RESULT(result);
	result = FMOD_MusicSystem_PrepareCue(aMusicSystem, MUSICCUE_LSA2_EPIANOAB, &aEPnoAb);
	CHECK_RESULT(result);
	result = FMOD_MusicSystem_PrepareCue(aMusicSystem, MUSICCUE_LSA2_BASSC,  &aBassC);
	CHECK_RESULT(result);
	result = FMOD_MusicSystem_PrepareCue(aMusicSystem, MUSICCUE_LSA2_BASSC,  &aBassE);
	CHECK_RESULT(result);
	result = FMOD_MusicSystem_PrepareCue(aMusicSystem, MUSICCUE_LSA2_BASSC,  &aBassAb);
	CHECK_RESULT(result);

	result = FMOD_MusicSystem_SetCallback(aMusicSystem, aSegmentCallback, 0);
	CHECK_RESULT(result);

	result = FMOD_MusicSystem_SetParameterValue(aMusicSystem, MUSICPARAM_LSA2_KEYCHANGE, 0.0f);
	CHECK_RESULT(result);
	result = FMOD_MusicPrompt_Begin(aPnoC);
	CHECK_RESULT(result);
	result = FMOD_MusicPrompt_Begin(aBassC);
	CHECK_RESULT(result);
	result = FMOD_MusicPrompt_Begin(aPerc);
	CHECK_RESULT(result);

	keyPlaying =  0;
	bonusStartKey =  0;
	changeTimeOfDay = false;
	dayTime = true;
	airAudioActive = true;
}

void AirUpdate()
{
	if (playingBonus && airAudioActive) {
		bonusSound();
	}
}

void Java_com_twittering_landseaair_FMODaudio_cStopAirAudio(JNIEnv *env, jobject thiz)
{
	FMOD_RESULT result = FMOD_OK;
	airAudioActive = false;
	result = FMOD_MusicSystem_Reset(aMusicSystem);
	CHECK_RESULT(result);
	result = FMOD_EventSystem_Unload(aEventSystem);
	CHECK_RESULT(result);
}

void Java_com_twittering_landseaair_FMODaudio_cChangeKey(JNIEnv *env, jobject thiz, jfloat keyChangeParamValue)
{
	FMOD_RESULT result = FMOD_OK;

    float _keyChangeParamValue = keyChangeParamValue;
	result = FMOD_MusicSystem_SetParameterValue(aMusicSystem, MUSICPARAM_LSA2_KEYCHANGE, _keyChangeParamValue);
	CHECK_RESULT(result);
}

void Java_com_twittering_landseaair_FMODaudio_cDaytime(JNIEnv *env, jobject thiz)
{
	changeTimeOfDay = true;
	dayTime = true;
}

void Java_com_twittering_landseaair_FMODaudio_cTransToNight(JNIEnv *env, jobject thiz)
{
	FMOD_RESULT result = FMOD_OK;

	result = FMOD_MusicPrompt_End(aPnoC);
	CHECK_RESULT(result);
	result = FMOD_MusicPrompt_End(aPnoE);
	CHECK_RESULT(result);
	result = FMOD_MusicPrompt_End(aPnoAb);
	CHECK_RESULT(result);
}

void Java_com_twittering_landseaair_FMODaudio_cNighttime(JNIEnv *env, jobject thiz)
{
	changeTimeOfDay = true;
	dayTime = false; //nighttime
}

void Java_com_twittering_landseaair_FMODaudio_cTransToDay(JNIEnv *env, jobject thiz)
{
	FMOD_RESULT result = FMOD_OK;

	result = FMOD_MusicPrompt_End(aEPnoC);
	CHECK_RESULT(result);
	result = FMOD_MusicPrompt_End(aEPnoE);
	CHECK_RESULT(result);
	result = FMOD_MusicPrompt_End(aEPnoAb);
	CHECK_RESULT(result);
}

void Java_com_twittering_landseaair_FMODaudio_cPlayBonusSound(JNIEnv *env, jobject thiz)
{
	FMOD_RESULT result = FMOD_OK;
	// melody consists of positions in the array of notes defining the major/minor scales in getPitch();
	// "7" is middle of the array (semitone transposition = 0)
	// "99" means "don't play a note" (rest)
	const int melody1[]  = {7,9,11,99,7,9,11,99,7,9,11};
	const int melody2[]  = {7,9,11,99,7,10,12,99,7,9,11,99,11,9,7};
	const int melody3[]  = {7,7,11,11,99,7,7,11,11};
	const int melody4[]  = {4,8,99,4,8,99,99,4,8,99,4,8};
	const int melody5[]  = {8,7,8,7,8,7,8,7,8,9,8,7};
	const int melody6[]  = {7,8,7,8,7,8,7,8,7,11};
	const int melody7[]  = {7,8,7,99,8,9,8,99,9,10,9,99,10,11,10,99,11,12,11};
	const int melody8[]  = {9,7,9,7,9,7,9,7,10,8,10,8,10,8,10,8,11,9,11,9,11,9,11,9};
	const int melody9[]  = {7,8,7,4,7,8,7,4,7,99,7,99,7};
	const int melody10[] = {11,10,9,11,10,9,8,10,9,8,7,8,7,8,7};

	if (playingBonus) {
		noteDx = 0; // if a bug is eaten before the previous bug melody has finished, start again
	} else {
		playingBonus = true;
	}

	bonusStartKey = keyPlaying;
	currMelody = (rand()%10)+1; // play random melody, allowing repeats
	int i;

	switch (currMelody) {
	case 1:
		numNotes = sizeof(melody1)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody1[i];}
		break;
	case 2:
		numNotes = sizeof(melody2)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody2[i];}
		break;
	case 3:
		numNotes = sizeof(melody3)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody3[i];}
		break;
	case 4:
		numNotes = sizeof(melody4)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody4[i];}
		break;
	case 5:
		numNotes = sizeof(melody5)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody5[i];}
		break;
	case 6:
		numNotes = sizeof(melody6)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody6[i];}
		break;
	case 7:
		numNotes = sizeof(melody7)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody7[i];}
		break;
	case 8:
		numNotes = sizeof(melody8)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody8[i];}
		break;
	case 9:
		numNotes = sizeof(melody9)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody9[i];}
		break;
	case 10:
		numNotes = sizeof(melody10)/sizeof(int);
		for (i=0; i<numNotes; i++) {bonusMelody[i] = melody10[i];}
		break;
	default:
		currMelody = 0; //reset for play in sequence
	}
}

void Java_com_twittering_landseaair_FMODaudio_cPlayBFSound(JNIEnv *env, jobject thiz)
{
	FMOD_RESULT result = FMOD_OK;
	FMOD_EVENT_PITCHUNITS units = FMOD_EVENT_PITCHUNITS_SEMITONES;

	if (airAudioActive) {
		// the flutter events randomly play one of 3 Major or minor sfx, depending the time (day = Major, night = minor)
		// the sfx are all in the key of C, so transpose them appropriately depending on the key
		float pitch;
		switch (keyPlaying)
		{
			case KEY_OF_C:
				pitch = 0;
				break;
			case KEY_OF_E:
				pitch = 4;
				break;
			case KEY_OF_Ab:
				pitch = -4;
				break;
		}

		if (dayTime) {
			result = FMOD_EventSystem_GetEvent(aEventSystem, "LSA2/LSAsfx/flutterMaj", FMOD_EVENT_DEFAULT, &aEvent);
			CHECK_RESULT(result);
		} else {
			result = FMOD_EventSystem_GetEvent(aEventSystem, "LSA2/LSAsfx/fluttermin", FMOD_EVENT_DEFAULT, &aEvent);
			CHECK_RESULT(result);
		}
		result = FMOD_Event_SetPitch(aEvent, pitch, units);
		CHECK_RESULT(result);
		result = FMOD_Event_Start(aEvent);
		CHECK_RESULT(result);
	}
}

jint Java_com_twittering_landseaair_FMODaudio_cCheckAirKey(JNIEnv *env, jobject thiz)
{
	//0=CMaj, 1=EMaj, 2=AbMajm, 3=Cmin, 4=Emin, 5=Abmin
	if (dayTime) {
		return keyPlaying;
	} else {
		return keyPlaying + 3;
	}
}

