/* Routines for handling mode flags and strings.
 *
 * 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"

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

/* List of user/channel modes and associated mode letters. */

ModeData usermodes[] = {
    ['o'] = {0x00000001},
    ['i'] = {0x00000002},
    ['w'] = {0x00000004},
};

ModeData chanmodes[] = {
    ['i'] = {0x00000001, 0, 0},
    ['m'] = {0x00000002, 0, 0},
    ['n'] = {0x00000004, 0, 0},
    ['p'] = {0x00000008, 0, 0},
    ['s'] = {0x00000010, 0, 0},
    ['t'] = {0x00000020, 0, 0},
    ['k'] = {0x00000040, 1, 1},
    ['l'] = {0x00000080, 1, 0},
    ['b'] = {0x80000000, 1, 1, 0, MI_MULTIPLE},
};

ModeData chanusermodes[] = {
    ['o'] = {0x00000001, 1, 1, '@'},
    ['v'] = {0x00000002, 1, 1, '+'},
};

/* The following are initialized by mode_setup(): */
int32 usermode_reg;		/* Usermodes applied to registered nicks */
int32 chanmode_reg;		/* Chanmodes applied to registered chans */
int32 chanmode_regonly;		/* Chanmodes indicating regnick-only channels*/
int32 chanmode_opersonly;	/* Chanmodes indicating oper-only channels */
int32 chanusermode_owner;	/* Chanuser-modes applied to channel owner */
char  chanmode_multiple[257];	/* Chanmodes that can be set multiple times */

/* Flag tables, used internally to speed up flag lookups.  0 indicates a
 * flag with no associated mode. */
static char userflags[31], chanflags[31], chanuserflags[31];

/* Table of channel user mode prefixes, used like flag tables above. */
static int32 prefixtable[256];

/* Tables used for fast lookups. */
static ModeData *modetable[] = {usermodes, chanmodes, chanusermodes};
static char *flagtable[] = {userflags, chanflags, chanuserflags};

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

/* Initialize flag tables and flag sets from mode tables.  Must be called
 * before any other mode_* function, and should be called again if the mode
 * tables are modified.
 */

void mode_setup(void)
{
    int i;
    ModeData *modelist;
    char *flaglist;
    int multi_index = 0;   /* index into chanmode_multiple[] */

    modelist = usermodes;
    flaglist = userflags;
    for (i = 0; i < 256; i++) {
	if (modelist[i].flag) {
	    int n = 0;
	    uint32 tmp = (uint32) modelist[i].flag;
	    if (modelist[i].info & MI_REGISTERED)
		usermode_reg |= tmp;
	    while (tmp >>= 1)
		n++;
	    if (n < 31)
		flaglist[n] = (char)i;
	}
    }

    modelist = chanmodes;
    flaglist = chanflags;
    for (i = 0; i < 256; i++) {
	if (modelist[i].flag) {
	    int n = 0;
	    uint32 tmp = (uint32) modelist[i].flag;
	    if (modelist[i].info & MI_REGISTERED)
		chanmode_reg |= tmp;
	    if (modelist[i].info & MI_REGNICKS_ONLY)
		chanmode_regonly |= tmp;
	    if (modelist[i].info & MI_OPERS_ONLY)
		chanmode_opersonly |= tmp;
	    if (modelist[i].info & MI_MULTIPLE)
		chanmode_multiple[multi_index++] = i;
	    while (tmp >>= 1)
		n++;
	    if (n < 31)
		flaglist[n] = (char)i;
	}
    }
    chanmode_multiple[multi_index] = 0;

    modelist = chanusermodes;
    flaglist = chanuserflags;
    for (i = 0; i < 256; i++) {
	if (modelist[i].flag) {
	    int n = 0;
	    uint32 tmp = (uint32) modelist[i].flag;
	    prefixtable[ (uint8)modelist[i].prefix ] = tmp;
	    if (modelist[i].info & MI_CHANOWNER)
		chanusermode_owner |= tmp;
	    while (tmp >>= 1)
		n++;
	    if (n < 31)
		flaglist[n] = (char)i;
	    if (modelist[i].plus_params!=1 || modelist[i].minus_params!=1) {
		log("modes: Warning: channel user mode `%c' takes %d/%d"
		    " parameters (should be 1/1)",
		    i, modelist[i].plus_params, modelist[i].minus_params);
	    }
	}
    }
}

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

/* Return the flag corresponding to the given mode character, or 0 if no
 * such mode exists.  Return MODE_INVALID if the mode exists but has no
 * assigned flag.  `which' indicates the mode set to be used: MODE_USER,
 * MODE_CHANNEL, or MODE_CHANUSER.
 */

int32 mode_char_to_flag(char c, int which)
{
    return modetable[which][(uint8)c].flag;
}

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

/* Return the number of parameters the given mode takes, as
 * (plus_params<<8) | (minus_params).  Return -1 if there is no such mode.
 */

int mode_char_to_params(char c, int which)
{
    ModeData *ptr = &modetable[which][(uint8)c];
    return ptr->plus_params<<8 | ptr->minus_params;
}

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

/* Return the mode character corresponding to the given flag, or 0 if no
 * such mode exists.  If more than one bit is set in `f', then the mode
 * character corresponding to the highest bit is used.
 */

char mode_flag_to_char(int32 f, int which)
{
    char *flaglist = flagtable[which];
    int n = 0, tmp = f;

    while (tmp >>= 1)
	n++;
    if (n >= 31)
	return 0;
    return flaglist[n];
}

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

/* Return the flag set corresponding to the given string of mode characters
 * in the given mode set.  If MODE_NOERROR is set in `which', invalid mode
 * characters are ignored; if not set, (CMODE_INVALID | modechar) is
 * returned for the first invalid mode character found.  
 */

int32 mode_string_to_flags(const char *s, int which)
{
    int32 flags = 0;
    const ModeData *modelist = modetable[which & ~MODE_NOERROR];

    while (*s) {
	int f = modelist[(uint8)*s].flag;
	if (!f) {
	    if (which & MODE_NOERROR) {
		s++;
		continue;
	    }
	    return MODE_INVALID | *s;
	}
	if (f != MODE_INVALID)
	    flags |= f;
	s++;
    }
    return flags;
}

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

/* Return the string of mode characters corresponding to the given flag
 * set.  If the flag set has invalid flags in it, they are ignored.
 * The returned string is stored in a static buffer which will be
 * overwritten on the next call.
 */

char *mode_flags_to_string(int32 flags, int which)
{
    static char buf[32];
    char *s = buf;
    int n = 0;
    const char *flaglist = flagtable[which];

    flags &= ~MODE_INVALID;
    while (flags) {
	if ((flags & 1) && flaglist[n])
	    *s++ = flaglist[n];
	n++;
	flags >>= 1;
    }
    *s = 0;
    return buf;
}

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

/* Return the flag corresponding to the given channel user mode prefix, or
 * 0 if no such mode exists.
 */

int32 cumode_prefix_to_flag(char c)
{
    return prefixtable[(uint8)c];
}

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