//=============================================================================
// FILE: app.cpp
//
//                  Copyright (c) 2017 QUALCOMM Technologies Inc.
//                              All Rights Reserved.
//
//=============================================================================

#include "app.h"

#include "../../common/spatialaudio_utility.h"
#include "glm/gtx/transform.hpp"
#include "svrConfig.h"
#include "svrGeometry.h"
#include "svrUtil.h"


using namespace Svr;
using namespace glm;


EXTERN_VAR(bool, gLooping);
EXTERN_VAR(bool, gLogObjectTransform);
EXTERN_VAR(bool, gRolloffBugWorkaround);

VAR(int, gRotation_Setting, 0, kVariableNonpersistent);

VAR(float, gRotationA_SpeedDegreesPerSecond, 72.f, kVariableNonpersistent);
VAR(float, gRotationA_AxisX, 0.f, kVariableNonpersistent);
VAR(float, gRotationA_AxisY, 1.f, kVariableNonpersistent);
VAR(float, gRotationA_AxisZ, 0.f, kVariableNonpersistent);

VAR(float, gRotationB_SpeedDegreesPerSecond, 72.f, kVariableNonpersistent);
VAR(float, gRotationB_AxisX, 1.f, kVariableNonpersistent);
VAR(float, gRotationB_AxisY, 0.f, kVariableNonpersistent);
VAR(float, gRotationB_AxisZ, 0.f, kVariableNonpersistent);

VAR(float, gRotationC_SpeedDegreesPerSecond, 72.f, kVariableNonpersistent);
VAR(float, gRotationC_AxisX, .57735f, kVariableNonpersistent);
VAR(float, gRotationC_AxisY, .57735f, kVariableNonpersistent);
VAR(float, gRotationC_AxisZ, .57735f, kVariableNonpersistent);

VAR(float, gRotationD_SpeedDegreesPerSecond, 0.f, kVariableNonpersistent);
VAR(float, gRotationD_AxisX, 0.f, kVariableNonpersistent);
VAR(float, gRotationD_AxisY, 1.f, kVariableNonpersistent);
VAR(float, gRotationD_AxisZ, 0.f, kVariableNonpersistent);

VAR(float, gSoundTimeToPlay, 2.5f, kVariableNonpersistent);
VAR(float, gSoundTimeToCrossfade, 1.5f, kVariableNonpersistent);


mat4 Soundfield::GetSoundfieldOrientation()
{
	return rotate(mSoundFieldCurrentRotationAboutAxis, mSoundFieldRotationAxis);
}

static void SetGazeBoxActiveRotationToGlobalUserConfig(bool*const gazeBoxActive, const int configFileSettingIndex)
{
	SetBoolsToFalse(&gazeBoxActive[Soundfield::kRotationOptionStartIndex], Soundfield::kRotationOptionsNum);
	gazeBoxActive[Soundfield::kRotationOptionStartIndex + configFileSettingIndex] = true;
}

bool Q3DAudioSoundfieldCreate(q3d_audio_client*const pSpatialAudio, Q3DSound*const pSoundField)
{
	assert(pSpatialAudio && pSoundField);
	assert(pSoundField->mFilename && pSoundField->mFilename[0]);

	pSoundField->mId = Q3DAudioSoundfieldCreate(pSpatialAudio, pSoundField->mFilename);
	if (gRolloffBugWorkaround)
	{
		//rolloff settings are irrelevant for soundfields, but this workaround wants to set the rolloff values anyway
		pSoundField->mRolloffType = Q3D_AUDIO_ROLLOFF_LOGARITHMIC;
		pSoundField->mRolloffMinDistance = 1.f;
		pSoundField->mRolloffMaxDistance = 8.f;
	}
	return Q3D_AUDIO_HANDLE_VALID(pSoundField->mId);
}
bool Q3DAudioStopSoundField(q3d_audio_client* const pSpatialAudio, Q3DSound*const q3dSound)
{
	assert(pSpatialAudio);
	Q3D_AUDIO_HANDLE_VALID(q3dSound->mId);

	const q3d_audio_result stopSoundResult = Q3DAudioSoundStopAndInvalidateHandle(pSpatialAudio, q3dSound->mId);
	Q3DAudioSoundfieldCreate(pSpatialAudio, q3dSound);
	return Q3D_AUDIO_RESULT_SUCCESS(stopSoundResult) && Q3D_AUDIO_HANDLE_VALID(q3dSound->mId);
}

