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

#pragma once

#include "../../../spatialAudioApi/public/Q3dAudioClient.h"
#include "svrApplication.h"
#include "svrCpuTimer.h"
#include "svrShader.h"

namespace Svr
{
	class SvrGeometry;
}


#define Q3D_SOUND_FILENAME_MAX_LEN 512
struct Q3DSound
{
	char							mFilename[Q3D_SOUND_FILENAME_MAX_LEN];
	q3d_audio_sound_id				mId;

	//used only for sound objects, not for soundfields or sound channels
	q3d_audio_distance_rolloff_type mRolloffType;
	float							mRolloffMinDistance;
	float							mRolloffMaxDistance;
};

class Q3DSoundCrossFaderCommon
{
public:
	Q3DSoundCrossFaderCommon();

	typedef bool(*StopSoundFunctionPtr)(q3d_audio_client* const, Q3DSound*const);

	void Play(
		q3d_audio_client*const spatialAudio,
		Q3DSound* sounds,
		StopSoundFunctionPtr pStopSound,
		const float soundsNum,
		const float timeToPlay,
		const float timeToCrossfade);

	int GetNextSoundIndex();
	void Stop();

	q3d_audio_client*			mpSpatialAudio;
	StopSoundFunctionPtr		mpStopSound;
	Q3DSound*					mpSound;
	int							mSoundNum;
	enum State { kPlaying, kCrossFading, kFadeIn, kFadeOut, kStopped } mState;
	int							mSoundCurrentIndex;
	int							mSoundLastPlayedIndex;
	float						mTimeInCurrentState;
	float						mTimeToPlay;
	float						mTimeToCrossfade;
};

class Q3DSoundCrossFader
{
public:
	Q3DSoundCrossFader() {};
	void Update(const float elapsedTime);
	void Stop();

	void Play(
		q3d_audio_client*const pSpatialAudio,
		Q3DSound* pSounds,
		Q3DSoundCrossFaderCommon::StopSoundFunctionPtr pStopSound,
		const float soundsNum,
		const float timeToPlay,
		const float timeToCrossfade);

	int GetSoundCurrentIndex() const { return mCommon.mSoundCurrentIndex; }
	int GetSoundLastPlayedIndex() const { return mCommon.mSoundLastPlayedIndex; }
	float GetTimeToPlay() const { return mCommon.mTimeToPlay; }
	float GetTimeToCrossfade() const { return mCommon.mTimeToCrossfade; }
	float GetTimeInCurrentState() const { return mCommon.mTimeInCurrentState; }
	Q3DSoundCrossFaderCommon::State GetCurrentState() const { return mCommon.mState; }

private:
	Q3DSoundCrossFaderCommon mCommon;
};

bool LoadTextureCommon(GLuint *pTexture, const char*const pFileName, const char*const pAppExternalPath);

void InitializeModelAndTexture(
	Svr::SvrGeometry**const pModelGeometry,
	Svr::SvrShader*const pShader,
	GLuint*const pTextureHandle,
	const char*const pAppExternalPath,
	const char*const pObjFileLocalPath,
	const char*const pVertexShaderFileRelativePath,
	const char*const pFragmentShaderFileRelativePath,
	const char*const pVertexShaderDebugName,
	const char*const pFragmentShaderDebugName,
	const char*const pTextureFileRelativePath);

void DestroyTexture(GLuint*const texture);

#define GAZE_SPHERE_COOLDOWN_SECONDS (1.f)			
#define GAZE_SPHERE_CHARGE_SECONDS (.5f)			
#define GAZE_SPHERE_CHARGE_HYSTERESIS_SECONDS (.25f)

#define SPATIAL_AUDIO_HSV_VALUE_DEACTIVATED (.35f)
#define SPATIAL_AUDIO_HSV_VALUE_ACTIVATED (1.f)


struct GazeSphere
{
	void Initialize(const float radius, const glm::vec3& center);

	float mRadius;
	glm::vec3 mCenter;

	float mCooldownTimer;
	float mChargeTimer;
	float mChargeHysteresisTimer;
};

class GazeSphereManager
{
public:
	GazeSphereManager();
	void Reset();

