// Pulse Audio bits for QtSoundmodem


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>
#include <pulse/simple.h>
#include <pulse/error.h>

#define UNUSED(x) (void)(x)

extern char CaptureNames[16][256];
extern char PlaybackNames[16][256];

extern int PlaybackCount;
extern int CaptureCount;

#include <dlfcn.h>

void *handle = NULL;
void *shandle = NULL;

pa_mainloop * (*ppa_mainloop_new)(void);
pa_mainloop_api * (*ppa_mainloop_get_api)(pa_mainloop * m);
pa_context * (*ppa_context_new)(pa_mainloop_api *mainloop, const char *name);
int (*ppa_context_connect)(pa_context * c, const char * server, pa_context_flags_t flags, const pa_spawn_api * 	api);
void (*ppa_context_set_state_callback)(pa_context * c, pa_context_notify_cb_t cb, void * userdata);
int (*ppa_mainloop_iterate)(pa_mainloop * m, int block, int * retval);
void (*ppa_mainloop_free)(pa_mainloop * m);
void (*ppa_context_disconnect)(pa_context * c);
void (*ppa_context_unref)(pa_context * c);
const char * (*ppa_strerror)(int error);
pa_context_state_t(*ppa_context_get_state)(const pa_context *c);
pa_operation * (*ppa_context_get_sink_info_list)(pa_context * c, pa_sink_info_cb_t cb, void * userdata);
pa_operation * (*ppa_context_get_source_info_list)(pa_context * c, pa_source_info_cb_t cb, void * userdata);	
void (*ppa_operation_unref)(pa_operation * o);
pa_operation_state_t(*ppa_operation_get_state)(const pa_operation * o);


pa_simple * (*ppa_simple_new)(const char * 	server,
	const char * 	name,
	pa_stream_direction_t 	dir,
	const char * 	dev,
	const char * 	stream_name,
	const pa_sample_spec * 	ss,
	const pa_channel_map * 	map,
	const pa_buffer_attr * 	attr,
	int * 	error) = NULL;

pa_usec_t(*ppa_simple_get_latency)(pa_simple * s, int * error);
int(*ppa_simple_read)(pa_simple * s, void * data, size_t bytes, int * error);
int(*ppa_simple_write)(pa_simple * s, void * data, size_t bytes, int * error);

int(*ppa_simple_flush)(pa_simple * s, int * error);
void(*ppa_simple_free)(pa_simple * s);
int(*ppa_simple_drain)(pa_simple * s, int * error);

void * getModule(void *handle, char * sym)
{
	return dlsym(handle, sym);
}

void * initPulse()
{
	// Load the pulse libraries

	if (handle)
		return handle;			// already done

	handle = dlopen("libpulse.so", RTLD_LAZY);

	if (!handle)
	{
		fputs(dlerror(), stderr);
		return NULL;
	}

	if ((ppa_mainloop_new = getModule(handle, "pa_mainloop_new")) == NULL) return NULL;
	if ((ppa_mainloop_get_api = getModule(handle, "pa_mainloop_get_api")) == NULL) return NULL;
	if ((ppa_context_new = getModule(handle, "pa_context_new")) == NULL) return NULL;
	if ((ppa_context_connect = getModule(handle, "pa_context_connect")) == NULL) return NULL;
	if ((ppa_context_set_state_callback = getModule(handle, "pa_context_set_state_callback")) == NULL) return NULL;
	if ((ppa_mainloop_iterate = getModule(handle, "pa_mainloop_iterate")) == NULL) return NULL;
	if ((ppa_mainloop_free = getModule(handle, "pa_mainloop_free")) == NULL) return NULL;
	if ((ppa_context_disconnect = getModule(handle, "pa_context_disconnect")) == NULL) return NULL;
	if ((ppa_context_unref = getModule(handle, "pa_context_unref")) == NULL) return NULL;
	if ((ppa_strerror = getModule(handle, "pa_strerror")) == NULL) return NULL;
	if ((ppa_context_get_state = getModule(handle, "pa_context_get_state")) == NULL) return NULL;
	if ((ppa_context_get_sink_info_list = getModule(handle, "pa_context_get_sink_info_list")) == NULL) return NULL;
	if ((ppa_context_get_source_info_list = getModule(handle, "pa_context_get_source_info_list")) == NULL) return NULL;
	if ((ppa_operation_unref = getModule(handle, "pa_operation_unref")) == NULL) return NULL;
	if ((ppa_operation_get_state = getModule(handle, "pa_operation_get_state")) == NULL) return NULL;

	shandle = dlopen("libpulse-simple.so", RTLD_LAZY);

	if (!shandle)
	{
		fputs(dlerror(), stderr);
		return NULL;
	}

	if ((ppa_simple_new = getModule(shandle, "pa_simple_new")) == NULL) return NULL;
	if ((ppa_simple_get_latency = getModule(shandle, "pa_simple_get_latency")) == NULL) return NULL;
	if ((ppa_simple_read = dlsym(shandle, "pa_simple_read")) == NULL) return NULL;
	if ((ppa_simple_write = dlsym(shandle, "pa_simple_write")) == NULL) return NULL;
	if ((ppa_simple_flush = dlsym(shandle, "pa_simple_flush")) == NULL) return NULL;
	if ((ppa_simple_drain = dlsym(shandle, "pa_simple_drain")) == NULL) return NULL;
	if ((ppa_simple_free = dlsym(shandle, "pa_simple_free")) == NULL) return NULL;

	return shandle;
}





