#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "lnode.h"	/* Only for sake of -g flag to cc (SYSV) ! */
#include "interpret.h"	/* Only for sake of -g flag to cc (SYSV) ! */
#include "comm.h"
#include "object.h"
#include "config.h"

/* Kludge because of cc -g ! */
struct sentence {
    int dummy;
};

extern char *malloc();
extern int d_flag;

#define MAX_PLAYERS	30

struct interactive *all_players[MAX_PLAYERS];

extern int errno;

/*
 * Interprocess communication interface to the backend.
 */

static msgqid;

/*
 * Use a lock file as the unique id for this message queue.
 * If the lock file is not present, then we was not started by
 * a frontend program, so we create the lock file ourselfs.
 */
prepare_ipc() {
    key_t key;
    int fd;

    key = ftok(IPC_IDENT_FILE, 0);
    if (key == (key_t)-1) {
	fd = creat(IPC_IDENT_FILE, 0666);
	if (fd == -1) {
	    perror(IPC_IDENT_FILE);
	    exit(1);
	}
	close(fd);
	key = ftok(IPC_IDENT_FILE, 0);
	if (key == (key_t)-1) {
	    perror(IPC_IDENT_FILE);
	    exit(1);
	}
	/* We have to create the message queue. */
	msgqid = msgget(key, IPC_CREAT|0666);
	if (msgqid == -1) {
	    perror("msgget");
	    exit(1);
	}
	return;
    }
    /* Find the already existing message queue. */
    msgqid = msgget(key, 0);
    if (msgqid == -1) {
	perror("msgget");
	exit(1);
    }
}

/*
 * This one is called when shutting down the MUD.
 */
ipc_remove() {
    int res;

    printf("Shutting down ipc...\n");
    res = msgctl(msgqid, IPC_RMID);
    if (res == -1)
	perror("msgctl IPC_RMID");
    if (unlink(IPC_IDENT_FILE) == -1) {
	perror(IPC_IDENT_FILE);
	fprintf(stderr, "Warning could not unlink %s\n", IPC_IDENT_FILE);
    }
}

add_message(fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
{
    char buff[1000], length;
    struct interactive *ip;

    ip = command_giver->interactive;
    if (ip == 0)
	error("Add_message to a non-interactive object: %s!\n",
	      command_giver->name);
    sprintf(buff, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
    if (d_flag)
	debug_message("[%s]: %s", command_giver->name, buff);
    length = strlen(buff);
    if (length + ip->mess_size > MAX_TEXT)
	flush_message();
    strcpy(ip->message + ip->mess_size, buff);
    ip->mess_size += length;
}

flush_message()
{
    struct interactive *ip = command_giver->interactive;
    int res;

    if (ip == 0)
	error("Add_message to a non-interactive object: %s!\n",
	      command_giver->name);
    if (ip->mess_size == 0)
	return;
    if (ip->mess_size == -1)
	ip->mess_size = 0;	/* Indicates a null message. */
    ip->mtype = ip->pid;
    while(1) {
	res = msgsnd(msgqid, (struct msgbuf *)ip,
		     ip->mess_size, 0);
	if (res == -1) {
	    if (errno == EINTR)
		continue;
	    perror("msgsnd");
	    abort();
	    exit(1);
	}
	ip->mess_size = 0;
	ip->message[0] = '\0';
	return;
    }
}

struct rec_buffer rec_buffer;

/*
 * Get a message from any player.
 * If we get a message, set command_giver to the players object and
 * return true.
 * If we are interrupted, return false (no message yet).
 */

get_message(buff, size)
    char *buff;
{
    int i, res;

    /*
     * Stay in this loop until we have a message to a logged on player.
     */
    while(1) {
	char *p;

	/* First flush all send messages. */
	for (i=0; i<MAX_PLAYERS; i++) {
	    if (all_players[i] != 0 && all_players[i]->mess_size > 0) {
		command_giver = all_players[i]->ob;
		flush_message();
	    }
	}
	res = msgrcv(msgqid, &rec_buffer, sizeof rec_buffer.text, 1, 0);
	if (res == -1 && errno == EINTR)
	    return 0;
	if (p = strchr(rec_buffer.text, '\n'))
	    *p = '\0';
	if (res == -1) {
	    perror("msgrcv");
	    exit(1);
	}
	/*
	 * Now we have a message. Find which object it is associated with
	 * this message by looking at the pid.
	 */
	for (i=0; i<MAX_PLAYERS; i++) {
	    if (all_players[i] == 0)
		continue;
	    if (all_players[i]->pid != rec_buffer.pid)
		continue;
	    command_giver = all_players[i]->ob;
	    strcpy(buff, rec_buffer.text);
	    return 1;
	}
	/*
	 * We come here when no object was associated with this pid.
	 * Then we add a new player !
	 */
	for (i=0; i<MAX_PLAYERS; i++) {
	    struct object *ob;
	    
	    if (all_players[i] != 0)
		continue;
	    current_object = 0;
	    ob = (clone_object("obj/player"))->u.ob;
	    if (ob == 0)
		error("Could not load 'obj/player'\n");
	    ob->enable_commands = 1;
	    ob->interactive =
		(struct interactive *)malloc(sizeof (struct interactive));
	    ob->interactive->logon_state = 0;
	    ob->interactive->pid = rec_buffer.pid;
	    ob->interactive->mess_size = 0;
	    all_players[i] = ob->interactive;
	    command_giver = ob;
	    ob->interactive->ob = ob;
	    (void)logon(ob, rec_buffer.text);
	    break;
	}
	/* Did we not find any free player data structure ? */
	if (i == MAX_PLAYERS)
	    send_null_message(rec_buffer.pid);
    }
}

remove_interactive(ob)
    struct object *ob;
{
    struct object *save = command_giver;
    int i;

    for (i=0; i<MAX_PLAYERS; i++) {
	if (all_players[i] != ob->interactive)
	    continue;
	command_giver = ob;
	flush_message();
	/* Send a null message to his frontend, to indicate
	 * that he is logged of.
	 */
	send_null_message(ob->interactive->pid);
	free(ob->interactive);
	all_players[i] = 0;
	ob->interactive = 0;
	command_giver = save;
	return 0;
    }
    fprintf(stderr, "Could not find and remove player %s\n", ob->name);
    exit(1);
}

static send_null_message(pid) {
    long mtype;
    int res;

    mtype = pid;
    res = msgsnd(msgqid, (struct msgbuf *)&mtype, 0, 0);
    if (res == -1) {
	perror("msgsnd null message");
	abort();
	exit(1);
    }
}