/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Pierre Souchay , Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2008-2019
 *					All rights reserved
 *
 *
 *  PulseAudio output module : output audio thru the PulseAudio daemon
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *
 *  GPAC 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; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <poll.h>
#include <pulse/pulseaudio.h>
#include <pulse/simple.h>
#include <gpac/modules/audio_out.h>

#define BUFF_SIZE 8192

typedef struct
{
	pa_simple *playback_handle;
	pa_sample_spec sample_spec;
	const char *output_name;
	const char *output_description;
	u32 errors;
	u32 consecutive_zero_reads;
	char data[BUFF_SIZE];
} PulseAudioContext;

static void
free_pulseaudio_resources (GF_AudioOutput * dr)
{
	PulseAudioContext *ctx;
	if (dr == NULL)
		return;
	ctx = (PulseAudioContext *) dr->opaque;
	if (ctx == NULL)
		return;
	if (ctx->playback_handle != NULL)
	{
		pa_simple_free (ctx->playback_handle);
	}
	ctx->playback_handle = NULL;
}

static GF_Err
PulseAudio_Setup (GF_AudioOutput * dr, void *os_handle,
                  u32 num_buffers, u32 total_duration)
{
	const char *opt;
	PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque;
	if (ctx == NULL)
		return GF_BAD_PARAM;
	opt = gf_module_get_key((GF_BaseInterface *)dr, "name");
	ctx->output_name = opt ? ctx->output_name : "GPAC";

	opt = gf_module_get_key((GF_BaseInterface *)dr, "description");
	ctx->output_description = opt  ? opt  : "GPAC Output";
	return GF_OK;
}

static void
PulseAudio_Shutdown (GF_AudioOutput * dr)
{
	int pa_error = 0;
	PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque;
	if (ctx == NULL)
		return;
	if (ctx->playback_handle)
	{
		GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO,
		        ("[PulseAudio] Closing PulseAudio output\n"));
		pa_simple_drain (ctx->playback_handle, &pa_error);
		if (pa_error)
		{
			GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO,
			        ("[PulseAudio] Error while closing PulseAudio output: %s\n",
			         pa_strerror (pa_error)));

		}
	}
}

static GF_Err
PulseAudio_Configure(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *audioFormat, u64 channel_cfg)
{
	int pa_error = 0;
	PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque;
	if (ctx->playback_handle != NULL)
	{
		GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO,
		        ("[PulseAudio] PulseAudio output already configured !\n"));
		/* Should not happen */
		pa_simple_flush (ctx->playback_handle, &pa_error);
		pa_simple_free (ctx->playback_handle);
		ctx->playback_handle = NULL;
	}

	//only support for PCM 16
	*audioFormat = GF_AUDIO_FMT_S16;

	ctx->consecutive_zero_reads = 0;
	ctx->sample_spec.format = PA_SAMPLE_S16NE;
	ctx->sample_spec.channels = *NbChannels;
	ctx->sample_spec.rate = *SampleRate;
	ctx->playback_handle = pa_simple_new (NULL,
	                                      ctx->output_name,
	                                      PA_STREAM_PLAYBACK,
	                                      NULL,
	                                      ctx->output_description,
	                                      &(ctx->sample_spec),
	                                      NULL, NULL, &pa_error);
	if (ctx->playback_handle == NULL || pa_error != 0)
	{
		GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO,
		        ("[PulseAudio] Error while allocating PulseAudio output: %s.\n",
		         pa_strerror (pa_error)));
		return GF_IO_ERR;
	}
	GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, ("[PulseAudio] Initialized - sampling rate %d - %d channels\n", ctx->sample_spec.rate, ctx->sample_spec.channels));
	return GF_OK;
}

