/*
 * playerlist.c
 * 
 * Copyright (C) 2006-2007  Josef Kufner <jk@myserver.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.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "playerlist.h"

#define DEBUG 0
#include "debug.h"

/* player info */
typedef struct {
	char *address;
	char *nick;
	char *role;
	char *score;
	char *conn_stat;
	int state, connected;
	char *description;
} PL_ITEM;

static PL_ITEM *playerlist = NULL;
static int playerlist_size = 4;
static int playerlist_is_sorted = 0;
static int playerlist_keep_roles = 1;

static PL_UPDATE_CALLBACK playerlist_update_callback = NULL;


static void playerlist_enlarge(void)
{
	int old_size = playerlist_size;
	playerlist_size <<= 1;
	DBG_MSG(2, "resizing array from %d to %d items", old_size, playerlist_size);
	playerlist = realloc(playerlist, sizeof(PL_ITEM) * playerlist_size);
	memset(playerlist + old_size, 0, sizeof(PL_ITEM) * old_size);
	if (playerlist == NULL) {
		perror("Player list array realloc failed");
		abort();
	}
}


static int playerlist_item_compare(const void *va, const void *vb)
{
	const PL_ITEM *a = (const PL_ITEM *)va;
	const PL_ITEM *b = (const PL_ITEM *)vb;

	if (a->address && b->address)
		return strcmp(a->address, b->address);
	else
		return (a->address != NULL) - (b->address != NULL);
}


static void playerlist_sort(void)
{
	if (!playerlist_is_sorted) {
		DBG_MSG(5, "start");
		qsort(playerlist, playerlist_size, sizeof(PL_ITEM), playerlist_item_compare);
		DBG_MSG(5, "done");
	}
	playerlist_is_sorted = 1;
}


static PL_ITEM *playerlist_item_lookup(const char *address)
{
	PL_ITEM key;
	key.address = (char *) address;
	
	DBG_MSG(4, "[ %s ]", key.address);
	playerlist_sort();
	return (PL_ITEM *) bsearch(&key, playerlist, playerlist_size, sizeof(PL_ITEM), playerlist_item_compare);
}


static void playerlist_run_update_callback(void)
{
	playerlist_sort();
	if (playerlist_update_callback)
		(*playerlist_update_callback)();
}


void playerlist_init(void)
{
	playerlist_enlarge();
	memset(playerlist, 0, sizeof(PL_ITEM) * playerlist_size / 2);
}


void playerlist_set_update_callback(PL_UPDATE_CALLBACK callback)
{
	playerlist_update_callback = callback;
}


void playerlist_set_keep_roles(int keep)
{
	playerlist_keep_roles = (keep == 1);
}


int playerlist_add(const char *address)
{
	PL_ITEM *a = NULL;
	
	if ((a = playerlist_item_lookup(address)))
		return 0;
	
	if (playerlist[0].address == NULL) {
		DBG_MSG(4, "have empty slot");
		a = &(playerlist[0]);
	} else {
		DBG_MSG(4, "no empty slot");
		playerlist_enlarge();
		a = &(playerlist[playerlist_size - 1]);
	}

	memset(a, 0, sizeof(PL_ITEM));
	a->address = strdup(address);
	playerlist_is_sorted = 0;

	playerlist_run_update_callback();

	return 1;
}


int playerlist_update(const char *address, const char *nick, const char *role, const char *score, const char *conn_stat)
{
	PL_ITEM *a = NULL;
	assert(address != NULL);
	
	DBG_MSG(3, "%s = ( %s, %s, %s, %s )", address, nick, role, score, conn_stat);

	if ((a = playerlist_item_lookup(address))) {
		if (nick) {
			if (a->nick)
				free(a->nick);
			a->nick = nick[0] ? strdup(nick) : NULL;
		}
		if (role) {
			if (a->role)
				free(a->role);
			a->role = role[0] ? strdup(role) : NULL;
		}
		if (score) {
			if (a->score)
				free(a->score);
			a->score = score[0] ? strdup(score) : NULL;
		}
		if (conn_stat) {
			if (a->conn_stat)
				free(a->conn_stat);
			a->conn_stat = conn_stat[0] ? strdup(conn_stat) : NULL;
		}
		
		playerlist_run_update_callback();
		return 1;
	} else {
		DBG_MSG(1, "\"%s\" not found !", address);
	}
	return 0;
}


int playerlist_update_state(const char *address, int state, const char *description)
{
	PL_ITEM *a = NULL;
	assert(address != NULL);

	if ((a = playerlist_item_lookup(address))) {
		a->state = state;
		if (a->description) {
			free(a->description);
		}
		a->description = description ? strdup(description) : NULL;

		playerlist_run_update_callback();
		return 1;
	}
	return 0;
}


