/*
 * 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 {
	PL_STATUS status;
	char *nick, *address;
	char *role;
	char *score;
	char *conn_stat;
	int ack;
} PL_ITEM;

PL_ITEM *playerlist = NULL;
int playerlist_size = 4;
int playerlist_is_sorted = 0;

static PL_UPDATE_CALLBACK playerlist_update_callback = NULL;


static void playerlist_enlarge(void)
{
	int old_size = playerlist_size;
	playerlist_size <<= 1;
	DBG_MSG(2, "playerlist_enlarge: 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)
{
	PL_ITEM *a = (PL_ITEM *)va;
	PL_ITEM *b = (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)
		qsort(playerlist, playerlist_size, sizeof(PL_ITEM), playerlist_item_compare);
	playerlist_is_sorted = 1;
}


static PL_ITEM *playerlist_item_lookup(const char *address)
{
	PL_ITEM key;
	key.address = (char *) address;
	
	DBG_MSG(4, "playerlist_item_lookup: [ %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);

	/*
	playerlist_add("bily");
	playerlist_update("bily", "Mr. White", "player 1", "n/a", "fake", 0);
	playerlist_add("cerny");
	playerlist_update("cerny", "Mr. Black", "player 2", "n/a", "fake", 0);
	*/
}


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


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, "playerlist_item_add: have empty slot");
		a = &(playerlist[0]);
	} else {
		DBG_MSG(4, "playerlist_item_add: 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);
	
	if ((a = playerlist_item_lookup(address))) {
		if (nick) {
			if (a->nick)
				free(a->nick);
			a->nick = strdup(nick);
		}
		if (role) {
			if (a->role)
				free(a->role);
			a->role = strdup(role);
		}
		if (score) {
			if (a->score)
				free(a->score);
			a->score = strdup(score);
		}
		if (conn_stat) {
			if (a->conn_stat)
				free(a->conn_stat);
			a->conn_stat = strdup(conn_stat);
		}
		
		playerlist_run_update_callback();
		return 1;
	}
	return 0;
}


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

	if ((a = playerlist_item_lookup(address))) {
		a->ack = ack;

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


int playerlist_remove(const char *address)
{
	PL_ITEM *a = NULL;
	assert(address != NULL);
	
	if ((a = playerlist_item_lookup(address))) {
		free(a->address);
		if (a->nick)
			free(a->nick);
		if (a->role)
			free(a->role);
		if (a->score)
			free(a->score);
		if (a->conn_stat)
			free(a->conn_stat);
		memset(a, 0, sizeof(PL_ITEM));
		
		playerlist_run_update_callback();
		return 1;
	}
	return 0;
}


int playerlist_remove_all(void)
{
	int i;

	for (i = 0; i < playerlist_size; i++) {
		free(playerlist[i].address);
		if (playerlist[i].nick)
			free(playerlist[i].nick);
		if (playerlist[i].role)
			free(playerlist[i].role);
		if (playerlist[i].score)
			free(playerlist[i].score);
		if (playerlist[i].conn_stat)
			free(playerlist[i].conn_stat);
		memset(&(playerlist[i]), 0, sizeof(PL_ITEM));
	}
	playerlist_run_update_callback();
	return 0;
}


int playerlist_get(const char *address, char **nick, char **role, char **score, char **conn_stat, int *ack)
{
	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 (ack)
			*ack = a->ack;
		return 1;
	}
	return 0;
}


int playerlist_list(int *iter, char **address, char **nick, char **role, char **score, char **conn_stat, int *ack)
{
	assert(iter != NULL);
	
	DBG_MSG(6, "playerlist_list: 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 (ack)
			*ack = playerlist[*iter].ack;
	} else {
		if (address)
			*address = NULL;
		if (nick)
			*nick = NULL;
		if (role)
			*role = NULL;
		if (score)
			*score = NULL;
		if (conn_stat)
			*conn_stat = NULL;
		if (ack)
			*ack = 0;
	}
	return *iter < playerlist_size;
}


void playerlist_clear_ack(void)
{
	int i;

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

int playerlist_have_all_acks(int ack)
{
	int i;

	/* check if all have 0 (no playing) or `ack` */
	DBG_MSG(4, "playerlist_have_all_ack: ack = %d", ack);
	for (i = 0; i < playerlist_size; i++) {
		if (playerlist[i].address) {
			if (playerlist[i].ack != ack && playerlist[i].ack != 0) {
				DBG_MSG(6, "playerlist_have_all_ack: NO");
				return 0;
			}
		}
	}
	DBG_MSG(6, "playerlist_have_all_ack: YES (ack = %d)", ack);
	return ack;
}