void Soundfield::Initialize()
{
	SpatialAudioCommon::Initialize();
	InitializeModel(
		&mpFloorModel,
		&mFloorShader,
		mAppContext.externalPath,
		"models/grounddisc.obj",
		"shaders/floor_v.glsl",
		"shaders/floor_f.glsl",
		"floorModelVs",
		"floorModelFs");

	InitializeModel(
		&mpSkyModel,
		&mSkyShader,
		mAppContext.externalPath,
		"models/skyBall.obj",
		"shaders/sky_v.glsl",
		"shaders/sky_f.glsl",
		"skyModelVs",
		"skyModelFs");


	InitializeModelAndTexture(
		&mpFocalSoundModel,
		&mFocalSoundShader,
		&mFocalSoundTexture,
		mAppContext.externalPath,
		"models/texturedCube.obj",
		"shaders/gazeBox_v.glsl",
		"shaders/gazeBox_f.glsl",
		"boxVs",
		"boxFs",
		"textures/focal_sound.ktx");

	InitializeGazeBoxes(GetGazeSphereManager(), kGazeBoxSoundfieldNum);
	
	
	const int strMax = SPATIAL_AUDIO_TEXTURE_PATH_MAX_LEN;
	char texturePaths[kGazeBoxSoundfieldNum][strMax];
	GetDefaultGazeBoxTexturePaths(texturePaths, kGazeBoxSoundfieldNum - kRotationOptionsNum);
	int texturePathIndex = kRotationOptionStartIndex;
	strncpy(texturePaths[texturePathIndex++], "textures/rotate_a.ktx", strMax);
	strncpy(texturePaths[texturePathIndex++], "textures/rotate_b.ktx", strMax);
	strncpy(texturePaths[texturePathIndex++], "textures/rotate_c.ktx", strMax);
	strncpy(texturePaths[texturePathIndex++], "textures/rotate_d.ktx", strMax);
	
	InitializeGazeBoxRenderResources(
		&mpGazeBoxModel,
		&mGazeBoxShader,
		mGazeBoxTexture,
        &mGazeBoxTextureSampler,
		mGazeBoxLastHsvSaturation,
		mGazeBoxLastHsvValue,
		texturePaths,
		mAppContext.externalPath,
		kGazeBoxSoundfieldNum);

	//reflect default settings in gaze boxes
	SetBoolsToFalse(&mGazeBoxActive[0], kGazeBoxSoundfieldNum);
	mGazeBoxActive[GetUserConfigReverbConfigurationIndex()] = true;
	SetGazeBoxActiveLooping(mGazeBoxActive, gLooping);
	SetGazeBoxActiveRotationToGlobalUserConfig(mGazeBoxActive, gRotation_Setting);

	const float offset = 5.f;
	{
		int focalSoundModelIndex = 0;
		mFocalSoundModelLocalOffset[focalSoundModelIndex++] = vec3(0.f, 0.f, offset);
		mFocalSoundModelLocalOffset[focalSoundModelIndex++] = vec3(0.f, 0.f, -offset);
	}
	{	
		//match colors to world axes
		int focalSoundModelIndex = 0;
		mFocalSoundModelColor[focalSoundModelIndex++] = vec3(0.f, 0.f, 1.f);
		mFocalSoundModelColor[focalSoundModelIndex++] = vec3(1.f, 1.f, 0.f);
	}

	int soundFieldIndex = 0;
	snprintf(mSoundField[soundFieldIndex++].mFilename, Q3D_SOUND_FILENAME_MAX_LEN, "%s/%s", mAppContext.externalPath, "audio/Jungle_FluffyinHeaven_Back_ACN_SN3D_FOA.wav");
	snprintf(mSoundField[soundFieldIndex++].mFilename, Q3D_SOUND_FILENAME_MAX_LEN, "%s/%s", mAppContext.externalPath, "audio/Jungle_EnglishCombinedChoir_Front_ACN_SN3D_FOA.wav");

	Q3DAudioPreloadSoundFiles(GetSpatialAudio(), mSoundField, SPATIAL_AUDIO_SOUND_FIELD_NUM);
	for (int i = 0; i < SPATIAL_AUDIO_SOUND_FIELD_NUM; ++i)
	{
		Q3DAudioSoundfieldCreate(GetSpatialAudio(), &mSoundField[i]);
	}

	assert(gSoundTimeToPlay >= gSoundTimeToCrossfade);
	mCrossfader.Play(
		GetSpatialAudio(),
		mSoundField,
		Q3DAudioStopSoundField,
		SPATIAL_AUDIO_SOUND_FIELD_NUM,
		gSoundTimeToPlay,
		gSoundTimeToCrossfade);

	SetRotationBehavior(gRotation_Setting);
	mSoundFieldCurrentRotationAboutAxis = 0.f;
	const mat4 soundFieldCurrentOrientation = GetSoundfieldOrientation();
	const q3d_audio_quatf soundFieldCurrentOrientationAudioQuat = GlmMat4ToSpatialAudioQuat(soundFieldCurrentOrientation);
	Q3DAudioSetSoundfieldRotation(
		GetSpatialAudio(), 
		mSoundField[mCrossfader.GetSoundCurrentIndex()].mId, 
		soundFieldCurrentOrientationAudioQuat,
		gLogObjectTransform);

	mSoundLastPlayedIndex = mCrossfader.GetSoundLastPlayedIndex();
}

