/* Convert other programs' databases to XML.
 *
 * IRC Services is copyright (c) 1996-2002 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.
 */

#define CONVERT_DB_MAIN
#include "convert-db.h"
#include "language.h"
#include "modules/misc/xml.h"

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

/* Data read in. */

NickGroupInfo *ngi_list;
NickInfo *ni_list;
ChannelInfo *ci_list;
NewsItem *news_list;
MaskData *md_list[256];
ServerStats *ss_list;
int32 maxusercnt;
time_t maxusertime;
char supass[PASSMAX];
int no_supass = 1;

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

/* NULL-terminated list of supported database types. */

static DBTypeInfo *dbtypes[] = {
    &dbtype_auspice,
    &dbtype_bolivia,
    &dbtype_daylight,
    &dbtype_epona,
    &dbtype_ircs_1_2,
    &dbtype_magick_14b2,
    &dbtype_ptlink,
    &dbtype_sirv,
    &dbtype_trircd_4_26,
    &dbtype_wrecked_1_2,
    NULL
};

/*************************************************************************/
/*************************** Exported routines ***************************/
/*************************************************************************/

/* Safe memory allocation.  We also clear the memory just to be clean, and
 * to make scalloc() easy to implement (it's a macro defined in
 * convert-db.h).
 */

void *smalloc(long size)
{
    void *ptr;
    if (!size)
	size = 1;
    ptr = malloc(size);
    if (!ptr) {
	fprintf(stderr, "Out of memory\n");
	exit(1);
    }
    memset(ptr, 0, size);
    return ptr;
}

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

/* Safe memory reallocation.  If the buffer is grown, any extra memory is
 * _not_ cleared.
 */

void *srealloc(void *ptr, long size)
{
    if (!size)
	size = 1;
    ptr = realloc(ptr, size);
    if (!ptr) {
	fprintf(stderr, "Out of memory\n");
	exit(1);
    }
    return ptr;
}

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

/* Safe string copying (from misc.c). */

char *strscpy(char *d, const char *s, size_t len)
{
    strncpy(d, s, len-1);
    d[len-1] = 0;
    return d;
}

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

/* Open a (Services pre-5.0 style) data file and check the version number.
 * Prints an error message and exits with 1 if either the file cannot be
 * opened or the version number is wrong.  If `version_ret' is non-NULL,
 * the version number is stored there.
 */

dbFILE *open_db_ver(const char *dir, const char *name, int32 min_version,
		    int32 max_version, int32 *version_ret)
{
    char filename[PATH_MAX+1];
    dbFILE *f;
    int32 ver;

    snprintf(filename, sizeof(filename), "%s/%s", dir, name);
    f = open_db(filename, "r", 0);
    if (!f) {
	fprintf(stderr, "Can't open %s for reading: %s", filename,
		strerror(errno));
	exit(1);
    }
    if (read_int32(&ver, f) < 0) {
	fprintf(stderr, "Error reading version number on %s\n", filename);
	exit(1);
    }
    if (ver < min_version || ver > max_version) {
	fprintf(stderr, "Wrong version number on %s\n", filename);
	exit(1);
    }
    if (version_ret)
	*version_ret = ver;
    return f;
}

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

/* Create a new NickGroupInfo, assign it an ID, and set default values. */

NickGroupInfo *new_nickgroupinfo(void)
{
    static uint32 id = 1;
    NickGroupInfo *ngi = scalloc(sizeof(*ngi), 1);
    ngi->id = id++;
    ngi->memos.memomax = MEMOMAX_DEFAULT;
    ngi->channelmax = CHANMAX_DEFAULT;
    ngi->language = LANG_DEFAULT;
    return ngi;
}

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

/* Retrieve the NickGroupInfo structure for the given nick.  Returns NULL
 * if the nick does not exist or is forbidden (i.e. has no NickGroupInfo).
 */

NickGroupInfo *get_nickgroupinfo_by_nick(const char *nick)
{
    NickInfo *ni = get_nickinfo(nick);
    return ni ? get_nickgroupinfo(ni->nickgroup) : NULL;
}

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

/* Set the OperServ privilege level (os_priv) for the nickgroup associated
 * with `nick' to `level', if it is not already greater than `level'.  Does
 * nothing if `nick' is NULL or does not have an associated nickgroup.
 */

void set_os_priv(const char *nick, int16 level)
{
    NickGroupInfo *ngi;

    if (!nick)
	return;
    ngi = get_nickgroupinfo_by_nick(nick);
    if (ngi && ngi->os_priv < level)
	ngi->os_priv = level;
}

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

/* Replacements for database functions.  add_*() functions are defined as
 * macros in convert-db.h. */

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

static NickGroupInfo *ngi_iter;

NickGroupInfo *get_nickgroupinfo(uint32 id)
{
    NickGroupInfo *ngi;
    if (!id)
	return NULL;
    LIST_SEARCH_SCALAR(ngi_list, id, id, ngi);
    return ngi;
}

NickGroupInfo *first_nickgroupinfo(void)
{
    ngi_iter = ngi_list;
    return next_nickgroupinfo();
}

NickGroupInfo *next_nickgroupinfo(void)
{
    NickGroupInfo *retval = ngi_iter;
    if (ngi_iter)
	ngi_iter = ngi_iter->next;
    return retval;
}

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

static NickInfo *ni_iter;

NickInfo *get_nickinfo(const char *nick)
{
    NickInfo *ni;
    LIST_SEARCH(ni_list, nick, nick, stricmp, ni);
    return ni;
}

