/*-*- linux-c -*-*/

/*
 * ALSA <-> PulseAudio plugins
 *
 * Copyright (c) 2006 by Pierre Ossman <ossman@cendio.se>
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/poll.h>

#include "pulse.h"

int pulse_check_connection(snd_pulse_t * p)
{
	pa_context_state_t state;

	assert(p);

	if (!p->context || !p->mainloop)
		return -EBADFD;

	state = pa_context_get_state(p->context);

	if (!PA_CONTEXT_IS_GOOD(state))
		return -EIO;

	return 0;
}

void pulse_context_success_cb(pa_context * c, int success, void *userdata)
{
	snd_pulse_t *p = userdata;

	assert(c);
	assert(p);

	pa_threaded_mainloop_signal(p->mainloop, 0);
}

int pulse_wait_operation(snd_pulse_t * p, pa_operation * o)
{
	assert(p);
	assert(o);

	for (;;) {
		int err;

		err = pulse_check_connection(p);
		if (err < 0)
			return err;

		if (pa_operation_get_state(o) != PA_OPERATION_RUNNING)
			break;

		pa_threaded_mainloop_wait(p->mainloop);
	}

	return 0;
}

static void context_state_cb(pa_context * c, void *userdata)
{
	pa_context_state_t state;
	snd_pulse_t *p = userdata;
	assert(c);

	state = pa_context_get_state(c);

	/* When we get disconnected, tell the process */
	if (!PA_CONTEXT_IS_GOOD(state))
		pulse_poll_activate(p);

	switch (state) {
	case PA_CONTEXT_READY:
	case PA_CONTEXT_TERMINATED:
	case PA_CONTEXT_FAILED:
		pa_threaded_mainloop_signal(p->mainloop, 0);
		break;

	case PA_CONTEXT_UNCONNECTED:
	case PA_CONTEXT_CONNECTING:
	case PA_CONTEXT_AUTHORIZING:
	case PA_CONTEXT_SETTING_NAME:
		break;
	}
}

static int make_nonblock(int fd) {
	int fl;

	if ((fl = fcntl(fd, F_GETFL)) < 0)
		return fl;

	if (fl & O_NONBLOCK)
		return 0;

	return fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

static int make_close_on_exec(int fd)
{
    return fcntl(fd, F_SETFD, FD_CLOEXEC);
}

snd_pulse_t *pulse_new(void)
{
	snd_pulse_t *p;
	int fd[2] = { -1, -1 };
	char proc[PATH_MAX], buf[PATH_MAX + 20];

	p = calloc(1, sizeof(snd_pulse_t));

	if (!p)
		return NULL;

	if (pipe(fd)) {
		free(p);
		return NULL;
	}

	p->main_fd = fd[0];
	p->thread_fd = fd[1];

	make_nonblock(p->main_fd);
	make_close_on_exec(p->main_fd);
	make_nonblock(p->thread_fd);
	make_close_on_exec(p->thread_fd);

	p->mainloop = pa_threaded_mainloop_new();
	if (!p->mainloop)
		goto fail;

	if (pa_get_binary_name(proc, sizeof(proc)))
		snprintf(buf, sizeof(buf), "ALSA plug-in [%s]",
			 pa_path_get_filename(proc));
	else
		snprintf(buf, sizeof(buf), "ALSA plug-in");
	buf[sizeof(buf)-1] = 0;

	p->context =
	    pa_context_new(pa_threaded_mainloop_get_api(p->mainloop), buf);

	if (!p->context)
		goto fail;

	pa_context_set_state_callback(p->context, context_state_cb, p);

	if (pa_threaded_mainloop_start(p->mainloop) < 0)
		goto fail;

	return p;

fail:
	pulse_free(p);

	return NULL;
}

void pulse_free(snd_pulse_t * p)
{
	if (p->mainloop)
		pa_threaded_mainloop_stop(p->mainloop);

	if (p->context) {
		pa_context_disconnect(p->context);
		pa_context_unref(p->context);
	}

	if (p->mainloop)
		pa_threaded_mainloop_free(p->mainloop);

	if (p->thread_fd >= 0)
		close(p->thread_fd);

	if (p->main_fd >= 0)
		close(p->main_fd);

	free(p);
}

int pulse_connect(snd_pulse_t * p, const char *server, int can_fallback)
{
	int err;
	pa_context_flags_t flags;
	pa_context_state_t state;

	assert(p);

	if (can_fallback)
		flags = PA_CONTEXT_NOAUTOSPAWN;
	else
		flags = 0;

	if (!p->context || !p->mainloop)
		return -EBADFD;

	state = pa_context_get_state(p->context);
	if (state != PA_CONTEXT_UNCONNECTED)
		return -EBADFD;

	pa_threaded_mainloop_lock(p->mainloop);

	err = pa_context_connect(p->context, server, flags, NULL);
	if (err < 0)
		goto error;

	for (;;) {
		pa_context_state_t state = pa_context_get_state(p->context);

		if (!PA_CONTEXT_IS_GOOD(state))
			goto error;

		if (state == PA_CONTEXT_READY)
			break;

		pa_threaded_mainloop_wait(p->mainloop);
	}

	pa_threaded_mainloop_unlock(p->mainloop);

	return 0;

      error:
	if (!can_fallback)
		SNDERR("PulseAudio: Unable to connect: %s\n",
		       pa_strerror(pa_context_errno(p->context)));

	pa_threaded_mainloop_unlock(p->mainloop);

	return -ECONNREFUSED;
}

void pulse_poll_activate(snd_pulse_t * p)
{
	static const char x = 'x';
	assert(p);

	write(p->thread_fd, &x, 1);
}

void pulse_poll_deactivate(snd_pulse_t * p)
{
	char buf[10];

	assert(p);

	/* Drain the pipe */
	while (read(p->main_fd, buf, sizeof(buf)) > 0);
}
