/* Convert other programs' databases to XML.
 *
 * 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.
 */

#define CONVERT_DB_MAIN
#define STANDALONE_NICKSERV
#define STANDALONE_CHANSERV
#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_anope,
    &dbtype_auspice,
    &dbtype_bolivia,
    &dbtype_cygnus,
    &dbtype_daylight,
    &dbtype_epona,
    &dbtype_hybserv,
    &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/duplication (from misc.c/memory.c). */

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

char *sstrdup(const char *s)
{
    int len = strlen(s)+1;
    char *d = smalloc(len);
    memcpy(d, s, len);
    return d;
}

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

/* NickInfo/NickGroupInfo allocation (from modules/nickserv/util.c).  The
 * NickInfo and NickGroupInfo (if appropriate) are added into their
 * respective hash tables.  Always succeeds (if the memory allocations
 * fail, aborts the program).
 */

#include "modules/nickserv/util.c"

NickInfo *makenick(const char *nick, NickGroupInfo **nickgroup_ret)
{
    NickInfo *ni = new_nickinfo();
    strscpy(ni->nick, nick, sizeof(ni->nick));
    if (nickgroup_ret) {
	NickGroupInfo *ngi = new_nickgroupinfo(ni->nick);
	while (get_nickgroupinfo(ngi->id)) {
	    /* We assume that eventually we'll find one that's not in use */
	    ngi->id = rand() + rand();
	    if (ngi->id == 0)
		ngi->id = 1;
	}
	ni->nickgroup = ngi->id;
	ARRAY_EXTEND(ngi->nicks);
	strscpy(ngi->nicks[0], nick, sizeof(ngi->nicks[0]));
	add_nickgroupinfo(ngi);
	*nickgroup_ret = ngi;
    }
    add_nickinfo(ni);
    return ni;
}

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

/* ChannelInfo allocation.  The channel is NOT added to its hash tables. */

#include "modules/chanserv/util.c"

ChannelInfo *makechan(const char *name)
{
    ChannelInfo *ci = new_channelinfo();
    strscpy(ci->name, name, sizeof(ci->name));
    return ci;
}

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

/* 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\n", 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;
}

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

/* 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;
}

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

/* Return the Services 5.0 channel access level that corresponds to the
 * given pre-5.0 level.  Code taken from modules/database/version4.c
 * (convert_old_level()).
 */

int16 convert_acclev(int16 old)
{
    if (old < 0)
	return -convert_acclev(-old);	/* avoid negative division */
    else if (old <= 25)
	return old*10;			/*    0..  25 ->   0..250 (10x) */
    else if (old <= 50)
	return 200 + old*2;		/*   25..  50 -> 250..300 ( 2x) */
    else if (old <= 100)
	return 280 + old*2/5;		/*   50.. 100 -> 300..320 ( 0.4x) */
    else if (old <= 1000)
	return 300 + old/5;		/*  100..1000 -> 320..500 ( 0.2x) */
    else if (old <= 2000)
	return 400 + old/10;		/* 1000..2000 -> 500..600 ( 0.1x) */
    else
	return 500 + old/20;		/* 2000..9999 -> 600..999 ( 0.05x) */
}

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

/* 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;
}

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

/* Perform sanity checks on the data after it's been read in. */

static void sanity_check_nicks(void);
static void sanity_check_nickgroups(void);
static void sanity_check_channels(void);
static void sanity_check_maskdata(void);

static void sanity_checks(void)
{
    sanity_check_nicks();
    sanity_check_nickgroups();
    sanity_check_channels();
    sanity_check_maskdata();
}

