#pragma once

#include "style.h"
#include "entity.h"
#include "sphere.h"
#include "slot.h"
#include "table.h"

class PlugStream;

//
//--------------------------------------------------------------------------
// Support types
//--------------------------------------------------------------------------
//

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Time types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

typedef long AudioTick;
typedef long AudioDivisionsPerBeat;
typedef long AudioTempo;

typedef long AudioFrameCount;
const AudioFrameCount NullAudioFrameCount = -1;
const AudioFrameCount DefaultAudioFrameDelay = 2;

extern const Scalar AudioDoubleTriggerCutoff;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Resource types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

typedef int AudioResourceID;
typedef int AudioVoiceCount;
typedef Scalar AudioRadiationProfile;
typedef Scalar AudioPitchCents;

enum AudioRenderType
{
	TransientAudioRenderType = 0,
	SustainedAudioRenderType = 1
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Source types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

enum AudioRepresentation
{
	InternalAudioRepresentation = 0,
	ExternalAudioRepresentation = 1
};

#define AUDIO_SOURCE_PRIORITY_COUNT (5)
enum AudioSourcePriority
{
	MinAudioSourcePriority = 0,
	LowAudioSourcePriority = 1,
	MedAudioSourcePriority = 2,
	HighAudioSourcePriority = 3,
	MaxAudioSourcePriority = 4
};

enum AudioSourceState
{
	StoppedAudioSourceState = 0,
	DormantAudioSourceState = 1,
	RunningAudioSourceState = 2,
	SuspendedAudioSourceState = 3
};

enum AudioSourceMixPresence
{
	ManualAudioSourceMixPresence = 0,
	MaxAudioSourceMixPresence = 1,
	HighAudioSourceMixPresence = 2,
	MedAudioSourceMixPresence = 3,
	LowAudioSourceMixPresence = 4,
	MinAudioSourceMixPresence = 5
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Control types ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

enum AudioControlID
{
	NullAudioControlID = 0,
	StartAudioControlID,
	StopAudioControlID,
	VolumeAudioControlID,
	PitchAudioControlID,
	BrightnessAudioControlID,
	AttackVolumeAudioControlID,
	AttackTimeAudioControlID,
	NoteAudioControlID,
	IdleAudioControlID,
	DurationAudioControlID,
	FlushMessagesAudioControlID,
	TempoAudioControlID,
	AudioControlIDCount
};

typedef Scalar AudioControlValue;

extern const AudioControlValue MaxAudioVolume;
extern const AudioControlValue MinAudioVolume;
extern const AudioControlValue LowAudioVolumeThreshold;

extern const AudioControlValue MaxAudioBrightness;
extern const AudioControlValue MinAudioBrightness;

extern const AudioControlValue MaxAudioAttack;
extern const AudioControlValue MinAudioAttack;

#if 0
//~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioClippingSphere ~~~~~~~~~~~~~~~~~~~~~~~~~~

class AudioClippingSphere SIGNATURED
{
public:
	AudioClippingSphere();
	~AudioClippingSphere();
	
	void
		SetReferenceRadius(Scalar radius);
	void
		SetCurrentOrigin(const Point3D &origin);
	void
		SetCurrentScale(Scalar scale);
	Logical
		Contains(const Point3D &point) const;

private:
	Scalar
		referenceRadius;
	Sphere
		currentSphere;
};

//~~~~~~~~~~~~~~~~~~~~~~~ AudioClippingSphere inlines ~~~~~~~~~~~~~~~~~~~~~~

inline AudioClippingSphere::AudioClippingSphere():
	referenceRadius(100.0f),
	currentSphere(0.0f, 0.0f, 0.0f, 100.0f)
{
}

inline AudioClippingSphere::~AudioClippingSphere()
{
}

inline void
	AudioClippingSphere::SetReferenceRadius(Scalar radius)
{
	Check(this);
	referenceRadius = radius;
	currentSphere.radius = radius;
}

inline void
	AudioClippingSphere::SetCurrentOrigin(const Point3D &origin)
{
	Check(this);
	currentSphere.center = origin;
}

inline void
	AudioClippingSphere::SetCurrentScale(Scalar scale)
{
	Check(this);
	currentSphere.radius = scale * referenceRadius;
}

inline Logical
	AudioClippingSphere::Contains(const Point3D &point) const
{
	Check(this);
	return currentSphere.Contains(point);
}
#endif

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioHead ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class AudioSource;

class AudioHead SIGNATURED
{
public:
	//
	//-----------------------------------------------------------------------
	// Construction, Destruction, Testing
	//-----------------------------------------------------------------------
	//
	AudioHead();
	~AudioHead();

	Logical
		TestInstance() const;

	//
	//-----------------------------------------------------------------------
	// Get current frame of audio simulation
	//-----------------------------------------------------------------------
	//
	AudioFrameCount
		GetAudioFrameCount()
     	{return audioFrameCount;}

	//
	//-----------------------------------------------------------------------
	// Sets the location and orientation of the head
	//-----------------------------------------------------------------------
	//
	virtual void
		LinkToEntity(Entity *entity);
	Entity*
		GetHeadEntity();