// Field list is here: http://0pointer.de/lennart/projects/pulseaudio/doxygen/structpa__sink__info.html
typedef struct pa_devicelist {
	uint8_t initialized;
	char name[512];
	uint32_t index;
	char description[256];
} pa_devicelist_t;

void pa_state_cb(pa_context *c, void *userdata);
void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata);
void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata);
int pa_get_devicelist(pa_devicelist_t *input, pa_devicelist_t *output);

// This callback gets called when our context changes state.  We really only
// care about when it's ready or if it has failed
void pa_state_cb(pa_context *c, void *userdata) {
	pa_context_state_t state;
	int *pa_ready = userdata;

	state = ppa_context_get_state(c);
	switch (state) {
		// There are just here for reference
	case PA_CONTEXT_UNCONNECTED:
	case PA_CONTEXT_CONNECTING:
	case PA_CONTEXT_AUTHORIZING:
	case PA_CONTEXT_SETTING_NAME:
	default:
		break;
	case PA_CONTEXT_FAILED:
	case PA_CONTEXT_TERMINATED:
		*pa_ready = 2;
		break;
	case PA_CONTEXT_READY:
		*pa_ready = 1;
		break;
	}
}

// pa_mainloop will call this function when it's ready to tell us about a sink.
// Since we're not threading, there's no need for mutexes on the devicelist
// structure
void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata)
{
	UNUSED(c);

	pa_devicelist_t *pa_devicelist = userdata;
	int ctr = 0;

	// If eol is set to a positive number, you're at the end of the list
	if (eol > 0) {
		return;
	}

	// We know we've allocated 16 slots to hold devices.  Loop through our
	// structure and find the first one that's "uninitialized."  Copy the
	// contents into it and we're done.  If we receive more than 16 devices,
	// they're going to get dropped.  You could make this dynamically allocate
	// space for the device list, but this is a simple example.
	for (ctr = 0; ctr < 16; ctr++) {
		if (!pa_devicelist[ctr].initialized) {
			strncpy(pa_devicelist[ctr].name, l->name, 511);
			strncpy(pa_devicelist[ctr].description, l->description, 255);
			pa_devicelist[ctr].index = l->index;
			pa_devicelist[ctr].initialized = 1;
			break;
		}
	}
}

// See above.  This callback is pretty much identical to the previous
void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) 
{
	UNUSED(c);

	pa_devicelist_t *pa_devicelist = userdata;
	int ctr = 0;

	if (eol > 0) {
		return;
	}

	for (ctr = 0; ctr < 16; ctr++) {
		if (!pa_devicelist[ctr].initialized) {
			strncpy(pa_devicelist[ctr].name, l->name, 511);
			strncpy(pa_devicelist[ctr].description, l->description, 255);
			pa_devicelist[ctr].index = l->index;
			pa_devicelist[ctr].initialized = 1;
			break;
		}
	}
}

