/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2012
 *					All rights reserved
 *
 *  This file is part of GPAC / linux_oss audio render module
 *
 *  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.
 *
 */


#if defined(__DARWIN__) || defined(__APPLE__)
#include <soundcard.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>

#else

#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>

#ifdef OSS_FIX_INC
#include <soundcard.h>
#else
#include <sys/soundcard.h>
#endif

#endif

#include <gpac/modules/audio_out.h>

#define OSS_AUDIO_DEVICE	"/dev/dsp"

typedef struct
{
	int audio_dev, sr, nb_ch;
	u32 buf_size, delay, num_buffers, total_duration;
	u32 force_sr;
	char *wav_buf;
} OSSContext;


#define OSSCTX()	OSSContext *ctx = (OSSContext *)dr->opaque;


static GF_Err OSS_Setup(GF_AudioOutput*dr, void *os_handle, u32 num_buffers, u32 total_duration)
{
	int audio;
	const char *opt;
	OSSCTX();

	opt = gf_modules_get_option((GF_BaseInterface *)dr, "OSS", "ForceSampleRate");
	if (opt) ctx->force_sr = atoi(opt);

	/*open OSS in non-blocking mode*/
	audio = open(OSS_AUDIO_DEVICE, 0);
	if (audio < 0) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[OSS] Cannot open audio device\n"));
		return GF_NOT_SUPPORTED;
	}

	/*set blocking mode back*/
	//fcntl(audio, F_SETFL, fcntl(audio, F_GETFL) & ~FNDELAY);
	ctx->audio_dev=audio;
	ctx->num_buffers = num_buffers;
	ctx->total_duration = total_duration;
	return GF_OK;
}

static void OSS_Shutdown(GF_AudioOutput*dr)
{
	OSSCTX();
	ioctl(ctx->audio_dev,SNDCTL_DSP_RESET,NULL);
	close(ctx->audio_dev);
	if (ctx->wav_buf) gf_free(ctx->wav_buf);
	ctx->wav_buf = NULL;
}


