#include "OpenSLEngine.h"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,"OPENSL_ENGINE.CPP", __VA_ARGS__)

using namespace std;


OpenSLEngine::OpenSLEngine()
 :m_musicVolume(0),
  m_effectVolume(0)
{}

OpenSLEngine::~OpenSLEngine()
{
	closeEngine();
}

/**********************************************************************************
 *   jni
 **********************************************************************************/
#define  CLASS_NAME "org/cocos2dx/lib/Cocos2dxHelper"

typedef struct JniMethodInfo_
{
	JNIEnv *    env;
	jclass      classID;
	jmethodID   methodID;
} JniMethodInfo;

extern "C" {
	static JNIEnv* getJNIEnv(void)
	{

		JavaVM* jvm = cocos2d::JniHelper::getJavaVM();
		if (NULL == jvm) {
			LOGD("Failed to get JNIEnv. JniHelper::getJavaVM() is NULL");
			return NULL;
		}

		JNIEnv *env = NULL;
		// get jni environment
		jint ret = jvm->GetEnv((void**)&env, JNI_VERSION_1_4);

		switch (ret) {
		case JNI_OK :
			// Success!
			return env;

		case JNI_EDETACHED :
			// Thread not attached

			// TODO : If calling AttachCurrentThread() on a native thread
			// must call DetachCurrentThread() in future.
			// see: http://developer.android.com/guide/practices/design/jni.html

			if (jvm->AttachCurrentThread(&env, NULL) < 0)
			{
				LOGD("Failed to get the environment using AttachCurrentThread()");
				return NULL;
			} else {
				// Success : Attached and obtained JNIEnv!
				return env;
			}

		case JNI_EVERSION :
			// Cannot recover from this error
			LOGD("JNI interface version 1.4 not supported");
		default :
			LOGD("Failed to get the environment using GetEnv()");
			return NULL;
		}
	}

	static jclass getClassID(JNIEnv *pEnv)
	{
		jclass ret = pEnv->FindClass(CLASS_NAME);
		if (! ret)
		{
			LOGD("Failed to find class of %s", CLASS_NAME);
		}

		return ret;
	}

	static bool getStaticMethodInfo(JniMethodInfo &methodinfo, const char *methodName, const char *paramCode)
	{
		jmethodID methodID = 0;
		JNIEnv *pEnv = 0;
		bool bRet = false;

		do 
		{
			pEnv = getJNIEnv();
			if (! pEnv)
			{
				break;
			}

			jclass classID = getClassID(pEnv);

			methodID = pEnv->GetStaticMethodID(classID, methodName, paramCode);
			if (! methodID)
			{
				LOGD("Failed to find static method id of %s", methodName);
				break;
			}

			methodinfo.classID = classID;
			methodinfo.env = pEnv;
			methodinfo.methodID = methodID;

			bRet = true;
		} while (0);

		return bRet;
	}
};


/*********************************************************************************
 *   helper
 ********************************************************************************/
#define PLAYSTATE_UNKNOWN 0
#define FILE_NOT_FOUND -1

#define ASSET_MANAGER_GETTER "getAssetManager"
#define LIBANDROID "libandroid.so"

#define MIN_VOLUME_MILLIBEL -4000
#define MAX_VOLUME_MILLIBEL 0
#define RANGE_VOLUME_MILLIBEL 4000

struct AudioPlayer
{
	SLDataSource audioSrc;
	SLObjectItf fdPlayerObject;
	SLPlayItf fdPlayerPlay;
	SLSeekItf fdPlayerSeek;
	SLVolumeItf fdPlayerVolume;
} musicPlayer; /* for background music */

typedef map<unsigned int, vector<AudioPlayer*>* > EffectList;
typedef pair<unsigned int, vector<AudioPlayer*>* > Effect;

void* s_pAndroidHandle  = NULL;
void* s_pOpenSLESHandle = NULL;

static EffectList& sharedList()
{
	static EffectList s_List;
	return s_List;
}