static void sanity_check_nicks(void)
{
    NickInfo *ni;
    NickGroupInfo *ngi;
    char *s;
    int i;

    for (ni = first_nickinfo(); ni; ni = next_nickinfo()) {

	/* Forbidden nicks should have no other data associated with them. */
	if (ni->status & NS_VERBOTEN) {
	    ni->status &= ~(NS_VERBOTEN);
	    ni->last_usermask = NULL;
	    ni->last_realmask = NULL;
	    ni->last_realname = NULL;
	    ni->last_quit = NULL;
	    ni->last_seen = 0;
	    if (ni->nickgroup) {
		fprintf(stderr, "BUG: Forbidden nickname %s associated with"
			" nickgroup %u!  Clearing nickgroup field.\n",
			ni->nick, ni->nickgroup);
		ni->nickgroup = 0;
	    }
	    ni->id_stamp = 0;
	    continue;
	}

	/* The nick isn't a forbidden nick.  First make sure it has the
	 * right nickgroup. */
	if (!ni->nickgroup) {
	    fprintf(stderr, "BUG: Nickname %s has no nickgroup!  Deleting.\n",
		    ni->nick);
	    del_nickinfo(ni);
	    continue;
	}
	ngi = get_nickgroupinfo(ni->nickgroup);
	if (!ngi) {
	    fprintf(stderr, "BUG: Nickname %s points to nonexistent nickgroup"
		    " %u!  Deleting.\n", ni->nick, ni->nickgroup);
	    del_nickinfo(ni);
	} else {
	    /* Nicknames in the nicks[] array should match those in the
	     * NickInfo structure, so use strcmp() (this keeps us from
	     * having to worry about irc_stricmp() idiosyncrasies). */
	    ARRAY_SEARCH_PLAIN(ngi->nicks, ni->nick, strcmp, i);
	    if (i >= ngi->nicks_count) {
		fprintf(stderr, "BUG: Nickname %s points to nickgroup %u,"
			" but the nickgroup doesn't contain the nickname!"
			"  Deleting.\n", ni->nick, ni->nickgroup);
		del_nickinfo(ni);
	    }
	}

	/* Clear unknown flags. */
	ni->status &= NS_PERMANENT;

	/* Make sure usermasks actually have non-empty user and host parts. */
	if (ni->last_usermask) {
	    s = strchr(ni->last_usermask, '@');
	    if (!s || s==ni->last_usermask || !s[1]) {
		fprintf(stderr, "Last usermask for nickname %s isn't a valid"
			" user@host mask, clearing.\n", ni->nick);
		ni->last_usermask = NULL;
	    }
	}
	if (ni->last_realmask) {
	    s = strchr(ni->last_realmask, '@');
	    if (!s || s==ni->last_realmask || !s[1]) {
		fprintf(stderr, "Last real usermask for nickname %s isn't"
			" a valid user@host mask, clearing.\n", ni->nick);
		ni->last_realmask = NULL;
	    }
	}

	/* Make sure last-seen time is not earlier than registered time.
	 * Allow zero, to accommodate cases where the nickname was
	 * registered by an outside entity without the user actually
	 * logging on. */
	if (ni->last_seen && ni->last_seen < ni->time_registered) {
	    fprintf(stderr, "Last-seen time for nickname %s is earlier than"
		    " registration time!  Setting last-seen time to"
		    " registration time.\n", ni->nick);
	    ni->last_seen = ni->time_registered;
	}

    }  /* for all nicks */
}

