/*
	aix: Driver for IBM RS/6000 with AIX Ultimedia Services

	copyright ?-2006 by the mpg123 project - free software under the terms of the LGPL 2.1
	see COPYING and AUTHORS files in distribution or http://mpg123.org
	initially written by Juergen Schoew and Tomas Oegren
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include <errno.h>
#include <fcntl.h>
#include <sys/audio.h>
#include <stropts.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/param.h>

#include "mpg123app.h"
#include "debug.h"

/* use AUDIO_BSIZE to set the msec for audio buffering in Ultimedia library
 */
/* #define AUDIO_BSIZE AUDIO_IGNORE */
#define AUDIO_BSIZE 200

static int rate_best_match(audio_output_t *ao)
{
	static long valid [ ] = {  5510,  6620,  8000,  9600, 11025, 16000, 18900,
							22050, 27420, 32000, 33075, 37800, 44100, 48000, 0 };
	int  i = 0;
	long best = 8000;

	if(!ai || ao->fn < 0 || ao->rate < 0) {
		return -1;
	} 
	
	while (valid [i]) {
		if (abs(valid[i] - ao->rate) < abs(best - ao->rate))
		{
			best = valid [i];
		}
		i = i + 1;
	}
	
	ao->rate = best;
	return best;
}

static int reset_parameters(audio_output_t *ao)
{
	audio_control  acontrol;
	audio_change   achange;
	audio_init     ainit;
	int ret;

	memset ( & achange, '\0', sizeof (achange));
	memset ( & acontrol, '\0', sizeof (acontrol));
	
	achange.balance        = 0x3fff0000;
	achange.balance_delay  = 0;
	achange.volume         = (long) (0x7fff << 16);
	achange.volume_delay   = 0;
	achange.input          = AUDIO_IGNORE;
	if (ao->flags == -1) achange.output = INTERNAL_SPEAKER;
	else achange.output      = 0;
	
	if(ao->flags & AUDIO_OUT_INTERNAL_SPEAKER)
	achange.output     |= INTERNAL_SPEAKER;
	if(ao->flags & AUDIO_OUT_HEADPHONES)
	achange.output     |= EXTERNAL_SPEAKER;
	if(ao->flags & AUDIO_OUT_LINE_OUT)
	achange.output     |= OUTPUT_1;
	if(ao->flags == 0)
	achange.output      = AUDIO_IGNORE;

	achange.treble         = AUDIO_IGNORE;
	achange.bass          = AUDIO_IGNORE;
	achange.pitch          = AUDIO_IGNORE;
	achange.monitor        = AUDIO_IGNORE;
	achange.dev_info       = (char *) NULL;
	
	acontrol.ioctl_request = AUDIO_CHANGE;
	acontrol.position      = 0;
	acontrol.request_info  = (char *) & achange;
	
	ret = ioctl (ao->fn, AUDIO_CONTROL, & acontrol);
	if (ret < 0)
	return ret;
	
	/* Init Device for new values */
	if (ao->rate >0) {
		memset ( & ainit, '\0', sizeof (ainit));
		ainit.srate                 = rate_best_match(ai);
		if (ao->channels > 0)
		ainit.channels          = ao->channels;
		else
		ainit.channels           = 1;
		switch (ao->format) {
			default :
				ainit.mode             = PCM;
				ainit.bits_per_sample  = 8;
				ainit.flags            = BIG_ENDIAN | TWOS_COMPLEMENT;
			break;
			case MPG123_ENC_SIGNED_16:
				ainit.mode             = PCM;
				ainit.bits_per_sample  = 16;
				ainit.flags            = BIG_ENDIAN | TWOS_COMPLEMENT;
			break;
			case MPG123_ENC_SIGNED_8:
				ainit.mode             = PCM;
				ainit.bits_per_sample  = 8;
				ainit.flags            = BIG_ENDIAN | TWOS_COMPLEMENT;
			break;
			case MPG123_ENC_UNSIGNED_16:
				ainit.mode             = PCM;
				ainit.bits_per_sample  = 16;
				ainit.flags            = BIG_ENDIAN | TWOS_COMPLEMENT | SIGNED;
			break;
			case MPG123_ENC_UNSIGNED_8:
				ainit.mode             = PCM;
				ainit.bits_per_sample  = 8;
				ainit.flags            = BIG_ENDIAN | TWOS_COMPLEMENT | SIGNED;
			break;
			case MPG123_ENC_ULAW_8:
				ainit.mode             = MU_LAW;
				ainit.bits_per_sample  = 8;
				ainit.flags            = BIG_ENDIAN | TWOS_COMPLEMENT;
			break;
			case MPG123_ENC_ALAW_8:
				ainit.mode             = A_LAW;
				ainit.bits_per_sample  = 8;
				ainit.flags            = BIG_ENDIAN | TWOS_COMPLEMENT;
			break;
		}
		ainit.operation            = PLAY;
		ainit.bsize                = AUDIO_BSIZE;
		
		ret = ioctl (ao->fn, AUDIO_INIT, & ainit);
		if (ret < 0) {
			error("Can't set new audio parameters!");
			return ret;
		}
	}
	
	acontrol.ioctl_request   = AUDIO_START;
	acontrol.request_info    = NULL;
	acontrol.position        = 0;
	
	ret = ioctl (ao->fn, AUDIO_CONTROL, & acontrol);
	if (ret < 0) {
	error("Can't reset audio!");
	return ret;
	}
	return 0;
}