	bool AddGazeSpheres(GazeSphere*const gazeSpheres, const int gazeSpheresNum);
	void Update(const glm::vec3& playerGazeRayOrigin, const glm::vec3& playerGazeRayDir, const float elapsedTime);
	bool GetGazeSphereActivatedThisUpdateIndex(int*const gazeSphereActivatedThisUpdateIndex) const;
	const GazeSphere*const GetGazeSpheres() const;
	int GetGazeSpheresNum() const;

	enum { kGazeSphereActivatedThisUpdateIndexNone = -1 };
	enum { kGazeSpheresMax = 32 };
private:
	GazeSphere mGazeSpheres[kGazeSpheresMax];
	int mGazeSpheresNum;

	int mGazeSphereActivatedThisUpdateIndex;
	int mGazeSphereLastActivatedIndex;
};

void InitializeGazeBoxes(GazeSphereManager*const pGazeSphereManager, const int boxNum);
#define SPATIAL_AUDIO_TEXTURE_PATH_MAX_LEN (64)
void InitializeGazeBoxRenderResources(
	Svr::SvrGeometry**const pModel,
	Svr::SvrShader*const pShader,
	GLuint*const pTextures,
    GLuint*const gazeBoxTextureSampler,
	float*const lastSaturation,
	float*const lastValue,
	const char(*const texturePaths)[SPATIAL_AUDIO_TEXTURE_PATH_MAX_LEN],
	const char*const pAppContextExternalPath,
	const int gazeBoxNum);
void DestroyGazeBoxRenderResources(
    Svr::SvrGeometry**const pModel,
    Svr::SvrShader*const pShader,
    GLuint*const pTextures,
    GLuint*const gazeBoxTextureSampler,
    const int gazeBoxNum);
void GetDefaultGazeBoxTexturePaths(char(*texturePathsDst)[SPATIAL_AUDIO_TEXTURE_PATH_MAX_LEN], const int texturesNum);
void GazeBoxRender(
	Svr::SvrGeometry*const pModel,
	Svr::SvrShader*const pShader,
	const GLuint*const pTextures,
    const GLuint textureSamplerId,
	const GazeSphere*const pGazeSpheres,
	const int gazeSpheresNum,
	const bool*const gazeBoxesActive,
	float*const gazeBoxLastHsvSaturation,
	float*const gazeBoxLastHsvValue,
	const float elapsedTime,
	const int whichEye,
	glm::mat4& projMtx,/*conceptually const*/
	glm::mat4*const viewMtx/*conceptually const*/);

bool ConfigurationIndexIsReverb(const int index);
bool ConfigurationIndexIsRolloff(const int index);

bool GetRolloffSettings(
	q3d_audio_distance_rolloff_type*const type,
	float*const minDistance,
	float*const maxDistance,
	const int rollOffConfigurationIndex);
int GetUserConfigRolloffConfigurationIndex();
int GetUserConfigReverbConfigurationIndex();


class SpatialAudioCommon : public Svr::SvrApplication
{
public:
	SpatialAudioCommon();

	//keep synchronized with list of texture names in GetDefaultGazeBoxTexturePaths()
	enum {	kGazeBoxPause, 
			kGazeBoxLoop, 
			kGazeBoxReverbA, 
			kGazeBoxReverbB, 
			kGazeBoxReverbC, 
			kGazeBoxRolloffA, 
			kGazeBoxRolloffB, 
			kGazeBoxRolloffC, 
			kGazeBoxNum };
	enum { kGazeBoxReverbNum=3, kGazeBoxRolloffNum=3, kGazeBoxMiscNum=2 };

	void Initialize();
	void Shutdown();
	void Update();
	void Render();
	void AllocateEyeBuffers();

protected:

	virtual void OnTouchScreen() {};
	virtual void RenderCallback(float elapsedTime) {};
	virtual void RenderModelCallback(int whichEye, float elapsedTime) {};

	glm::vec3 GetPlayerHeadPositionWorldSpace();
	glm::quat GetPlayerHeadOrientationWorldSpace();

	q3d_audio_client* GetSpatialAudio();
	const q3d_audio_client* GetSpatialAudio() const;
	
	glm::mat4*const GetViewMtx();
	const glm::mat4*const GetViewMtx() const;