static void sanity_check_nickgroups(void)
{
    NickInfo *ni;
    NickGroupInfo *ngi;
    char *s;
    int i;

    for (ngi = first_nickgroupinfo(); ngi; ngi = next_nickgroupinfo()) {

	/* Make sure nickgroups don't contain extra nicks. */
	ARRAY_FOREACH(i, ngi->nicks) {
	    ni = get_nickinfo(ngi->nicks[i]);
	    if (!ni) {
		fprintf(stderr, "BUG: Nickgroup %u contains nonexistent"
			" nickname %s!  Removing nickname from group.\n",
			ngi->id, ngi->nicks[i]);
		ARRAY_REMOVE(ngi->nicks, i);
		if (ngi->mainnick >= i)
		    ngi->mainnick--;
		i--;  /* to make sure we don't skip any */
	    }
	}

	/* Check for empty nickgroups (possibly from extraneous nickname
	 * removal above). */
	if (ngi->nicks_count == 0) {
	    fprintf(stderr, "Removing empty nickgroup %u.\n", ngi->id);
	    del_nickgroupinfo(ngi);
	    continue;
	}

	/* Make sure main nick is a valid index. */
	if (ngi->mainnick >= ngi->nicks_count) {
	    fprintf(stderr, "Invalid main nick index in nickgroup %u,"
		    " resetting.\n", ngi->id);
	    ngi->mainnick = 0;
	}

	/* If an authcode is set, make sure the reason is valid.  If not,
	 * clear the auth-related fields. */
	if (ngi->authcode && (ngi->authreason < NICKAUTH_MIN
			   || ngi->authreason > NICKAUTH_MAX)) {
	    fprintf(stderr, "Authentication code set for nickgroup %u but"
		    " reason code invalid, setting to SETAUTH.\n", ngi->id);
	    ngi->authreason = NICKAUTH_SETAUTH;
	} else {
	    ngi->authset = 0;
	    ngi->authreason = 0;
	}

	/* Clear all unknown flags. */
	ngi->flags &= NF_ALLFLAGS;

	/* Make sure language setting is in range. */
	if ((ngi->language < 0 || ngi->language >= NUM_LANGS)
	 && ngi->language != LANG_DEFAULT
	) {
	    fprintf(stderr, "Invalid language set for nickgroup %u,"
		    " resetting to default.\n", ngi->id);
	    ngi->language = LANG_DEFAULT;
	}

	/* Make sure access, autojoin, and ignore counts are non-negative. */
	if (ngi->access_count < 0) {
	    fprintf(stderr, "BUG: Access entry count for nickgroup %u is"
		    " negative!  Clearing.\n", ngi->id);
	    ngi->access = NULL;
	    ngi->access_count = 0;
	}
	if (ngi->ajoin_count < 0) {
	    fprintf(stderr, "BUG: Autojoin entry count for nickgroup %u is"
		    " negative!  Clearing.\n", ngi->id);
	    ngi->ajoin = NULL;
	    ngi->ajoin_count = 0;
	}
	if (ngi->ignore_count < 0) {
	    fprintf(stderr, "BUG: Ignore entry count for nickgroup %u is"
		    " negative!  Clearing.\n", ngi->id);
	    ngi->ignore = NULL;
	    ngi->ignore_count = 0;
	}

	/* Make sure all access entries have non-empty user and host parts. */
	ARRAY_FOREACH (i, ngi->access) {
	    if (!ngi->access[i]
	     || !(s = strchr(ngi->access[i], '@'))
	     || s == ngi->access[i]
	     || !s[1]
	    ) {
		fprintf(stderr, "Access entry %d for nickgroup %u %s,"
			" deleting.\n", i, ngi->id,
			!ngi->access[i] ? "is empty"
			                : "isn't a valid user@host mask");
		ARRAY_REMOVE(ngi->access, i);
		i--;
	    }
	}

	/* Make sure memo count is non-negative. */
	if (ngi->memos.memos_count < 0) {
	    fprintf(stderr, "BUG: Memo count for nickgroup %u is negative!"
		    "  Clearing.\n", ngi->id);
	    ngi->memos.memos = NULL;
	    ngi->memos.memos_count = 0;
	}

	/* Clear unknown flags from memos. */
	ARRAY_FOREACH (i, ngi->memos.memos)
	    ngi->memos.memos[i].flags &= MF_ALLFLAGS;

    }  /* for all nickgroups */
}

