/***
  This file is part of PulseAudio.

  Copyright 2004-2006 Lennart Poettering

  PulseAudio 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.

  PulseAudio 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
  General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with PulseAudio; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/

/*
 * The Qubes OS Project, http://www.qubes-os.org
 *
 * Copyright (C) 2010  Rafal Wojtczuk  <rafal@invisiblethingslab.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <sys/select.h>

#include <pulse/xmalloc.h>

#include <pulsecore/core-error.h>
#include <pulsecore/sink.h>
#include <pulsecore/source.h>
#include <pulsecore/module.h>
#include <pulsecore/core-util.h>
#include <pulsecore/modargs.h>
#include <pulsecore/log.h>
#include <pulsecore/thread.h>
#include <pulsecore/thread-mq.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/socket-util.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include "module-vchan-sink-symdef.h"
#include "qubes-vchan-sink.h"
#include <libvchan.h>

PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("VCHAN sink/source");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(false);
PA_MODULE_USAGE("sink_name=<name for the sink> "
		"sink_properties=<properties for the sink> "
		"source_name=<name for the source> "
		"source_properties=<properties for the source> "
		"format=<sample format> "
		"rate=<sample rate>"
		"channels=<number of channels> "
		"channel_map=<channel map>");

#define DEFAULT_SINK_NAME "vchan_output"
#define DEFAULT_SOURCE_NAME "vchan_input"

#define VCHAN_BUF 8192

struct userdata {
	pa_core *core;
	pa_module *module;
	pa_sink *sink;
	pa_source *source;

	struct libvchan *play_ctrl;
	struct libvchan *rec_ctrl;

	pa_thread *thread;
	pa_thread_mq thread_mq;
	pa_rtpoll *rtpoll;

	pa_memchunk memchunk_sink;
	pa_memchunk memchunk_source;

	pa_rtpoll_item *play_rtpoll_item;
	pa_rtpoll_item *rec_rtpoll_item;
};

static const char *const valid_modargs[] = {
	"sink_name",
	"sink_properties",
	"source_name",
	"source_properties",
	"format",
	"rate",
	"channels",
	"channel_map",
	NULL
};

static int sink_process_msg(pa_msgobject * o, int code, void *data,
			    int64_t offset, pa_memchunk * chunk)
{
	int r;
	struct userdata *u = PA_SINK(o)->userdata;
	int state;
	switch (code) {
	case PA_SINK_MESSAGE_SET_STATE:
		state = PA_PTR_TO_UINT(data);
		r = pa_sink_process_msg(o, code, data, offset, chunk);
		if (r >= 0) {
			pa_log("sink cork req state =%d, now state=%d\n", state,
			       (int) (u->sink->state));
		}
		return r;

	case PA_SINK_MESSAGE_GET_LATENCY:{
			size_t n = 0;
			n += u->memchunk_sink.length;

			*((pa_usec_t *) data) =
			    pa_bytes_to_usec(n, &u->sink->sample_spec);
			return 0;
		}
	}

	return pa_sink_process_msg(o, code, data, offset, chunk);
}

static int source_process_msg(pa_msgobject * o, int code, void *data,
			    int64_t offset, pa_memchunk * chunk)
{
	int r;
	struct userdata *u = PA_SOURCE(o)->userdata;
	int state;
	switch (code) {
	case PA_SOURCE_MESSAGE_SET_STATE:
		state = PA_PTR_TO_UINT(data);
		r = pa_source_process_msg(o, code, data, offset, chunk);
		if (r >= 0) {
			pa_log("source cork req state =%d, now state=%d\n", state,
			       (int) (u->source->state));
			uint32_t cmd = 0;
			if (u->source->state != PA_SOURCE_RUNNING && state == PA_SOURCE_RUNNING)
				cmd = QUBES_PA_SOURCE_START_CMD;
			else if (u->source->state -= PA_SOURCE_RUNNING && state != PA_SOURCE_RUNNING)
				cmd = QUBES_PA_SOURCE_STOP_CMD;
			if (cmd != 0) {
				if (libvchan_write(u->rec_ctrl, (char*)&cmd, sizeof(cmd)) < 0) {
					pa_log("vchan: failed to send record cmd");
					return -1;
				}
			}
		}
		return r;

	case PA_SOURCE_MESSAGE_GET_LATENCY:{
			size_t n = 0;
			n += u->memchunk_source.length;

			*((pa_usec_t *) data) =
			    pa_bytes_to_usec(n, &u->source->sample_spec);
			return 0;
		}
	}

	return pa_source_process_msg(o, code, data, offset, chunk);
}

static int write_to_vchan(struct libvchan *ctrl, char *buf, int size)
{
	static int all = 0, waited = 0, nonwaited = 0, full = 0;
	ssize_t l;
	fd_set rfds;
	struct timeval tv = { 0, 0 };
	int ret, fd = libvchan_fd_for_select(ctrl);
	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);
	all++;
	ret = select(fd + 1, &rfds, NULL, NULL, &tv);
	if (ret == -1) {
		pa_log("Failed to select() in vchan: %s",
		       pa_cstrerror(errno));
		return -1;
	}
	if (ret) {
		if (libvchan_wait(ctrl) < 0) {
			pa_log("Failed libvchan_wait");
			return -1;
		}
		waited++;
	} else
		nonwaited++;
	if (libvchan_buffer_space(ctrl)) {
		l = libvchan_write(ctrl, buf, size);
	} else {
		l = -1;
		errno = EAGAIN;
		full++;
	}
	if ((all % 8000) == 0) {
		pa_log
		    ("write_to_vchan: all=%d waited=%d nonwaited=%d full=%d\n",
		     all, waited, nonwaited, full);
	}
	return l;
}

static int process_sink_render(struct userdata *u)
{
	pa_assert(u);

	if (u->memchunk_sink.length <= 0)
		pa_sink_render(u->sink, libvchan_buffer_space(u->play_ctrl), &u->memchunk_sink);

	pa_assert(u->memchunk_sink.length > 0);

	for (;;) {
		ssize_t l;
		void *p;

		p = pa_memblock_acquire(u->memchunk_sink.memblock);
		l = write_to_vchan(u->play_ctrl, (char *) p +
				   u->memchunk_sink.index, u->memchunk_sink.length);
		pa_memblock_release(u->memchunk_sink.memblock);

		pa_assert(l != 0);

		if (l < 0) {

			if (errno == EINTR)
				continue;
			else if (errno == EAGAIN)
				return 0;
			else {
				pa_log
				    ("Failed to write data to VCHAN: %s",
				     pa_cstrerror(errno));
				return -1;
			}

		} else {

			u->memchunk_sink.index += (size_t) l;
			u->memchunk_sink.length -= (size_t) l;

			if (u->memchunk_sink.length <= 0) {
				pa_memblock_unref(u->memchunk_sink.memblock);
				pa_memchunk_reset(&u->memchunk_sink);
			}
		}

		return 0;
	}
}

static int process_source_data(struct userdata *u)
{
	ssize_t l;
	void *p;
	if (!u->memchunk_source.memblock) {
		u->memchunk_source.memblock = pa_memblock_new(u->core->mempool, 16*1024); // at least vchan buffer size
		u->memchunk_source.index = u->memchunk_source.length = 0;
	}

	pa_assert(pa_memblock_get_length(u->memchunk_source.memblock) > u->memchunk_source.index);

	p = pa_memblock_acquire(u->memchunk_source.memblock);
	l = libvchan_read(u->rec_ctrl, p + u->memchunk_source.index, pa_memblock_get_length(u->memchunk_source.memblock) - u->memchunk_source.index);
	pa_memblock_release(u->memchunk_source.memblock);
	pa_log_debug("process_source_data %d", l);

	if (l <= 0) {
		/* vchan disconnected/error */
		pa_log("Failed to read data from vchan");
		return -1;
	} else {

		u->memchunk_source.length = (size_t) l;
		pa_source_post(u->source, &u->memchunk_source);
		u->memchunk_source.index += (size_t) l;

		if (u->memchunk_source.index >= pa_memblock_get_length(u->memchunk_source.memblock)) {
			pa_memblock_unref(u->memchunk_source.memblock);
			pa_memchunk_reset(&u->memchunk_source);
		}
	}
	return 0;
}

