//=============================================================================
// 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(int, gRolloffA_Type);
EXTERN_VAR(float, gRolloffA_MinDistance);
EXTERN_VAR(float, gRolloffA_MaxDistance);

VAR(float, gVerticalExtremaSeconds, 4.f, kVariableNonpersistent);
VAR(float, gVerticalFunctionPeriodSeconds, 40.f, kVariableNonpersistent);
VAR(float, gHorizontalFunctionPeriodSeconds, 5.f, kVariableNonpersistent);
VAR(float, gHorizontalRadiusFunctionHalfPeriodSeconds, 5.f, kVariableNonpersistent);
VAR(float, gMinRadius, 3.f, kVariableNonpersistent);
VAR(float, gMaxRadius, 7.f, kVariableNonpersistent);
VAR(float, gHalfHeight, 6.f, kVariableNonpersistent);
VAR(float, gSoundTimeToPlay, 2.f, kVariableNonpersistent);
VAR(float, gSoundTimeToCrossfade, 1.f, kVariableNonpersistent);

void ObjectDynamic::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");

	InitializeModel(
		&mpObjectModel,
		&mObjectShader,
		mAppContext.externalPath,
		"models/torus.obj",
		"shaders/model_v.glsl",
		"shaders/model_f.glsl",
		"modelVs",
		"modelFs");

	InitializeGazeBoxes(GetGazeSphereManager(), kGazeBoxNum);
	char texturePaths[kGazeBoxNum][SPATIAL_AUDIO_TEXTURE_PATH_MAX_LEN];
	GetDefaultGazeBoxTexturePaths(texturePaths, kGazeBoxNum);
	InitializeGazeBoxRenderResources(
		&mpGazeBoxModel,
		&mGazeBoxShader,
		mGazeBoxTexture,
        &mGazeBoxTextureSampler,
		mGazeBoxLastHsvSaturation,
		mGazeBoxLastHsvValue,
		texturePaths,
		mAppContext.externalPath,
		kGazeBoxNum);
	SetGazeBoxActiveDefaultsReverbRolloff(mGazeBoxActive, gLooping);

	mSoundObjectVerticalNormalizedFunctionArg = mSoundObjectHorizontalNormalizedFunctionArg = 0.f;
	mSoundObjectHorizontalRadiusNormalizedFunctionArg = 0.f;
	mSoundObjectHorizontalRadiusGrowing = true;

	int fileNameIndex = 0;
	snprintf(mSoundObject[fileNameIndex++].mFilename, Q3D_SOUND_FILENAME_MAX_LEN, "%s/%s", mAppContext.externalPath, "audio/4_Fluffy in Heaven.wav");
	snprintf(mSoundObject[fileNameIndex++].mFilename, Q3D_SOUND_FILENAME_MAX_LEN, "%s/%s", mAppContext.externalPath, "audio/6_English Combined Choir.wav");

	Q3DAudioPreloadSoundFiles(GetSpatialAudio(), mSoundObject, SPATIAL_AUDIO_SOUND_OBJECT_NUM);
	
	q3d_audio_distance_rolloff_type rolloffType;
	float rolloffMinDistance;
	float rolloffMaxDistance;
	GetRolloffSettings(&rolloffType, &rolloffMinDistance, &rolloffMaxDistance, GetUserConfigRolloffConfigurationIndex());

	Q3DAudioObjectCreateArray(
		GetSpatialAudio(), 
		mSoundObject, 
		SPATIAL_AUDIO_SOUND_OBJECT_NUM, 
		rolloffType,
		rolloffMinDistance,
		rolloffMaxDistance);

	const vec3 position = vec3(0.f);
	mMdlMtx = glm::translate(glm::mat4(1.0f), position);

	mCrossfader.Play(
		GetSpatialAudio(),
		mSoundObject,
		Q3DAudioSoundStop,
		SPATIAL_AUDIO_SOUND_OBJECT_NUM,
		gSoundTimeToPlay,
		gSoundTimeToCrossfade);
}