void Soundfield::Shutdown()
{
    if (mCrossfader.GetCurrentState() != Q3DSoundCrossFaderCommon::kStopped)
    {
        mCrossfader.Stop();
    }
    if (GetSpatialAudio())
    {
        Q3DAudioUnloadSoundFiles(GetSpatialAudio(), mSoundField, SPATIAL_AUDIO_SOUND_FIELD_NUM);
    }

    DestroyGazeBoxRenderResources(&mpGazeBoxModel, &mGazeBoxShader, mGazeBoxTexture, &mGazeBoxTextureSampler, kGazeBoxSoundfieldNum);

    if (mpSkyModel)
    {
        DestroyGeometry(&mpSkyModel);
    }
    mSkyShader.Destroy();

    if (mpFloorModel)
    {
        DestroyGeometry(&mpFloorModel);
    }
	mFloorShader.Destroy();

    if (mpFocalSoundModel)
    {
        DestroyGeometry(&mpFocalSoundModel);
    }
	mFocalSoundShader.Destroy();
    
    if (mFocalSoundTexture)
    {
        DestroyTexture(&mFocalSoundTexture);
    }

	SpatialAudioCommon::Shutdown();
}


void Soundfield::OnTouchScreen()
{
}

void Soundfield::RenderCallback(float elapsedTime)
{
	mCrossfader.Update(elapsedTime);
	if (mSoundLastPlayedIndex != mCrossfader.GetSoundLastPlayedIndex())
	{
		//the crossfader has started playing a new sound, which does not care about the previous sound's pause state
		mSoundLastPlayedIndex = mCrossfader.GetSoundLastPlayedIndex();
		mGazeBoxActive[kGazeBoxPause] = false;
	}

	//soundfield rotation
	if (mSoundFieldRotationSpeedRadiansPerSecond != 0.f)
	{
		const float rotateAmountRadians = elapsedTime * mSoundFieldRotationSpeedRadiansPerSecond;
		mSoundFieldCurrentRotationAboutAxis += rotateAmountRadians;
		const q3d_audio_quatf soundFieldCurrentOrientationAudioQuat = GlmMat4ToSpatialAudioQuat(GetSoundfieldOrientation());
		for (int i = 0; i < SPATIAL_AUDIO_SOUND_FIELD_NUM; ++i)
		{
			Q3DAudioSetSoundfieldRotation(
				GetSpatialAudio(), 
				mSoundField[i].mId, 
				soundFieldCurrentOrientationAudioQuat, 
				gLogObjectTransform);
		}
	}

	//gaze box processing
	int gazeSphereActivatedIndex;
	if (GetGazeSphereManager()->GetGazeSphereActivatedThisUpdateIndex(&gazeSphereActivatedIndex))
	{
		if (ConfigurationIndexIsReverb(gazeSphereActivatedIndex))
		{
			SetReverbAndReflectInBoolArray(mGazeBoxActive, gazeSphereActivatedIndex);
		}
		else if (gazeSphereActivatedIndex == kGazeBoxPause)
		{
			if (IsPaused())
			{
				for (int i = 0; i < SPATIAL_AUDIO_SOUND_FIELD_NUM; ++i)
				{
					Q3DAudioResumeSound(GetSpatialAudio(), mSoundField[i].mId);
				}
			}
			else
			{
				for (int i = 0; i < SPATIAL_AUDIO_SOUND_FIELD_NUM; ++i)
				{
					Q3DAudioPauseSound(GetSpatialAudio(), mSoundField[i].mId);
				}
			}
			PausedInvertAndReflectInGazeBox(mGazeBoxActive);
		}
		else if (gazeSphereActivatedIndex == kGazeBoxLoop)
		{
			gLooping = !gLooping;
			mGazeBoxActive[kGazeBoxLoop] = gLooping;
		}
		else if(gazeSphereActivatedIndex >= kRotationOptionStartIndex &&
				gazeSphereActivatedIndex < kRotationOptionStartIndex + kRotationOptionsNum)
		{
			const int rotationOptionConfigurationIndex = gazeSphereActivatedIndex - kRotationOptionStartIndex;
			SetRotationBehavior(rotationOptionConfigurationIndex);
		}
	}
}

