
/* FUNCTION list 	---	---	---	---	---	---	---

*/

//~ RaDIaT1oN: fix preprocessor tags, file open

/* BASE headers	---	---	---	---	---	---	--- */
//#include "agi.h"
#include "../agi.h"

/* LIBRARY headers	---	---	---	---	---	---	--- */
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>

#define WRITE_TO_DISK 0

#if WRITE_TO_DISK

#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#endif

/* OTHER headers	---	---	---	---	---	---	--- */
#include "../list.h"

#include "sound_gen.h"
#include "pcm_out.h"
#include "pcm_out_sdl.h"

/* PROTOTYPES	---	---	---	---	---	---	--- */

struct sdl_channel_struct
{
	int handle;
	int (*callback)(void *userdata, u8 *stream, int len);
	void *userdata;
	
	int avail;
};
typedef struct sdl_channel_struct SDL_CHAN;

static int pcm_out_sdl_init(int freq, int format);
static void pcm_out_sdl_shutdown(void);
static void pcm_out_sdl_avail(void);
static int pcm_out_sdl_open( int (*callback)(void *userdata, Uint8 *stream, int len), void *userdata);
static void pcm_out_sdl_close(int handle);
static void pcm_out_sdl_state_set(int );
static int pcm_out_sdl_state_get(void);
static void pcm_out_sdl_lock(void);
static void pcm_out_sdl_unlock(void);
static void sdl_callback(void *userdata, u8 *stream, int len);

/* VARIABLES	---	---	---	---	---	---	--- */

static u8 handles_used = 0;

static LIST *chan_list = 0;


#if WRITE_TO_DISK
struct data_struct
{
	u8 *data;
	int len;
};
typedef struct data_struct DATA;
	
static LIST *list_data = 0;
static int file_handle = 0;
#endif


/* CODE	---	---	---	---	---	---	---	--- */


// initialises the driver pointers
void pcm_out_sdl_drv_init(void *void_ptr)
{
	PCM_OUT_DRIVER *drv;
	
	drv = (PCM_OUT_DRIVER *) void_ptr;
	assert(drv);
	
	drv->ptr_init = pcm_out_sdl_init;
	drv->ptr_shutdown = pcm_out_sdl_shutdown;
	drv->ptr_avail = pcm_out_sdl_avail;
	drv->ptr_open = pcm_out_sdl_open;
	drv->ptr_close = pcm_out_sdl_close;
	drv->ptr_state_set = pcm_out_sdl_state_set;
	drv->ptr_state_get = pcm_out_sdl_state_get;
	drv->ptr_lock = pcm_out_sdl_lock;
	drv->ptr_unlock = pcm_out_sdl_unlock;
	drv->type = PCM_OUT_SDL;
}


static int pcm_out_sdl_init(int freq, int format)
{
	SDL_AudioSpec wanted;
	
	(void) freq;
	(void) format;

	printf("pcm_out_sdl_init(): Initialising SDL audio subsystem... ");
	#if 0
	if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0)
	{
		printf("\npcm_out_sdl_init(): unable to initialise SDL audio subsystem.\n");
		printf("%s\n", SDL_GetError());
		return -1;
	}
	#endif

	
	// sets the freq/type
	wanted.freq = 44100;
	wanted.format = AUDIO_S16LSB;
	wanted.samples = 256;
	wanted.channels = 1;
	wanted.callback = sdl_callback;
	wanted.userdata = 0;
	
	// opens up calback
	if (SDL_OpenAudio(&wanted, 0) != 0)
	{
		printf("\npcm_out_sdl_init(): unable to open audio.\n");
		return -1;
	}
	
	// pauses callback
	pcm_out_sdl_state_set(0);
	
#if WRITE_TO_DISK
	list_data = list_new(sizeof(DATA));
#endif
	
	printf("done.\n");
	
	return 0;
}

static void pcm_out_sdl_shutdown(void)
{

	// set state to stop
	pcm_out_sdl_state_set(0);	
	
	// close all remaining channels
	if (chan_list)
	{
		list_free(chan_list);
		chan_list=0;
	}
	
	// shutdown audio subsystem
	//SDL_CloseAudio();
	
	//SDL_QuitSubSystem(SDL_INIT_AUDIO);
	
#if WRITE_TO_DISK
	if (list_data)
	{
		DATA *cur;
		int amount;
		
		cur = list_element_head(list_data);
#ifdef _WIN32
		file_handle = open("sound.raw", O_BINARY|O_RDWR|O_CREAT, S_IREAD|S_IWRITE);
#else
		file_handle = open("sound.raw", O_RDWR|O_CREAT, S_IREAD|S_IWRITE);
#endif
		printf("handle = %d\n", file_handle);
		while (cur)
		{
			printf("writing %d bytes...\n", cur->len);
			amount = write(file_handle, cur->data, cur->len);
			if ((amount != cur->len) || (amount <= -1))
			{
				printf("error! %d\n", amount);
				print_err_code();
			}
			cur = node_next(cur);
		}
		close(file_handle);
	}
#endif
}