void ObjectDynamic::Shutdown()
{
    if (mCrossfader.GetCurrentState() != Q3DSoundCrossFaderCommon::kStopped)
    {
        mCrossfader.Stop();
    }

    if (GetSpatialAudio())
    {
        Q3DAudioUnloadSoundFiles(GetSpatialAudio(), mSoundObject, SPATIAL_AUDIO_SOUND_OBJECT_NUM);
    }

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

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

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

    if (mpObjectModel)
    {
        DestroyGeometry(&mpObjectModel);
    }
	mObjectShader.Destroy();

	SpatialAudioCommon::Shutdown();
}

void ObjectDynamic::OnTouchScreen()
{
}

void ObjectDynamic::RenderCallback(float elapsedTime)
{
	if (!IsPaused())
	{
		//calculate vertical position
		const float verticalFunctionPeriodSeconds = gVerticalFunctionPeriodSeconds;
		mSoundObjectVerticalNormalizedFunctionArg += elapsedTime/verticalFunctionPeriodSeconds;
		if (mSoundObjectVerticalNormalizedFunctionArg >= 1.f)
		{
			mSoundObjectVerticalNormalizedFunctionArg -= 1.f;
		}
		const float halfHeight = gHalfHeight;
		vec3 position;
		const float verticalFunctionNormalized = ClampNormalizedRange(mSoundObjectVerticalNormalizedFunctionArg);
		position.y = sinf(verticalFunctionNormalized*2.f*M_PI)*halfHeight;


		//calculate horizontal position
		const float horizontalFunctionPeriodSeconds = gHorizontalFunctionPeriodSeconds;
		mSoundObjectHorizontalNormalizedFunctionArg += elapsedTime/horizontalFunctionPeriodSeconds;
		if (mSoundObjectHorizontalNormalizedFunctionArg >= 1.f)
		{
			mSoundObjectHorizontalNormalizedFunctionArg -= 1.f;
		}


		const float horizontalRadiusFunctionHalfPeriodSeconds = gHorizontalRadiusFunctionHalfPeriodSeconds;
		mSoundObjectHorizontalRadiusNormalizedFunctionArg += elapsedTime / horizontalFunctionPeriodSeconds;
		if (mSoundObjectHorizontalRadiusNormalizedFunctionArg >= 1.f)
		{
			mSoundObjectHorizontalRadiusNormalizedFunctionArg -= 1.f;
			mSoundObjectHorizontalRadiusGrowing = !mSoundObjectHorizontalRadiusGrowing;
		}
		const float minRadius = gMinRadius;
		const float maxRadius = gMaxRadius;
		float minRadiusLerp = minRadius;
		float maxRadiusLerp = maxRadius;
		if(!mSoundObjectHorizontalRadiusGrowing)
		{
			minRadiusLerp = maxRadius;
			maxRadiusLerp = minRadius;
		}
		const float horizontalRadiusFunctionArg = ClampNormalizedRange(mSoundObjectHorizontalNormalizedFunctionArg);
		float radius = minRadiusLerp + horizontalRadiusFunctionArg*(maxRadiusLerp - minRadiusLerp);

		//if object is nearing a vertical half-period, lerp horizontal position calculated above with top/bottom of cylinder
		const float verticalExtremaSeconds = gVerticalExtremaSeconds;
		const float verticalExtremaNormalized = verticalExtremaSeconds / verticalFunctionPeriodSeconds;
		bool lerpToVerticalExtrema = false;
		float lerpToVerticalExtremaVal = 0.f;
		const float verticalNormalizedTemporalDistanceToHighExtrema = fabs(mSoundObjectVerticalNormalizedFunctionArg - .25f);
		const float verticalNormalizedTemporalDistanceToLowExtrema = fabs(mSoundObjectVerticalNormalizedFunctionArg - .75f);
		if (verticalNormalizedTemporalDistanceToHighExtrema < verticalExtremaNormalized)
		{
			lerpToVerticalExtrema = true;
			lerpToVerticalExtremaVal = verticalNormalizedTemporalDistanceToHighExtrema / verticalExtremaNormalized;
		}
		else if (verticalNormalizedTemporalDistanceToLowExtrema < verticalExtremaNormalized)
		{
			lerpToVerticalExtrema = true;
			lerpToVerticalExtremaVal = verticalNormalizedTemporalDistanceToLowExtrema / verticalExtremaNormalized;
		}
		if (lerpToVerticalExtrema)
		{
			radius *= lerpToVerticalExtremaVal;
		}

		const float horizontalFunctionArg = ClampNormalizedRange(mSoundObjectHorizontalNormalizedFunctionArg)*2.f*M_PI;
		position.x = sinf(horizontalFunctionArg)*radius;
		position.z = cosf(horizontalFunctionArg)*radius;

		//set sound object positions
		mMdlMtx = glm::translate(glm::mat4(1.f), position);
		for (int i = 0; i < SPATIAL_AUDIO_SOUND_OBJECT_NUM; ++i)
		{
			Q3DAudioSetObjectPosition(GetSpatialAudio(), mSoundObject[i].mId, position, gLogObjectTransform);
		}

		mCrossfader.Update(elapsedTime);
	}


	//gaze box processing
	int gazeSphereActivatedIndex;
	if (GetGazeSphereManager()->GetGazeSphereActivatedThisUpdateIndex(&gazeSphereActivatedIndex))
	{
		if (gazeSphereActivatedIndex >= 0 && gazeSphereActivatedIndex < SpatialAudioCommon::kGazeBoxNum)
		{
			if (ConfigurationIndexIsReverb(gazeSphereActivatedIndex))
			{
				SetReverbAndReflectInBoolArray(mGazeBoxActive, gazeSphereActivatedIndex);
			}
			else if (gazeSphereActivatedIndex == kGazeBoxPause)
			{
				//global pause
				if (IsPaused())
				{
					const q3d_audio_result resumeResult = q3dclient_audio_resume(GetSpatialAudio());
					LOGI("q3d_audio_resume()=%i", resumeResult);
					Q3D_AUDIO_ASSERT_SUCCESS(resumeResult);
				}
				else
				{
					const q3d_audio_result pauseResult = q3dclient_audio_pause(GetSpatialAudio());
					LOGI("q3d_audio_pause()=%i", pauseResult);
					Q3D_AUDIO_ASSERT_SUCCESS(pauseResult);
				}

				PausedInvertAndReflectInGazeBox(mGazeBoxActive);
			}
			else if (gazeSphereActivatedIndex == kGazeBoxLoop)
			{
				gLooping = !gLooping;
				mGazeBoxActive[kGazeBoxLoop] = gLooping;
			}
			else
			{
				q3d_audio_distance_rolloff_type type;
				float minDistance;
				float maxDistance;
				GetRolloffSettings(&type, &minDistance, &maxDistance, gazeSphereActivatedIndex);

				for (int i = 0; i < SPATIAL_AUDIO_SOUND_OBJECT_NUM; ++i)
				{
					Q3DSound& sound = mSoundObject[i];
					sound.mRolloffType = (q3d_audio_distance_rolloff_type)type;
					sound.mRolloffMinDistance = minDistance;
					sound.mRolloffMaxDistance = maxDistance;

					Q3DAudioObjectSetRolloff(
						GetSpatialAudio(),
						sound.mId,
						sound.mRolloffType,
						sound.mRolloffMinDistance,
						sound.mRolloffMaxDistance);
				}

				SetBoolsToFalse(&mGazeBoxActive[kGazeBoxRolloffA], kGazeBoxRolloffNum);
				mGazeBoxActive[gazeSphereActivatedIndex] = true;
			}
		}
	}
}

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


	//floor model render
	glm::mat4 floorModelTranslate = glm::translate(glm::mat4(1.0f), glm::vec3(0.0, -200.0f, 0.0f));
	glm::mat4 floorRotateForward = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
	glm::mat4 floorRotateUp = glm::rotate(glm::mat4(1.0f), glm::radians(135.0f), glm::vec3(0.0f, 1.0f, 0.0f));

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

	glm::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();


	//render sound object visualization
	mObjectShader.Bind();

	glm::vec4 mdlColor(1.f, 0.f, 0.f, 1.f);
	mObjectShader.SetUniformMat4("projMtx", GetProjMtx());
	mObjectShader.SetUniformMat4("viewMtx", GetViewMtx()[whichEye]);
	mObjectShader.SetUniformMat4("mdlMtx", mMdlMtx);
	mObjectShader.SetUniformVec4("modelColor", mdlColor);

	mpObjectModel->Submit();
}

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

