/*
 *  Persistent data support
 *  Copyright (c) by Jaroslav Kysela <perex@suse.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.
 *
 */

#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "persist.h"

/*

 */

struct persist {
	char *key;
	char *data;
	int size;	/* size of data with key and this structure */
	struct persist *next;
};

/*

 */

static struct persist *persist_first = NULL;
static struct persist *persist_last = NULL;
#if LinuxVersionCode(2, 3, 1) <= LINUX_VERSION_CODE
static DECLARE_MUTEX(persist_mutex);
#else
static struct semaphore persist_mutex = MUTEX;
#endif

/*

 */

struct persist *persist_find_key(char *key, struct persist **prev)
{
	struct persist *persist = persist_first;

	if (prev)
		*prev = NULL;
	while (persist) {
		if (!strcmp(persist->key, key))
			return persist;
		if (prev)
			*prev = persist;
		persist = persist->next;
	}
	return NULL;
}

/*

 */

void persist_lock(int up)
{
	if (up) {
		MOD_INC_USE_COUNT;
	} else {
		MOD_DEC_USE_COUNT;
	}
}

int persist_store(char *key, const char *data, int data_len)
{
	struct persist *persist, *prev;
	int size;

#if 0
	printk("persist_store: key '%s', data_len = %i\n", key, data_len);
#endif
	down(&persist_mutex);
	persist = persist_find_key(key, &prev);
	if (persist) {
		if (prev) {
			prev->next = persist->next;
			if (persist_last == persist)
				persist_last = prev;
		} else {
			persist_first = persist->next;
			if (!persist_first)
				persist_last = NULL;
		}
		size = sizeof(struct persist) + strlen(key) + 1 + data_len;
		if (size != persist->size) {
			kfree(persist);
			persist = NULL;
		}
	}
	if (!persist) {
		size = sizeof(struct persist) + strlen(key) + 1 + data_len;
		persist = (struct persist *) kmalloc(size, GFP_KERNEL);
		if (!persist) {
			up(&persist_mutex);
			return -ENOMEM;
		}
	}
	memset(persist, 0, sizeof(struct persist));
	persist->size = sizeof(struct persist);
	strcpy(persist->key = ((char *) persist) + persist->size, key);
	persist->size += strlen(persist->key) + 1;
	persist->data = ((char *)persist) + persist->size;
	memcpy(persist->data, data, data_len);
	persist->size += data_len;
	if (!persist_first) {
		persist_first = persist_last = persist;
	} else {
		persist_last->next = persist;
		persist_last = persist;
	}
	up(&persist_mutex);
	return 0;
}

int persist_restore(char *key, char *data, int data_len)
{
	int tmp;
	struct persist *persist;

	down(&persist_mutex);
	persist = persist_find_key(key, NULL);
	if (!persist) {
		up(&persist_mutex);
		return -ENOENT;
	}
	tmp = persist->size - sizeof(struct persist) - strlen(key) - 1;
	if (tmp < data_len)
		data_len = tmp;
	memcpy(data, persist->data, data_len);
	up(&persist_mutex);
	return data_len;
}

int persist_length(char *key)
{
	int tmp;
	struct persist *persist;

	down(&persist_mutex);
	persist = persist_find_key(key, NULL);
	if (!persist) {
		up(&persist_mutex);
		return -ENOENT;
	}
	tmp = persist->size - sizeof(struct persist) - strlen(key) - 1;
	up(&persist_mutex);
	return tmp;
}

int persist_present(char *key)
{
	struct persist *persist;

	down(&persist_mutex);
	persist = persist_find_key(key, NULL);
	up(&persist_mutex);
	return persist ? 1 : 0;
}

int persist_remove(char *key)
{
	struct persist *persist, *prev;

	down(&persist_mutex);
	persist = persist_find_key(key, &prev);
	if (!persist) {
		up(&persist_mutex);
		return -ENOENT;
	}
	if (prev) {
		prev->next = persist->next;
		if (persist_last == persist)
			persist_last = prev;
	} else {
		persist_first = persist->next;
		if (!persist_first)
			persist_last = NULL;
	}
	kfree(persist);
	up(&persist_mutex);
	return 0;
}

/*
 *  module
 */

#ifdef MODULE

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_persist_export;
#endif

int init_module(void)
{
#ifndef LINUX_2_1
	if (register_symtab(&snd_symbol_table_persist_export) < 0)
		return -ENOMEM;
#endif
	return 0;
}

void cleanup_module(void)
{
	struct persist *persist, *next;

	for (persist = persist_first; persist; persist = next) {
		next = persist->next;
#if 0
		printk("persist: removing '%s', %i bytes\n", persist->key, persist->size);
#endif
		persist_remove(persist->key);
	}
}

#endif				/* MODULE */