unsigned int _Hash(const char *key)
{
	unsigned int len = strlen(key);
	const char *end=key+len;
	unsigned int hash;

	for (hash = 0; key < end; key++)
	{
		hash *= 16777619;
		hash ^= (unsigned int) (unsigned char) toupper(*key);
	}
	return (hash);
}

SLInterfaceID getInterfaceID(const char *value)
{
	// clear the error stack
	dlerror();
	SLInterfaceID* IID = (SLInterfaceID*)dlsym(s_pOpenSLESHandle, value);
	const char* errorInfo = dlerror();
	if (errorInfo)
	{
		LOGD("Get interface id: %s from OpenSL failed", errorInfo);
		IID = NULL;
	}
	return *IID;
}

void* getFuncPtr(const char *value)
{
	// clear the error stack
	dlerror();
	void* funcPtr = dlsym(s_pOpenSLESHandle, value);
	const char* errorInfo = dlerror();
	if (errorInfo)
	{
		LOGD("Get function from OpenSL failed: %s", errorInfo);
		funcPtr = NULL;
	}
	return funcPtr;
}

int getFileDescriptor(const char * filename, off_t & start, off_t & length)
{
	JniMethodInfo methodInfo;
	if (! getStaticMethodInfo(methodInfo, ASSET_MANAGER_GETTER, "()Landroid/content/res/AssetManager;"))
	{
		methodInfo.env->DeleteLocalRef(methodInfo.classID);
		return FILE_NOT_FOUND;
	}
	jobject assetManager = methodInfo.env->CallStaticObjectMethod(methodInfo.classID, methodInfo.methodID);
	methodInfo.env->DeleteLocalRef(methodInfo.classID);

	AAssetManager* (*AAssetManager_fromJava)(JNIEnv* env, jobject assetManager);
	AAssetManager_fromJava = (AAssetManager* (*)(JNIEnv* env, jobject assetManager))
		dlsym(s_pAndroidHandle, "AAssetManager_fromJava");
	AAssetManager* mgr = AAssetManager_fromJava(methodInfo.env, assetManager);
	assert(NULL != mgr);

	AAsset* (*AAssetManager_open)(AAssetManager* mgr, const char* filename, int mode);
	AAssetManager_open = (AAsset* (*)(AAssetManager* mgr, const char* filename, int mode))
		dlsym(s_pAndroidHandle, "AAssetManager_open");
	AAsset* Asset = AAssetManager_open(mgr, filename, AASSET_MODE_UNKNOWN);
	if (NULL == Asset)
	{
		//LOGD("file not found! Stop preload file: %s", filename);
		return FILE_NOT_FOUND;
	}

	// open asset as file descriptor
	int (*AAsset_openFileDescriptor)(AAsset* asset, off_t* outStart, off_t* outLength);
	AAsset_openFileDescriptor = (int (*)(AAsset* asset, off_t* outStart, off_t* outLength))
		dlsym(s_pAndroidHandle, "AAsset_openFileDescriptor");
	int fd = AAsset_openFileDescriptor(Asset, &start, &length);
	assert(0 <= fd);

	void (*AAsset_close)(AAsset* asset);
	AAsset_close = (void (*)(AAsset* asset))
		dlsym(s_pAndroidHandle, "AAsset_close");
	AAsset_close(Asset);

	return fd;
}


/**********************************************************************************
 *   engine
 **********************************************************************************/
static SLObjectItf s_pEngineObject = NULL;
static SLEngineItf s_pEngineEngine = NULL;
static SLObjectItf s_pOutputMixObject = NULL;

