/*  _______         ____    __         ___    ___
 * \    _  \       \    /  \  /       \   \  /   /       '   '  '
 *  |  | \  \       |  |    ||         |   \/   |         .      .
 *  |  |  |  |      |  |    ||         ||\  /|  |
 *  |  |  |  |      |  |    ||         || \/ |  |         '  '  '
 *  |  |  |  |      |  |    ||         ||    |  |         .      .
 *  |  |_/  /        \  \__//          ||    |  |
 * /_______/ynamic    \____/niversal  /__\  /____\usic   /|  .  . ibliotheque
 *                                                      /  \
 *                                                     / .  \
 * alplay.c - Functions to play a DUH through         / / \  \
 *            an Allegro audio stream.               | <  /   \_
 *                                                   |  \/ /\   /
 * By entheh.                                         \_  /  > /
 *                                                      | \ / /
 *                                                      |  ' /
 *                                                       \__/
 */

#include <stdlib.h>

#include <allegro5/allegro5.h>
#include <allegro5/kcm_audio.h>

#include "a5dumb.h"



#define ADP_PLAYING 1

struct AL_DUH_PLAYER
{
	int flags;
	long bufsize;
	int freq;
	ALLEGRO_STREAM *stream;
	DUH_SIGRENDERER *sigrenderer; /* If this is NULL, stream is invalid. */
	float volume;
	int silentcount;

	ALLEGRO_THREAD * thread;
};

static AL_DUH_PLAYER * al_default_duh_player = NULL;
static DUH * al_default_duh = NULL;

bool al_init_dumb_addon(int resampler)
{
	dumb_resampling_quality = resampler;
	atexit(&dumb_exit);
	dumb_register_stdfiles();
	return true;
}

void * al_duh_update_thread(ALLEGRO_THREAD * thread, void * arg)
{
	AL_DUH_PLAYER * dp = (AL_DUH_PLAYER *)arg;
	ALLEGRO_EVENT_QUEUE * queue;
	void * vptr;
	unsigned short *fragment;
	long n;
	long size;
	int n_channels;

	queue = al_create_event_queue();
	al_register_event_source(queue, *((ALLEGRO_EVENT_SOURCE **)(dp->stream)));

	while(1)
	{
		ALLEGRO_EVENT event;

		al_wait_for_event(queue, &event);

//		if(event.type == ALLEGRO_EVENT_STREAM_EMPTY_FRAGMENT && !dp->stream->is_draining)
		if(event.type == ALLEGRO_EVENT_STREAM_EMPTY_FRAGMENT)
		{
			if(!al_get_stream_buffer(dp->stream, &vptr))
			{
				return NULL;
			}
			fragment = vptr;

			n = duh_render(dp->sigrenderer, 16, 0, dp->volume, 65536.0 / dp->freq, dp->bufsize, fragment);

			if (n == 0)
			{
				if (++dp->silentcount >= 2)
				{
					duh_end_sigrenderer(dp->sigrenderer);
					if(al_set_stream_buffer(dp->stream, (void **) fragment) != 0)
					{
					}
					al_destroy_stream(dp->stream);
					dp->sigrenderer = NULL;
					return NULL;
				}
			}

			n_channels = duh_sigrenderer_get_n_channels(dp->sigrenderer);
			n *= n_channels;
			size = dp->bufsize * n_channels;
			for (; n < size; n++)
			{
				fragment[n] = 0x8000;
			}
			if(al_set_stream_buffer(dp->stream, (void **) fragment) != 0)
			{
			}
		}
		if(al_get_thread_should_stop(thread))
		{
			break;
		}
	}

	al_destroy_event_queue(queue);

	return NULL;
}

AL_DUH_PLAYER *al_start_duh(DUH *duh, int n_channels, long pos, float volume, long bufsize, int freq)
{
	AL_DUH_PLAYER *dp;
	ALLEGRO_CHANNEL_CONF c_conf;

	/* This restriction is imposed by Allegro. */
	ASSERT(n_channels > 0);
	ASSERT(n_channels <= 2);

	if (!duh)
		return NULL;

	dp = malloc(sizeof(*dp));
	if (!dp)
		return NULL;

	dp->flags = ADP_PLAYING;
	dp->bufsize = bufsize;
	dp->freq = freq;
	if(n_channels == 1)
	{
		c_conf = ALLEGRO_CHANNEL_CONF_1;
	}
	else
	{
		c_conf = ALLEGRO_CHANNEL_CONF_2;
	}

	dp->stream = al_create_stream(4, bufsize, freq, ALLEGRO_AUDIO_DEPTH_INT16, c_conf);

	if (!dp->stream) {
		free(dp);
		return NULL;
	}
	al_attach_stream_to_mixer(al_get_default_mixer(), dp->stream);

	dp->sigrenderer = dumb_it_start_at_order(duh, n_channels, pos);

	if (!dp->sigrenderer) {
		al_destroy_stream(dp->stream);
		free(dp);
		return NULL;
	}
	dp->thread = al_create_thread(al_duh_update_thread, dp);
	if(!dp->thread)
	{
		return NULL;
	}

	dp->volume = volume;
	dp->silentcount = 0;
	al_start_thread(dp->thread);

	return dp;
}



void al_stop_duh(AL_DUH_PLAYER *dp)
{
	if(dp)
	{
		al_destroy_thread(dp->thread);
		if(dp->sigrenderer)
		{
			duh_end_sigrenderer(dp->sigrenderer);
//			al_drain_stream(dp->stream);
			al_destroy_stream(dp->stream);
		}
		free(dp);
	}
}



