/* @@@HEAD@@@
//
*/

#define _main_

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include "config.h"
#include "defs.h"
#include "y.tab.h"
#include "codegen.h"
#include "opcodes.h"
#include "cdc_types.h"
#include "object.h"
#include "data.h"
#include "ident.h"
#include "cdc_string.h"
#include "match.h"
#include "cache.h"
#include "sig.h"
#include "db.h"
#include "util.h"
#include "io.h"
#include "log.h"
#include "dump.h"
#include "execute.h"
#include "token.h"
#include "modules.h"

internal void initialize(int argc, char **argv);
internal void main_loop(void);
internal void usage (char * name);

/*
// --------------------------------------------------------------------
//
// Rather obvious, no?
//
*/

int main(int argc, char **argv) {
    initialize(argc, argv);
    main_loop();

    /* Sync the cache, flush output buffers, and exit normally. */
    cache_sync();
    db_close();
    flush_output();

    return 0;
}

/*
// --------------------------------------------------------------------
//
// Initialization
//
*/

#define nextarg() { \
        argv++; \
        argc--; \
        if (!argc) { \
            usage(name); \
            fprintf(stderr, "** No followup argument to -%s.\n", opt); \
            exit(0); \
        } \
    }

#define addarg(__str) { \
        arg.type = STRING; \
        str = string_from_chars(__str, strlen(__str)); \
	arg.u.str = str; \
        string_discard(str); \
        args = list_add(args, &arg); \
    } \

internal void initialize(int argc, char **argv) {
    FILE     * fp;
    object_t * obj;
    list_t   * parents,
             * args;
    int        use_textdump, 
               force_text_dump = 0; 
    string_t * str;
    data_t     arg,
             * d;
    char     * opt,
             * name,
             * basedir = NULL;

    name = *argv;
    argv++;
    argc--;

    /* Ditch stdin, so we can reuse the file descriptor */
    fclose(stdin);

    /* Initialize internal tables and variables. */
    init_codegen();
    init_ident();
    init_op_table();
    init_match();
    init_util();
    init_sig();
    init_execute();
    init_scratch_file();
    init_token();
    init_modules(argc, argv);
    init_defs();

    /* db argument list */
    args = list_new(0);

    /* parse arguments */
    while (argc) {
        opt = *argv;
        if (*opt == '-' && strlen(opt) == 2) {
            opt++;
            switch (*opt) {
                case 'b':
                    nextarg();
                    strcpy(c_dir_binary, *argv);
                    break;
                case 'r':
                    nextarg();
                    strcpy(c_dir_root, *argv);
                    break;
                case 't':
                    nextarg();
                    strcpy(c_dir_textdump, *argv);
                    break;
                case 'e':
                    nextarg();
                    strcpy(c_dir_bin, *argv);
                    break;
                case 'f':
                    force_text_dump++;
                    break;
                case 'h':
                    usage(name);
                    exit(0);
                    break;
                default:
                    addarg(*argv);
            }
        } else {
            if (basedir == NULL)
                basedir = *argv;
            else
                addarg(*argv);
        }
        argv++;
        argc--;
    }

    if (basedir == NULL)
        basedir = ".";

    /* Switch into database directory. */
    if (chdir(basedir) == F_FAILURE) {
        usage(name);
	fprintf(stderr, "** Couldn't change to base directory \"%s\".\n",
                basedir);
	exit(1);
    }

    /* people like to know what is up */
    fprintf(stderr, "Initializing database...");

    /* Initialize database and network modules. */
    init_cache();
#if 1
    init_binary_db();
#else
    use_textdump = init_db(0);
#endif

    /* Order of operations note: it might seem like we'd want to read the text
     * dump (if we're going to) before making sure there's a root and system
     * object.  However, this way is correct, since the textdump reader can
     * evaluate arbitrary ColdC code and thus should start with a consistent
     * database. */

    /* Make sure there is a root object. */
    obj = cache_retrieve(ROOT_DBREF);
    if (!obj) {
	parents = list_new(0);
	obj = object_new(ROOT_DBREF, parents);
	list_discard(parents);
    }
    cache_discard(obj);

    /* Make sure there is a system object. */
    obj = cache_retrieve(SYSTEM_DBREF);
    if (!obj) {
	parents = list_new(1);
	d = list_empty_spaces(parents, 1);
	d->type = DBREF;
	d->u.dbref = ROOT_DBREF;
	obj = object_new(SYSTEM_DBREF, parents);
	list_discard(parents);
    }
    cache_discard(obj);

#if DISABLED
    /* Read a text dump if there was no existing binary database. */
    if (use_textdump) {
        write_err("Reading from textdump...");
	fp = fopen("textdump", "r");
	if (!fp) {
	    fail_to_start("Couldn't open text dump file.");
	} else {
	    text_dump_read(fp);
	    fclose(fp);
	}
    }
#endif

    printf("Sending Startup Message.\n");
    /* Send a startup message to the system object. */
    arg.type = LIST;
    arg.u.list = args;
    task(NULL, SYSTEM_DBREF, startup_id, 1, &arg);
    list_discard(args);
}

/*
// --------------------------------------------------------------------
//
// The core of the interpreter, while this is looping it is interpreting
//
*/

internal void main_loop(void) {
    int             seconds = 0;
    time_t          next_heartbeat = 0,
                    last_heartbeat = 0,
                    t = 0;

    while (running) {
	/* delete any defunct connection or server records */
	flush_defunct();

	/* Sanity check: make sure there are no objects in active chains. */
	/*	cache_sanity_check(); */

	/* Find number of seconds before next heartbeat. */
	if (heartbeat_freq != -1) {
	    next_heartbeat = (last_heartbeat -
			      (last_heartbeat % heartbeat_freq)
			      ) + heartbeat_freq;
	    time(&t);
	    seconds = (t >= next_heartbeat) ? 0 : next_heartbeat - t;
	    seconds = (paused ? 0 : seconds);
            /* fprintf(stderr, "seconds: %d\n", seconds); */
	}

        /* wait seconds for something to happen */
        handle_io_event_wait(seconds);

        /* input */
        handle_connection_input();

        /* handle new or pending connections */
	handle_new_and_pending_connections();

        /* do heartbeat? */
	if (heartbeat_freq != -1) {
	    time(&t);

            /* yep */
	    if (t >= next_heartbeat) {
                /* call heartbeat on $sys */
		last_heartbeat = t;
		task(NULL, SYSTEM_DBREF, heartbeat_id, 0);

                /* clenup the cache */
                cache_cleanup();
	    }
	}

        /* output */
        handle_connection_output();

        /* complete paused tasks */
	if (paused)
            run_paused_tasks();
    }
}

internal void usage (char * name) {
    fprintf(stderr, "\n\
Usage: %s [options] [base dir]\n\n\
    base directory will default to \".\" if unspecified.\n\n\
    Options which the driver does not understand are passed onto\n\
    the database with $sys.startup().\n\n\
Options:\n\n\
    -b <binary>      binary db directory name, default: \"binary\"\n\
    -r <root>        root file directory name, default: \"root\"\n\
    -t <textdump>    textdump filename, default: \"textdump\"\n\
    -e <bindir>      executables directory name, default: \"root/bin\"\n\
    -f               force loading from a textdump\n\
\n",  name);
}

#undef _main_