/*
 * Copyright (C) 2001		faster	(lqx@cic.tsinghua.edu.cn)
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
#include "common/setup_before.h"
#include "setup.h"

#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
#else
# ifdef HAVE_MALLOC_H
#  include <malloc.h>
# endif
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#else
# ifdef HAVE_STRINGS_H
#  include <strings.h>
# endif
# ifdef HAVE_MEMORY_H
#  include <memory.h>
# endif
#endif
#include <limits.h>
#include <ctype.h>

#include "charlock.h"
#include "common/introtate.h"
#include "common/setup_after.h"

/* Fixme: for simplification, no multiple realm support now */

/* local functions */
static int cl_insert_to_gsq_list(unsigned int gsid, t_charlockinfo *pcl);
static int cl_delete_from_gsq_list(t_charlockinfo *pcl);
static unsigned int string_hash(char const *string);


/* variables */
static unsigned int		clitbl_len = 0;
static unsigned int		gsqtbl_len = 0;
static t_charlockinfo		* * clitbl = NULL;
static t_charlockinfo		* * gsqtbl = NULL;


int cl_init(unsigned int tbllen, unsigned int maxgs)
{
	if (!tbllen || !maxgs) return -1;
	cl_destroy();

	clitbl = (t_charlockinfo**)malloc(tbllen*sizeof(t_charlockinfo**));
	if (!clitbl) return -1;
	gsqtbl = (t_charlockinfo**)malloc(maxgs*sizeof(t_charlockinfo**));
	if (!gsqtbl) {
		free(clitbl);
		return -1;
	}
	memset(clitbl, 0, tbllen*sizeof(t_charlockinfo**));
	memset(gsqtbl, 0, maxgs*sizeof(t_charlockinfo**));
	clitbl_len = tbllen;
	gsqtbl_len = maxgs;
	return 0;
}


int cl_destroy(void)
{
	unsigned int	i;
	t_charlockinfo	* ptl, * ptmp;

	if (clitbl) {
		for (i=0; i<clitbl_len; i++) {
			ptl=clitbl[i];
			while (ptl) {
				ptmp=ptl;
				ptl=ptl->next;
				free(ptmp);
			}
		}
		free(clitbl);
	}
	if (gsqtbl) free(gsqtbl);
	clitbl = gsqtbl = NULL;
	clitbl_len = gsqtbl_len = 0;
	return 0;
}


int cl_query_charlock_status(unsigned char *charname,
			unsigned char *realmname, unsigned int *gsid)
{
	t_charlockinfo	*pcl;
	unsigned int	hashval;

	if (!charname || !realmname) return -1;
	if (!clitbl_len || !gsqtbl) return -1;
	if (strlen(charname)>=MAX_CHARNAME_LEN) return -1;

	hashval = string_hash(charname) % clitbl_len;
	pcl = clitbl[hashval];
	while(pcl)
	{
		if (strcasecmp(pcl->charname, charname) == 0) {
			*gsid = pcl->gsid;
			return 1;	/* found the char, it is locked */
		}
		pcl = pcl->next;
	}
	return 0;	/* not found, it is unlocked */
}


int cl_lock_char(unsigned char *charname,
			unsigned char *realmname, unsigned int gsid)
{
	t_charlockinfo	*pcl, *ptmp;
	unsigned int	hashval;

	if (!charname || !realmname) return -1;
	if (!clitbl_len || !gsqtbl) return -1;
	if (strlen(charname)>=MAX_CHARNAME_LEN) return -1;

	hashval = string_hash(charname) % clitbl_len;
	pcl = clitbl[hashval];
	ptmp = NULL;
	while(pcl)
	{
		if (strcasecmp(pcl->charname, charname) == 0)
			return 0;	/* found the char is already locked */
		ptmp = pcl;
		pcl = pcl->next;
	}

	/* not found, locked it */
	pcl = (t_charlockinfo*)malloc(sizeof(t_charlockinfo));
	if (!pcl) return -1;	/* no free memory available :( */
	memset(pcl, 0, sizeof(t_charlockinfo));
	strncpy(pcl->charname, charname, MAX_CHARNAME_LEN-1);
	strncpy(pcl->realmname, realmname, MAX_REALMNAME_LEN-1);
	pcl->gsid = gsid;

	/* add to hash table link list */
	if (ptmp) ptmp->next = pcl;
	else clitbl[hashval] = pcl;
	/* add to gs specified link list */
	cl_insert_to_gsq_list(gsid, pcl);

	return 0;
}


int cl_unlock_char(unsigned char *charname, unsigned char *realmname)
{
	t_charlockinfo	*pcl, *ptmp;
	unsigned int	hashval;

	if (!charname || !realmname) return -1;
	if (!clitbl_len || !gsqtbl) return 0;
	if (strlen(charname)>=MAX_CHARNAME_LEN) return -1;

	hashval = string_hash(charname) % clitbl_len;
	pcl = clitbl[hashval];
	ptmp = NULL;
	while(pcl)
	{
		if (strcasecmp(pcl->charname, charname) == 0) {
			cl_delete_from_gsq_list(pcl);
			if (ptmp) ptmp->next = pcl->next;
			else clitbl[hashval] = pcl->next;
			free(pcl);
			return 0;
		}
		ptmp = pcl;
		pcl = pcl->next;
	}
	return 0;
}


int cl_unlock_all_char_by_gsid(unsigned int gsid)
{
	unsigned int	index_pos;
	t_charlockinfo	*pcl, *pnext;

	index_pos = gsid % gsqtbl_len;
	pcl = gsqtbl[index_pos];
	while(pcl)
	{
		pnext = pcl->gsqnext;
		cl_unlock_char(pcl->charname, pcl->realmname);
		pcl = pnext;
	}
	return 0;
}


static int cl_insert_to_gsq_list(unsigned int gsid, t_charlockinfo *pcl)
{
	unsigned int	index_pos;
	t_charlockinfo	*ptmp;

	if (!pcl) return -1;

	index_pos = gsid % gsqtbl_len;
	ptmp = gsqtbl[index_pos];
	gsqtbl[index_pos] = pcl;
	if (ptmp) {
		pcl->gsqnext = ptmp;
		ptmp->gsqprev = pcl;
	}
	return 0;
}


static int cl_delete_from_gsq_list(t_charlockinfo *pcl)
{
	unsigned int	index_pos;
	t_charlockinfo	*pprev, *pnext;

	if (!pcl) return -1;

	index_pos = (pcl->gsid) % gsqtbl_len;
	pprev = pcl->gsqprev;
	pnext = pcl->gsqnext;
	if (pprev) pprev->gsqnext = pnext;
	else gsqtbl[index_pos] = pnext;
	if (pnext) pnext->gsqprev = pprev;

	return 0;
}


static unsigned int string_hash(char const *string)
{
	unsigned int	i;
	unsigned int	pos;
	unsigned int	hash;
	unsigned int	ch;

	if (!string) return 0;

	for (hash=0,pos=0,i=0; i<strlen(string); i++)
	{
		if (isascii((int)string[i]))
			ch = (unsigned int)(unsigned char)tolower((int)string[i]);
		else
			ch = (unsigned int)(unsigned char)string[i];
		hash ^= ROTL(ch,pos,sizeof(unsigned int)*CHAR_BIT);
		pos += CHAR_BIT-1;
	}

	return hash;
}

