/*
 *  Routines for driver control
 *  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 "driver.h"
#include "control.h"

struct snd_stru_control_ioctl {
	snd_control_ioctl_t fioctl;
	struct snd_stru_control_ioctl *next;
};

static struct snd_stru_control_ioctl *snd_first_ioctl = NULL;

static snd_open_t snd_control_open;
static int snd_control_open(void *data, snd_ocb_t *ocb )
{
	snd_card_t *card = (snd_card_t *)data;
	snd_control_t *control;

	if (!card)
		return -ENXIO;
	control = (snd_control_t *) snd_calloc(sizeof(snd_control_t));
	if (!control)
		return -ENOMEM;
	control->card = card;
	ocb->private_data = control;
	return 0;
}

static snd_close_t snd_control_close;
static int snd_control_close(snd_ocb_t *ocb )
{
	snd_control_t *control;

	if (ocb->private_data) {
		control = (snd_control_t *) ocb->private_data;
		ocb->private_data = NULL;
		snd_free(control, sizeof(snd_control_t));
	}
	return 0;
}

static int snd_control_hw_info(snd_card_t * card, snd_control_t * control, unsigned int cmd, unsigned long arg)
{
	struct snd_stru_control_ioctl *p;
	struct snd_ctl_hw_info info;

	memset(&info, 0, sizeof(info));

	snd_mutex_lock(card, control);
	info.type = card->type;
	strncpy(info.id, card->id, 8);
	strncpy(info.abbreviation, card->abbreviation, sizeof(info.abbreviation) - 1);
	strncpy(info.name, card->shortname, sizeof(info.name) - 1);
	strncpy(info.longname, card->longname, sizeof(info.longname) - 1);
	info.switches = snd_switch_count(&card->switches);
	for (p = snd_first_ioctl; p; p = p->next)
		p->fioctl(card, control, cmd, (unsigned long) &info);
	snd_mutex_unlock(card, control);
	memcpy((void *) arg, &info, sizeof(struct snd_ctl_hw_info));
	return sizeof(struct snd_ctl_hw_info);
}

static int snd_control_switch_read(snd_card_t * card, snd_switch_t *_sw)
{
	return snd_switch_read(&card->switches, card, _sw);
}

static int snd_control_switch_write(snd_card_t * card, snd_switch_t *_sw)
{
	static char *root_only[] =
	{
		SND_CTL_SW_JOYSTICK,
		SND_CTL_SW_JOYSTICK_ADDRESS,
		NULL
	};
	snd_switch_t sw;
	char **root_str = root_only;

	memcpy(&sw, _sw, sizeof(sw));
	while (*root_str) {
		if (!strcmp(sw.name, *root_str)) {
//			if (current->euid != 0 && current->egid != 0) {
//				snd_mutex_up(card, control);
//				return -EPERM;
//			}
		}
		root_str++;
	}
	return snd_switch_write(&card->switches, card, _sw);
}

static snd_ioctl_t snd_control_ioctl;
static int snd_control_ioctl(snd_ocb_t *ocb, unsigned int cmd, unsigned long arg, int len)
{
	snd_control_t *control;
	snd_card_t *card;
	struct snd_stru_control_ioctl *p;
	int err, tmp;

	control = (snd_control_t *) ocb->private_data;
	if (!control)
		return -EINVAL;
	card = control->card;
	if (!card)
		return -EINVAL;
	switch (cmd) {
	case SND_CTL_IOCTL_PVERSION:
		return snd_ioctl_out((long *) arg, SND_CTL_VERSION);
	case SND_CTL_IOCTL_HW_INFO:
		return snd_control_hw_info(card, control, cmd, arg);
	case SND_CTL_IOCTL_SWITCHES:
		*(int *)arg = snd_switch_count(&card->switches);
		return sizeof(int);
	case SND_CTL_IOCTL_SWITCH_READ:
		if ((tmp = snd_control_switch_read(card, (snd_switch_t *) arg)) < 0)
			return tmp;
		return sizeof(snd_switch_t);
	case SND_CTL_IOCTL_SWITCH_WRITE:
		return snd_control_switch_write(card, (snd_switch_t *) arg);
	}
	snd_mutex_lock(card, control);
	for (p = snd_first_ioctl; p; p = p->next) {
		err = p->fioctl(card, control, cmd, arg);
		if (err != -EAGAIN) {
			snd_mutex_unlock(card, control);
			return err;
		}
	}
	snd_mutex_unlock(card, control);
	return -ENXIO;
}

int snd_control_register_ioctl(snd_control_ioctl_t fcn)
{
	struct snd_stru_control_ioctl *p, *pn;

	pn = (struct snd_stru_control_ioctl *)
			snd_calloc(sizeof(struct snd_stru_control_ioctl));
	if (!pn)
		return -ENOMEM;
	pn->fioctl = fcn;
	if (!snd_first_ioctl) {
		snd_first_ioctl = pn;
	} else {
		for (p = snd_first_ioctl; p->next; p = p->next);
		p->next = pn;
	}
	return 0;
}

int snd_control_unregister_ioctl(snd_control_ioctl_t fcn)
{
	struct snd_stru_control_ioctl *p, *pn, *po;

	if (!snd_first_ioctl) {
		return -EINVAL;
	}
	p = snd_first_ioctl;
	if (p->fioctl == fcn) {
		snd_first_ioctl = p->next;
		snd_free(p, sizeof(struct snd_stru_control_ioctl));
		return 0;
	}
	for (; p; p = pn) {
		pn = p->next;
		if (pn && pn->fioctl == fcn) {
			po = pn;
			p->next = pn = pn->next;
			snd_free(po, sizeof(struct snd_stru_control_ioctl));
			return 0;
		}
	}
	return -EINVAL;
}

int snd_control_switch_add(snd_card_t * card, snd_kswitch_t * ksw)
{
	return snd_switch_add(&card->switches, ksw);
}

int snd_control_switch_remove(snd_card_t * card, snd_kswitch_t * ksw)
{
	return snd_switch_remove(&card->switches, ksw);
}

snd_kswitch_t *snd_control_switch_new(snd_card_t * card, snd_kswitch_t * ksw, void *private_data)
{
	snd_kswitch_t *sw;
	
	sw = snd_switch_new(ksw);
	if (!sw)
		return NULL;
	if (snd_control_switch_add(card, sw) < 0) {
		snd_switch_free_one(sw);
		return NULL;
	}
	sw->private_data = private_data;
	return sw;
}

/*
 *  INIT PART
 */