	glm::mat4& GetProjMtx();
	const glm::mat4& GetProjMtx() const;

	bool IsPaused() const;
	void PausedInvertAndReflectInGazeBox(bool*const gazeBoxActive);

	bool SetReverb(const int configFileSettingIndex);
	bool SetReverbAndReflectInBoolArray(bool*const gazeBoxActive, const int gazeSphereActivatedIndex);


	GazeSphereManager* GetGazeSphereManager();
private:
	q3d_audio_client*			mpSpatialAudio;
	bool						mPaused;
	bool						mTouchDownLastFrame;

	glm::vec3					mPlayerHeadPositionWorldSpace;
	glm::quat					mPlayerHeadOrientationWorldSpace;

	int                         mFrameCount;
	unsigned int                mLastFPSTime;
	float                       mCurrentFPS;

	glm::mat4                   mViewMtx[2];
	glm::mat4                   mProjMtx;
	glm::vec3                   mHeadPosition;

	Svr::SvrBufferedCpuTimer    mFrameTimer;

	//an image with an obvious orientation
	GLuint                      mOrientationTexture;

	int                         mCurrentEyeBuffer;
	Svr::SvrRenderTarget        mSingleSampleBuffers[SVR_NUM_EYE_BUFFERS][SVR_NUM_EYES];
	Svr::SvrRenderTarget		mOverlayBuffers[SVR_NUM_EYE_BUFFERS][SVR_NUM_EYES];
	Svr::SvrRenderTarget		mSingleSampleOverlayBuffers[SVR_NUM_EYE_BUFFERS][SVR_NUM_EYES];

	GazeSphereManager			mGazeSphereManager;

	Svr::SvrGeometry*           mpReticleModel;
	Svr::SvrShader              mpReticleShader;
};


bool Q3DAudioPreloadSoundFiles(q3d_audio_client*const pSpatialAudio,const Q3DSound*const pSoundFileNames,const int soundsNum);
bool Q3DAudioUnloadSoundFiles(q3d_audio_client*const pSpatialAudio,const Q3DSound*const pSound, const int soundsNum);

bool Q3DAudioObjectCreate(q3d_audio_client* const pSpatialAudio,Q3DSound*const pSound);
void Q3DAudioObjectCreateArray(
	q3d_audio_client*const pSpatialAudio,
	Q3DSound* pSoundObject,
	const int soundObjectNum,
	const q3d_audio_distance_rolloff_type rolloffType,
	const float	rolloffMinDistance,
	const float	rolloffMaxDistance);

bool Q3DAudioSoundStop(q3d_audio_client* const pSpatialAudio,Q3DSound*const pSound);

void Q3DAudioObjectSetRolloffDatafields(
	Q3DSound* pSoundObject,
	const q3d_audio_distance_rolloff_type rolloffType,
	const float	rolloffMinDistance,
	const float	rolloffMaxDistance);

float ClampNormalizedRange(const float v);
void RgbToHsv(float*const hueDegrees, float*const saturation, float*const value, const glm::vec3& rgb);
glm::vec3 HsvToRgb(const float hueDegrees, const float saturation, const float value);
float InfiniteImpulseResponseCalc(const float lastVal, const float target, const float responseFactor);
float InfiniteImpulseResponseFactorCalc(const float factor, const float elapsedTime);
float Lerp(const float start, const float end, const float timeCurrentCountdownFromMax, const float timeMax);

glm::vec3 GazeSphereColorProcess(
	float*const gazeSphereLastHsvSaturation,
	float*const gazeSphereLastHsvValue,
	const GazeSphere& gazeSphere,
	const glm::vec3& defaultRgb,
	const bool gazeSphereActive,
	const float elapsedTime);

void SetBoolsToFalse(bool*const firstElement, const int elementsNum);
void InitializeLastSaturationAndValue(float*const lastSaturation, float*const lastValue, const glm::vec3& defaultColor);
void SetGazeBoxActiveDefaultsReverbRolloff(bool*const gazeBoxActive, const bool looping);
void SetGazeBoxActiveLooping(bool*const gazeBoxActive, const bool looping);
GLuint GazeBoxSamplerCreate();
void GazeBoxSamplerDestroy(GLuint*const gazeBoxTextureSampler);