/*
 * ALSA <-> Audioflinger PCM I/O plugin
 *
 * Copyright (c) 2010 by Motorola Corporation
 * Copyright (C) 2011-2012 Canonical, Ltd.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */

#include <sys/socket.h>
#include <alsa/asoundlib.h>
#include <alsa/pcm_external.h>
#include "waudio.h"
#include <signal.h>

static int loglevel = 0;

#define LOG(level, ...) \
        {if (loglevel >= level) printf( __VA_ARGS__); }

#define LOGD(...)   LOG(2, __VA_ARGS__)
#define LOGI(...)   LOG(1, __VA_ARGS__)

typedef struct snd_pcm_android {
	snd_pcm_ioplug_t io;
	int fd[2];
	void *data;
	audio_format_t format;
	int rate;
	int channels;
	int period;
	unsigned int frame_size;
	ssize_t hw_ptr;
	int delay;
        int muted;
} snd_pcm_android_t;

static int g_muted = 0;

static void mute_handler (int signum)
{
	switch (signum) {
	case SIGUSR1:
		g_muted = 1;
		break;
	case SIGUSR2:
		g_muted = 0;
		break;
	}
}

static void signals_setup (void)
{
	struct sigaction action;

	memset (&action, 0, sizeof (action));
	action.sa_handler = mute_handler;

	sigaction (SIGUSR1, &action, NULL);
	sigaction (SIGUSR2, &action, NULL);
}

static int android_poll_revents(snd_pcm_ioplug_t *io ATTRIBUTE_UNUSED,
		struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
{
	snd_pcm_android_t *android = io->private_data;

	LOGD("android_poll_revents enter\n");

	assert(pfds && nfds == 1 && revents);

	LOGD("android_poll_revents usleep start\n");
	usleep(android->period * 500000 / android->rate);
	LOGD("android_poll_revents usleep %dms end\n",
			android->period * 500000 / android->rate);
	*revents = pfds[0].revents;

	LOGD("android_poll_revents return\n");
	return 0;
}

/* Callback from AudioTrack to consume the remaining of the buffer */
void android_process_cb(int event, void *cookie, void *info)
{
	snd_pcm_uframes_t xfer = 0;
	const snd_pcm_channel_area_t *areas;
	int nframes;

	LOGD("android_process_cb start\n");

	snd_pcm_ioplug_t *io = (snd_pcm_ioplug_t *) cookie;
	snd_pcm_android_t *android = io->private_data;

	snd_pcm_channel_area_t dst_areas[android->channels];

	/* only handle EVENT_MORE_DATA Event */
	if (event != EVENT_MORE_DATA)
		return;

	/* check whether need to mute audio track */
	if (android->muted != g_muted) {
		if (android->data == NULL)
			return;

		AudioTrack_mute((TrackHandle) android->data, g_muted);
		android->muted = g_muted;
	}

	buffer_struct *buffer = (buffer_struct *) info;

	/* calculate frame count needed */
	nframes = buffer->size / android->frame_size;

	/* init dst areas from buffer */
	unsigned int channel;
	unsigned int channels = android->channels;
	snd_pcm_channel_area_t* bareas = dst_areas;
	for (channel = 0; channel < channels; ++channel, ++bareas) {
		bareas->addr = buffer->raw;
		bareas->first = channel *
			snd_pcm_format_physical_width(io->format);
		bareas->step = android->frame_size * 8;
	}

	if (io->state != SND_PCM_STATE_RUNNING) {
		LOGD("not running, silence it \n");
		snd_pcm_areas_silence(dst_areas, 0, android->channels,
				nframes, io->format);
		return;
	}

	/* get src areas from io mmap */
	areas = snd_pcm_ioplug_mmap_areas(io);

	/* read data */
	while (xfer < nframes) {
		snd_pcm_uframes_t frames = nframes - xfer;
		snd_pcm_uframes_t offset = android->hw_ptr;
		snd_pcm_uframes_t cont = io->buffer_size - offset;

		if (cont < frames)
			frames = cont;
		if (io->stream == SND_PCM_STREAM_PLAYBACK)
			snd_pcm_areas_copy(dst_areas, xfer, areas, offset,
					android->channels, frames, io->format);

		android->hw_ptr += frames;
		android->hw_ptr %= io->buffer_size;
		xfer += frames;
	}

	LOGD("android_process_cb write %d bytes\n", buffer->size);
}

static snd_pcm_sframes_t android_read(snd_pcm_ioplug_t *io,
				  const snd_pcm_channel_area_t *areas,
				  snd_pcm_uframes_t offset,
				  snd_pcm_uframes_t size)
{
	snd_pcm_android_t *android = io->private_data;
	char *buf;
	ssize_t result;

	LOGD("android_read enter\n");

	/* we handle only an interleaved buffer */
	buf = (char *) areas->addr + (areas->first + areas->step * offset) / 8;
	size *= android->frame_size;

	result = AudioRecord_read((RecordHandle) android->data, buf, size);

	write(android->fd[0], buf, 1);	// for polling

	if (result <= 0) {
		LOGD("android_read result <=0\n");
		return result;
	}

	android->hw_ptr += result / android->frame_size;
	android->hw_ptr %= io->buffer_size;

	LOGD("android_read leave - sframes :%d\n", result / android->frame_size);

	return result / android->frame_size;
}

static snd_pcm_sframes_t android_pointer(snd_pcm_ioplug_t *io)
{
	snd_pcm_android_t *android = io->private_data;

	LOGD("android_pointer %d\n", android->hw_ptr);
	return android->hw_ptr;
}

static int android_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp)
{
	snd_pcm_android_t *android = io->private_data;

	if (android->data == NULL)
		return -EBADFD;

	*delayp = android->delay + io->buffer_size;
	LOGD("android_delay %d\n", android->delay);
	return 0;
}