static void thread_func(void *userdata)
{
	struct userdata *u = userdata;
	char buf[2048]; // max ring buffer size

	pa_assert(u);

	pa_log_debug("Thread starting up");

	pa_thread_mq_install(&u->thread_mq);
	for (;;) {
		struct pollfd *play_pollfd;
		struct pollfd *rec_pollfd;
		int ret;

		play_pollfd = pa_rtpoll_item_get_pollfd(u->play_rtpoll_item, NULL);
		rec_pollfd = pa_rtpoll_item_get_pollfd(u->rec_rtpoll_item, NULL);

		if (play_pollfd->revents & POLLIN) {
			if (libvchan_wait(u->play_ctrl) < 0)
				goto fail;
			play_pollfd->revents = 0;
		}

		if (rec_pollfd->revents & POLLIN) {
			if (libvchan_wait(u->rec_ctrl) < 0)
				goto fail;
			rec_pollfd->revents = 0;
		}

		/* Render some data and write it to the fifo */
		if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {

			if (u->sink->thread_info.rewind_requested)
				pa_sink_process_rewind(u->sink, 0);

			if (libvchan_buffer_space(u->play_ctrl)) {
				if (process_sink_render(u) < 0)
					goto fail;
			}
		}

		if (u->source->thread_info.state == PA_SOURCE_RUNNING) {
			while (libvchan_data_ready(u->rec_ctrl)) {
				if (process_source_data(u) < 0)
					goto fail;
			}
		} else {
			/* discard the data */
			if (libvchan_data_ready(u->rec_ctrl))
				if (libvchan_read(u->rec_ctrl, buf, sizeof(buf)) < 0)
					goto fail;
		}

		/* Hmm, nothing to do. Let's sleep */
		play_pollfd->events = POLLIN;
		rec_pollfd->events = POLLIN;

		if ((ret = pa_rtpoll_run(u->rtpoll, true)) < 0)
			goto fail;

		if (ret == 0)
			goto finish;
	}

      fail:
	/* If this was no regular exit from the loop we have to continue
	 * processing messages until we received PA_MESSAGE_SHUTDOWN */
	pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core),
			  PA_CORE_MESSAGE_UNLOAD_MODULE, u->module,
			  0, NULL, NULL);
	pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);

      finish:
	pa_log_debug("Thread shutting down");
}