bool Soundfield::SetRotationBehavior(const int configFileSettingIndex)
{
	switch (configFileSettingIndex)
	{
		case kRotationOptionA:
		{
			mSoundFieldRotationSpeedRadiansPerSecond = radians(gRotationA_SpeedDegreesPerSecond);
			mSoundFieldRotationAxis = vec3(gRotationA_AxisX, gRotationA_AxisY, gRotationA_AxisZ);
			break;
		}
		case kRotationOptionB:
		{
			mSoundFieldRotationSpeedRadiansPerSecond = radians(gRotationB_SpeedDegreesPerSecond);
			mSoundFieldRotationAxis = vec3(gRotationB_AxisX, gRotationB_AxisY, gRotationB_AxisZ);
			break;
		}
		case kRotationOptionC:
		{
			mSoundFieldRotationSpeedRadiansPerSecond = radians(gRotationC_SpeedDegreesPerSecond);
			mSoundFieldRotationAxis = vec3(gRotationC_AxisX, gRotationC_AxisY, gRotationC_AxisZ);
			break;
		}
		case kRotationOptionD:
		{
			mSoundFieldRotationSpeedRadiansPerSecond = radians(gRotationD_SpeedDegreesPerSecond);
			mSoundFieldRotationAxis = vec3(gRotationD_AxisX, gRotationD_AxisY, gRotationD_AxisZ);
			break;
		}
		default:
		{
			assert(false);
		}
	}
	SetGazeBoxActiveRotationToGlobalUserConfig(mGazeBoxActive, configFileSettingIndex);
}