bool createAudioPlayerBySource(AudioPlayer* player)
{
	// configure audio sink
	SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, s_pOutputMixObject};
	SLDataSink audioSnk = {&loc_outmix, NULL};

	// create audio player
	const SLInterfaceID ids[3] = {
		getInterfaceID("SL_IID_SEEK"), getInterfaceID("SL_IID_MUTESOLO"), getInterfaceID("SL_IID_VOLUME")};
	const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
	SLresult result = (*s_pEngineEngine)->CreateAudioPlayer(s_pEngineEngine, &(player->fdPlayerObject), &(player->audioSrc), &audioSnk, 3, ids, req);
	if (SL_RESULT_MEMORY_FAILURE == result)
	{
		return false;
	}

	// realize the player
	result = (*(player->fdPlayerObject))->Realize(player->fdPlayerObject, SL_BOOLEAN_FALSE);
	assert(SL_RESULT_SUCCESS == result);

	// get the play interface
	result = (*(player->fdPlayerObject))->GetInterface(player->fdPlayerObject, getInterfaceID("SL_IID_PLAY"), &(player->fdPlayerPlay));
	assert(SL_RESULT_SUCCESS == result);

	// get the volume interface
	result = (*(player->fdPlayerObject))->GetInterface(player->fdPlayerObject, getInterfaceID("SL_IID_VOLUME"), &(player->fdPlayerVolume));
	assert(SL_RESULT_SUCCESS == result);

	// get the seek interface
	result = (*(player->fdPlayerObject))->GetInterface(player->fdPlayerObject, getInterfaceID("SL_IID_SEEK"), &(player->fdPlayerSeek));
	assert(SL_RESULT_SUCCESS == result);

	return true;
}

bool initAudioPlayer(AudioPlayer* player, const char* filename) 
{
	// configure audio source
	off_t start, length;
	int fd = getFileDescriptor(filename, start, length);
	if (FILE_NOT_FOUND == fd)
	{
		FILE* fp = fopen(filename , "rb");
		if(fp){
			SLDataLocator_URI loc_fd = {SL_DATALOCATOR_URI , (SLchar*)filename};
			SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
			player->audioSrc.pLocator = &loc_fd;
			player->audioSrc.pFormat = &format_mime;
			return createAudioPlayerBySource(player);
		}
		LOGD("file not found! Stop preload file: %s", filename);
		return false;
	}
	SLDataLocator_AndroidFD loc_fd = {SL_DATALOCATOR_ANDROIDFD, fd, start, length};
	SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
	player->audioSrc.pLocator = &loc_fd;
	player->audioSrc.pFormat = &format_mime;

	return createAudioPlayerBySource(player);
}

void destroyAudioPlayer(AudioPlayer * player)
{
	if (player && player->fdPlayerObject != NULL)
	{
		SLresult result;
		result = (*(player->fdPlayerPlay))->SetPlayState(player->fdPlayerPlay, SL_PLAYSTATE_STOPPED);
		assert(SL_RESULT_SUCCESS == result);

		(*(player->fdPlayerObject))->Destroy(player->fdPlayerObject);
		player->fdPlayerObject = NULL;
		player->fdPlayerPlay = NULL;
		player->fdPlayerSeek = NULL;
		player->fdPlayerVolume = NULL;
	}
}

