/* dump.c: Routines to handle binary and text database dumps. */
#define _POSIX_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "x.tab.h"
#include "dump.h"
#include "cache.h"
#include "object.h"
#include "log.h"
#include "data.h"
#include "config.h"
#include "util.h"
#include "execute.h"
#include "grammar.h"
#include "db.h"
#include "ident.h"

static Method *text_dump_get_method(FILE *fp, Object *obj, char *name);

extern int cur_search;

/* Binary dump.  This dump must not allocate any memory, since we may be
 * performing it under low-memory conditions. */
int binary_dump(void)
{
    cache_sync();
    return 1;
}

/* Text dump.  This dump can allocate memory, and thus shouldn't be used as a
 * panic dump for low-memory situations. */
int text_dump(void)
{
    FILE *fp;
    Object *obj;

    /* Open the output file. */
    fp = open_scratch_file("textdump", "w");
    if (!fp)
	return 0;

    /* Now dump the database. */
    cur_search++;
    for (obj = cache_first(); obj; obj = cache_next()) {
	object_text_dump(obj->dbref, fp);
	cache_discard(obj);
    }

    close_scratch_file(fp);
    return 1;
}

void text_dump_read(FILE *fp)
{
    String *line;
    Object *obj = NULL;
    List *parents;
    Data d;
    long dbref = -1, name;
    char *p;
    Method *method;

    /* Initialize parents to an empty list. */
    parents = list_new(0);

    while ((line = fgetstring(fp))) {

	if (!strnccmp(line->s, "parent", 6) && isspace(line->s[6])) {
	    for (p = line->s + 7; isspace(*p); p++);

	    /* Add this parent to the parents list. */
	    d.type = DBREF;
	    d.u.dbref = ident_get(p);
	    if (cache_check(d.u.dbref))
		parents = list_add(parents, &d);
	    else
		write_log("Invalid parent %s", ident_name(d.u.dbref));
	    ident_discard(d.u.dbref);

	} else if (!strnccmp(line->s, "object", 6) && isspace(line->s[6])) {
	    for (p = line->s + 7; isspace(*p); p++);

	    /* If the parents list is empty, and this isn't "root", parent it
	     * to root. */
	    if (!parents->len && strcmp(p, "root") != 0) {
		write_log("Orphan object %s parented to root", p);
		if (!cache_check(root_id))
		    fail_to_start("Root object not first in text dump.");
		d.type = DBREF;
		d.u.dbref = root_id;
		parents = list_add(parents, &d);
	    }

	    /* Get the dbref. */
	    dbref = ident_get(p);

	    /* Discard the old object if we had one.  Also see if dbref already
	     * exists, and delete it if it does. */
	    if (obj)
		cache_discard(obj);
	    obj = cache_retrieve(dbref);
	    if (obj) {
		obj->dead = 1;
		cache_discard(obj);
	    }

	    /* Create the new object. */
	    obj = object_new(dbref, parents);
	    list_discard(parents);
	    parents = list_new(0);

	} else if (!strnccmp(line->s, "var", 3) && isspace(line->s[3])) {
	    for (p = line->s + 4; isspace(*p); p++);

	    /* Get variable owner. */
	    dbref = parse_ident(&p);

	    /* Skip spaces and get variable name. */
	    while (isspace(*p))
		p++;
	    name = parse_ident(&p);

	    /* Skip spaces and get variable value. */
	    while (isspace(*p))
		p++;
	    data_from_literal(&d, p);

	    /* Create the variable. */
	    object_put_var(obj, dbref, name, &d);

	    ident_discard(dbref);
	    ident_discard(name);
	    data_discard(&d);

	} else if (!strccmp(line->s, "eval")) {
	    method = text_dump_get_method(fp, obj, "<eval>");
	    if (method) {
		task_eval(NULL, obj, method);
		method_discard(method);
	    }

	} else if (!strnccmp(line->s, "method", 6) && isspace(line->s[6])) {
	    for (p = line->s + 7; *p == ' '; p++);
	    method = text_dump_get_method(fp, obj, p);
	    if (method) {
		name = ident_get(p);
		object_add_method(obj, name, method);
		method_discard(method);
		ident_discard(name);
	    }
	}
	string_discard(line);
    }

    if (obj)
	cache_discard(obj);
    list_discard(parents);
}

static Method *text_dump_get_method(FILE *fp, Object *obj, char *name)
{
    Method *method;
    List *code, *errors;
    String *line;
    Data d;
    int i;

    code = list_new(0);
    d.type = STRING;
    while ((line = fgetstring(fp))) {
	if (line->len == 1 && *line->s == '.') {
	    /* End of the code.  Compile the method, display any error
	     * messages we may have received, and return the method. */
	    string_discard(line);
	    method = compile(obj, code->el, code->len, &errors);
	    list_discard(code);
	    for (i = 0; i < errors->len; i++) {
		write_log("$%I %s: %S", obj->dbref, name,
			  data_sptr(&errors->el[i]),
			  errors->el[i].u.substr.span);
	    }
	    list_discard(errors);
	    return method;
	}

	substr_set_to_full_string(&d.u.substr, line);
	code = list_add(code, &d);
	string_discard(line);
    }

    /* We ran out of lines.  This wasn't supposed to happen. */
    write_log("Text dump ended inside method.");
    return NULL;
}