static int do_conn(struct userdata *u)
{
	int fd;
	u->play_ctrl = libvchan_server_init(QUBES_PA_SINK_VCHAN_PORT);
	if (!u->play_ctrl) {
		pa_log("libvchan_server_init play failed\n");
		return -1;
	}
	u->rec_ctrl = libvchan_server_init(QUBES_PA_SOURCE_VCHAN_PORT);
	if (!u->rec_ctrl) {
		pa_log("libvchan_server_init rec failed\n");
		return -1;
	}
	fd = libvchan_fd_for_select(u->play_ctrl);
	pa_log("play libvchan_fd_for_select=%d, ctrl=%p\n", fd, u->play_ctrl);
	fd = libvchan_fd_for_select(u->rec_ctrl);
	pa_log("rec libvchan_fd_for_select=%d, ctrl=%p\n", fd, u->rec_ctrl);
	return 0;
}


int pa__init(pa_module * m)
{
	struct userdata *u;
	pa_sample_spec ss;
	pa_channel_map map;
	pa_modargs *ma;
	struct pollfd *pollfd;
	pa_sink_new_data data_sink;
	pa_source_new_data data_source;

	pa_assert(m);

	pa_log("vchan module loading");
	if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
		pa_log("Failed to parse module arguments.");
		goto fail;
	}

	ss = m->core->default_sample_spec;
	map = m->core->default_channel_map;
	if (pa_modargs_get_sample_spec_and_channel_map
	    (ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
		pa_log
		    ("Invalid sample format specification or channel map");
		goto fail;
	}

	u = pa_xnew0(struct userdata, 1);
	u->core = m->core;
	u->module = m;
	m->userdata = u;
	pa_memchunk_reset(&u->memchunk_sink);
	pa_memchunk_reset(&u->memchunk_source);
	u->rtpoll = pa_rtpoll_new();
	pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);

	if ((do_conn(u)) < 0) {

		pa_log("get_early_allocated_vchan: %s",
		       pa_cstrerror(errno));
		goto fail;
	}
	/* SINK preparation */
	pa_sink_new_data_init(&data_sink);
	data_sink.driver = __FILE__;
	data_sink.module = m;
	pa_sink_new_data_set_name(&data_sink,
				  pa_modargs_get_value(ma,
						       "sink_name",
						       DEFAULT_SINK_NAME));
	pa_proplist_sets(data_sink.proplist,
			 PA_PROP_DEVICE_STRING, DEFAULT_SINK_NAME);
	pa_proplist_setf(data_sink.proplist,
			PA_PROP_DEVICE_DESCRIPTION,
			"Unix VCHAN sink");
	pa_sink_new_data_set_sample_spec(&data_sink, &ss);
	pa_sink_new_data_set_channel_map(&data_sink, &map);

	if (pa_modargs_get_proplist
		(ma, "sink_properties", data_sink.proplist, PA_UPDATE_REPLACE) < 0) {
		pa_log("Invalid properties");
		pa_sink_new_data_done(&data_sink);
		goto fail;
	}

	u->sink = pa_sink_new(m->core, &data_sink, PA_SINK_LATENCY);
	pa_sink_new_data_done(&data_sink);

	if (!u->sink) {
		pa_log("Failed to create sink.");
		goto fail;
	}

	u->sink->parent.process_msg = sink_process_msg;
	u->sink->userdata = u;

	pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
	pa_sink_set_rtpoll(u->sink, u->rtpoll);
	pa_sink_set_max_request(u->sink, VCHAN_BUF);
	pa_sink_set_fixed_latency(u->sink,
				  pa_bytes_to_usec
				  (VCHAN_BUF,
				   &u->sink->sample_spec));

	u->play_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
	pollfd = pa_rtpoll_item_get_pollfd(u->play_rtpoll_item, NULL);
	pollfd->fd = libvchan_fd_for_select(u->play_ctrl);
	pollfd->events = POLLIN;
	pollfd->revents = 0;

	/* SOURCE preparation */
	pa_source_new_data_init(&data_source);
	data_source.driver = __FILE__;
	data_source.module = m;
	pa_source_new_data_set_name(&data_source, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME));
	pa_proplist_sets(data_source.proplist, PA_PROP_DEVICE_STRING, DEFAULT_SOURCE_NAME);
	pa_proplist_setf(data_source.proplist, PA_PROP_DEVICE_DESCRIPTION, "Unix VCHAN source");
	pa_source_new_data_set_sample_spec(&data_source, &ss);
	pa_source_new_data_set_channel_map(&data_source, &map);

	if (pa_modargs_get_proplist(ma, "source_properties", data_source.proplist, PA_UPDATE_REPLACE) < 0) {
		pa_log("Invalid properties");
		pa_source_new_data_done(&data_source);
		goto fail;
	}

	u->source = pa_source_new(m->core, &data_source, PA_SOURCE_LATENCY);
	pa_source_new_data_done(&data_source);

	if (!u->source) {
		pa_log("Failed to create source.");
		goto fail;
	}

	u->source->parent.process_msg = source_process_msg;
	u->source->userdata = u;

	pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
	pa_source_set_rtpoll(u->source, u->rtpoll);
	pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(PIPE_BUF, &u->source->sample_spec));

	u->rec_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
	pollfd = pa_rtpoll_item_get_pollfd(u->rec_rtpoll_item, NULL);
	pollfd->fd = libvchan_fd_for_select(u->rec_ctrl);
	pollfd->events = POLLIN;
	pollfd->revents = 0;

