/* 
 * Copyright (C) 2003 Tim Martin
 *
 * 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 <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>

#include "connections.h"
#include "protocol.h"
#include "player.h"
#include "map.h"
#include "senkenconfig.h"

typedef struct txn_op_s {
    struct txn_op_s *next;
    void *op;
} txn_op_t;

struct connection_s {
    int fd;
    FILE *stream;

    player_t *player;

    struct connection_s *next;
    struct connection_s *prev;

    int active_txn;
    txn_op_t *txn_op_list;
};

connection_t *connectionlist = NULL;
FILE *logfile = NULL;
extern map_t *map;

/* forward declarations */
static void connections_remove(connection_t *conn);



int
connections_setlogging(const char *file)
{
    if (logfile) {
	fclose(logfile);
    }

    logfile = fopen(file,"w+");
    if (!logfile) return -1;

    return 0;
}

void
connection_setfd(connection_t *conn, player_t *player, void *rock)
{
    fd_set *fds = (fd_set *)rock;

    FD_SET(conn->fd, fds);
}

void
connection_checkfd(connection_t *conn, player_t *player, void *rock)
{
    fd_set *fds = (fd_set *)rock;

    if (FD_ISSET(conn->fd, fds)) {
	char line[1024];
	
	if (fgets(line, sizeof(line)-1, conn->stream)) {
	    if (logfile) {
		fprintf(logfile,"C: %s",line);
		fflush(logfile);
	    }
	    protocol_handle_line(conn, line);
	} else {
	    connections_remove(conn);	    
	}
    }
}

void
connections_enumerate(connection_enumerate_func *func, void *rock)
{
    connection_t *c = connectionlist;

    while (c) {
	connection_t *next;

	/* get next first since func() might remove our current one */
	next = c->next;

	func(c, c->player, rock);
	
	c = next;
    }
}

static void
connections_remove(connection_t *conn)
{
    close(conn->fd);
    
    if (conn == connectionlist) {
	connectionlist = connectionlist->next;
    } else {
	connection_t *prev = conn->prev;
	connection_t *next = conn->next;

	if (prev) prev->next = next;
	if (next) next->prev = prev;
    }
    
    /* xxx free everything else too */
    if (conn->player) {

	if (map) {
	    map_player_quit(map, conn->player);
	}

	player_delete(conn->player);

	/* xxx remove player's possessions too */
    }
    free(conn);
}

int
connections_add(int newsock)
{
    connection_t *connection;

    /*
     * Allocate and fill in a new connection structure
     */
    connection = calloc(sizeof(connection_t), 1);
    if (!connection) {
	printf(_("memory allocation failure\n"));
	return -1;
    }
	    
    connection->fd = newsock;
    connection->stream = fdopen(newsock, "w+");


    /*
     * Now add it to the list
     */
    connection->next = NULL;
    connection->prev = NULL;

    if (!connectionlist) {
	connectionlist = connection;
    } else {
	connection->next = connectionlist;
	connectionlist->prev = connection;
	connectionlist = connection;
    }

    return 0;
}

static void
send_to_one(connection_t *conn, player_t *player, void *rock)
{
    char *data = (char *)rock;

    connection_write(conn, 0, data);
}

void
connections_send_to_all(char *msg, ...) 
{
    char str[4096];
    va_list ap;
    va_start(ap, msg);

    vsnprintf(str, sizeof(str)-1, msg, ap);

    va_end(ap);

    connections_enumerate(&send_to_one, (void *)str);
}

void connection_write(connection_t *conn, int flush, char *msg, ...)
{
    char str[4096];
    va_list ap;
    int len;
    int wrote;

    va_start(ap, msg);

    len = vsnprintf(str, sizeof(str)-1, msg, ap);

    wrote = fwrite(str, 1, len, conn->stream);
    if (wrote != len) {
	printf("wrote %d when should have written %d\n", wrote, len);
    }

    if (flush) {
	if (fflush(conn->stream)) {
	    printf(_("Flush error: %s\n"), strerror(errno));
	}
    }
    if (logfile) {
	fprintf(logfile,"S: %s",str);
    }

    va_end(ap);
}

static void
flush_one(connection_t *conn, player_t *player, void *rock)
{
    fflush(conn->stream);
}

void
connections_flush_all(void)
{
    connections_enumerate(&flush_one, NULL);
}

void
connection_close(connection_t *conn)
{
    fclose(conn->stream);

    close(conn->fd);
    conn->fd = -1;
}

player_t *
connection_getplayer(connection_t *conn)
{
    return conn->player;
}

int
connection_setplayer(connection_t *conn, const char *name)
{
    int r;

    r = player_new((char *)name, &conn->player);
    if (r) return r;

    r =player_setopennumber(conn->player);
    if (r) return r;


    return 0;
}

connection_t *connections_getconnection(player_t *player)
{
    connection_t *c = connectionlist;

    while (c) {

	if (c->player == player) return c;

	c = c->next;
    }

    return NULL;
}

int
connection_txn_start(connection_t *conn)
{
    if (conn->active_txn) return -1;

    conn->active_txn = 1;

    return 0;
}

int connection_txn_add(connection_t *conn, void *op)
{
    txn_op_t *tail;
    txn_op_t *new_op = malloc(sizeof(txn_op_t));
    if (!new_op) return -1;

    new_op->op = op;
    new_op->next = NULL;

    tail = conn->txn_op_list;
    if (tail) {
	while (tail->next) {
	    tail = tail->next;
	}
	tail->next = new_op;
    } else {	
	conn->txn_op_list = new_op;
    }

    return 0;
}

int connection_txn_enumerate(connection_t *conn, connection_txn_enumerate_func *func, void *rock)
{
    txn_op_t *op = conn->txn_op_list;

    while (op) {

	func(op->op, rock);

	op = op->next;
    }

    return 0;
}

int connection_txn_clear(connection_t *conn)
{
    txn_op_t *op = conn->txn_op_list;

    while (op) {
	txn_op_t *next = op->next;

	free(op->op);
	free(op);

	op = next;
    }

    conn->txn_op_list = NULL;
    conn->active_txn = 0;
    return 0;
}

int connection_txn_active(connection_t *conn)
{
    return conn->active_txn;
}