static GF_Err OSS_ConfigureOutput(GF_AudioOutput*dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg)
{
	int format, blockalign, nb_bufs, frag_spec;
	long flags;
	OSSCTX();

	if (!ctx) return GF_BAD_PARAM;
	/* reset and reopen audio-device */
	ioctl(ctx->audio_dev,SNDCTL_DSP_RESET,NULL);
	close(ctx->audio_dev);
	if (ctx->wav_buf) gf_free(ctx->wav_buf);
	ctx->wav_buf = NULL;
	ctx->audio_dev=open(OSS_AUDIO_DEVICE,O_WRONLY|O_NONBLOCK);
	if (!ctx->audio_dev) return GF_IO_ERR;

	/* Make the file descriptor use blocking writes with fcntl() so that
	 we don't have to handle sleep() ourselves*/
	flags = fcntl(ctx->audio_dev, F_GETFL);
	flags &= ~O_NONBLOCK;
	if (fcntl(ctx->audio_dev, F_SETFL, flags) < 0 ) return GF_IO_ERR;

	ctx->nb_ch = (int) (*NbChannels);
	if (ioctl(ctx->audio_dev, SNDCTL_DSP_CHANNELS, &ctx->nb_ch)==-1) return GF_IO_ERR;

	blockalign = ctx->nb_ch;
	if ((*nbBitsPerSample) == 16) {
		blockalign *= 2;
		format = AFMT_S16_LE;
	} else {
		format = AFMT_S8;
	}
	if(ioctl(ctx->audio_dev, SNDCTL_DSP_SETFMT,&format)==-1) return GF_IO_ERR;
	ctx->sr = (*SampleRate);
	if(ioctl(ctx->audio_dev, SNDCTL_DSP_SPEED,&ctx->sr)==-1) return GF_IO_ERR;

	nb_bufs = ctx->num_buffers ? ctx->num_buffers : 8;
	ctx->buf_size = (*SampleRate * blockalign * ctx->total_duration) / (1000 * nb_bufs);
	frag_spec = 4;
	while (ctx->buf_size > (1<<(frag_spec+1)))
		frag_spec++;

	ctx->buf_size = 1<<frag_spec;

	ctx->delay = (1000*ctx->buf_size) / (*SampleRate * blockalign);
	frag_spec = ((nb_bufs<<16) & 0xFFFF0000) | frag_spec;

	ctx->delay = (1000*ctx->buf_size*nb_bufs) / (*SampleRate * blockalign);
	if ( ioctl(ctx->audio_dev, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0 ) return GF_IO_ERR;

	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[OSS] setup %d buffers %d bytes each (%d ms buffer delay)", nb_bufs, ctx->buf_size, ctx->delay));
	ctx->wav_buf = gf_realloc(ctx->wav_buf, ctx->buf_size*sizeof(char));
	if(!ctx->wav_buf) return GF_OUT_OF_MEM;
	memset(ctx->wav_buf, 0, ctx->buf_size*sizeof(char));
	return GF_OK;
}

static void OSS_WriteAudio(GF_AudioOutput*dr)
{
	u32 written;
	OSSCTX();
	written = dr->FillBuffer(dr->audio_renderer, ctx->wav_buf, ctx->buf_size);
	/*this will also perform sleep*/
	if (written) {
		u32 reallyWritten = write(ctx->audio_dev, ctx->wav_buf, written);
		if (reallyWritten != written) {
			GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[OSS] Failed to write all audio to device, has written %u, should have %u", reallyWritten, written));
		}
	}
}

static void OSS_SetVolume(GF_AudioOutput*dr, u32 Volume) {}
static void OSS_SetPan(GF_AudioOutput*dr, u32 Pan) {}
static void OSS_SetPriority(GF_AudioOutput*dr, u32 Priority) {}
static u32 OSS_GetAudioDelay(GF_AudioOutput*dr)
{
	OSSCTX()
	return ctx->delay;
}

/*
 * to get the best matching samplerate the oss-device can be set up
 * with the desired sr. if not supported the returned value contains the
 * best matching sr.
 *
 * todo: supported samplerate could depend on nb_channels and format
 */
static GF_Err OSS_QueryOutputSampleRate(GF_AudioOutput*dr, u32 *desired_sr, u32 *NbChannels, u32 *nbBitsPerSample)
{
#ifdef FORCE_SR_LIMIT
	*NbChannels = 2;
	if (!( *desired_sr % 11025)) return GF_OK;
	if (*desired_sr<22050) *desired_sr = 22050;
	else *desired_sr = 44100;
	return GF_OK;
#else
	/* reset and reopen audio-device */
	int i;
	OSSCTX();
	if (ctx->force_sr) {
		*desired_sr = ctx->force_sr;
		return GF_OK;
	}
	i=*desired_sr;
	if(ioctl(ctx->audio_dev, SNDCTL_DSP_SPEED,&i)==-1) return GF_IO_ERR;
	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[OSS] uses samplerate %d for desired sr %d\n", i, *desired_sr));
	*desired_sr = i;
	i = *NbChannels;
	if(ioctl(ctx->audio_dev,SNDCTL_DSP_CHANNELS, &i)==-1) return GF_IO_ERR;
	GF_LOG(GF_LOG_DEBUG, GF_LOG_MMIO, ("[OSS] uses %d channels for %d desired ones\n", i, *NbChannels));
	*NbChannels = i;
	if(ioctl(ctx->audio_dev, SNDCTL_DSP_SPEED,&ctx->sr)==-1) return GF_OK;
	if(ioctl(ctx->audio_dev,SNDCTL_DSP_CHANNELS, &ctx->nb_ch)==-1) return GF_OK;
	return GF_OK;
#endif
}

void *NewOSSRender()
{
	OSSContext *ctx;
	GF_AudioOutput*driv;
	ctx = gf_malloc(sizeof(OSSContext));
	if(!ctx)
		return NULL;
	memset(ctx, 0, sizeof(OSSContext));
	driv = gf_malloc(sizeof(GF_AudioOutput));
	if(!driv)
	{
		gf_free(ctx);
		ctx=NULL;
		return NULL;
	}
	memset(driv, 0, sizeof(GF_AudioOutput));
	driv->opaque = ctx;
	driv->SelfThreaded = 0;
	driv->Setup = OSS_Setup;
	driv->Shutdown = OSS_Shutdown;
	driv->ConfigureOutput = OSS_ConfigureOutput;
	driv->GetAudioDelay = OSS_GetAudioDelay;
	driv->SetVolume = OSS_SetVolume;
	driv->SetPan = OSS_SetPan;
	driv->SetPriority = OSS_SetPriority;
	driv->QueryOutputSampleRate = OSS_QueryOutputSampleRate;
	driv->WriteAudio = OSS_WriteAudio;

	GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "OSS Audio Output", "gpac distribution");
	return driv;
}

void DeleteOSSRender(void *ifce)
{
	GF_AudioOutput*dr = (GF_AudioOutput*) ifce;
	OSSContext *ctx = (OSSContext *)dr->opaque;
	gf_free(ctx);
	gf_free(dr);
}


/*
 * ********************************************************************
 * interface
 */
GPAC_MODULE_EXPORT
const u32 *QueryInterfaces()
{
	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 NewOSSRender();
	return NULL;
}

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

GPAC_MODULE_STATIC_DECLARATION( oss )