void OpenSLEngine::createEngine(void* pHandle)
{
	assert(pHandle != NULL);
	s_pOpenSLESHandle = pHandle;

	// clear the error stack
	dlerror();
	s_pAndroidHandle = dlopen(LIBANDROID, RTLD_LAZY);
	const char* errorInfo = dlerror();
	if (errorInfo)
	{
		LOGD("%s", errorInfo);
		return;
	}

	SLresult result;
	if (s_pEngineObject == NULL)
	{
		// create engine
		SLresult (*slCreateEngine)(SLObjectItf *pEngine, SLuint32 numOptions, const SLEngineOption *pEngineOptions, SLuint32 numInterfaces, const SLInterfaceID *pInterfaceIds, const SLboolean * pInterfaceRequired );
		slCreateEngine = (SLresult (*)(SLObjectItf *pEngine, SLuint32 numOptions, const SLEngineOption *pEngineOptions, SLuint32 numInterfaces, const SLInterfaceID *pInterfaceIds, const SLboolean * pInterfaceRequired ))
			getFuncPtr("slCreateEngine");
		result = slCreateEngine(&s_pEngineObject, 0, NULL, 0, NULL, NULL);
		assert(SL_RESULT_SUCCESS == result);

		// realize the engine
		result = (*s_pEngineObject)->Realize(s_pEngineObject, SL_BOOLEAN_FALSE);
		assert(SL_RESULT_SUCCESS == result);

		// get the engine interface, which is needed in order to create other objects
		result = (*s_pEngineObject)->GetInterface(s_pEngineObject, getInterfaceID("SL_IID_ENGINE"), &s_pEngineEngine);
		assert(SL_RESULT_SUCCESS == result);

		// create output mix
		const SLInterfaceID ids[1] = {getInterfaceID("SL_IID_ENVIRONMENTALREVERB")};
		const SLboolean req[1] = {SL_BOOLEAN_FALSE};
		result = (*s_pEngineEngine)->CreateOutputMix(s_pEngineEngine, &s_pOutputMixObject, 1, ids, req);
		assert(SL_RESULT_SUCCESS == result);

		// realize the output mix object in sync. mode
		result = (*s_pOutputMixObject)->Realize(s_pOutputMixObject, SL_BOOLEAN_FALSE);
		assert(SL_RESULT_SUCCESS == result);
	}
}

void OpenSLEngine::closeEngine()
{
	// destroy background players
	destroyAudioPlayer(&musicPlayer);

	// destroy effect players
	vector<AudioPlayer*>* vec;
	EffectList::iterator p = sharedList().begin();
	while (p != sharedList().end())
	{
		vec = p->second;
		for (vector<AudioPlayer*>::iterator iter = vec->begin() ; iter != vec->end() ; ++ iter)
		{
			destroyAudioPlayer(*iter);
		}
		vec->clear();
		p++;
	}
	sharedList().clear();

	// destroy output mix interface
	if (s_pOutputMixObject)
	{
		(*s_pOutputMixObject)->Destroy(s_pOutputMixObject);
		s_pOutputMixObject = NULL;
	}

	// destroy opensl engine
	if (s_pEngineObject)
	{
		(*s_pEngineObject)->Destroy(s_pEngineObject);
		s_pEngineObject = NULL;
		s_pEngineEngine = NULL;
	}

	LOGD("%s", "engine destory");
}


/**********************************************************************************
 *   sound effect
 **********************************************************************************/
typedef struct _CallbackContext
{
	vector<AudioPlayer*>* vec;
	AudioPlayer* player;
} CallbackContext;

void PlayOverEvent(SLPlayItf caller, void* pContext, SLuint32 playEvent)
{
	CallbackContext* context = (CallbackContext*)pContext;
	if (playEvent == SL_PLAYEVENT_HEADATEND)
	{
		vector<AudioPlayer*>::iterator iter;
		for (iter = (context->vec)->begin() ; iter != (context->vec)->end() ; ++ iter)
		{
			if (*iter == context->player)
			{
				(context->vec)->erase(iter);
				break;
			}
		}
		destroyAudioPlayer(context->player);
		free(context);
	}
}

void setSingleEffectVolume(AudioPlayer* player, SLmillibel volume)
{
	SLresult result;
	result = (*(player->fdPlayerVolume))->SetVolumeLevel(player->fdPlayerVolume, volume);
	assert(result == SL_RESULT_SUCCESS);
}

int getSingleEffectState(AudioPlayer * player)
{
	SLuint32 state = 0;
	SLresult result;
	result = (*(player->fdPlayerPlay))->GetPlayState(player->fdPlayerPlay, &state);
	assert(result == SL_RESULT_SUCCESS);

	return (int)state;
}