static void sanity_check_channels(void)
{
    ChannelInfo *ci;
    char *s;
    int i;

    for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {

	/* Forbidden channels should have no other data associated with
	 * them. */
	if (ci->flags & CI_VERBOTEN) {
	    ci->founder = 0;
	    ci->successor = 0;
	    memset(ci->founderpass, 0, sizeof(ci->founderpass));
	    ci->desc = NULL;
	    ci->url = NULL;
	    ci->email = NULL;
	    ci->last_used = 0;
	    ci->last_topic = NULL;
	    memset(ci->last_topic_setter, 0, sizeof(ci->last_topic_setter));
	    ci->last_topic_time = 0;
	    ci->flags &= ~(CI_VERBOTEN);
	    ci->suspendinfo = NULL;
	    ci->levels = NULL;
	    ci->access = NULL;
	    ci->access_count = 0;
	    ci->akick = NULL;
	    ci->akick_count = 0;
	    ci->mlock_on = NULL;
	    ci->mlock_off = NULL;
	    ci->mlock_limit = 0;
	    ci->mlock_key = NULL;
	    ci->mlock_link = NULL;
	    ci->mlock_flood = NULL;
	    ci->mlock_joindelay = 0;
	    ci->mlock_joinrate1 = 0;
	    ci->mlock_joinrate2 = 0;
	    ci->entry_message = NULL;
	    ci->memos.memos = NULL;
	    ci->memos.memos_count = 0;
	    ci->memos.memomax = MEMOMAX_DEFAULT;
	    continue;
	}

	/* Channel is not forbidden.  First make sure it has a (valid)
	 * founder. */
	if (!ci->founder) {
	    fprintf(stderr, "BUG: Channel %s has no founder!  Deleting.\n",
		    ci->name);
	    del_channelinfo(ci);
	} else if (!get_nickgroupinfo(ci->founder)) {
	    fprintf(stderr, "BUG: Channel %s founder nickgroup %u is"
		    " missing!  Deleting channel.\n", ci->name, ci->founder);
	    del_channelinfo(ci);
	}

	/* Make sure that the successor is valid if set. */
	if (ci->successor && !get_nickgroupinfo(ci->successor)) {
	    fprintf(stderr, "BUG: Channel %s successor nickgroup %u is"
		    " missing!  Clearing.", ci->name, ci->successor);
	    ci->successor = 0;
	}

	/* Make sure last-used time is not earlier than registered time.
	 * Allow zero, to accommodate cases where the channel was
	 * registered by an outside entity. */
	if (ci->last_used && ci->last_used < ci->time_registered) {
	    fprintf(stderr, "Last-used time for channel %s is earlier than"
		    " registration time!  Setting last-used time to"
		    " registration time.\n", ci->name);
	    ci->last_used = ci->time_registered;
	}

	/* If there is no saved topic, clear the topic setter and time. */
	if (!ci->last_topic) {
	    memset(ci->last_topic_setter, 0, sizeof(ci->last_topic_setter));
	    ci->last_topic_time = 0;
	}

	/* Clear unknown flags. */
	ci->flags &= CI_ALLFLAGS;

	/* Check privilege level settings for vaility. */
	if (ci->levels) {
	    for (i = 0; i < CA_SIZE; i++) {
		if (ci->levels[i] < ACCLEV_INVALID
		 || ci->levels[i] > ACCLEV_FOUNDER
		) {
		    fprintf(stderr, "Privilege level %d on channel %s is"
			    "invalid; resetting all levels to defaults.\n",
			    i, ci->name);
		    ci->levels = NULL;
		    break;
		}
	    }
	}

	/* Check access level nickgroups and levels for validity. */
	ARRAY_FOREACH (i, ci->access) {
	    if (!ci->access[i].nickgroup)
		continue;
	    if (!get_nickgroupinfo(ci->access[i].nickgroup)) {
		fprintf(stderr, "BUG: Channel %s access entry %d has an"
			" invalid nickgroup!  Clearing.\n", ci->name, i);
		ci->access[i].nickgroup = 0;
		ci->access[i].level = 0;
	    } else if (ci->access[i].level <= ACCLEV_INVALID
		    || ci->access[i].level >= ACCLEV_FOUNDER) {
		fprintf(stderr, "BUG: Channel %s access entry %d has an"
			"out-of-range level (%d)!  Clearing.\n",
			ci->name, i, ci->access[i].level);
		ci->access[i].nickgroup = 0;
		ci->access[i].level = 0;
	    }
	}

	/* Make sure all autokick entries have non-empty user and host
	 * parts. */
	ARRAY_FOREACH (i, ci->akick) {
	    if (!ci->akick[i].mask)
		continue;
	    s = strchr(ci->akick[i].mask, '@');
	    if (!s || s==ci->akick[i].mask || !s[1]) {
		fprintf(stderr, "Autokick entry %d for channel %s isn't a"
			" valid user@host mask, deleting.\n", i, ci->name);
		ci->akick[i].mask = NULL;
	    }
	}

	/* Make sure memo count is non-negative. */
	if (ci->memos.memos_count < 0) {
	    fprintf(stderr, "BUG: Memo count for channel %s is negative!"
		    "  Clearing.\n", ci->name);
	    ci->memos.memos = NULL;
	    ci->memos.memos_count = 0;
	}

	/* Clear unknown flags from memos. */
	ARRAY_FOREACH (i, ci->memos.memos)
	    ci->memos.memos[i].flags &= MF_ALLFLAGS;

    }  /* for all channels */

    /* Check that channel successors and access list entries have valid
     * nickgroup IDs, and that access levels are in range */
    for (ci = first_channelinfo(); ci; ci = next_channelinfo()) {
	if (ci->flags & CI_VERBOTEN)
	    continue;
    }
}