	//
	//-----------------------------------------------------------------------
	// Get the ear to world linear matrix
	//-----------------------------------------------------------------------
	//
	virtual LinearMatrix
		GetEarToWorld();

	//
	//-----------------------------------------------------------------------
	// Execute
	//-----------------------------------------------------------------------
	//
	virtual void
		Execute();

	//
	//-----------------------------------------------------------------------
	// Sets the distance between ears.  Expressed in meters.
	//-----------------------------------------------------------------------
	//
	void
		SetDistanceBetweenEars(Scalar distance)
			{distanceBetweenEars = distance;}
	Scalar
		GetDistanceBetweenEars()
			{return distanceBetweenEars;}

	//
	//-----------------------------------------------------------------------
	// Controls Doppler effects
	//-----------------------------------------------------------------------
	//
	void
		ControlDopplerEffect(
			Scalar doppler_constant,
			Scalar sound_speed
		);
	Scalar
		GetAudioSoundSpeed()
			{return audioSoundSpeed;}
	AudioPitchCents
		GetAudioDopplerConstant()
			{return audioDopplerConstant;}

	//
	//-----------------------------------------------------------------------
	// Reverb scaling
	//-----------------------------------------------------------------------
	//
	void
		SetReverbToDryRatio(Scalar ratio)
			{reverbToDryRatio = ratio;}
	Scalar
		GetReverbToDryRatio()
			{return reverbToDryRatio;}

	// HACK
	void
		SetGlobalReverbScale(Scalar global_reverb_scale)
			{globalReverbScale = global_reverb_scale;}
	Scalar
		GetGlobalReverbScale()
			{return globalReverbScale;}

	//
	//-----------------------------------------------------------------------
	// Controls the attenuation of an audio source with respect to its
	// distance from the head.
	//-----------------------------------------------------------------------
	//
	void
		ControlAmplitudeRollOff(
			Scalar exponent,
			Scalar amplitude_rolloff_knee,
			Scalar distance_scale
		);
	Scalar
		GetAmplitudeRollOffExponent()
			{return amplitudeRollOffExponent;}
	Scalar
		GetAmplitudeRollOffKnee()
			{return amplitudeRollOffKnee;}
	Scalar
		GetAmplitudeRollOffDistanceScale()
			{return amplitudeRollOffDistanceScale;}

	//
	//-----------------------------------------------------------------------
	// Controls the attenuation of the high frequencies
	// of an audio source with respect to its distance from
	// the head.
	//-----------------------------------------------------------------------
	//
	void
		ControlHighFrequencyRollOff(
			Scalar exponent,
			Scalar high_frequency_rolloff_knee,
			Scalar distance_scale
		);
	Scalar
		GetHighFrequencyRollOffExponent()
			{return highFrequencyRollOffExponent;}
	Scalar
		GetHighFrequencyRollOffKnee()
			{return highFrequencyRollOffKnee;}
	Scalar
		GetHighFrequencyRollOffDistanceScale()
			{return highFrequencyRollOffDistanceScale;}

	//
	//-----------------------------------------------------------------------
	// Controls source compression
	//-----------------------------------------------------------------------
	//
	void
		ControlSourceCompression(
			Scalar exponent,
			Scalar scale
		);
	Scalar
		GetSourceCompressionExponent()
			{return sourceCompressionExponent;}
	Scalar
		GetSourceCompressionScale()
			{return sourceCompressionScale;}

	//
	//-----------------------------------------------------------------------
	// Defines the audio clipping sphere
	//-----------------------------------------------------------------------
	//
	void
		DefineClippingSphere(Scalar radius);
	Logical
		IsPointClipped(const Point3D &point);
	Scalar
		GetClippingRadius()
			{return clippingRadius;}

	//
	//-----------------------------------------------------------------------
	// ITD Difference
	//-----------------------------------------------------------------------
	//
	void
		SetITDDifference(Scalar itd_difference)
			{itdDifference = itd_difference;}
	Scalar
		GetITDDifference()
			{return itdDifference;}

	//
	//-----------------------------------------------------------------------
	// The SetPositionalCulling method allows the rendering process to
	// not play audio locations whose positions are masked by other
	// audio sources. This has found to be useful for controlling
	// audio when resources are limited, but must be
	// selectable globally or on a per audio effect basis.
	//-----------------------------------------------------------------------
	//
	void
		SetPositionalCulling(Logical turn_on);

private:
	//
	//-----------------------------------------------------------------------
	// Private data
	//-----------------------------------------------------------------------
	//
	AudioFrameCount
  	audioFrameCount;
	SlotOf<Entity*>
		headEntitySocket;