void setSingleEffectState(AudioPlayer * player, int state)
{
	SLresult result;
	if (player->fdPlayerPlay != NULL)
	{
		// don't set to PAUSED state if it's already set to STOPPED state
		int oldState = getSingleEffectState(player);
		if (oldState == SL_PLAYSTATE_STOPPED && state == SL_PLAYSTATE_PAUSED)
		{
			return;
		}
		result = (*(player->fdPlayerPlay))->SetPlayState(player->fdPlayerPlay, state);
		assert(SL_RESULT_SUCCESS == result);
	}
}

void resumeSingleEffect(AudioPlayer * player)
{
	int state = getSingleEffectState(player);
	// only resume the effect that has been paused
	if (state == SL_PLAYSTATE_PAUSED)
	{
		setSingleEffectState(player, SL_PLAYSTATE_PLAYING);
	}
}

bool OpenSLEngine::recreatePlayer(const char* filename)
{
	unsigned int effectID = _Hash(filename);
	EffectList::iterator p = sharedList().find(effectID);
	vector<AudioPlayer*>* vec = p->second;
	AudioPlayer* newPlayer = new AudioPlayer();
	if (!initAudioPlayer(newPlayer, filename))
	{
		LOGD("%s", "failed to recreate");
		return false;
	}
	vec->push_back(newPlayer);

	// set callback
	SLresult result;
	CallbackContext* context = new CallbackContext();
	context->vec = vec;
	context->player = newPlayer;
	result = (*(newPlayer->fdPlayerPlay))->RegisterCallback(newPlayer->fdPlayerPlay, PlayOverEvent, (void*)context);
	assert(SL_RESULT_SUCCESS == result);

	result = (*(newPlayer->fdPlayerPlay))->SetCallbackEventsMask(newPlayer->fdPlayerPlay, SL_PLAYEVENT_HEADATEND);
	assert(SL_RESULT_SUCCESS == result);

	// set volume 
	setSingleEffectVolume(newPlayer, m_effectVolume);
	setSingleEffectState(newPlayer, SL_PLAYSTATE_STOPPED);
	setSingleEffectState(newPlayer, SL_PLAYSTATE_PLAYING);

	// LOGD("vec count is %d of effectID %d", vec->size(), effectID);
	return true;
}

unsigned int OpenSLEngine::preloadEffect(const char * filename)
{
	unsigned int nID = _Hash(filename);
	// if already exists
	EffectList::iterator p = sharedList().find(nID);
	if (p != sharedList().end())
	{
		return nID;
	}

	AudioPlayer* player = new AudioPlayer();
	if (!initAudioPlayer(player, filename))
	{
		free(player);
		return FILE_NOT_FOUND;
	}
	
	// set the new player's volume as others'
	setSingleEffectVolume(player, m_effectVolume);

	vector<AudioPlayer*>* vec = new vector<AudioPlayer*>;
	vec->push_back(player);
	sharedList().insert(Effect(nID, vec));
	return nID;
}

void OpenSLEngine::unloadEffect(const char * filename)
{
	unsigned int nID = _Hash(filename);

	EffectList::iterator p = sharedList().find(nID);
	if (p != sharedList().end())
	{
		vector<AudioPlayer*>* vec = p->second;
		for (vector<AudioPlayer*>::iterator iter = vec->begin() ; iter != vec->end() ; ++ iter)
		{
			destroyAudioPlayer(*iter);
		}
		vec->clear();
		sharedList().erase(nID);
	}
}

int OpenSLEngine::getEffectState(unsigned int effectID)
{
	int state = PLAYSTATE_UNKNOWN;
	EffectList::iterator p = sharedList().find(effectID);
	if (p != sharedList().end())
	{
		vector<AudioPlayer*>* vec = p->second;
		// get the last player's state
		vector<AudioPlayer*>::reverse_iterator r_iter = vec->rbegin();
		state = getSingleEffectState(*r_iter);
	}
	return state;
}

