#include "gxaudio.hpp"
#include "gxchannel.hpp"
#include "gxsound.hpp"
#include <set>
#include <vector>
#include <map>

#include <stdutil.hpp>

extern "C" {
#include <fmod.h>
}

struct StaticChannel : public gxChannel {
	virtual void play() = 0;
};

struct SoundChannel : public gxChannel {
	SoundChannel() : channel(-1) {}
	void set(int n)
	{
		channel = n;
	}
	void stop()
	{
		FSOUND_StopSound(channel);
	}
	void setPaused(bool paused)
	{
		FSOUND_SetPaused(channel, paused);
	}
	void setPitch(int pitch)
	{
		FSOUND_SetFrequency(channel, pitch);
	}
	void setVolume(float volume)
	{
		FSOUND_SetVolume(channel, volume * 255.0f);
	}
	void setPan(float pan)
	{
		FSOUND_SetPan(channel, (pan + 1) * 127.5f);
	}
	void set3d(const float pos[3], const float vel[3])
	{
		FSOUND_3D_SetAttributes(channel, (float*)pos, (float*)vel);
	}
	bool isPlaying()
	{
		return FSOUND_IsPlaying(channel) ? true : false;
	}

	private:
	int channel;
};

struct CDChannel : public gxChannel {
	void play(int track, int mode)
	{
		stop();
		int cd_mode = FSOUND_CD_PLAYONCE;
		if (mode == gxAudio::CD_MODE_LOOP)
			cd_mode = FSOUND_CD_PLAYLOOPED;
		else if (mode == gxAudio::CD_MODE_ALL)
			cd_mode = FSOUND_CD_PLAYCONTINUOUS;
		FSOUND_CD_SetPlayMode(0, cd_mode);
		FSOUND_CD_Play(0, track);
	}
	void stop()
	{
		FSOUND_CD_Stop(0);
	}
	void setPaused(bool paused)
	{
		FSOUND_CD_SetPaused(0, paused);
	}
	void setPitch(int pitch) {}
	void setVolume(float volume)
	{
		FSOUND_CD_SetVolume(0, volume * 255.0f);
	}
	void setPan(float pan) {}
	void set3d(const float pos[3], const float vel[3]) {}
	bool isPlaying()
	{
		return true;
	}
};

struct StreamChannel : public StaticChannel {
	StreamChannel(FSOUND_STREAM* s) : stream(s)
	{
		channel = FSOUND_Stream_Play(FSOUND_FREE, stream);
	}
	~StreamChannel()
	{
		FSOUND_Stream_Close(stream);
	}
	void play()
	{
		stop();
		channel = FSOUND_Stream_Play(FSOUND_FREE, stream);
	}
	void stop()
	{
		FSOUND_Stream_Stop(stream);
		channel = -1;
	}
	void setPaused(bool paused)
	{
		FSOUND_SetPaused(channel, paused);
	}
	void setPitch(int pitch)
	{
		FSOUND_SetFrequency(channel, pitch);
	}
	void setVolume(float volume)
	{
		FSOUND_SetVolume(channel, volume * 255.0f);
	}
	void setPan(float pan)
	{
		FSOUND_SetPan(channel, (pan + 1) * 127.5f);
	}
	void set3d(const float pos[3], const float vel[3]) {}
	bool isPlaying()
	{
		return FSOUND_IsPlaying(channel) ? true : false;
	}

	private:
	FSOUND_STREAM* stream;
	int            channel;
};

struct MusicChannel : public StaticChannel {
	MusicChannel(FMUSIC_MODULE* m) : module(m)
	{
		play();
	}
	~MusicChannel()
	{
		FMUSIC_FreeSong(module);
	}
	void play()
	{
		FMUSIC_PlaySong(module);
	}
	void stop()
	{
		FMUSIC_StopSong(module);
	}
	void setPaused(bool paused)
	{
		FMUSIC_SetPaused(module, paused);
	}
	void setPitch(int pitch) {}
	void setVolume(float volume)
	{
		FMUSIC_SetMasterVolume(module, volume * 255.0f);
	}
	void setPan(float pan) {}
	void set3d(const float pos[3], const float vel[3]) {}
	bool isPlaying()
	{
		return FMUSIC_IsFinished(module) ? false : true;
	}

	private:
	FMUSIC_MODULE* module;
};

static std::set<gxSound*>               sound_set;
static std::vector<gxChannel*>     channels;
static std::map<std::string, StaticChannel*> songs;
static CDChannel*                  cdChannel;

static int                   next_chan;
static std::vector<SoundChannel*> soundChannels;