static int android_start(snd_pcm_ioplug_t *io)
{
	snd_pcm_android_t *android = io->private_data;

	LOGI("android start\n");

	if (android->data == NULL)
		return -EBADFD;

	if (io->stream == SND_PCM_STREAM_PLAYBACK)
		AudioTrack_start((TrackHandle) android->data);
	else {
		AudioRecord_start((RecordHandle) android->data);
		/* Fake Android's hw ptr so Alsa can start polling
		 * Ideally this would be given by Android, but for that we'd
		 * need to use a callback while reading from the record
		 * handle, similar with what we have for track handle. */
		android->hw_ptr = 500;
	}

	LOGI("android start return no err\n");
	return 0;
}

static int android_stop(snd_pcm_ioplug_t *io)
{
	snd_pcm_android_t *android = io->private_data;

	LOGI("android stop\n");

	if (android->data == NULL)
		return -EBADFD;

	if (io->stream == SND_PCM_STREAM_PLAYBACK)
		AudioTrack_stop((TrackHandle) android->data);
	else
		AudioRecord_stop((TrackHandle) android->data);

	LOGI("android stop return no err\n");
	return 0;
}

static int android_prepare(snd_pcm_ioplug_t *io)
{
	snd_pcm_android_t *android = io->private_data;
	status_type status;
	audio_channel_mask_t channel;

	LOGI("android_prepare\n");

	android->hw_ptr = 0;
	android->muted = 0;

	if (android->data) {
		if (io->stream == SND_PCM_STREAM_PLAYBACK)
			DestoryAudioTrack((TrackHandle) android->data);
		else
			DestoryAudioRecord((RecordHandle) android->data);
		android->data = NULL;
	}

	if (io->stream == SND_PCM_STREAM_PLAYBACK) {
	        LOGI("android_prepare: creating audio track (playback)\n");
		android->data = CreateAudioTrack();
	} else {
	        LOGI("android_prepare: creating audio record\n");
		android->data = CreateAudioRecord();
	}

	if (android->data == NULL) {
		SNDERR("cannot allocate audio track/record");
		return -ENOMEM;
	}

	if (io->stream == SND_PCM_STREAM_PLAYBACK) {
		if (android->channels == 2)
			channel = AUDIO_CHANNEL_OUT_STEREO;
		else if (android->channels == 1)
			channel = AUDIO_CHANNEL_OUT_MONO;
		else
			return -EIO;

	        LOGI("android_prepare: track set (music track), rate: %d, format: %x, channel: %x\n",
				android->rate, android->format, channel);
		status = AudioTrack_set_asyn(
				(TrackHandle) android->data, AUDIO_STREAM_MUSIC,
				(uint32) android->rate, android->format,
				channel, android_process_cb, io);

		android->delay = AudioTrack_latency(
			(TrackHandle) android->data) * android->rate / 1000;
	} else {
		if (android->channels == 2)
			channel = AUDIO_CHANNEL_IN_STEREO;
		else if (android->channels == 1)
			channel = AUDIO_CHANNEL_IN_MONO;
		else
			return -EIO;

	        LOGI("android_prepare: record set, rate: %d, format: %x, channel: %x\n",
				android->rate, android->format, channel);
		status = AudioRecord_set((RecordHandle) android->data,
				AUDIO_SOURCE_DEFAULT, (uint32) android->rate,
				android->format, channel);
	}

	/* walkaround for pulseaudio alsasink */
	char buf[1];
	write(android->fd[0], buf, 1);

	if (status != OK) {
	        LOGI("android_prepare: failed preparing android track/record, status: %d\n",
				status);
		return -EIO;
	}

	LOGI("android_prepare return no err\n");
	return 0;
}