static int open_aix(audio_output_t *ao)
{
	audio_init ainit;
	int ret;

	if(!ao->device) {
		if(getenv("AUDIODEV")) {
			ao->device = getenv("AUDIODEV");
			ao->fn = open(ao->device,O_WRONLY);
		} else {
			ao->device = "/dev/paud0/1";                   /* paud0 for PCI */
			ao->fn = open(ao->device,O_WRONLY);
			if ((ao->fn == -1) & (errno == ENOENT)) {
				ao->device = "/dev/baud0/1";                 /* baud0 for MCA */
				ao->fn = open(ao->device,O_WRONLY);
			}  
		}
	} else ao->fn = open(ao->device,O_WRONLY);
	
	if(ao->fn < 0) {
		error("Can't open audio device!");
		return ao->fn;
	}
	
	/* Init to default values */
	memset ( & ainit, '\0', sizeof (ainit));
	ainit.srate            = 44100;
	ainit.channels         = 2;
	ainit.mode             = PCM;
	ainit.bits_per_sample  = 16;
	ainit.flags            = BIG_ENDIAN | TWOS_COMPLEMENT;
	ainit.operation        = PLAY;
	ainit.bsize            = AUDIO_BSIZE;
	
	ret = ioctl (ao->fn, AUDIO_INIT, & ainit);
	if (ret < 0) return ret;
	
	reset_parameters(ai);
	return ao->fn;
}



static int get_formats_aix(audio_output_t *ao)
{
	/* ULTIMEDIA DOCUMENTATION SAYS:
	The Ultimedia Audio Adapter supports fourteen sample rates you can use to
	capture and playback audio data. The rates are (in kHz): 5.51, 6.62, 8.0,
	9.6, 11.025, 16.0, 18.9, 22.050, 27.42, 32.0, 33.075, 37.8, 44.1, and 48.0.
	These rates are supported for mono and stereo PCM (8- and 16-bit), mu-law,
	and A-law. 
	*/
	
	long rate;
	
	rate = ao->rate;
	rate_best_match(ai);
	if (ao->rate == rate)
		return (MPG123_ENC_SIGNED_16|MPG123_ENC_UNSIGNED_16|
			MPG123_ENC_UNSIGNED_8|MPG123_ENC_SIGNED_8|
			MPG123_ENC_ULAW_8|MPG123_ENC_ALAW_8);
	else
		return 0;
}

static int write_aix(audio_output_t *ao,unsigned char *buf,int len)
{
	return write(ao->fn,buf,len);
}

static int close_aix(audio_output_t *ao)
{
	audio_control acontrol;
	audio_buffer  abuffer;
	int           ret,i;
	
	/* Don't close the audio-device until it's played all its contents */
	memset ( & acontrol, '\0', sizeof ( acontrol ) );
	acontrol.request_info = &abuffer;
	acontrol.position = 0;
	i=50;   /* Don't do this forever on a bad day :-) */
	
	while (i-- > 0) {
		if ((ioctl(ao->fn, AUDIO_BUFFER, &acontrol))< 0) {
			error1("buffer read failed: %d", errno);
			break;
		} else {
			if (abuffer.flags <= 0) break;
		}
		usleep(200000); /* sleep 0.2 sec */
	}
	
	memset ( & acontrol, '\0', sizeof ( acontrol ) );
	acontrol.ioctl_request = AUDIO_STOP;
	acontrol.request_info  = NULL;
	acontrol.position      = 0;
	
	ret = ioctl ( ao->fn, AUDIO_CONTROL, & acontrol );
	if (ret < 0) error("Can't close audio!");
	
	ret = close (ao->fn);
	if (ret < 0) error("Can't close audio!");
	
	return 0;
}

static void flush_aix(audio_output_t *ao)
{
}

static int init_aix(audio_output_t* ao)
{
	if (ao==NULL) return -1;

	/* Set callbacks */
	ao->open = open_aix;
	ao->flush = flush_aix;
	ao->write = write_aix;
	ao->get_formats = get_formats_aix;
	ao->close = close_aix;

	/* Success */
	return 0;
}



/* 
	Module information data structure
*/
mpg123_module_t mpg123_output_module_info = {
	/* api_version */	MPG123_MODULE_API_VERSION,
	/* name */			"AIX",						
	/* description */	"Output audio on IBM RS/6000 with AIX Ultimedia Services.",
	/* revision */		"$Rev: 932 $",						
	/* handle */		NULL,
	
	/* init_output */	init_aix,						
};

