/* Routines for looking up commands in a *Serv command list.
 *
 * IRC Services is copyright (c) 1996-2009 Andrew Church.
 *     E-mail: <achurch@achurch.org>
 * Parts written by Andrew Kempe and others.
 * This program is free but copyrighted software; see the file COPYING for
 * details.
 */

#include "services.h"
#include "modules.h"
#include "commands.h"
#include "language.h"

/*************************************************************************/

typedef struct commandlist_ CommandList;
typedef struct commandarray_ CommandArray;

struct commandlist_ {
    CommandList *next, *prev;
    Module *id;
    CommandArray *first_array;
};

struct commandarray_ {
    CommandArray *next, *prev;
    Command *commands;
};

/*************************************************************************/

/* List of all commands registered. */
static CommandList *cmdlist;

/*************************************************************************/
/*************************************************************************/

/* Set up a new command list using the given module pointer as an ID value.
 * Fails if a command list associated with `id' already exists.
 */

int new_commandlist(Module *id)
{
    CommandList *cl;

    LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
    if (cl)
	return 0;
    cl = smalloc(sizeof(*cl));
    cl->id = id;
    cl->first_array = NULL;
    LIST_INSERT(cl, cmdlist);
    return 1;
}

/*************************************************************************/

/* Register a command array under the given ID.  Fails if there is no
 * command list associated with `id', `array' is NULL, `array' has already
 * been added to the list, or there are multiple command entries in `array'
 * with the same name (case-insensitive).  If an entry in `array' has the
 * same name as a previously-registered entry, the entry in `array' will
 * take precendence, and a pointer to the previous entry will be stored in
 * the `next' field of the entry.
 */

int register_commands(Module *id, Command *array)
{
    CommandList *cl;
    CommandArray *ca;
    Command *c, *c2;

    /* Basic sanity checking */
    if (!array)
	return 0;
    LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
    if (!cl)
	return 0;
    LIST_SEARCH_SCALAR(cl->first_array, commands, array, ca);
    if (ca)
	return 0;
    /* Check for duplicate commands and set `next' pointers */
    for (c = array; c->name; c++) {
	for (c2 = c+1; c2->name; c2++) {
	    if (stricmp(c->name, c2->name) == 0)
		return 0;
	}
	c->next = lookup_cmd(id, c->name);
    }
    /* All's well, insert it in the list and return success */
    ca = smalloc(sizeof(*ca));
    ca->commands = array;
    LIST_INSERT(ca, cl->first_array);
    return 1;
}

/*************************************************************************/

/* Unregister a command array from the given ID.  Fails if there is no
 * command list associated with `id' or `array' was not in the list in the
 * first place.
 */

int unregister_commands(Module *id, Command *array)
{
    CommandList *cl;
    CommandArray *ca;

    LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
    if (!cl)
	return 0;
    LIST_SEARCH_SCALAR(cl->first_array, commands, array, ca);
    if (!ca)
	return 0;
    LIST_REMOVE(ca, cl->first_array);
    free(ca);
    return 1;
}

/*************************************************************************/

/* Delete the command list associated with the given ID.  Fails if there is
 * no command list associated with `id' or the command list is not empty.
 */

int del_commandlist(Module *id)
{
    CommandList *cl;

    LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
    if (!cl || cl->first_array)
	return 0;
    LIST_REMOVE(cl, cmdlist);
    free(cl);
    return 1;
}

/*************************************************************************/
/*************************************************************************/

/* Returns the Command structure associated with the given command for the
 * given command list (`id'), or NULL if no such command exists.
 */

Command *lookup_cmd(Module *id, const char *name)
{
    CommandList *cl;
    CommandArray *ca;
    Command *c;

    LIST_SEARCH_SCALAR(cmdlist, id, id, cl);
    if (!cl)
	return NULL;
    LIST_FOREACH (ca, cl->first_array) {
	for (c = ca->commands; c->name; c++) {
	    if (stricmp(c->name, name) == 0)
		return c;
	}
    }
    return NULL;
}

/*************************************************************************/

/* Run the routine for the given command, if it exists and the user has
 * privilege to do so; if not, print an appropriate error message.
 */

void run_cmd(const char *service, User *u, Module *id, const char *cmd)
{
    Command *c = lookup_cmd(id, cmd);

    if (c && c->routine) {
	if ((c->has_priv == NULL) || c->has_priv(u))
	    c->routine(u);
	else
	    notice_lang(service, u, ACCESS_DENIED);
    } else {
    	notice_lang(service, u, UNKNOWN_COMMAND_HELP, cmd, service);
    }
}

/*************************************************************************/

/* Print a help message for the given command.  Multiple spaces between
 * words are compressed to a single space before looking up the command
 * (this will cause `cmd' to be modified).
 */

void help_cmd(const char *service, User *u, Module *id, char *cmd)
{
    Command *c;
    char *s;

    s = cmd-1;
    while ((s = strpbrk(s+1, " \t")) != NULL) {
	char *t = s + strspn(s, " \t");
	if (t > s+1)
	    memmove(s+1, t, strlen(t)+1);
    }

    c = lookup_cmd(id, cmd);
    if (c) {
	const char *p1 = c->help_param1,
	           *p2 = c->help_param2,
	           *p3 = c->help_param3,
	           *p4 = c->help_param4;

	if (c->helpmsg_all >= 0)
	    notice_help(service, u, c->helpmsg_all, p1, p2, p3, p4);

	if (is_oper(u)) {
	    if (c->helpmsg_oper >= 0)
		notice_help(service, u, c->helpmsg_oper, p1, p2, p3, p4);
	    else if (c->helpmsg_all < 0)
		notice_lang(service, u, NO_HELP_AVAILABLE, cmd);
	} else {
	    if (c->helpmsg_reg >= 0)
		notice_help(service, u, c->helpmsg_reg, p1, p2, p3, p4);
	    else if (c->helpmsg_all < 0)
		notice_lang(service, u, NO_HELP_AVAILABLE, cmd);
	}

    } else {

	notice_lang(service, u, NO_HELP_AVAILABLE, cmd);

    }
}

/*************************************************************************/