int pa_get_devicelist(pa_devicelist_t *input, pa_devicelist_t *output) {
	// Define our pulse audio loop and connection variables
	pa_mainloop *pa_ml;
	pa_mainloop_api *pa_mlapi;
	pa_operation *pa_op;
	pa_context *pa_ctx;


	// We'll need these state variables to keep track of our requests
	int state = 0;
	int pa_ready = 0;

	// Initialize our device lists
	memset(input, 0, sizeof(pa_devicelist_t) * 16);
	memset(output, 0, sizeof(pa_devicelist_t) * 16);

	// Create a mainloop API and connection to the default server
	pa_ml = ppa_mainloop_new();
	pa_mlapi = ppa_mainloop_get_api(pa_ml);
	pa_ctx = ppa_context_new(pa_mlapi, "test");

	// This function connects to the pulse server
	ppa_context_connect(pa_ctx, NULL, 0, NULL);


	// This function defines a callback so the server will tell us it's state.
	// Our callback will wait for the state to be ready.  The callback will
	// modify the variable to 1 so we know when we have a connection and it's
	// ready.
	// If there's an error, the callback will set pa_ready to 2
	ppa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready);

	// Now we'll enter into an infinite loop until we get the data we receive
	// or if there's an error
	for (;;) {
		// We can't do anything until PA is ready, so just iterate the mainloop
		// and continue
		if (pa_ready == 0) {
			ppa_mainloop_iterate(pa_ml, 1, NULL);
			continue;
		}
		// We couldn't get a connection to the server, so exit out
		if (pa_ready == 2) {
			ppa_context_disconnect(pa_ctx);
			ppa_context_unref(pa_ctx);
			ppa_mainloop_free(pa_ml);
			return -1;
		}
		// At this point, we're connected to the server and ready to make
		// requests
		switch (state) {
			// State 0: we haven't done anything yet
		case 0:
			// This sends an operation to the server.  pa_sinklist_info is
			// our callback function and a pointer to our devicelist will
			// be passed to the callback The operation ID is stored in the
			// pa_op variable
			pa_op = ppa_context_get_sink_info_list(pa_ctx,
				pa_sinklist_cb,
				output
			);

			// Update state for next iteration through the loop
			state++;
			break;
		case 1:
			// Now we wait for our operation to complete.  When it's
			// complete our pa_output_devicelist is filled out, and we move
			// along to the next state
			if (ppa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
				ppa_operation_unref(pa_op);

				// Now we perform another operation to get the source
				// (input device) list just like before.  This time we pass
				// a pointer to our input structure
				pa_op = ppa_context_get_source_info_list(pa_ctx,
					pa_sourcelist_cb,
					input
				);
				// Update the state so we know what to do next
				state++;
			}
			break;
		case 2:
			if (ppa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
				// Now we're done, clean up and disconnect and return
				ppa_operation_unref(pa_op);
				ppa_context_disconnect(pa_ctx);
				ppa_context_unref(pa_ctx);
				ppa_mainloop_free(pa_ml);
				return 0;
			}
			break;
		default:
			// We should never see this state
			fprintf(stderr, "in state %d\n", state);
			return -1;
		}
		// Iterate the main loop and go again.  The second argument is whether
		// or not the iteration should block until something is ready to be
		// done.  Set it to zero for non-blocking.
		ppa_mainloop_iterate(pa_ml, 1, NULL);
	}
}

int listpulse()
{
	int ctr;
	
	PlaybackCount = 0;
	CaptureCount = 0;


	// This is where we'll store the input device list
	pa_devicelist_t pa_input_devicelist[16];

	// This is where we'll store the output device list
	pa_devicelist_t pa_output_devicelist[16];

	if (pa_get_devicelist(pa_input_devicelist, pa_output_devicelist) < 0) {
		fprintf(stderr, "failed to get device list\n");
		return 1;
	}

	printf("Pulse Playback Devices\n\n");

	for (ctr = 0; ctr < 16; ctr++)
	{
		if (!pa_output_devicelist[ctr].initialized)
			break;

		printf("Name: %s\n", pa_output_devicelist[ctr].name);
		strcpy(&PlaybackNames[PlaybackCount++][0], pa_output_devicelist[ctr].name);

	}

	printf("Pulse Capture Devices\n\n");

	for (ctr = 0; ctr < 16; ctr++)
	{
		if (!pa_input_devicelist[ctr].initialized)
			break;

		printf("Name: %s\n", pa_input_devicelist[ctr].name);
		strcpy(&CaptureNames[CaptureCount++][0], pa_input_devicelist[ctr].name);
	}
	return 0;
}