void OpenSLEngine::setEffectState(unsigned int effectID, int state, bool isClear)
{
	EffectList::iterator p = sharedList().find(effectID);
	if (p != sharedList().end())
	{
		vector<AudioPlayer*>* vec = p->second;
		if (state == SL_PLAYSTATE_STOPPED || state == SL_PLAYSTATE_PAUSED)
		{
			// if stopped, clear the recreated players which are unused
			if (isClear)
			{
				setSingleEffectState(*(vec->begin()), state);
				vector<AudioPlayer*>::reverse_iterator r_iter = vec->rbegin();
				for (int i = 1, size = vec->size() ; i < size ; ++ i)
				{
					destroyAudioPlayer(*r_iter);
					r_iter ++;
					vec->pop_back();
				}
			}
			else
			{
				vector<AudioPlayer*>::iterator iter;
				for (iter = vec->begin() ; iter != vec->end() ; ++ iter)
				{
					setSingleEffectState(*iter, state);
				}
			}
		}
		else
		{
			setSingleEffectState(*(vec->rbegin()), state);
		}
	}
}

void OpenSLEngine::setAllEffectState(int state)
{
	EffectList::iterator p;
	for (p = sharedList().begin(); p != sharedList().end(); p ++)
	{
		vector<AudioPlayer*>* vec = p->second;
		for (vector<AudioPlayer*>::iterator iter = vec->begin() ; iter != vec->end() ; ++ iter)
		{
			setSingleEffectState(*iter, state);
		}
	}
}

void OpenSLEngine::resumeEffect(unsigned int effectID)
{
	EffectList::iterator p = sharedList().find(effectID);
	if (p != sharedList().end())
	{
		vector<AudioPlayer*>* vec = p->second;
		for (vector<AudioPlayer*>::iterator iter = vec->begin() ; iter != vec->end() ; ++ iter)
		{
			resumeSingleEffect(*iter);
		}
	}
}

void OpenSLEngine::resumeAllEffects()
{
	int state;
	EffectList::iterator p;
	for (p = sharedList().begin(); p != sharedList().end() ; ++ p)
	{
		vector<AudioPlayer*>* vec = p->second;
		for (vector<AudioPlayer*>::iterator iter = vec->begin() ; iter != vec->end() ; ++ iter)
		{
			resumeSingleEffect(*iter);
		}
	}
}

void OpenSLEngine::setEffectLooping(unsigned int effectID, bool isLooping)
{
	SLresult result;
	vector<AudioPlayer*>* vec = sharedList()[effectID];
	assert(NULL != vec);

	// get the first effect player that to be set loop config
	vector<AudioPlayer*>::iterator iter = vec->begin();
	AudioPlayer * player = *iter;

	if (player && player->fdPlayerSeek) 
	{
		result = (*(player->fdPlayerSeek))->SetLoop(player->fdPlayerSeek, (SLboolean) isLooping, 0, SL_TIME_UNKNOWN);
		assert(SL_RESULT_SUCCESS == result);
	}
}

void OpenSLEngine::setEffectsVolume(float volume)
{
	assert(volume <= 1.0f && volume >= 0.0f);
	m_effectVolume = int (RANGE_VOLUME_MILLIBEL * volume) + MIN_VOLUME_MILLIBEL;
	
	SLresult result;
	EffectList::iterator p;
	AudioPlayer * player;
	for (p = sharedList().begin() ; p != sharedList().end() ; ++ p)
	{
		vector<AudioPlayer*>* vec = p->second;
		for (vector<AudioPlayer*>::iterator iter = vec->begin() ; iter != vec->end() ; ++ iter)
		{
			player = *iter;
			result = (*(player->fdPlayerVolume))->SetVolumeLevel(player->fdPlayerVolume, m_effectVolume);
			assert(SL_RESULT_SUCCESS == result);
		}
	}
}

float OpenSLEngine::getEffectsVolume()
{
	float volume = (m_effectVolume - MIN_VOLUME_MILLIBEL) / (1.0f * RANGE_VOLUME_MILLIBEL);
	return volume;
}