// get available options
static void pcm_out_sdl_avail(void)
{
	// return 16bit, 8bit, 22khz, 41khz
}

// uses a char to store 8 bits.  each bit represents a used handle
// easier to determine which handles are free.

static int handle_new(void)
{
	int handle = 1;
	u8 mask = 0x80;
	
	while ( (mask) && (handles_used&mask) )
	{
		mask >>= 1;
		handle++;
	}
	
	// if mask == 0 then handles_uses is untouched.
	handles_used |= mask;
	
	return (mask) ? handle : 0;
}

static void handle_free(int handle)
{
	u8 mask = 0x80;
	
	assert(handle > 0);
	assert(handle <= 8);
	
	mask >>= handle - 1;
	mask ^= 0xFF;
	
	handles_used &= mask;
}

// returns 0 if failed
// else returns handle
static int pcm_out_sdl_open( int (*callback)(void *userdata, Uint8 *stream, int len), void *userdata)
{
	SDL_CHAN chan;
	SDL_CHAN *chan_new;

	pcm_out_sdl_lock();
	
	if (chan_list == 0)
		chan_list = list_new(sizeof(SDL_CHAN));

	chan.handle = handle_new();
	if (chan.handle == 0)
		return 0;

	chan.callback = callback;
	chan.userdata = userdata;
	chan.avail = 1;
	
	chan_new = list_add(chan_list);
	memcpy(chan_new, &chan, sizeof(SDL_CHAN) );
	
	pcm_out_sdl_unlock();
	return chan.handle;
}

static void pcm_out_sdl_close(int handle)
{
	SDL_CHAN *ch;
	(void) handle;
	
	if (chan_list)
	{
		// find the one with a handle
		ch = list_element_head(chan_list);
		while ((ch) && (ch->handle != handle))
			ch=node_next(ch);
		
		// kill it
		if (ch)
		{
			handle_free(ch->handle);
			list_remove(chan_list, ch);
		}
	}
}

// 1 = playing
// 0 = stopped
static void pcm_out_sdl_state_set(int snd_state)
{
	SDL_PauseAudio(snd_state?0:1);
}

static int pcm_out_sdl_state_get(void)
{
	return (SDL_GetAudioStatus() == SDL_AUDIO_PLAYING);
}

// lock audio thread so data can be changed
static void pcm_out_sdl_lock(void)
{
	SDL_LockAudio();
}

// unlock thread
static void pcm_out_sdl_unlock(void)
{
	SDL_UnlockAudio();
}



static void sdl_callback(void *userdata, u8 *stream, int len)
{
	s16* chan_data = alloca(len);
	int stream_len, stream_count;
	s16 *s_ptr, *c_ptr;
	
	SDL_CHAN *ch;
	int num_chan;
	int output = 0;
	
#if WRITE_TO_DISK
	DATA *new_data;
#endif

	(void) userdata;
	
	assert(stream);

	SDL_memset(stream,0,len);
	
	if (chan_list != NULL)
	{
		num_chan = list_length(chan_list);
		ch = list_element_head(chan_list);
		
		stream_len = len / (int)sizeof(s16);
		while (ch != NULL)
		{
			// get channel data(chan.userdata)
			if (ch->avail)
			{
				if (ch->callback( ch->userdata, (u8 *)chan_data, len) == 0)
				{
					// divide by number of channels then add to stream
					stream_count = stream_len;
					s_ptr = (s16*)stream;
					c_ptr = (s16*)chan_data;
					output = 1;
					
					while(stream_count--)
						*(s_ptr++) += *(c_ptr++) / num_chan ;
				}
				else
				{
					ch->avail = 0;
				}
			}
			
			ch = node_next(ch);
		}
	}
	
#if WRITE_TO_DISK
	new_data = list_add(list_data);
	new_data->data = (u8 *)a_malloc(len);
	memcpy(new_data->data, stream, len);
	new_data->len = len;
#endif
	
	if (!output)
	{
		sndgen_kill_thread();
		// call shutdown routin
	}
	
}

