#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pulse/pulseaudio.h>

#define ROUND_POS(x) ((int)((x) + 0.5f))

//TODO pa_operation_unref(o);

/* context vars */
static int default_sink_index; // should always point to default sink
static int command_sink_index; // commands will modify this sink


/* output callbacks */
static void sink_info_volume_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
	pa_volume_t vol = pa_cvolume_avg(&i->volume);
	if (eol) return;

	printf("v%d,%d,%.02f,%d\n", i->index, ROUND_POS(vol*100.0f/PA_VOLUME_NORM), pa_sw_volume_to_dB(vol), i->mute);
	fflush(stdout);
}

static void sink_info_sink_desc_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
	if (eol) return;
	printf("s%d,%s,%s\n", i->index, i->name, i->description);
	fflush(stdout);
}

static void sink_info_default_index_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
	if (eol) return;
	default_sink_index = i->index;
	printf("f%d\n", default_sink_index);
	fflush(stdout);
}

static void server_info_default_sink_callback(pa_context *c, const pa_server_info *i, void *userdata) {
	pa_context_get_sink_info_by_name(c, i->default_sink_name, sink_info_default_index_callback, userdata);
}

/* subscription callback is called whenever a listened-to event occurs */
static void subscription_callback(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
	switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
		case PA_SUBSCRIPTION_EVENT_SINK:
			// check if sink was removed
			if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
				printf("x%d\n", idx);
				fflush(stdout);
				return;
			}

			// check if sink was added
			if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)
				pa_context_get_sink_info_by_index(c, idx, sink_info_sink_desc_callback, userdata);

			// output the volume
			pa_context_get_sink_info_by_index(c, idx, sink_info_volume_callback, userdata);
			break;
		case PA_SUBSCRIPTION_EVENT_SERVER:
			pa_context_get_server_info(c, server_info_default_sink_callback, userdata);
			break;
	}
}

/* context state will really only change at startup or exit etc. */
static void context_state_callback(pa_context *c, void *userdata) {
	if (pa_context_get_state(c) == PA_CONTEXT_READY) {
		pa_context_set_subscribe_callback(c, subscription_callback, userdata);

		// output initial default sink
		pa_context_get_server_info(c, server_info_default_sink_callback, userdata);

		// set command sink equal to the default sink on startup
		command_sink_index = default_sink_index;

		// output initial sink descriptions for each sink
		pa_context_get_sink_info_list(c, sink_info_sink_desc_callback, userdata);

		// output initial volume levels for each sink
		pa_context_get_sink_info_list(c, sink_info_volume_callback, userdata);

		// subscribe to sink & server events to listen to changes
		// other events can be subscribed to if desired
		pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SERVER, NULL, NULL);
	}
}

/* stdin commands */
static void sink_info_set_volume_callback(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {
	char *cmd = (char *)userdata;
	pa_cvolume *cvol = (pa_cvolume *)&i->volume; // disregard const qualifier, avoid warnings :)
	pa_volume_t vol = (pa_volume_t)(atof(&cmd[1]) / 100 * PA_VOLUME_NORM); // sneaky substring, super safe
	if (eol) return;

	switch(cmd[1]) {
		case '\n': pa_context_get_sink_info_by_index(c, i->index, sink_info_volume_callback, userdata); return;
		case '+': cvol = pa_cvolume_inc(cvol, vol); break;
		case '-': cvol = pa_cvolume_dec(cvol, vol * -1); break; // should be no underflow w/ uint32 since 0dB is only 2^16
		default : cvol = pa_cvolume_set(cvol, i->volume.channels, vol); break;
	}

	pa_context_set_sink_volume_by_index(c, i->index, cvol, NULL, NULL);
}

/* handle stdin commands */
static void handle_command(pa_context *c, char *cmd) {
	// send the command to each callback using userdata
	switch (cmd[0]) {
		case 'v':
			pa_context_get_sink_info_by_index(c, command_sink_index, sink_info_set_volume_callback, (void *)cmd);
			break;
	}
}

/* main */
int main() {
	// initialize threaded pulse mainloop
	pa_threaded_mainloop *mainloop = pa_threaded_mainloop_new();
	pa_mainloop_api *mainloop_api = pa_threaded_mainloop_get_api(mainloop);
	pa_context *context = pa_context_new(mainloop_api, "pavolctld");
	char stdin_buffer[256];

	pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);

	pa_context_set_state_callback(context, context_state_callback, NULL);

	// main loop
	pa_threaded_mainloop_start(mainloop);

	// listen for commands
	while (fgets(stdin_buffer, sizeof(stdin_buffer), stdin) != NULL)
		handle_command(context, stdin_buffer);

	// clean up
	pa_context_disconnect(context);
	pa_context_unref(context);
	pa_threaded_mainloop_free(mainloop);

	return 0;
}