/*
 *  Advanced Linux Sound Architecture
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <sys/rsrcdbmgr.h>
#include "driver.h"

extern dispatch_t *snd_dpp;
static resmgr_io_funcs_t snd_io_funcs;
static resmgr_connect_funcs_t snd_connect_funcs;
static iofunc_mount_t snd_io_mountpt;

static int snd_open(resmgr_context_t *ctp, io_open_t *msg, RESMGR_HANDLE_T *handle, void *extra)
{
	snd_attr_t *snd_attr = (snd_attr_t *)handle;
	snd_ocb_t *ocb;
	snd_open_t *ptr;
	int	ret;

	if ((ocb = snd_calloc (sizeof(snd_ocb_t))) == NULL) {
		snd_printk("couldn't allocate %d bytes\n", sizeof (iofunc_ocb_t));
		return (_RESMGR_ERRNO (ENOMEM));
	}

	if (iofunc_open (ctp, msg, &snd_attr->attr, NULL, NULL) == -1) {
		snd_printk("iofunc_open failed\n");
		snd_free (ocb, sizeof(snd_ocb_t));
		return (_RESMGR_ERRNO (errno));
	}
	if ((ptr = snd_attr->io.open) == NULL) {
		snd_free (ocb, sizeof(snd_ocb_t));
		return (_RESMGR_ERRNO(ENXIO));
	}
	ocb->iofunc_ocb.ioflag = msg->connect.ioflag & (~_IO_FLAG_MASK | msg->connect.access);
	if ((ret = ptr(snd_attr->data, ocb)) < 0 ) {
		snd_free (ocb, sizeof(snd_ocb_t));
		return (_RESMGR_ERRNO(-ret));
	}	
	
	if (iofunc_ocb_attach (ctp, msg, &ocb->iofunc_ocb, &snd_attr->attr, &snd_io_funcs) == -1) {
		snd_printk("ocb_attach failed\n");
		if (snd_attr->io.close)
			snd_attr->io.close(ocb);
		snd_free (ocb, sizeof(snd_ocb_t));
		return (_RESMGR_ERRNO (errno));
	}
	IOFUNC_NOTIFY_INIT(ocb->notify);	
	return(_RESMGR_ERRNO(EOK));
}


static int snd_read(resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb)
{
	snd_attr_t *snd_attr;
	snd_read_t *ptr;
	int nbytes, nonblock, error;
	snd_context_t snd_ctx;

	snd_attr = 	(snd_attr_t *)((iofunc_ocb_t *)ocb)->attr;
	
	if ((ptr = snd_attr->io.read) == NULL)
		return 	_RESMGR_ERRNO(ENXIO);

	if ((error = iofunc_read_verify(ctp, msg, ocb, &nonblock)) != EOK)
		return error;

	snd_ctx.ocb = (snd_ocb_t *)ocb;
	snd_ctx.rctp = ctp;
	snd_ctx.rw_offset = 0;
	snd_ctx.nonblock = nonblock;

	if ((nbytes = ptr(&snd_ctx, msg->i.nbytes)) >= 0 ) {
		_IO_SET_READ_NBYTES(ctp, nbytes);
		return _RESMGR_ERRNO(EOK);
	}
	return _RESMGR_ERRNO(-nbytes);
}

static int snd_write(resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb)
{
	snd_attr_t *snd_attr;
	int nbytes, nonblock, error;
	snd_write_t *ptr;
	snd_context_t snd_ctx;

	snd_attr = 	(snd_attr_t *)((iofunc_ocb_t *)ocb)->attr;

	if ((ptr = snd_attr->io.write) == NULL)
		return _RESMGR_ERRNO(ENXIO);
	
	if ((error = iofunc_write_verify(ctp, msg, ocb, &nonblock)) != EOK)
		return error;

	snd_ctx.ocb = (snd_ocb_t *)ocb;
	snd_ctx.rctp = ctp;
	snd_ctx.rw_offset = sizeof(msg->i);
	snd_ctx.nonblock = nonblock;

	if ((nbytes = ptr(&snd_ctx, msg->i.nbytes)) >= 0 ) {
		_IO_SET_WRITE_NBYTES(ctp, nbytes);
		return _RESMGR_ERRNO(EOK);
	} 
	return _RESMGR_ERRNO(-nbytes);
}
                                                                
static int snd_devctl(resmgr_context_t *ctp, io_devctl_t *msg, RESMGR_OCB_T *ocb)
{
	snd_attr_t *snd_attr;
	snd_ioctl_t *ptr;
	char *buffer, *allocd;
	iov_t		iov;
	long		dcmd;
	int			error, nbytes;
	
	snd_attr = 	(snd_attr_t *)((iofunc_ocb_t *)ocb)->attr;

	if ( ( error = iofunc_devctl(ctp, msg, ocb, &snd_attr->attr ) ) != _RESMGR_DEFAULT )
		return error;

	error = EOK;			
	if ((ptr = snd_attr->io.ioctl) == NULL)
		return _RESMGR_ERRNO(ENXIO);
	
	if (!(get_device_direction(dcmd = msg->i.dcmd) & DEVDIR_TOFROM))
		nbytes = 0, buffer = allocd = NULL;
	else if ((nbytes = msg->i.nbytes) <= ctp->msg_max_size - sizeof(*msg))
		buffer = _DEVCTL_DATA(*msg), allocd = NULL;
	else if ((buffer = allocd = snd_malloc((nbytes + 3) & ~3)) == NULL)
		return (_RESMGR_ERRNO(ENOMEM));
	else if ((get_device_direction(dcmd) & DEVDIR_TO)) {
		SETIOV(&iov, buffer, nbytes);
		if ( resmgr_msgreadv(ctp, &iov, 1, sizeof(msg->i)) == -1 )
			goto error_out;
	}
	if ((nbytes = ptr(ocb, dcmd, (long)buffer, nbytes )) < 0 )
		error = -nbytes;
	msg->o.ret_val = error;
	if (error == EOK && get_device_direction(dcmd) & DEVDIR_FROM ) {
		SETIOV(&iov, buffer, nbytes);
		if ( resmgr_msgwritev(ctp, &iov, 1, sizeof(msg->o)) == -1 )
			goto error_out;
	}
	if (allocd != NULL)
		snd_free(allocd, (nbytes + 3) & ~3);
	return((error == EOK) ? _RESMGR_PTR(ctp, &msg->o, sizeof(msg->o)) : _RESMGR_ERRNO(error));
error_out:
	error = errno;
	if (allocd != NULL)
		snd_free(allocd, (nbytes + 3) & ~3);
	return(_RESMGR_ERRNO(error));
}

static int snd_ionotify(resmgr_context_t *ctp, io_notify_t *msg, RESMGR_OCB_T *ocb)
{
	snd_attr_t *snd_attr;
	snd_ionotify_t *ptr;
	int trig = 0, ret, notify = 0;
	
	snd_attr = 	(snd_attr_t *)((iofunc_ocb_t *)ocb)->attr;

	if ((ptr = snd_attr->io.io_notify) == NULL)
		return _RESMGR_ERRNO(ENXIO);

	if ((ret = ptr((snd_ocb_t*)ocb, &notify)) >= 0 ) {
		if (notify & NOTIFY_INPUT)
			trig |= _NOTIFY_COND_INPUT;
		if(notify & NOTIFY_OUTPUT)
			trig |= _NOTIFY_COND_OUTPUT;
		ret = iofunc_notify(ctp, msg, ((snd_ocb_t *)ocb)->notify, trig, 0, 0);
	} else 
		ret = _RESMGR_ERRNO(-ret);
	return ret;
}

static int snd_lock_ocb(resmgr_context_t *ctp, void *reserved, RESMGR_OCB_T *ocb)
{
	return _RESMGR_ERRNO(EOK);
}

static int snd_unlock_ocb(resmgr_context_t *ctp, void *reserved, RESMGR_OCB_T *ocb)
{
	return _RESMGR_ERRNO(EOK);
}

static int snd_close(resmgr_context_t *ctp, void *resrvd, RESMGR_OCB_T *ocb)
{
	snd_attr_t *snd_attr;
	snd_close_t *ptr;
	int ret;
	
	snd_attr = 	(snd_attr_t *)((iofunc_ocb_t *)ocb)->attr;

	// Cancel any io_notify in effect.
	iofunc_notify_remove(ctp, ((snd_ocb_t*)ocb)->notify);
  
  	if ((ptr = snd_attr->io.close) == NULL) {
		snd_free (ocb, sizeof(snd_ocb_t));
		return _RESMGR_ERRNO(ENXIO);
	}
	
	ret = ptr((snd_ocb_t*)ocb);
	snd_free (ocb, sizeof(snd_ocb_t));
	return _RESMGR_ERRNO(-ret);
}

snd_attr_t *snd_register_io(void *data, char *name, snd_io_t *io)
{
    snd_attr_t 		*snd_attr;
	resmgr_attr_t	attr;
	char 			*dev_name;
			
	if ((snd_attr = snd_calloc(sizeof(snd_attr_t))) == NULL )
		return NULL;
	
	if ((dev_name = snd_malloc(strlen(name) + strlen("/dev/") + 1)) == NULL ) {
		snd_free(snd_attr, sizeof(snd_attr_t));
		return NULL;
	}
	strcpy(dev_name,"/dev/");
	strcat(dev_name, name);
    iofunc_attr_init (&snd_attr->attr, S_IFCHR | 0666, NULL, NULL);
	snd_attr->attr.mount = &snd_io_mountpt;
	snd_attr->attr.rdev = snd_io_mountpt.dev;
	snd_attr->attr.inode = 0;
	snd_attr->data = data;		
	snd_attr->io = *io;

	memset(&attr, 0, sizeof(attr));
	attr.nparts_max = 1;
	snd_attr->id = resmgr_attach(snd_dpp, &attr, dev_name, _FTYPE_ANY, 0, &snd_connect_funcs, &snd_io_funcs, &snd_attr->attr);
	snd_free(dev_name, strlen(name) + strlen("/dev/") + 1);
	if ( snd_attr->id < 0 ) {
		snd_free(snd_attr, sizeof(snd_attr_t));
		return NULL;
	}
	return snd_attr;
}

void snd_unregister_io(snd_attr_t *attr)
{
	resmgr_detach(snd_dpp, attr->id, _RESMGR_DETACH_ALL);
	snd_free(attr, sizeof(snd_attr_t));
}

int snd_copy_to_user(snd_context_t *ctp, void *buffer, int nbytes)
{
	iov_t	iov;

	SETIOV(&iov, buffer, nbytes);
	return (ctp->rw_offset += resmgr_msgwritev(ctp->rctp, &iov, 1, ctp->rw_offset));
}

int snd_copy_from_user(snd_context_t *ctp, void *buffer, int nbytes)
{
	iov_t	iov;

	SETIOV(&iov, buffer, nbytes);
	return (ctp->rw_offset += resmgr_msgreadv(ctp->rctp, &iov, 1, ctp->rw_offset));
}

int snd_ioctl_out( long *arg, long value)
{
	*arg = value;
	return sizeof( long );
}

long snd_ioctl_in( long *arg )
{
	return *arg;
}

void snd_notify_trigger(snd_ocb_t *ocb, int n, int ndx)
{
	int notify = 0;
	if (ndx & NOTIFY_INPUT)
		notify |= IOFUNC_NOTIFY_INPUT;
	if(ndx & NOTIFY_OUTPUT)
		notify |= IOFUNC_NOTIFY_OUTPUT;
	return(iofunc_notify_trigger(ocb->notify, n, notify));
}

void snd_sound_init(void)
{
	    // grab the defaults
	memset(&snd_io_mountpt, 0, sizeof(snd_io_mountpt));
	snd_io_mountpt.dev = rsrcdbmgr_minor_attach(0, -1);

    iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &snd_connect_funcs,
                      _RESMGR_IO_NFUNCS, &snd_io_funcs);

	snd_connect_funcs.open = snd_open;
								  
	snd_io_funcs.read = snd_read;
	snd_io_funcs.write = snd_write;									  
	snd_io_funcs.close_ocb = snd_close;
	snd_io_funcs.devctl = snd_devctl;
	snd_io_funcs.notify = snd_ionotify;
	snd_io_funcs.lock_ocb = snd_lock_ocb;
	snd_io_funcs.unlock_ocb = snd_unlock_ocb;
}

void snd_sound_cleanup(void)
{
//	int idx;
//	snd_minor_t *minor;

#if 0
	for (idx = 0; idx < SND_OS_MINORS; idx++) {
		minor = snd_minors[idx];
		if (minor) {
			if (minor->unregister)
				minor->unregister(idx);
			else
				snd_unregister_minor(idx);
		}
	}
	for (idx = 0; idx < SND_OS_MINORS; idx++) {
		minor = snd_minors[idx];
		if (minor && minor->unregister)
			printk("%s device minor %i not unregistered!!!\n", idx < SND_MINOR_BEGIN ? "Native" : "OSS", idx < SND_MINOR_BEGIN ? idx & SND_MINOR_OSS_MASK : idx);
	}
	rsrcdbmgr_minor_detach(0, snd_io_mountpt.dev);
#endif
}

int snd_start (void)
{
    thread_pool_attr_t		pool_attr;
	thread_pool_t			*tpp;
    pthread_attr_t      	attr;
    sigset_t            	signals;
	siginfo_t       		info;

	memset(&pool_attr, 0, sizeof(pool_attr));
	pool_attr.handle = snd_dpp;
	pool_attr.context_alloc = resmgr_context_alloc;
	pool_attr.block_func = resmgr_block;
	pool_attr.handler_func = resmgr_handler;
	pool_attr.context_free = resmgr_context_free;
	pool_attr.lo_water = 2;                      // min active threads
	pool_attr.hi_water = 4;                      // destroy threads until count
	pool_attr.increment = 1;                     // thread creatation count
	pool_attr.maximum = 50;                      // max threads

	if ((tpp = thread_pool_create(&pool_attr, POOL_FLAG_EXIT_SELF)) == NULL ) {
		perror("thread_pool_create() failed\n");
		return(-1);
	}
	sigfillset(&signals);                   // No signals to resmgr threads
	pthread_sigmask(SIG_BLOCK, &signals, NULL);
	pthread_attr_init(&attr);
	pthread_attr_setstacksize(&attr, 8192);

	if( pthread_create( NULL, &attr, (void *)thread_pool_start, tpp ) ) {
		snd_printk("snd_start:  Unable to initialize resmgr\n" );
		return( EXIT_FAILURE );
	}
	  
	sigfillset( &signals );
	sigdelset( &signals, SIGTERM );
	sigprocmask( SIG_BLOCK, &signals, NULL );
			   
	sigemptyset( &signals );
	sigaddset( &signals, SIGTERM );
	while( SignalWaitinfo( &signals, &info ) == -1 )
		;
	return(EXIT_SUCCESS);
}