int playerlist_update_connected(const char *address, int state)
{
	PL_ITEM *a = NULL;
	assert(address != NULL);

	if ((a = playerlist_item_lookup(address))) {
		a->connected = state;

		playerlist_run_update_callback();
		return 1;
	}
	return 0;
}

int playerlist_update_connected_all(int state)
{
	int i;
	
	for (i = 0; i < playerlist_size; i++) {
		playerlist[i].connected = state == 1;
	}
	playerlist_run_update_callback();
	return 0;
}


static int playerlist_remove_item(PL_ITEM *item)
{
	assert(item != NULL);

	if (item->address) {
		free(item->address);
		item->address = NULL;
		
		if (item->nick)
			free(item->nick);
		if (item->role)
			free(item->role);
		if (item->score)
			free(item->score);
		if (item->conn_stat)
			free(item->conn_stat);
		if (item->description)
			free(item->description);

		return 1;
	} else {
		return 0;
	}
}


int playerlist_remove(const char *address)
{
	PL_ITEM *a = NULL;
	assert(address != NULL);
	
	if ((a = playerlist_item_lookup(address))) {
		if (playerlist_keep_roles && a->role) {
			a->connected = 0;
		} else {
			playerlist_remove_item(a);
			playerlist_is_sorted = 0;
		}
		playerlist_run_update_callback();
		return 1;
	}
	return 0;
}


int playerlist_remove_all(void)
{
	int i;

	for (i = 0; i < playerlist_size; i++) {
		playerlist_remove_item(&(playerlist[i]));
	}
	playerlist_run_update_callback();
	return 0;
}


int playerlist_clean(int not_connected_only, int no_role_only)
{
	int i;

	for (i = 0; i < playerlist_size; i++) {
		if(playerlist[i].address && (!no_role_only || (no_role_only && playerlist[i].role == 0))
				&& (!not_connected_only || (not_connected_only && playerlist[i].connected == 0)))
		{
			playerlist_remove_item(&(playerlist[i]));
			playerlist_is_sorted = 0;
		}
	}
	playerlist_run_update_callback();
	return 0;
}


int playerlist_get(const char *address, char **nick, char **role, char **score, char **conn_stat, int *state, char **description, int *connected)
{
	PL_ITEM *a = NULL;
	assert(address != NULL);
	
	if ((a = playerlist_item_lookup(address))) {
		if (nick)
			*nick = a->nick;
		if (role)
			*role = a->role;
		if (score)
			*score = a->score;
		if (conn_stat)
			*conn_stat = a->conn_stat;
		if (state)
			*state = a->state;
		if (description)
			*description = a->description;
		if (connected)
			*connected = a->connected;
		return 1;
	}
	return 0;
}


const char *playerlist_get_nick(const char *address)
{
	PL_ITEM *i;
	return (i = playerlist_item_lookup(address)) && i->nick ? i->nick : address;
}


int playerlist_list(int *iter, char **address, char **nick, char **role, char **score, char **conn_stat, int *state, char **description, int *connected)
{
	assert(iter != NULL);
	
	DBG_MSG(6, "iter = %d", *iter);
	if (*iter < playerlist_size && *iter >= 0) {
		if (address)
			*address = playerlist[*iter].address;
		if (nick)
			*nick = playerlist[*iter].nick;
		if (role)
			*role = playerlist[*iter].role;
		if (score)
			*score = playerlist[*iter].score;
		if (conn_stat)
			*conn_stat = playerlist[*iter].conn_stat;
		if (state)
			*state = playerlist[*iter].state;
		if (description)
			*description = playerlist[*iter].description;
		if (connected)
			*connected = playerlist[*iter].connected;
	} else {
		if (address)
			*address = NULL;
		if (nick)
			*nick = NULL;
		if (role)
			*role = NULL;
		if (score)
			*score = NULL;
		if (conn_stat)
			*conn_stat = NULL;
		if (state)
			*state = 0;
		if (description)
			*description = NULL;
		if (connected)
			*connected = 0;
	}
	return *iter < playerlist_size;
}


void playerlist_clear_state(void)
{
	int i;

	DBG_MSG(4, "");
	for (i = 0; i < playerlist_size; i++) {
		playerlist[i].state = 0;
	}
	playerlist_run_update_callback();
}


void playerlist_clear_game_info(void)
{
	int i;

	for (i = 0; i < playerlist_size; i++) {
		if (playerlist[i].role)
			free(playerlist[i].role);
		playerlist[i].role = NULL;
	}
	playerlist_run_update_callback();
}


int playerlist_have_all_states(const int *state_mask, int state_mask_len)
{
	int i;

	/* check if all have 0 (no playing) or `state` */
	DBG_MSG(4, "");
	for (i = 0; i < playerlist_size; i++) {
		if (playerlist[i].address && playerlist[i].connected) {
			if (state_mask[playerlist[i].state] == 0) {
				DBG_MSG(6, "NO");
				return 0;
			}
		}
	}
	DBG_MSG(6, "YES");
	return 1;
}