void Soundfield::RenderModelCallback(int whichEye, float elapsedTime)
{
	GazeBoxRender(
		mpGazeBoxModel,
		&mGazeBoxShader,
		mGazeBoxTexture,
        mGazeBoxTextureSampler,
		GetGazeSphereManager()->GetGazeSpheres(),
		kGazeBoxSoundfieldNum,
		mGazeBoxActive,
		mGazeBoxLastHsvSaturation,
		mGazeBoxLastHsvValue,
		elapsedTime,
		whichEye,
		GetProjMtx(),
		GetViewMtx());

	//floor model render: calculate the "identity transform for the floor"
	const mat4 floorModelTranslate = translate(mat4(1.0f), vec3(0.0, -200.0f, 0.0f));
	const mat4 floorRotateForward = rotate(mat4(1.0f), radians(-90.0f), vec3(1.0f, 0.0f, 0.0f));
	const mat4 floorRotateUp = rotate(mat4(1.0f), radians(135.f), vec3(0.0f, 1.0f, 0.0f));

	mat4 floorScale = mat4(1.0f);
	floorScale[3][3] = 1.0f;

	mat4 floorModelLocalToWorld = floorModelTranslate*floorScale*floorRotateUp*floorRotateForward;

	mFloorShader.Bind();
	mFloorShader.SetUniformMat4("projMtx", GetProjMtx());
	mFloorShader.SetUniformMat4("viewMtx", GetViewMtx()[whichEye]);
	mFloorShader.SetUniformMat4("mdlMtx", floorModelLocalToWorld);

	mpFloorModel->Submit();


	//sky model render	
	mat4 skyModelLocalToWorld = mat4(1.f);

	mSkyShader.Bind();
	mSkyShader.SetUniformMat4("projMtx", GetProjMtx());
	mSkyShader.SetUniformMat4("viewMtx", GetViewMtx()[whichEye]);
	mSkyShader.SetUniformMat4("mdlMtx", skyModelLocalToWorld);
	mpSkyModel->Submit();


	//focal sound model render
	q3d_audio_quatf soundfieldRotation;
	Q3DAudioGetSoundfieldRotation(
		GetSpatialAudio(), 
		&soundfieldRotation, 
		mSoundField[mCrossfader.GetSoundCurrentIndex()].mId, 
		gLogObjectTransform);

	const mat3 soundfieldOrientation = mat3_cast(quat(soundfieldRotation.qw, soundfieldRotation.qx, soundfieldRotation.qy, soundfieldRotation.qz));

	const int currentSoundfieldIndex = mCrossfader.GetSoundLastPlayedIndex();
	const vec3 focalSoundWorldPos = soundfieldOrientation*mFocalSoundModelLocalOffset[currentSoundfieldIndex] + GetPlayerHeadPositionWorldSpace();
	
	//lookat matrix: xPositive/right face is the textured face we want to always face the player
	const vec3 focalSoundRight = normalize(GetPlayerHeadPositionWorldSpace() - focalSoundWorldPos);
	const vec3 focalSoundForward = normalize(cross(focalSoundRight, GetPlayerHeadOrientationWorldSpace()*vec3(-1.f,0.f,0.f)));//the player's left vector is the box's forward vector
	const vec3 focalSoundUp = cross(focalSoundForward, focalSoundRight);

	mat4 focalSoundLocalToWorldTransform;
	focalSoundLocalToWorldTransform[0] = vec4(focalSoundRight.x, focalSoundRight.y, focalSoundRight.z, 0.f);
	focalSoundLocalToWorldTransform[1] = vec4(focalSoundUp.x, focalSoundUp.y, focalSoundUp.z, 0.f);
	focalSoundLocalToWorldTransform[2] = vec4(focalSoundForward.x, focalSoundForward.y, focalSoundForward.z, 0.f);
	focalSoundLocalToWorldTransform[3] = vec4(focalSoundWorldPos.x, focalSoundWorldPos.y, focalSoundWorldPos.z, 1.f);

	GL(glEnable(GL_BLEND));
	GL(glBlendEquation(GL_FUNC_ADD));
	GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));

	mFocalSoundShader.Bind();
	mFocalSoundShader.SetUniformMat4("projMtx", GetProjMtx());
	mFocalSoundShader.SetUniformMat4("viewMtx", GetViewMtx()[whichEye]);
	mFocalSoundShader.SetUniformMat4("mdlMtx", focalSoundLocalToWorldTransform);
	mFocalSoundShader.SetUniformSampler("srcTex", mFocalSoundTexture, GL_TEXTURE_2D, mGazeBoxTextureSampler);

	float focalSoundModelAlpha;
	if (mCrossfader.GetCurrentState() == Q3DSoundCrossFaderCommon::kCrossFading)
	{
		focalSoundModelAlpha = ClampNormalizedRange(mCrossfader.GetTimeInCurrentState()/mCrossfader.GetTimeToCrossfade());
	}
	else
	{
		//currently there's one visual model to represent the focal sound direction, so fade out early before the next sound starts to crossfade in (eg no visual crossfade)
		const float timeInFullAlpha = mCrossfader.GetTimeToPlay() - mCrossfader.GetTimeToCrossfade();
		assert(timeInFullAlpha >= 0);
		const float timeToFadeOut = mCrossfader.GetTimeInCurrentState() - timeInFullAlpha;
		focalSoundModelAlpha = 1.f - ClampNormalizedRange(timeToFadeOut / mCrossfader.GetTimeToCrossfade());
	}

	const vec3& modelColor3 = mFocalSoundModelColor[currentSoundfieldIndex];
	vec4 modelColor(modelColor3.x, modelColor3.y, modelColor3.z, focalSoundModelAlpha);
	mFocalSoundShader.SetUniformVec4("modelColor", modelColor);

	mpFocalSoundModel->Submit();

	GL(glDisable(GL_BLEND));
}

namespace Svr
{
    Svr::SvrApplication* CreateApplication()
    {
        return new Soundfield();
    }
}