static snd_io_t snd_control_io =
{
	"control",

	NULL,
	NULL,
	snd_control_open,	/* open */
	snd_control_close, /* close */
	NULL,
	snd_control_ioctl,	/* ioctl */
};

void snd_control_store(snd_card_t * card)
{
	int size;
	unsigned int pos;
	long err = 0;
	char *buffer;
	snd_kswitch_list_t *lswitch;
	char key[32];
	
	lswitch = &card->switches;
	snd_switch_lock(lswitch, 0);
	size = sizeof(unsigned) +
	       sizeof(card->id) + sizeof(card->longname) +
	       snd_switch_size(lswitch);
	buffer = (char *) snd_malloc(size);
	if (!buffer) {
		snd_printk("snd: cannot store control settings - no enough memory\n" );
		snd_switch_lock(lswitch, 1);
		return;
	}
	pos = 0;
	memcpy(buffer + pos, card->id, sizeof(card->id));
	pos += sizeof(card->id);
	memcpy(buffer + pos, card->longname, sizeof(card->longname));
	pos += sizeof(card->longname);
//	err = snd_switch_store(lswitch, card);
	snd_switch_lock(lswitch, 1);
	if (err < 0) {
		snd_printd("cannot store control switch settings\n");
		snd_free(buffer, size);
		return;
	}
	sprintf(key, "snd:%i/control", card->number);
//	snd_persist_store(key, buffer, size);
	snd_free(buffer, size);
}

void snd_control_restore(snd_card_t * card)
{
	int size = 0;
	unsigned int pos;
//	long err;
	char *buffer;
	snd_kswitch_list_t *lswitch;
	char key[32];
	
	lswitch = &card->switches;
	sprintf(key, "snd:%i/control", card->number);
//	size = snd_persist_length(key);
	if (size <= 0)
		return;
	buffer = (char *) snd_malloc(size);
	if (!buffer) {
		snd_printk("cannot restore control settings - no enough memory\n" );
		return;
	}
//	size = snd_persist_restore(key, buffer, size);
	/* ok. I'm very paranoid in bellow code */
	if (size < (int)(sizeof(card->id) + sizeof(card->longname))) {
		snd_free(buffer, size);
		return;
	}
	pos = 0;
	if (strcmp(buffer + pos, card->id)) {
		snd_free(buffer, size);
		return;
	}
	pos += sizeof(card->id);
	if (strcmp(buffer + pos, card->longname)) {
		snd_free(buffer, size);
		return;
	}
	pos += sizeof(card->longname);
	snd_switch_lock(lswitch, 0);
//	err = snd_switch_restore(lswitch, card);
	snd_switch_lock(lswitch, 1);
//	if (err < 0)
//		snd_printd("cannot restore control switch settings\n");
	snd_free(buffer, size);
}

int snd_control_register(snd_card_t *card)
{
	int cardnum, err = 0;
	char name[16];
	
	if (!card)
		return -EINVAL;
	cardnum = card->number;
	if (cardnum < 0 || cardnum >= SND_CARDS)
		return -EINVAL;
	sprintf(name, "sndctl%i", cardnum);
	if ((card->attr = snd_register_io(card, name, &snd_control_io)) == NULL)
		err = -errno;
	return err;
}

int snd_control_unregister(snd_card_t *card)
{
	int cardnum;

	if (!card)
		return -EINVAL;
	cardnum = card->number;
	if (cardnum < 0 || cardnum >= SND_CARDS)
		return -EINVAL;
	snd_unregister_io(card->attr);
	return 0;
}
