//=============================================================================
// 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, gLogSoundVolume);
VAR(float, gSoundTimeToPlay, 14.f, kVariableNonpersistent);
VAR(float, gSoundTimeToVolumeFade, 2.f, kVariableNonpersistent);

static bool Q3DAudioStopSoundChannel(q3d_audio_client* const pSpatialAudio, Q3DSound*const pSound)
{
	assert(pSound);
	return Q3DAudioSoundStopAndInvalidateHandle(pSpatialAudio, pSound->mId);
}

static bool Q3DAudioSoundChannelCreate(q3d_audio_client*const pSpatialAudio, Q3DSound*const pSoundChannel)
{
	assert(pSpatialAudio);
	assert(pSoundChannel && pSoundChannel->mFilename && pSoundChannel->mFilename[0]);

	pSoundChannel->mId = Q3DAudioSoundChannelCreate(pSpatialAudio, pSoundChannel->mFilename);
	return Q3D_AUDIO_HANDLE_VALID(pSoundChannel->mId);
}

//since there can be only one sound channel created at a time, the sound channel "crossfading" is performed uniquely
void Q3DSoundChannelCrossFader::Play(
	q3d_audio_client*const pSpatialAudio,
	Q3DSound* pSounds,
	Q3DSoundCrossFaderCommon::StopSoundFunctionPtr pStopSound,
	const float soundsNum,
	const float timeToPlay,
	const float timeToCrossfade)
{
	//create the first sound before passing it off to Play()
	Q3DAudioSoundChannelCreate(pSpatialAudio, &pSounds[0]);
	mCommon.Play(pSpatialAudio, pSounds, pStopSound, soundsNum, timeToPlay, timeToCrossfade);
}

void Q3DSoundChannelCrossFader::Update(const float elapsedTime)
{
	assert(mCommon.mState != Q3DSoundCrossFaderCommon::kStopped);
	if (mCommon.mState == Q3DSoundCrossFaderCommon::kStopped)
	{
		return;
	}

	mCommon.mTimeInCurrentState += elapsedTime;

	//state switch
	const float timeMax = (mCommon.mState == Q3DSoundCrossFaderCommon::kPlaying) ? mCommon.mTimeToPlay : mCommon.mTimeToCrossfade;
	if (mCommon.mTimeInCurrentState >= timeMax)
	{
		//switch states
		mCommon.mTimeInCurrentState -= timeMax;

		switch (mCommon.mState)
		{
			case Q3DSoundCrossFaderCommon::kPlaying:
			{
				mCommon.mState = Q3DSoundCrossFaderCommon::kFadeOut;
				break;
			}
			case Q3DSoundCrossFaderCommon::kFadeOut:
			{
				mCommon.mState = Q3DSoundCrossFaderCommon::kFadeIn;

				//just finished fading out the current sound
				mCommon.mpStopSound(mCommon.mpSpatialAudio, &mCommon.mpSound[mCommon.mSoundCurrentIndex]);

				const int newSoundIndex = mCommon.GetNextSoundIndex();
				Q3DSound& newSound = mCommon.mpSound[newSoundIndex];
				Q3DAudioSoundChannelCreate(mCommon.mpSpatialAudio, &newSound);
				Q3DAudioSoundPlay(mCommon.mpSpatialAudio, newSound.mId, gLooping);

				//fade in the next sound
				Q3DAudioSetSoundVolume(mCommon.mpSpatialAudio,newSound.mId, 0.f, gLogSoundVolume);
				mCommon.mSoundCurrentIndex = newSoundIndex;//the new sound is the current sound
				break;
			}
			case Q3DSoundCrossFaderCommon::kFadeIn:
			{
				mCommon.mState = Q3DSoundCrossFaderCommon::kPlaying;
				break;
			}
			default:
			{
				assert(false);
				break;
			}
		}
	}

	//state update
	switch (mCommon.mState)
	{
		case Q3DSoundCrossFaderCommon::kPlaying:
		{
			break;//do nothing
		}
		case Q3DSoundCrossFaderCommon::kFadeIn:
		case Q3DSoundCrossFaderCommon::kFadeOut:
		{
			float volume;
			const q3d_audio_sound_id currentSoundId = mCommon.mpSound[mCommon.mSoundCurrentIndex].mId;
			Q3DAudioGetSoundVolume(mCommon.mpSpatialAudio, volume, currentSoundId, gLogSoundVolume);

			const float volumeIncrementAbsoluteVal = (elapsedTime / mCommon.mTimeToCrossfade);
			const float volumeIncrement = mCommon.mState ==
				Q3DSoundCrossFaderCommon::kFadeOut ? -volumeIncrementAbsoluteVal : volumeIncrementAbsoluteVal;

			Q3DAudioSetSoundVolume(
				mCommon.mpSpatialAudio, 
				currentSoundId, 
				ClampNormalizedRange(volume + volumeIncrement), 
				gLogSoundVolume);
			break;
		}
		default:
		{
			assert(false);
			break;
		}
	}
}