static void sanity_check_maskdata(void)
{
    MaskData *md;
    char *s;
    int i;

    /* Make sure all MaskData entries actually have masks allocated. */
    for (i = 0; i < 256; i++) {
	for (md = first_maskdata(i); md; md = next_maskdata(i)) {
	    if (!md->mask)
		del_maskdata(i, md);
	}
    }

    /* Make sure every autokill has a valid user@host mask and a reason. */
    for (md = first_maskdata(MD_AKILL); md; md = next_maskdata(MD_AKILL)) {
	s = strchr(md->mask, '@');
	if (!s || s==md->mask || !s[1]) {
	    fprintf(stderr, "Autokill %s isn't a valid user@host mask,"
		    " deleting.\n", md->mask);
	    del_maskdata(MD_AKILL, md);
	    continue;
	}
	if (!md->reason)
	    md->reason = "Reason unknown";
    }
}

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

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

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

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

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

#ifdef CLEAN_COMPILE
    free_nickinfo(NULL);
    free_nickgroupinfo(NULL);
    free_channelinfo(NULL);
#endif

    /* Parse command-line parameters */
    newac = 1;
    newav = malloc(sizeof(*newav) * ac);
    newav[0] = av[0];
    for (i = 1; i < ac; i++) {
	if (strcmp(av[i],"-v") == 0) {
	    verbose++;
	} else if (strcmp(av[i],"-h") == 0
		|| strcmp(av[i],"-?") == 0
		|| strcmp(av[i],"-help") == 0
		|| strcmp(av[i],"--help") == 0) {
	    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 (av[i][0] == '-') {
	    newav[newac++] = av[i];
	} 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, newac, newav);
    if (verbose)
	fprintf(stderr, "Data files successfully loaded.\n");

    /* Do sanity checks. */
    if (verbose)
	fprintf(stderr, "Checking data integrity...\n");
    sanity_checks();

    /* Write the database to standard output in XML format */
    if (verbose)
	fprintf(stderr, "Writing converted data...\n");
    xml_export((xml_writefunc_t)fprintf, stdout);

    /* Success */
    if (verbose)
	fprintf(stderr, "Done.\n");
    return 0;
}

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