#include <allegro.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "heap.h"
#include "noise.h"

#undef OSCILLOSCOPE
#undef STDOUT

static int next_sample(struct player_t* player, sample_t** sample);
static int next_note(struct player_t* player, struct channel_t* channel);

/* Return a new music player */
struct player_t* create_player(int length, int bits, int stereo, int rate,
		int vol, int pan, int max_voices)
{
	AUDIOSTREAM* stream = play_audio_stream(length, bits, stereo, rate, vol,
			pan);
	struct player_t* player = malloc(sizeof(struct player_t));
	if (stream == NULL || player == NULL) return NULL;

	player->channels = create_basic_heap(struct channel_t);
	if (player->channels == NULL) {
		stop_audio_stream(stream);
		free(player);
		return NULL;
	}

	player->voices = create_fixed_heap(struct voice_t, max_voices);
	if (player->voices == NULL) {
		stop_audio_stream(stream);
		destroy_heap(player->channels);
		free(player);
		return NULL;
	}

	player->stream = stream;
	player->max_voices = max_voices;
	player->buf_length = length;
	player->bits = bits;
	player->stereo = stereo;
	player->rate = rate;
	player->vol = vol;
	player->pan = pan;
	player->max_sample = (1 << bits) - 1;
	player->exp_buf = malloc(sizeof(void*) * max_voices);

	return player;
}

/* Destroy a music player */
void destroy_player(struct player_t* player)
{
	destroy_heap(player->channels);
	destroy_heap(player->voices);
	stop_audio_stream(player->stream);
	free(player);
}

/* Add a channel to a player */
void add_channel(struct player_t* player, struct note_t* notes, int num_notes,
		int repeat, double vol, double pan,
		struct instrument_t* instrument, int tempo, double beat_note,
		int transpose)
{
	struct channel_t* channel = new_from_heap(player->channels);
	channel->notes = notes;
	channel->num_notes = num_notes;
	channel->note_idx = -1;
	channel->remain = 0;
	channel->repeat = repeat;
	channel->vol = vol;
	channel->pan = pan;
	channel->instrument = instrument;
	channel->tempo = tempo;
	channel->beat_note = beat_note;
	channel->transpose = transpose;
	next_note(player, channel);

	if (player->channels->count > player->max_voices)
		player->exp_buf = realloc(player->exp_buf, sizeof(void*) *
				player->channels->count);
}

/* Add a channel to a player */
void add_channel_struct(struct player_t* player, struct channel_t* channel)
{
	add_channel(player, channel->notes, channel->num_notes,
			channel->repeat, channel->vol, channel->pan,
			channel->instrument, channel->tempo,
			channel->beat_note, channel->transpose);
}

/* Fill a player's audio buffer */
int fill_buffer(struct player_t* player)
{
	sample_t* p = get_audio_stream_buffer(player->stream);
	sample_t* p_end;
	int retval = 1;
	if (p == NULL) return 1;

	/* Fill the buffer, sample by sample */
	p_end = p + player->buf_length * (1 + player->stereo);
	while (p < p_end) {
		if (!next_sample(player, &p)) {
			/* No more music! */
			bzero(p, p_end - p);
			retval = 0;
			break;
		}
	}
	free_audio_stream_buffer(player->stream);

	return retval;
}

/* Grab the next sample from the channels */
static int next_sample(struct player_t* player, sample_t** sample)
{
	static int j, old;
	int i;
	double amp;
	double sample_l = 0;
	double sample_r = 0;
#ifdef STDOUT
	int val;
#endif

	if (player->voices->count > 0) {
		/* Collect samples from voices */
		struct voice_t** voice_p =
				(struct voice_t**)player->voices->data;
		struct voice_t** voice_end = voice_p + player->voices->count;
		for (; voice_p < voice_end; voice_p++) {
			struct voice_t* voice = *voice_p;
			amp = voice->instrument->func(voice) * voice->vol;
			sample_l += amp * voice->pan;
			sample_r += amp * (1 - voice->pan);

			voice->step++;
			if (voice->step == voice->dur)
				player->exp_buf[player->num_exp++] = voice;
		}

		/* Store samples in buffer */
		*((*sample)++) = player->max_sample * sample_l /
				player->max_voices;
		*((*sample)++) = player->max_sample * sample_r /
				player->max_voices;
#ifdef STDOUT
		val = (sample_l + sample_r) * 65535 / 2;
		printf("%c%c", val & 255, val >> 8);
#endif
#ifdef OSCILLOSCOPE
		if (j == 0) {printf("\033[2J\033[H"); fflush(stdout);}
		if (j < 24 * 12 && j % 12 == 0) {
			int x = (sample_l + sample_r) * 75 / player->max_voices;

			printf("%.3f ", (sample_l + sample_r) / (2 *
					player->max_voices));
			if (old < x) {
				for (i = 0; i < old; i++)
					printf(":");
				printf("`");
				for (i = old + 1; i < x; i++)
					printf("-");
				printf(".");
			} else if (old == x) {
				for (i = 0; i < x; i++)
					printf(":");
				printf("|");
			} else {
				for (i = 0; i < x; i++)
					printf(":");
				printf(".");
				for (i = x + 1; i < old; i++)
					printf("-");
				printf("'");
			}
			printf("\n");
			fflush(stdout);
			old = x;
		}
#endif
		j++;
		/* Kill expired voices */
		for (i = 0; i < player->num_exp; i++)
			delete_from_heap(player->voices, player->exp_buf[i]);
		player->num_exp = 0;
	} else {
		*((*sample)++) = 0;
		*((*sample)++) = 0;
	}

	if (player->channels->count > 0) {
		/* Progress through notes */
		struct channel_t** channel_p =
				(struct channel_t**)player->channels->data;
		struct channel_t** channel_end =
				channel_p + player->channels->count;
		for (; channel_p < channel_end; channel_p++) {
			struct channel_t* channel = *channel_p;
			channel->remain--;
			if (channel->remain != 0) continue;
			if (!next_note(player, channel))
				player->exp_buf[player->num_exp++] = channel;
			j = 0;
		}

		/* Kill expired channels */
		for (i = 0; i < player->num_exp; i++) {
			delete_from_heap(player->channels, player->exp_buf[i]);
		}
		player->num_exp = 0;
	}

	/* Is there anything left to play? */
	return player->voices->count > 0 || player->channels->count > 0;
}

/* Go to the next note in a song */
static int next_note(struct player_t* player, struct channel_t* channel)
{
	struct voice_t* voice;
	int val;
	int dur;

	channel->note_idx++;
	if (channel->note_idx == channel->num_notes) {
		/* All done with this channel? */
		if (channel->repeat == 0) return 0;

		/* Repeat channel */
		channel->note_idx = 0;
		if (channel->repeat > 0) channel->repeat--;
	}

	/* Start next note or rest */
	val = channel->notes[channel->note_idx].val;
	dur = player->rate * 60 / channel->tempo / channel->beat_note *
			channel->notes[channel->note_idx].dur;
	if (val > INT_MIN) {
		/* Start next note */
		voice = new_from_heap(player->voices);
		if (voice != NULL) {
			val += channel->transpose;
			voice->freq = 220 * pow(2, val / 12.0);
			voice->period = player->rate / voice->freq;
			voice->step = 0;
			voice->dur = dur * channel->instrument->sustain;
			voice->vol = channel->vol;
			voice->pan = channel->pan;
			voice->instrument = channel->instrument;
		}
	}
	channel->remain = dur;

	return 1;
}