#if PA_CHECK_VERSION(0,9,22)
	if (!(u->thread = pa_thread_new("vchan-sink", thread_func, u))) {
#else
	if (!(u->thread = pa_thread_new(thread_func, u))) {
#endif
		pa_log("Failed to create thread.");
		goto fail;
	}

	pa_sink_put(u->sink);
	pa_source_put(u->source);

	pa_modargs_free(ma);

	return 0;

      fail:
	if (ma)
		pa_modargs_free(ma);

	pa__done(m);

	return -1;
}

int pa__get_n_used(pa_module * m)
{
	struct userdata *u;

	pa_assert(m);
	pa_assert_se(u = m->userdata);

	return pa_sink_linked_by(u->sink);
}

void pa__done(pa_module * m)
{
	struct userdata *u;

	pa_assert(m);

	if (!(u = m->userdata))
		return;

	if (u->sink)
		pa_sink_unlink(u->sink);

	if (u->source)
		pa_source_unlink(u->source);

	if (u->thread) {
		pa_asyncmsgq_send(u->thread_mq.inq, NULL,
				  PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
		pa_thread_free(u->thread);
	}

	pa_thread_mq_done(&u->thread_mq);

	if (u->sink)
		pa_sink_unref(u->sink);

	if (u->source)
		pa_source_unref(u->source);

	if (u->memchunk_sink.memblock)
		pa_memblock_unref(u->memchunk_sink.memblock);

	if (u->memchunk_source.memblock)
		pa_memblock_unref(u->memchunk_source.memblock);

	if (u->play_rtpoll_item)
		pa_rtpoll_item_free(u->play_rtpoll_item);

	if (u->rec_rtpoll_item)
		pa_rtpoll_item_free(u->rec_rtpoll_item);

	if (u->rtpoll)
		pa_rtpoll_free(u->rtpoll);

	if (u->play_ctrl)
		libvchan_close(u->play_ctrl);

	if (u->rec_ctrl)
		libvchan_close(u->rec_ctrl);

	pa_xfree(u);
}