	Scalar
		clippingRadius;
	Scalar
		distanceBetweenEars;
	Scalar
		amplitudeRollOffExponent,
		amplitudeRollOffKnee,
		amplitudeRollOffDistanceScale;
	Scalar
		highFrequencyRollOffExponent,
		highFrequencyRollOffKnee,
		highFrequencyRollOffDistanceScale;
	Scalar
		reverbToDryRatio;
	Scalar
		audioSoundSpeed;
	Scalar
		audioDopplerConstant;
	Scalar
		sourceCompressionExponent,
		sourceCompressionScale;
	Scalar
		itdDifference;
	Scalar
		globalReverbScale;
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioHead inlines ~~~~~~~~~~~~~~~~~~~~~~~~~

inline Entity*
  AudioHead::GetHeadEntity()
{
  Check(this);
  return headEntitySocket.GetCurrent();
}

//##########################################################################
//#########################    AudioComponent    ###########################
//##########################################################################

class AudioComponent__ReceiveControlMessage;

class AudioComponent:
	public Component
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction, Destruction, Testing
//
public:
	AudioComponent(
		PlugStream *stream,
		SharedData &shared_data = DefaultData
	);
	~AudioComponent();

	Logical
		TestInstance() const;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Audio Component Methods
//
public:
	void
		AddWatcher(Component *component);

	void
		ExecuteWatchers();

	void
		Execute();

	virtual void
		ReceiveControl(
			AudioControlID control_ID,
			AudioControlValue control_value
		);

	void
		PostReceiveControl(
			AudioControlID control_ID,
			AudioControlValue control_value
		);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Message Support
//
public:
	enum {
		ReceiveControlMessageID = Component::NextMessageID,
     NextMessageID
	};

	typedef AudioComponent__ReceiveControlMessage
		ReceiveControlMessage;

	static const HandlerEntry
		MessageHandlerEntries[];
	//static MessageHandlerSet MessageHandlers;
	static MessageHandlerSet& GetMessageHandlers();

	void
		ReceiveControlMessageHandler(
			ReceiveControlMessage *message
		);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
	static Derivation *GetClassDerivations();
	static SharedData
		DefaultData;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Private Data
//
private:
	AudioFrameCount
		nextExecuteWatcherFrame;
	ChainOf<Component*>
		audioWatcherSocket;
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~ AudioComponent inlines ~~~~~~~~~~~~~~~~~~~~~~~~

inline void
	AudioComponent::AddWatcher(Component *component)
{
	Check(component);
	Check(&audioWatcherSocket);
	audioWatcherSocket.Add(component);
}

//~~~~~~~~~~~~~~~~~~ AudioComponent__ReceiveControlMessage ~~~~~~~~~~~~~~~~~

class AudioComponent__ReceiveControlMessage:
	public Receiver::Message
{
	friend class AudioComponent;

public:
	AudioComponent__ReceiveControlMessage(
		AudioControlID control_ID,
		AudioControlValue control_value
	);

private:
	AudioControlID
		controlID;
	AudioControlValue
		controlValue;
};

//~~~~~~~~~~~~~~~~~~~~~~~~ Resource type inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~~

inline MemoryStream&
	MemoryStream_Read(
		MemoryStream* stream,
		AudioRenderType *output
	)
{
	return stream->ReadBytes(output, sizeof(*output));
}

inline MemoryStream&
	MemoryStream_Write(
		MemoryStream* stream,
		const AudioRenderType *input
	)
{
	return stream->WriteBytes(input, sizeof(*input));
}

inline MemoryStream&
	MemoryStream_Read(
		MemoryStream* stream,
		AudioRepresentation *output
	)
{
	return stream->ReadBytes(output, sizeof(*output));
}

inline MemoryStream&
	MemoryStream_Write(
		MemoryStream* stream,
		const AudioRepresentation *input
	)
{
	return stream->WriteBytes(input, sizeof(*input));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~ Source type inlines ~~~~~~~~~~~~~~~~~~~~~~~~~~

inline MemoryStream&
	MemoryStream_Read(
		MemoryStream* stream,
		AudioSourceState *output
	)
{
	return stream->ReadBytes(output, sizeof(*output));
}

inline MemoryStream&
	MemoryStream_Write(
		MemoryStream* stream,
		const AudioSourceState *input
	)
{
	return stream->WriteBytes(input, sizeof(*input));
}

inline MemoryStream&
	MemoryStream_Read(
		MemoryStream* stream,
		AudioSourcePriority *output
	)
{
	return stream->ReadBytes(output, sizeof(*output));
}

inline MemoryStream&
	MemoryStream_Write(
		MemoryStream* stream,
		const AudioSourcePriority *input
	)
{
	return stream->WriteBytes(input, sizeof(*input));
}

inline MemoryStream&
	MemoryStream_Read(
		MemoryStream* stream,
		AudioSourceMixPresence *output
	)
{
	return stream->ReadBytes(output, sizeof(*output));
}

inline MemoryStream&
	MemoryStream_Write(
		MemoryStream* stream,
		const AudioSourceMixPresence *input
	)
{
	return stream->WriteBytes(input, sizeof(*input));
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~ Control type inlines ~~~~~~~~~~~~~~~~~~~~~~~~~

inline MemoryStream&
	MemoryStream_Read(
		MemoryStream* stream,
		AudioControlID *output
	)
{
	return stream->ReadBytes(output, sizeof(*output));
}

inline MemoryStream&
	MemoryStream_Write(
		MemoryStream* stream,
		const AudioControlID *input
	)
{
	return stream->WriteBytes(input, sizeof(*input));
}