/* Check whether hw params are valid */
static int android_hw_params(snd_pcm_ioplug_t *io,
			 snd_pcm_hw_params_t *params ATTRIBUTE_UNUSED)
{
	snd_pcm_android_t *android = io->private_data;
	int err = 0;

	assert(android);

	LOGI("android_hw_params\n");

	android->frame_size =
		(snd_pcm_format_physical_width(io->format) * io->channels) / 8;

	/* config audio format */
	LOGD("io format is %d\n", io->format);

	switch (io->format) {
	case SND_PCM_FORMAT_U8:
		android->format = AUDIO_FORMAT_PCM_8_BIT;
		break;
	case SND_PCM_FORMAT_S16_LE:
		android->format = AUDIO_FORMAT_PCM_16_BIT;
		break;
	case SND_PCM_FORMAT_S16_BE:
		android->format = AUDIO_FORMAT_PCM_16_BIT;
		break;
	default:
		SNDERR("android: Unsupported format %s\n",
				snd_pcm_format_name(io->format));
		err = -EINVAL;
		goto finish;
	}

	/* config rate and channel */
	LOGD("io rate is %d, channel is %d\n", io->rate, io->channels);

	if (io->rate > 48000 || io->channels > 2) {
		err = -EINVAL;
		goto finish;
	}

	android->rate = io->rate;
	android->channels = io->channels;

	/* config period size */
	LOGD("io period is %d\n", (int) io->period_size);
	android->period = io->period_size;

finish:
	LOGI("android_hw_params return err=%d\n", err);
	return err;
}

static int android_pause(snd_pcm_ioplug_t  *io, int enable)
{
	snd_pcm_android_t *android = io->private_data;
	int err = 0;

	LOGI("android_pause, enable=%d\n", enable);
	if (enable) {
		if (io->stream == SND_PCM_STREAM_PLAYBACK)
			AudioTrack_pause((TrackHandle) android->data);
	        /*
		else
			AudioRecord_pause((RecordHandle) android->data);
		*/
	} else {
		if (io->stream == SND_PCM_STREAM_PLAYBACK)
			AudioTrack_start((TrackHandle) android->data);
		/*
		else
			AudioRecord_start((RecordHandle) android->data);
		*/
	}

	LOGI("android_pause, return err=%d\n", err);
	return err;
}

#define ARRAY_SIZE(ary)	(sizeof(ary)/sizeof(ary[0]))

static int android_hw_constraint(snd_pcm_android_t *android)
{
	snd_pcm_ioplug_t *io = &android->io;
	static const snd_pcm_access_t access_list[] = {
		SND_PCM_ACCESS_RW_INTERLEAVED
	};
	static const unsigned int formats[] = {
		SND_PCM_FORMAT_U8,
		SND_PCM_FORMAT_S16_LE,
		SND_PCM_FORMAT_S16_BE,
	};

	int err;

	LOGI("android_hw_constraint\n");
	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
				ARRAY_SIZE(access_list), access_list);
	if (err < 0)
		return err;

	err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
				ARRAY_SIZE(formats), formats);
	if (err < 0)
		return err;

	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
				1, 2);
	if (err < 0)
		return err;

	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE,
				8000, 48000);
	if (err < 0)
		return err;

	err = snd_pcm_ioplug_set_param_minmax(io,
				SND_PCM_IOPLUG_HW_BUFFER_BYTES,
				1, 1 * 1024 * 1024);
	if (err < 0)
		return err;

	err = snd_pcm_ioplug_set_param_minmax(io,
				SND_PCM_IOPLUG_HW_PERIOD_BYTES,
				128, 0.5 * 1024 * 1024);
	if (err < 0)
		return err;

	err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS,
				2, 1024);
	if (err < 0)
		return err;

	LOGI("android_hw_constraint return no err\n");
	return 0;
}