NickInfo *first_nickinfo(void)
{
    ni_iter = ni_list;
    return next_nickinfo();
}

NickInfo *next_nickinfo(void)
{
    NickInfo *retval = ni_iter;
    if (ni_iter)
	ni_iter = ni_iter->next;
    return retval;
}

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

static ChannelInfo *ci_iter;

ChannelInfo *get_channelinfo(const char *channel)
{
    ChannelInfo *ci;
    LIST_SEARCH(ci_list, name, channel, stricmp, ci);
    return ci;
}

ChannelInfo *first_channelinfo(void)
{
    ci_iter = ci_list;
    return next_channelinfo();
}

ChannelInfo *next_channelinfo(void)
{
    ChannelInfo *retval = ci_iter;
    if (ci_iter)
	ci_iter = ci_iter->next;
    return retval;
}

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

static NewsItem *news_iter;

NewsItem *first_news(void)
{
    news_iter = news_list;
    return next_news();
}

NewsItem *next_news(void)
{
    NewsItem *retval = news_iter;
    if (news_iter)
	news_iter = news_iter->next;
    return retval;
}

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

static MaskData *md_iter[256];

MaskData *first_maskdata(uint8 type)
{
    md_iter[type] = md_list[type];
    return next_maskdata(type);
}

MaskData *next_maskdata(uint8 type)
{
    MaskData *retval = md_iter[type];
    if (md_iter[type])
	md_iter[type] = md_iter[type]->next;
    return retval;
}

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

static ServerStats *ss_iter;

ServerStats *first_serverstats(void)
{
    ss_iter = ss_list;
    return next_serverstats();
}

ServerStats *next_serverstats(void)
{
    ServerStats *retval = ss_iter;
    if (ss_iter)
	ss_iter = ss_iter->next;
    return retval;
}

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

int get_operserv_data(int what, void *ptr)
{
    switch (what) {
      case OSDATA_MAXUSERCNT:
	*(int32 *)ptr = maxusercnt;
	break;
      case OSDATA_MAXUSERTIME:
	*(time_t *)ptr = maxusertime;
	break;
      case OSDATA_SUPASS:
	*(char **)ptr = no_supass ? NULL : supass;
	break;
      default:
	return 0;
    }
    return 1;
}

/*************************************************************************/
/****************************** Main program *****************************/
/*************************************************************************/

static void usage(const char *progname)
{
    int i;

    fprintf(stderr, "Usage: %s [-v] [+program-name] sourcedir\n"
		    "The following program names are known:\n", progname);
    for (i = 0; dbtypes[i]; i++)
	fprintf(stderr, "    %s\n", dbtypes[i]->id);
    exit(1);
}

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

int main(int ac, char **av)
{
    char *sourcedir = NULL;	/* Source data file directory */
    int verbose = 0;		/* Verbose output? */
    void (*load)(const char *, int) = NULL;
    int i;
    char oldpath[PATH_MAX+1], newpath[PATH_MAX+1];

    /* Parse command-line parameters */
    for (i = 1; i < ac; i++) {
	if (av[i][0] == '-') {
	    if (av[i][1] == 'v') {
		verbose++;
	    } else {
		if (av[i][1] != 'h')
		    fprintf(stderr, "Unknown option -%c\n", av[i][1]);
		usage(av[0]);
	    }
	} else if (av[i][0] == '+') {
	    int j;
	    for (j = 0; dbtypes[j]; j++) {
		if (stricmp(av[i]+1, dbtypes[j]->id) == 0) {
		    load = dbtypes[j]->load;
		    break;
		}
	    }
	    if (!load) {
		fprintf(stderr, "Unknown database type `%s'\n", av[i]+1);
		usage(av[0]);
	    }
	} else {
	    if (sourcedir) {
		fprintf(stderr, "Only one source directory may be specified\n");
		usage(av[0]);
	    }
	    sourcedir = av[i];
	}
    }

    /* Make sure that we have a source directory and that it's valid */
    if (!sourcedir) {
	fprintf(stderr, "Directory name must be specified\n");
	usage(av[0]);
    }
    if (access(sourcedir, R_OK) < 0) {
	perror(sourcedir);
	exit(1);
    }

    /* If the source directory name is relative, make it absolute */
    if (*sourcedir != '/') {
	if (!getcwd(oldpath, sizeof(oldpath))) {
	    perror("Unable to read current directory name");
	    fprintf(stderr, "Try using an absolute pathname for the source directory.\n");
	    return 1;
	}
	if (strlen(oldpath) + 1 + strlen(sourcedir) + 1 > sizeof(newpath)) {
	    fprintf(stderr, "Source directory pathname too long\n");
	    return 1;
	}
	sprintf(newpath, "%s/%s", oldpath, sourcedir);
	sourcedir = newpath;
    }

    /* If we weren't given a database type, try to figure one out */
    if (!load) {
	for (i = 0; dbtypes[i]; i++) {
	    const char *s;
	    if ((s = dbtypes[i]->check(sourcedir)) != NULL) {
		fprintf(stderr, "Found %s databases\n", s);
		load = dbtypes[i]->load;
		break;
	    }
	}
	if (!load) {
	    fprintf(stderr, "Can't determine database type; use +name option\n");
	    usage(av[0]);
	}
    }

    /* Actually load the databases.  If an error occurs, load() will abort the
     * program for us */
    load(sourcedir, verbose);

    /* Write the database to standard output in XML format */
    xml_export((xml_writefunc_t)fprintf, stdout);

    /* Success */
    return 0;
}

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