static void
PulseAudio_WriteAudio (GF_AudioOutput * dr)
{
	int written = 0;
	int pa_error = 0;
	PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque;
	if (ctx == NULL || ctx->playback_handle == NULL)
	{
		if (ctx == NULL || ctx->errors == 0)
		{
			if (ctx != NULL)
				ctx->errors++;
			GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO,
			        ("[PulseAudio] unable to connect to a PulseAudio daemon!\n"))
		}
		return;
	}
	written = dr->FillBuffer (dr->audio_renderer, ctx->data, BUFF_SIZE / 4);
	if (written <= 0)
	{
		ctx->consecutive_zero_reads++;
		if (ctx->consecutive_zero_reads < 5) {
			gf_sleep(5);
		} else if (ctx->consecutive_zero_reads < 25) {
			gf_sleep(10);
		} else {
			gf_sleep(33);
		}
		return;
	}
	ctx->consecutive_zero_reads = 0;
	/*written = */pa_simple_write (ctx->playback_handle, ctx->data, written, &pa_error);
	if (pa_error != 0)
	{
		if (ctx->errors < 1)
			GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO,
			        ("[PulseAudio] Write failure: %s\n", pa_strerror (pa_error)));
		ctx->errors++;
	}
	else
	{
		ctx->errors = 0;
	}
}

static u32
PulseAudio_GetAudioDelay (GF_AudioOutput * dr)
{
	pa_usec_t delay = 0;
	int pa_error = 0;
	u32 ms_delay = 0;
	PulseAudioContext *ctx = (PulseAudioContext *) dr->opaque;
	if (ctx == NULL || ctx->playback_handle == NULL)
	{
		GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO,
		        ("[PulseAudio] missing connection to pulseaudio daemon!\n"))
		return 0;
	}
	delay = pa_simple_get_latency (ctx->playback_handle, &pa_error);
	if (pa_error)
	{
		GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO,
		        ("[PulseAudio] Error while retrieving pulseaudio delay: %s.\n",
		         pa_strerror (pa_error)));
		return 0;
	}
	ms_delay = (u32) (delay / 1000);
	GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, ("[PulseAudio] Audio delay: %llu us.\n",
	                                    delay));
	return ms_delay;
}

static GF_GPACArg PulseAudioArgs[] = {
	GF_DEF_ARG("name", NULL, "name for PulseAudio", "GPAC", NULL, GF_ARG_STRING, 0),
	GF_DEF_ARG("description", NULL, "description for PulseAudio", "GPAC output", NULL, GF_ARG_STRING, 0),
	{0},
};

void *
NewPulseAudioOutput ()
{
	PulseAudioContext *ctx;
	GF_AudioOutput *driv;
	GF_SAFEALLOC (ctx, PulseAudioContext);
	if (!ctx)
		return NULL;

	GF_SAFEALLOC (driv, GF_AudioOutput);
	if (!driv)
	{
		gf_free(ctx);
		return NULL;
	}
	driv->opaque = ctx;
	ctx->playback_handle = NULL;
	ctx->errors = 0;
	driv->SelfThreaded = 0;
	driv->Setup = PulseAudio_Setup;
	driv->Shutdown = PulseAudio_Shutdown;
	driv->Configure = PulseAudio_Configure;
	driv->GetAudioDelay = PulseAudio_GetAudioDelay;
	driv->WriteAudio = PulseAudio_WriteAudio;
	driv->description = "Audio output using PulseAudio";
	driv->args = PulseAudioArgs;

	GF_REGISTER_MODULE_INTERFACE (driv, GF_AUDIO_OUTPUT_INTERFACE, "pulse", "gpac distribution");
	return driv;
}

void
DeletePulseAudioOutput (void *ifce)
{
	GF_AudioOutput *dr = (GF_AudioOutput *) ifce;
	free_pulseaudio_resources (dr);
	if (dr != NULL) {
		if (dr->opaque)
			gf_free(dr->opaque);
		dr->opaque = NULL;
		gf_free(dr);
	}
}


/*
 * ********************************************************************
 * interface
 */
GPAC_MODULE_EXPORT
const u32 *QueryInterfaces(u32 InterfaceType)
{
	static u32 si [] = {
		GF_AUDIO_OUTPUT_INTERFACE,
		0
	};
	return si;
}

GPAC_MODULE_EXPORT
GF_BaseInterface *LoadInterface (u32 InterfaceType)
{
	if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE)
		return NewPulseAudioOutput ();
	return NULL;
}

GPAC_MODULE_EXPORT
void ShutdownInterface (GF_BaseInterface * ifce)
{
	if (ifce->InterfaceType == GF_AUDIO_OUTPUT_INTERFACE)
		DeletePulseAudioOutput ((GF_AudioOutput *) ifce);
}

GPAC_MODULE_STATIC_DECLARATION( pulseaudio )
