#pragma once

#include <string>
#include <memory>
#include <functional>
#include <random>
#include <map>
#include "glm/glm.hpp"


// stupid soloud includes windows.h
#ifndef WIN32_LEAN_AND_MEAN
	#define WIN32_LEAN_AND_MEAN
#endif
#define NOMINMAX
#include "soloud.h"
#include "soloud_wav.h"

// forward declarations
/*namespace SoLoud
{
	class Soloud;
	class Wav;
}*/

class SoundManager;
class SoundFile;


struct PlayOptions
{
	PlayOptions() = default;
	//TODO: default value prohibit the use of initilizer lists in VS2015
	PlayOptions(bool l, float v, bool p=false) :
		looped(l),
		volume(v),
		priority(p)
	{
	};
	bool looped = false;
	float volume = 1.0f;
	bool priority = false; // if the sound should always be audible
};


// a instance of a sound
class SoundController
{
	friend SoundFile;
	friend SoundManager;
private:
	SoundController(SoundFile* file, SoLoud::Soloud* engine, bool start_playing, bool play_3d, PlayOptions play_options); //only called by sound file
public:
	SoundController() = delete; // empy controllers should be optional<SoundController> or unique_ptr<SoundController>
	SoundController(const SoundController&) = delete;
	SoundController& operator=(const SoundController&) = delete;

	SoundController(SoundController&&);
	SoundController& operator=(SoundController&&);

	~SoundController();
public:
	void Restart(PlayOptions=PlayOptions()); // plays the sound from the beginning
	bool IsPlaying(); // returns, whether the sound currently plays
	void Stop();
	void SetRandomPosition(); // usefull for looped sounds
	void SetPosition(glm::vec3 position);

	void FadeOut(float time);
private:
	SoLoud::Soloud* m_engine = nullptr;
	SoundFile* m_file = nullptr;
	int m_sound = -1; // id of currently playing sound (that is controlled by this object)
	
	// in case we need to restart the sound
	bool m_play_3d;
};


// a sound file that was loaded and can be used to create new instances of it.
class SoundFile
{
	friend SoundManager;
	friend SoundController;
private:
	SoundFile(SoundManager* soundManager, SoLoud::Wav* sound); // only called by SoundManager
public:
	SoundFile() = delete; // empy SoundFiles should be optional<SoundFile> or unique_ptr<SoundFile>
	SoundFile(const SoundFile&) = delete;
	SoundFile& operator= (const SoundFile&) = delete;

	SoundFile(SoundFile&&);
	SoundFile& operator= (SoundFile&&); // performs a swap

	void Play(PlayOptions=PlayOptions());// todo: soloud is apparently not const-correct
	SoundController PlayControlled(bool start_playing=true, bool play_3d=false, PlayOptions = PlayOptions());

private:
	SoundManager* m_SoundManager;
	SoLoud::Soloud* m_engine;
	SoLoud::Wav* m_sound;
};


class SoundManager
{
	friend SoundController;
	friend SoundFile;
public:
	SoundManager();
	SoundManager(SoundManager&) = delete;
	SoundManager(SoundManager&&) = delete;
	~SoundManager();
	SoundFile LoadSound(std::string filename);
	void SetPosition(glm::vec3 position, glm::vec3 look_dir, glm::vec3 up);

	void SetDefaultAttenuation(float min_distance, float max_distance, float value, unsigned int model);
	void update();

	void DisableLoading(); // load no sounds to increase loading time
private:
	SoLoud::Soloud m_SoundEngine;

	// used to set sounds to random playback position
	std::mt19937 m_rnd;

	std::map<std::string, std::unique_ptr<SoLoud::Wav>> m_loaded_sounds;

	// default attenuation model
	float m_default_min_distance = 1;
	float m_default_max_distance = 1500;
	float m_default_attenuation_value = 1;
	unsigned int m_default_attenuation_model = SoLoud::AudioSource::ATTENUATION_MODELS::INVERSE_DISTANCE;

	bool m_disable_loading = false;
};