static gxChannel* allocSoundChannel(int n)
{
	SoundChannel* chan = 0;
	for (int k = 0; k < soundChannels.size(); ++k) {
		chan = soundChannels[next_chan];
		if (!chan) {
			chan = soundChannels[next_chan] = new SoundChannel();
			channels.push_back(chan);
		} else if (chan->isPlaying()) {
			chan = 0;
		}
		if (++next_chan == soundChannels.size())
			next_chan = 0;
		if (chan)
			break;
	}

	if (!chan) {
		next_chan = soundChannels.size();
		soundChannels.resize(soundChannels.size() * 2);
		for (int k = next_chan; k < soundChannels.size(); ++k)
			soundChannels[k] = 0;
		chan = soundChannels[next_chan++] = new SoundChannel();
		channels.push_back(chan);
	}

	chan->set(n);
	return chan;
}

gxAudio::gxAudio(gxRuntime* r) : runtime(r)
{
	next_chan = 0;
	soundChannels.resize(4096);
	for (int k = 0; k < 4096; ++k)
		soundChannels[k] = 0;

	cdChannel = new CDChannel();
	channels.push_back(cdChannel);
}

gxAudio::~gxAudio()
{
	//free all channels
	for (; channels.size(); channels.pop_back())
		delete channels.back();
	//free all sound_set
	while (sound_set.size())
		freeSound(*sound_set.begin());
	soundChannels.clear();
	songs.clear();

	FSOUND_Close();
}

gxChannel* gxAudio::play(FSOUND_SAMPLE* sample)
{
	int n = FSOUND_PlaySound(FSOUND_FREE, sample);
	return n >= 0 ? allocSoundChannel(n) : 0;
}

gxChannel* gxAudio::play3d(FSOUND_SAMPLE* sample, const float pos[3], const float vel[3])
{
	int n = FSOUND_PlaySoundEx(FSOUND_FREE, sample, 0, true);
	if (n < 0)
		return 0;
	FSOUND_3D_SetAttributes(n, (float*)pos, (float*)vel);
	FSOUND_SetPaused(n, false);
	return allocSoundChannel(n);
}

void gxAudio::pause() {}

void gxAudio::resume() {}

gxSound* gxAudio::loadSound(const std::string& f, bool use3d)
{
	int flags = FSOUND_NORMAL | (use3d ? FSOUND_FORCEMONO : FSOUND_2D);

	FSOUND_SAMPLE* sample = FSOUND_Sample_Load(FSOUND_FREE, f.c_str(), flags, 0, 0);
	if (!sample)
		return 0;

	gxSound* sound = new gxSound(this, sample);
	sound_set.insert(sound);
	return sound;
}

gxSound* gxAudio::verifySound(gxSound* s)
{
	return sound_set.count(s) ? s : 0;
}

void gxAudio::freeSound(gxSound* s)
{
	if (sound_set.erase(s))
		delete s;
}

void gxAudio::setPaused(bool paused)
{
	FSOUND_SetPaused(FSOUND_ALL, paused);
}

void gxAudio::setVolume(float volume) {}

void gxAudio::set3dOptions(float roll, float dopp, float dist)
{
	FSOUND_3D_SetRolloffFactor(roll);
	FSOUND_3D_SetDopplerFactor(dopp);
	FSOUND_3D_SetDistanceFactor(dist);
}

void gxAudio::set3dListener(const float pos[3], const float vel[3], const float forward[3], const float up[3])
{
	FSOUND_3D_Listener_SetAttributes((float*)pos, (float*)vel, forward[0], forward[1], forward[2], up[0], up[1], up[2]);
	FSOUND_Update();
}

gxChannel* gxAudio::playFile(const std::string& t, bool use_3d)
{
	std::string                                     f    = tolower(t);
	StaticChannel*                        chan = 0;
	std::map<std::string, StaticChannel*>::iterator it   = songs.find(f);
	if (it != songs.end()) {
		chan = it->second;
		chan->play();
		return chan;
	} else if (f.find(".raw") != std::string::npos || f.find(".wav") != std::string::npos
			   || f.find(".mp2") != std::string::npos || f.find(".mp3") != std::string::npos
			   || f.find(".ogg") != std::string::npos || f.find(".wma") != std::string::npos
			   || f.find(".asf") != std::string::npos) {
		FSOUND_STREAM* stream = FSOUND_Stream_Open(f.c_str(), use_3d, 0, 0);
		if (!stream)
			return 0;
		chan = new StreamChannel(stream);
	} else {
		FMUSIC_MODULE* module = FMUSIC_LoadSong(f.c_str());
		if (!module)
			return 0;
		chan = new MusicChannel(module);
	}
	channels.push_back(chan);
	songs[f] = chan;
	return chan;
}

gxChannel* gxAudio::playCDTrack(int track, int mode)
{
	cdChannel->play(track, mode);
	return cdChannel;
}