pa_simple * OpenPulsePlayback(char * Server)
{
	pa_simple * s;
	pa_sample_spec ss;
	ss.format = PA_SAMPLE_S16NE;
	ss.channels = 2;
	ss.rate = 12000;
	int error;


	s = (*ppa_simple_new)(NULL,               // Use the default server.
		"QtSM",           // Our application's name.
		PA_STREAM_PLAYBACK,
		Server,

		"Playback",         // Description of our stream.
		&ss,                // Our sample format.
		NULL,               // Use default channel map
		NULL,               // Use default buffering attributes.
		&error
	);

	if (s == 0)
		printf("Playback pa_simple_new() failed: %s\n", ppa_strerror(error));
	else
		printf("Playback Handle %x\n", (unsigned int)s);

	return s;
}

pa_simple * OpenPulseCapture(char * Server)
{
	pa_simple * s;
	pa_sample_spec ss;
	ss.format = PA_SAMPLE_S16NE;
	ss.channels = 2;
	ss.rate = 12000;
	int error;

	pa_buffer_attr attr;

	attr.maxlength = -1;
	attr.tlength = -1;
	attr.prebuf = -1;
	attr.minreq = -1;
	attr.fragsize = 512;


	s = (*ppa_simple_new)(NULL,               // Use the default server.
		"QtSM",           // Our application's name.
		PA_STREAM_RECORD,
		Server,
		"Capture",            // Description of our stream.
		&ss,                // Our sample format.
		NULL,               // Use default channel map
		&attr, 
		&error
	);

	if (s == 0)
		printf("Capture pa_simple_new() failed: %s\n", ppa_strerror(error));
	else
		printf("Capture Handle %x\n", (unsigned int)s);

	return s;
}

pa_simple * spc = 0;			// Capure Handle
pa_simple * spp = 0;			// Playback Handle

int pulse_audio_open(char * CaptureDevice, char * PlaybackDevice)
{
	pa_usec_t latency;
	int error;
		
	spc = OpenPulseCapture(CaptureDevice);
	spp = OpenPulsePlayback(PlaybackDevice);

	if (spc && spp)
	{
		if ((latency = ppa_simple_get_latency(spc, &error)) == (pa_usec_t)-1) {
			printf("cap simple_get_latency() failed: %s\n", ppa_strerror(error));
		}
		else
			printf("cap %0.0f usec    \n", (float)latency);

		if ((latency = ppa_simple_get_latency(spp, &error)) == (pa_usec_t)-1) {
			printf("play simple_get_latency() failed: %s\n", ppa_strerror(error));
		}
		else
			printf("play %0.0f usec    \n", (float)latency);

		return 1;
	}
	else
		return 0;

}

void pulse_audio_close()
{
	int error;

	ppa_simple_flush(spc, &error);
	ppa_simple_free(spc);
	spc = 0;

	ppa_simple_drain(spp, &error);
	ppa_simple_free(spp);
	spp = 0;
}


int pulse_read(short * samples, int nSamples)
{
	int error;
	int nBytes = nSamples * 4;

	if (spc == 0)
		return 0;

	if (ppa_simple_read(spc, samples, nBytes, &error) < 0)
	{
		printf("Pulse pa_simple_read() failed: %s\n", ppa_strerror(error));
		return 0;
	}

	return nSamples;
}


int pulse_write(short * ptr, int len)
{
	int k;
	int error;

	if (spp == 0)
		return 0;

	k = ppa_simple_write(spp, ptr, len * 4, &error);

	if (k < 0)
	{
		printf("Pulse pa_simple_write() failed: %s\n", ppa_strerror(error));
		return -1;
	}

	return 0;
}

void pulse_flush()
{
	int error;

	if (spp == 0)
		return;

	if (ppa_simple_flush(spp, &error) < 0)
		printf("Pulse pa_simple_flush() failed: %s\n", ppa_strerror(error));
}