void al_pause_duh(AL_DUH_PLAYER *dp)
{
	if (dp && dp->sigrenderer && (dp->flags & ADP_PLAYING)) {
		al_set_stream_playing(dp->stream, false);
//		voice_stop(dp->stream->voice);
		dp->flags &= ~ADP_PLAYING;
	}
}



void al_resume_duh(AL_DUH_PLAYER *dp)
{
	if (dp && dp->sigrenderer && !(dp->flags & ADP_PLAYING)) {
		al_set_stream_playing(dp->stream, true);
//		voice_start(dp->stream->voice);
		dp->flags |= ADP_PLAYING;
	}
}



void al_duh_set_priority(AL_DUH_PLAYER *dp, int priority)
{
//	if (dp && dp->sigrenderer)
//		voice_set_priority(dp->stream->voice, priority);
}



void al_duh_set_volume(AL_DUH_PLAYER *dp, float volume)
{
	if (dp)
		dp->volume = volume;
}

float al_duh_get_volume(AL_DUH_PLAYER *dp)
{
	return dp ? dp->volume : 0;
}

long al_duh_get_position(AL_DUH_PLAYER *dp)
{
	return dp ? duh_sigrenderer_get_position(dp->sigrenderer) : -1;
}

AL_DUH_PLAYER *al_duh_encapsulate_sigrenderer(DUH_SIGRENDERER *sigrenderer, float volume, long bufsize, int freq)
{
	AL_DUH_PLAYER *dp;
	int n_channels;
	ALLEGRO_CHANNEL_CONF c_conf;

	if (!sigrenderer)
		return NULL;

	dp = malloc(sizeof(*dp));
	if (!dp)
		return NULL;

	n_channels = duh_sigrenderer_get_n_channels(sigrenderer);
	if(n_channels == 1)
	{
		c_conf = ALLEGRO_CHANNEL_CONF_1;
	}
	else
	{
		c_conf = ALLEGRO_CHANNEL_CONF_2;
	}

	/* This restriction is imposed by Allegro. */
	ASSERT(n_channels > 0);
	ASSERT(n_channels <= 2);

	dp->flags = ADP_PLAYING;
	dp->bufsize = bufsize;
	dp->freq = freq;

	dp->stream = al_create_stream(4, bufsize, freq, ALLEGRO_AUDIO_DEPTH_UINT16, c_conf);

	if (!dp->stream) {
		free(dp);
		return NULL;
	}

	dp->stream = al_create_stream(4, bufsize, freq, ALLEGRO_AUDIO_DEPTH_UINT16, c_conf);

	if (!dp->stream) {
		free(dp);
		return NULL;
	}

	dp->volume = volume;
	dp->silentcount = 0;

	return dp;
}



DUH_SIGRENDERER *al_duh_get_sigrenderer(AL_DUH_PLAYER *dp)
{
	return dp ? dp->sigrenderer : NULL;
}



/* IMPORTANT: This function will return NULL if the music has ended. */
// Should this be changed? User might want to hack the underlying SIGRENDERER
// and resurrect it (e.g. change pattern number), before it gets destroyed...
DUH_SIGRENDERER *al_duh_decompose_to_sigrenderer(AL_DUH_PLAYER *dp)
{
	if (dp) {
		DUH_SIGRENDERER *sigrenderer = dp->sigrenderer;
		if (sigrenderer) al_destroy_stream(dp->stream);
		free(dp);
		return sigrenderer;
	}
	return NULL;
}



/* DEPRECATED */
AL_DUH_PLAYER *al_duh_encapsulate_renderer(DUH_SIGRENDERER *dr, float volume, long bufsize, int freq)
{
	return al_duh_encapsulate_sigrenderer(dr, volume, bufsize, freq);
}



/* DEPRECATED */
DUH_SIGRENDERER *al_duh_get_renderer(AL_DUH_PLAYER *dp)
{
	return al_duh_get_sigrenderer(dp);
}



/* DEPRECATED */
DUH_SIGRENDERER *al_duh_decompose_to_renderer(AL_DUH_PLAYER *dp)
{
	return al_duh_decompose_to_sigrenderer(dp);
}


/* easy access functions */

/* return the player so you can use more advanced features if you want
   you can safely ignore the return value if all you want is to play a mod */
AL_DUH_PLAYER * al_play_module(const char * fn, int pattern, bool loop)
{
	al_default_duh = dumb_load_xm_quick(fn);
	if(!al_default_duh)
	{
		al_default_duh = dumb_load_it_quick(fn);
		if(!al_default_duh)
		{
			al_default_duh = dumb_load_mod_quick(fn);
			if(!al_default_duh)
			{
				al_default_duh = dumb_load_s3m_quick(fn);
				if(!al_default_duh)
				{
					return NULL;
				}
			}
		}
	}
	al_default_duh_player = al_start_duh(al_default_duh, 2, pattern, 1.0, 4096, 44100);
	return al_default_duh_player;
}

bool al_set_module_pattern(int pattern)
{
	bool paused = !(al_default_duh_player->flags & ADP_PLAYING);

	if(paused)
	{
		al_resume_module();
	}
	al_stop_duh(al_default_duh_player);
	al_default_duh_player = al_start_duh(al_default_duh, 2, pattern, 1.0, 4096, 44100);
	if(al_default_duh_player)
	{
		if(paused)
		{
			al_pause_module();
		}
		return true;
	}
	printf("failed to set pattern!\n");
	return false;
}

void al_stop_module(void)
{
	al_stop_duh(al_default_duh_player);
	unload_duh(al_default_duh);
}

void al_pause_module(void)
{
	al_pause_duh(al_default_duh_player);
}

void al_resume_module(void)
{
	al_resume_duh(al_default_duh_player);
}