void Q3DSoundChannelCrossFader::Stop()
{
	mCommon.Stop();
}

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

	int soundChannelIndex = 0;
	snprintf(mSoundChannel[soundChannelIndex++].mFilename, Q3D_SOUND_FILENAME_MAX_LEN, "%s/%s", mAppContext.externalPath, "audio/channel_sound_stereo.wav");
	snprintf(mSoundChannel[soundChannelIndex++].mFilename, Q3D_SOUND_FILENAME_MAX_LEN, "%s/%s", mAppContext.externalPath, "audio/channel_sound_5.1.wav");
	snprintf(mSoundChannel[soundChannelIndex++].mFilename, Q3D_SOUND_FILENAME_MAX_LEN, "%s/%s", mAppContext.externalPath, "audio/channel_sound_7.1.wav");
	
	Q3DAudioPreloadSoundFiles(GetSpatialAudio(), mSoundChannel, SPATIAL_AUDIO_SOUND_CHANNEL_NUM);	
	mSoundChannelCrossFader.Play(
		GetSpatialAudio(), 
		mSoundChannel, 
		Q3DAudioStopSoundChannel,
		SPATIAL_AUDIO_SOUND_CHANNEL_NUM, 
		gSoundTimeToPlay, 
		gSoundTimeToVolumeFade);
	mSoundLastPlayedIndex = mSoundChannelCrossFader.GetSoundCurrentIndex();

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

void SoundChannel::Shutdown()
{
    if (mSoundChannelCrossFader.GetCurrentState() != Q3DSoundCrossFaderCommon::kStopped)
    {
        mSoundChannelCrossFader.Stop();
        Q3DAudioUnloadSoundFiles(GetSpatialAudio(), mSoundChannel, SPATIAL_AUDIO_SOUND_CHANNEL_NUM);
    }

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

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

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

	SpatialAudioCommon::Shutdown();
}

void SoundChannel::OnTouchScreen()
{
}

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

	//gaze box processing
	int gazeSphereActivatedIndex;
	if (GetGazeSphereManager()->GetGazeSphereActivatedThisUpdateIndex(&gazeSphereActivatedIndex))
	{
		if (gazeSphereActivatedIndex == kGazeBoxPause)
		{
			if (IsPaused())
			{
				Q3DAudioResumeSound(GetSpatialAudio(), mSoundChannel[mSoundChannelCrossFader.GetSoundCurrentIndex()].mId);
			}
			else
			{
				Q3DAudioPauseSound(GetSpatialAudio(), mSoundChannel[mSoundChannelCrossFader.GetSoundCurrentIndex()].mId);
			}
			PausedInvertAndReflectInGazeBox(mGazeBoxActive);
		}
		else if (gazeSphereActivatedIndex == kGazeBoxLoop)
		{
			gLooping = !gLooping;
			mGazeBoxActive[kGazeBoxLoop] = gLooping;
		}
	}
}

void SoundChannel::RenderModelCallback(int whichEye, float elapsedTime)
{
	GazeBoxRender(
		mpGazeBoxModel,
		&mGazeBoxShader,
		mGazeBoxTexture,
        mGazeBoxTextureSampler,
		GetGazeSphereManager()->GetGazeSpheres(),
		kGazeBoxSoundchannelNum,
		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));
	mat4 floorModelLocalToWorld = floorModelTranslate*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();
}

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