static int android_close(snd_pcm_ioplug_t *io)
{
	snd_pcm_android_t *android = io->private_data;

	LOGI("android_close\n");

	if (android->data) {
		if (io->stream == SND_PCM_STREAM_PLAYBACK)
			DestoryAudioTrack((TrackHandle)android->data);
		else
			DestoryAudioRecord((RecordHandle)android->data);
	}

	if (android->fd[0] >= 0)
		close(android->fd[0]);
	if (android->fd[1] >= 0)
		close(android->fd[1]);
	android->fd[0] = -1;
	android->fd[1] = -1;
	android->format = 0;
	android->rate = 0;
	android->channels = 0;
	android->period = 0;
	android->frame_size = 0;
	android->data = NULL;
	android->hw_ptr = 0;
	android->delay = 0;
	free(android);

	LOGI("android_close return no err\n");
	return 0;
}

static const snd_pcm_ioplug_callback_t android_playback_callback = {
	.start = android_start,
	.stop = android_stop,
	.pointer = android_pointer,
	.close = android_close,
	.hw_params = android_hw_params,
	.prepare = android_prepare,
	.pause = android_pause,
	.poll_revents = android_poll_revents,
	.delay = android_delay,
};

static const snd_pcm_ioplug_callback_t android_capture_callback = {
	.start = android_start,
	.stop = android_stop,
	.transfer = android_read,
	.pointer = android_pointer,
	.close = android_close,
	.hw_params = android_hw_params,
	.prepare = android_prepare,
	.pause = android_pause,
};

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);
}

SND_PCM_PLUGIN_DEFINE_FUNC(android)
{
	snd_config_iterator_t i, next;
	int err;
	snd_pcm_android_t *android;
	char *env = NULL;

	snd_config_for_each(i, next, conf) {
		snd_config_t *n = snd_config_iterator_entry(i);
		const char *id;
		if (snd_config_get_id(n, &id) < 0)
			continue;
		if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0
				|| strcmp(id, "hint") == 0
				|| strcmp(id, "device") == 0)
			continue;

		SNDERR("Unknown field %s", id);
		return -EINVAL;
	}

	if ((env = getenv("ALSA_ANDROID_DBG"))) {
		loglevel = atoi (env);
		if (loglevel < 0 || loglevel > 3)
			loglevel = 0;
	}

	android = calloc(1, sizeof(*android));
	if (!android) {
		SNDERR("cannot allocate");
		return -ENOMEM;
	}
	android->fd[0] = -1;
	android->fd[1] = -1;
	android->format = 0;
	android->rate = 0;
	android->channels = 0;
	android->period = 0;
	android->frame_size = 0;
	android->data = NULL;
	android->hw_ptr = 0;
	android->delay = 0;
	android->muted = 0;

	/* init pair fd, and set it as nonblock mode */
	socketpair(AF_LOCAL, SOCK_STREAM, 0, android->fd);
	make_nonblock(android->fd[0]);
        /* make_nonblock(android->fd[1]); */

	android->io.version = SND_PCM_IOPLUG_VERSION;
	android->io.name = "ALSA <-> android PCM I/O Plugin";
	android->io.poll_fd = android->fd[1];
	android->io.poll_events = stream == SND_PCM_STREAM_PLAYBACK ?
		POLLOUT : POLLIN;
	android->io.mmap_rw = stream == SND_PCM_STREAM_PLAYBACK ? 1 : 0;
	android->io.callback = stream == SND_PCM_STREAM_PLAYBACK ?
		&android_playback_callback : &android_capture_callback;
	android->io.private_data = android;

	if (mode & SND_PCM_NONBLOCK)
		LOGI("mode is SND_PCM_NONBLOCK\n");

	err = snd_pcm_ioplug_create(&android->io, name, stream, mode);
	if (err < 0)
		goto error;

	if ((err = android_hw_constraint(android)) < 0) {
		snd_pcm_ioplug_delete(&android->io);
		return err;
	}

	*pcmp = android->io.pcm;

        /* setup mute signal handler */
	signals_setup();

	return 0;

error:
	free(android);
	return err;
}

SND_PCM_PLUGIN_SYMBOL(android);
