/* Game modules for Xconq.
   Copyright (C) 1991-1996, 1998-2000 Stanley T. Shebs.

Xconq is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.  See the file COPYING.  */

#include "conq.h"
#include "kernel.h"

extern char *readerrbuf;

static void do_one_variant(Module *module, struct a_variant *var,
			   Obj *varsetdata);

/* List of all known modules. Their descriptions can co-exist in memory,
   even if their contents cannot. */

Module *modulelist;

/* The main module defining the game in effect. */

Module *mainmodule;

char *moduledesigbuf = NULL;

/* Empty out the list of modules. */

void
clear_game_modules(void)
{
    modulelist = mainmodule = NULL;
}

/* Create a brand-new game module. */

Module *
create_game_module(char *name)
{
    Module *module = (Module *) xmalloc(sizeof(Module));

    /* Fill in nonzero slots. */
    copy_module(module, NULL);
    /* The module's name must never be NULL. */
    if (name == NULL)
      name = "";
    module->name = copy_string(name);
    /* Add to front of module list. */
    module->next = modulelist;
    modulelist = module;
    return module;
}

Module *
find_game_module(char *name)
{
    Module *module;

    if (name != NULL) {
	for_all_modules(module) {
	    if (module->name && strcmp(name, module->name) == 0)
	      return module;
	}
    }
    return NULL;
}


/* Produce a module of the given name, either by finding it or creating it. */

Module *
get_game_module(char *name)
{
    Module *module = find_game_module(name);

    if (module != NULL)
      return module;
    return create_game_module(name);
}

/* Make a game module for the given name and maybe bolt it into the include
   list of another module. */

Module *
add_game_module(char *name, Module *includer)
{
    Module *module = get_game_module(name), *other;

    if (includer) {
	/* Add to the end of the list of include files. */
	if (includer->include == NULL) {
	    includer->include = includer->lastinclude = module;
	} else {
	    for (other = includer->include; other != NULL; other = other->nextinclude) {
		/* already here, just return it. */
		if (module == other)
		  return module;
	    }
	    includer->lastinclude->nextinclude = module;
	    includer->lastinclude = module;
	}
    } else {
	/* an error? */
    }
    return module;
}

/* Sometimes we find ourselves lacking a game to provide meaning and
   context for interpretation; this routine loads the standard game
   (or a specified alternative default) immediately, but only makes
   it the main module if none defined. */

void
load_default_game(void)
{
    extern char *standard_game_name;
    char *defaultname = standard_game_name;
    Module *module, *module2;

    /* If we have a different default module, use it instead. */
    if (mainmodule != NULL
	&& !empty_string(mainmodule->defaultbasemodulename)) {
    	defaultname = mainmodule->defaultbasemodulename;
    }
    module = get_game_module(defaultname);
    if (mainmodule == NULL)
      mainmodule = module;
    load_game_description(module);
    /* Recurse one level of default base module. */
    /* (should recurse indefinitely, or not?) */
    if (!empty_string(module->defaultbasemodulename)) {
	module2 = get_game_module(module->defaultbasemodulename);
	load_game_module(module2, TRUE);
    }
    load_game_module(module, TRUE);
}

/* Attempt to read just the first form in a module and use it as a
   description of the module.  Return true if this worked, false
   otherwise. */

int
load_game_description(Module *module)
{
    Obj *form, *thecar;
    char *name;

    /* Module is already open, don't mess with it. */
    if (module->open)
      return FALSE;
    if (open_module(module, FALSE)) {
	if ((form = read_form(module->fp,
			      &(module->startlineno),
			      &(module->endlineno)))
	    != lispeof) {
	    if (consp(form) && symbolp(thecar = car(form))) {
		name = c_string(thecar);
		if (keyword_code(name) == K_GAME_MODULE) {
		    interp_game_module(form, module);
		    close_module(module);
		    /* Note that module is still not considered "loaded". */
		    return TRUE;
		}
	    }
	}
	close_module(module);
    }
    return FALSE;
}

/* Game files can live in library directories or somewhere else.  This
   function tries to find a file, open it, and load the contents. */

void
load_game_module(Module *module, int dowarn)
{
    char ch;
    Module *module2;

    if (numutypes == 0) {
	load_game_description(module);
	if (!empty_string(module->defaultbasemodulename)) {
	    module2 = get_game_module(module->defaultbasemodulename);
	    load_game_module(module2, TRUE);
	}
    }
    if (open_module(module, dowarn)) {
	if (module->fp) {
	    /* Peek at the first character - was 'X' in old format files. */
	    ch = getc(module->fp);
	    ungetc(ch, module->fp);
	    if (ch == 'X') {
		init_error("\"%s\" is probably an obsolete Xconq file; in any case, it cannot be used.",
			   module->filename);
	    } else {
		/* New format, read it all. */
		read_forms(module);
	    }
	} else {
	    /* (should be able to read from contents string) */
	}
	/* We're done, can close. */
	close_module(module);
	/* Mark the module as having been loaded - note that this will
	   happen even if there were horrible errors. */
	module->loaded = TRUE;
	/* If the turn number has been set explicitly to a positive
	   value, assume that a saved game is being restored into the
	   middle of the turn. */
	if (g_turn() > 0)
	  midturnrestore = TRUE;
	/* If the random state has been set explicitly to a
	   nonnegative value, use it to reseed the generator. */
	if (g_random_state() >= 0)
	  init_xrandom(g_random_state());
	/* Make all the cross-references right. */
	patch_object_references();
    }
}

void
load_base_module(Module *module)
{
    char *basename = module->basemodulename;
    Module *basemodule;

    if (!empty_string(basename)) {
	basemodule = find_game_module(basename);
	if (basemodule == NULL)
	  basemodule = add_game_module(basename, module);
	if (basemodule->loaded) {    
	    Dprintf("Base module `%s' already loaded.\n", basename);
	} else {
	    Dprintf("Loading base module `%s' ...\n", basename);
	    load_game_module(basemodule, FALSE);
	    Dprintf("... Done loading `%s'.\n", basename);
	}
    }
}

/* Given a module, attempt to open it. */

int
open_module(Module *module, int dowarn)
{
    FILE *fp = NULL;

    /* Don't open more than once. */
    if (module->open && dowarn) {
	init_warning("Module \"%s\" is already open, ignoring attempt to reopen",
		     module->name);
	return FALSE;
    }
    /* Don't open if already loaded. */
    if (module->loaded && dowarn) {
	init_warning("Module \"%s\" is already loaded, ignoring attempt to reload",
		     module->name);
	return FALSE;
    }
    if (module->contents) {
	/* Uninterpreted contents already available, init the ptr. */
	module->sp = module->contents;
	Dprintf("Reading module \"%s\" from string ...\n", module->name);
    } else if ((fp = open_module_library_file(module)) != NULL) {
	/* Found the module in a library directory. */
	Dprintf("Reading module \"%s\" from library file \"%s\" ...\n",
		module->name, module->filename);
	module->fp = fp;
    } else if ((fp = open_module_explicit_file(module)) != NULL) {
	Dprintf("Reading module \"%s\" from file \"%s\" ...\n",
		module->name, module->filename);
	module->fp = fp;
    } else {
	if (dowarn) {
	    if (module->name) {
	    	init_warning("Can't find module \"%s\" anywhere",
			     module->name);
	    } else {
	    	init_warning("Can't find unnamed module anywhere");
	    }
	}
	return FALSE;
    }
    /* It worked, mark this module as open. */
    module->open = TRUE;
    return TRUE;
}

/* Read info about a side's preferences and setup. */

/* This assumes one form only, probably too restrictive. */
/* should read all the forms, use the relevant ones. */
/* (how does this interact with other defaults?) */
/* (should be delayed until player can confirm it...) */

/* (update to work like other module stuff? then can use resources etc) */
/* (fix so that correct name can be found reliably) */

int
load_side_config(Side *side)
{
#if 0
    FILE *fp;
    Obj *config;
    Module *module;

    /* (should incorp config name somehow, also be sys-dependent) */
    module = create_game_module(side->player->name);

    /* Don't use fopen directly, use open_module which indirectly calls 
    open_file to ensure mac/unix cross-platform compatibility. */    
    if (open_module(module, FALSE)) {
	if ((config = read_form(module->fp,
				&(module->startlineno),
				&(module->endlineno)))
	    != lispeof) {
	    /* interpret the config */
	    Dprintf("Interpreting %s config form", side_desig(side));
	    Dprintlisp(config);
	    Dprintf("\n");
	    fill_in_side(side, config, TRUE);
	} else {
	    /* no config form in the file */
	}
    } else {
	init_warning("Module \"%s\" could not be opened", module->name);
	/* Not a disaster, keep going */
    }
#endif
    return FALSE;
}

/* Read an entire file, attempting to pick up objects in it. */

/* (does this interp game-module form twice if description previously
   loaded?) */

void
read_forms(Module *module)
{
    Obj *form;

    Dprintf("Trying to read a new format file \"%s\"...\n", module->name);
    if (module->fp != NULL) {
	while ((form = read_form(module->fp,
			     &(module->startlineno),
			     &(module->endlineno)))
		!= lispeof) {
	    interp_form(module, form);
	}
    } else {
	while ((form = read_form_from_string(module->sp,
			     &(module->startlineno),
			     &(module->endlineno),
			     &(module->sp)))
		!= lispeof) {
	    interp_form(module, form);
	}
    }
    /* Clean up after any print forms that might have been done. */
    end_printing_forms();
    Dprintf("... Done reading \"%s\".\n", module->name);
}

void
set_variant_value(int which, int v1, int v2, int v3)
{
    Variant *var;

    var = &(mainmodule->variants[which]);
    var->newvalues[0] = v1;
    var->newvalues[1] = v2;
    var->newvalues[2] = v3;
}

/* Interpret the given list of variants, using the given list to
   fill in values not already supplied in the variant. */

void
do_module_variants(Module *module, Obj *lis)
{
    int i, found, didsome;
    Obj *restset, *varset;
    Variant *var;

    if (module->variants == NULL)
      return; /* error? */
    didsome = FALSE;
    for_all_list(lis, restset) {
	varset = car(restset);
	found = FALSE;
	for (i = 0; module->variants[i].id != lispnil; ++i) {
	    var = &(module->variants[i]);
	    if (equal(car(varset), var->id)) {
		do_one_variant(module, var, cdr(varset));
		found = TRUE;
		didsome = TRUE;
	    }
	}
	if (!found) {
	    read_warning("Mystifying variant");
	}
    }
    /* Now implement all the defaults. */
    for (i = 0; module->variants[i].id != lispnil; ++i) {
	var = &(module->variants[i]);
	if (!var->used) {
	    do_one_variant(module, var, lispnil);
	    didsome = TRUE;
	}
    }
    if (didsome) {
	/* Make all the cross-references right. */
	patch_object_references();
	/* Recheck everything, the variants might have broken something. */
	check_game_validity();
    }
}

/* Implement the effect of the single given variant. */

static void
do_one_variant(Module *module, Variant *var, Obj *varsetdata)
{
    int val, caseval;
    int width = 0, height = 0, circumference /*, latitude, longitude*/;
    int rtime, rtimeperturn, rtimeperside;
    char *vartypename = c_string(var->id);
    Obj *restcases, *headcase, *rest, *filler, *rawcaseval;

    if (Debug) {
	if (readerrbuf == NULL)
	  readerrbuf = (char *) xmalloc(BUFSIZE);
	if (varsetdata != lispnil)
	  sprintlisp(readerrbuf, varsetdata, BUFSIZE);
	else
	  sprintf(readerrbuf, "%d %d %d",
		  var->newvalues[0], var->newvalues[1], var->newvalues[2]);
    	Dprintf("Module %s variant %s being set to `%s'\n",
	    	module_desig(module), vartypename, readerrbuf);
    }
    switch (keyword_code(vartypename)) {
      case K_WORLD_SEEN:
	val = var->newvalues[0];
	if (varsetdata != lispnil)
	  val = c_number(eval(car(varsetdata)));
	var->hasintvalue = TRUE;
	var->intvalue = val;
	set_g_terrain_seen(val);
	break;
      case K_SEE_ALL:
	val = var->newvalues[0];
	if (varsetdata != lispnil)
	  val = c_number(eval(car(varsetdata)));
	var->hasintvalue = TRUE;
	var->intvalue = val;
	set_g_see_all(val);
	break;
      case K_SEQUENTIAL:
	val = var->newvalues[0];
	if (varsetdata != lispnil)
	  val = c_number(eval(car(varsetdata)));
	var->hasintvalue = TRUE;
	var->intvalue = val;
	set_g_use_side_priority(val);
	break;
      case K_PEOPLE:
	val = var->newvalues[0];
	if (varsetdata != lispnil)
	  val = c_number(eval(car(varsetdata)));
	var->hasintvalue = TRUE;
	var->intvalue = val;
	set_g_people(val);
	break;
      case K_ECONOMY:
	val = var->newvalues[0];
	if (varsetdata != lispnil)
	  val = c_number(eval(car(varsetdata)));
	var->hasintvalue = TRUE;
	var->intvalue = val;
	set_g_economy(val);
	break;
      case K_SUPPLY:
	val = var->newvalues[0];
	if (varsetdata != lispnil)
	  val = c_number(eval(car(varsetdata)));
	var->hasintvalue = TRUE;
	var->intvalue = val;
	set_g_supply(val);
	break;
      case K_WORLD_SIZE:
      	filler = lispnil;
	if (varsetdata != lispnil) {
	    filler = varsetdata;
	}
	/* Pick the width and height out of the list. */
	if (filler != lispnil) {
	    width = c_number(eval(car(filler)));
	    filler = cdr(filler);
	} else {
	    width = var->newvalues[0];
	}
	if (filler != lispnil) {
	    height = c_number(eval(car(filler)));
	    filler = cdr(filler);
	} else {
	    height = var->newvalues[1];
	}
	/* Pick up a circumference if given. */
	if (filler != lispnil) {
	    circumference = c_number(eval(car(filler)));
	    filler = cdr(filler);
	} else {
	    circumference = var->newvalues[2];
	}
	/* This is more useful after the circumference has been set. */
	if (width > 0 && height > 0)
	  set_area_shape(width, height, TRUE);
	set_world_circumference(circumference, TRUE);
#if 0 /* (redo as separate variant) */
	/* Pick up latitude and longitude if given. */
	if (filler != lispnil) {
	    latitude = c_number(eval(car(filler)));
	    /* (should use a setter routine?) */
	    area.latitude = latitude;
	    filler = cdr(filler);
	}
	if (filler != lispnil) {
	    longitude = c_number(eval(car(filler)));
	    /* (should use a setter routine?) */
	    area.longitude = longitude;
	    filler = cdr(filler);
	}
#endif
	/* (should record settings somehow) */
	break;
      case K_REAL_TIME:
      	filler = lispnil;
	if (varsetdata != lispnil) {
	    filler = varsetdata;
	}
	if (filler != lispnil) {
	    rtime = c_number(eval(car(filler)));
	    filler = cdr(filler);
	} else {
	    rtime = var->newvalues[0];
	}
	if (filler != lispnil) {
	    rtimeperside = c_number(eval(car(filler)));
	    filler = cdr(filler);
	} else {
	    rtimeperside = var->newvalues[1];
	}
	if (filler != lispnil) {
	    rtimeperturn = c_number(eval(car(filler)));
	    filler = cdr(filler);
	} else {
	    rtimeperturn = var->newvalues[2];
	}
	/* If the values were specified, tweak the official
	   realtime globals. */
	if (rtime > 0)
	  set_g_rt_for_game(rtime);
	if (rtimeperside > 0)
	  set_g_rt_per_side(rtimeperside);
	if (rtimeperturn > 0)
	  set_g_rt_per_turn(rtimeperturn);
	/* (should record settings somehow) */
	break;
      default:
	/* This is the general case. */
	val = var->newvalues[0];
	if (varsetdata != lispnil)
	  val = c_number(eval(car(varsetdata)));
	var->hasintvalue = TRUE;
	var->intvalue = val;
	for_all_list(var->cases, restcases) {
	    headcase = car(restcases);
	    rawcaseval = eval(car(headcase));
	    if (numberp(rawcaseval))
	      caseval = c_number(rawcaseval);
	    if (numberp(rawcaseval) && caseval == val) {
		for_all_list(cdr(headcase), rest) {
		    interp_form(module, car(rest));
	    	}
	    }
	}
	/* Clean up after printing, might have been print forms in variant. */
	end_printing_forms();
	break;
    }
    /* Flag the variant as having been specified. */
    var->used = TRUE;
}

/* Given a module and a original, copy over properties of the original
   module. */

void
copy_module(Module *module, Module *origmodule)
{
    char *name;
    Module *next, *incl, *nextincl, *lastincl;

    /* No matter what, preserve the existing name. */
    name = module->name;
    if (origmodule != NULL) {
	/* Save away the module's links to other modules. */
	next = module->next;
	incl = module->include;
	nextincl = module->nextinclude;
	lastincl = module->lastinclude;
	memcpy(module, origmodule, sizeof(Module));
	/* By default, we copy everything through; but keep a separate
	   copy of the original name and variants, because the calling
	   routine may overwrite with other values. */
	/* (should test each property individually here?) */
	if (empty_string(origmodule->origmodulename)) {
	    /* If no original module, use the main module. */
	    module->origmodulename = origmodule->name;
	    module->origvariants = origmodule->variants;
	    module->origversion = origmodule->version;
	}
	/* Kill off caches that should not be replicated. */
	module->contents = NULL;
	module->sp = NULL;
	module->fp = NULL;
	module->open = FALSE;
	module->loaded = FALSE;
	/* Restore links. */
	module->next = next;
	module->include = incl;
	module->nextinclude = nextincl;
	module->lastinclude = lastincl;
    } else {
	/* If there is no original from which to copy, just do some
	   generic clearing/init. */
	memset(module, 0, sizeof(Module));
	module->instructions = lispnil;
	module->notes = lispnil;
	module->designnotes = lispnil;
	module->startlineno = 1;
	module->endlineno = 1;
    }
    module->name = name;
}

void
init_module_reshape(Module *module)
{
    /* Seed all the reshaping parameters with reasonable values. */
    module->maybe_reshape = TRUE;
    module->subarea_width = area.width;
    module->subarea_height = area.height;
    module->subarea_x = module->subarea_y = 0;
    module->final_subarea_width = area.width;
    module->final_subarea_height = area.height;
    module->final_subarea_x = module->final_subarea_y = 0;
    module->final_width = area.width;  module->final_height = area.height;
    module->final_circumference = world.circumference;
}

/* This is true if any actual reshaping is required. */

int
reshape_the_output(Module *module)
{
    return (module->maybe_reshape
	    && (module->subarea_width != area.width
		|| module->subarea_height != area.height
		|| module->subarea_x != 0
		|| module->subarea_y != 0
		|| module->final_subarea_width != area.width
		|| module->final_subarea_height != area.height
		|| module->final_subarea_x != 0
		|| module->final_subarea_y != 0
		|| module->final_width != area.width
		|| module->final_height != area.height
		|| module->final_circumference != world.circumference));
}

/* Check if the proposed reshape will actually work. */

int
valid_reshape(Module *module)
{
    /* (should check hexagon shaping) */
    if (module->subarea_width > area.width
	|| module->subarea_height > area.height)
      return FALSE;
    /* (should check other offsets) */
    if (module->final_width < 3 || module->final_height < 3)
      return FALSE;
    return TRUE;
}

/* Close the module. */

void
close_module(Module *module)
{
    if (module->sp) {
	module->sp = NULL;
    }
    if (module->fp) {
	fclose(module->fp);
	module->fp = NULL;
    }
    module->open = FALSE;
}

/* Return a description of the module. */

char *
module_desig(Module *module)
{
    if (moduledesigbuf == NULL)
      moduledesigbuf = xmalloc(BUFSIZE);
    sprintf(moduledesigbuf, "module %s (%s)",
	    module->name, (module->title ? module->title : "no title"));
    return moduledesigbuf;
}

/* (random code below, should be sent to better places) */

/* If a special symbol, we might not have to fail.  Note that although
   this looks like Lisp-level code, it knows about unit types and so
   forth, so is higher-level. */

int
lazy_bind(Obj *sym)
{
    int u, m, t, a;
    Obj *value;

    switch (keyword_code(c_string(sym))) {
      case K_USTAR:
	value = lispnil;
	/* Since consing glues onto the front, iterate backwards
	   through the types. */
	for (u = numutypes - 1; u >= 0; --u)
	  value = cons(new_utype(u), value);
	break;
      case K_MSTAR:
	value = lispnil;
	for (m = nummtypes - 1; m >= 0; --m)
	  value = cons(new_mtype(m), value);
	break;
      case K_TSTAR:
	value = lispnil;
	for (t = numttypes - 1; t >= 0; --t)
	  value = cons(new_ttype(t), value);
	break;
      case K_ASTAR:
	value = lispnil;
	for (a = numatypes - 1; a >= 0; --a)
	  value = cons(new_atype(a), value);
	break;
      default:
	return FALSE;
    }
    setq(sym, value);
    return TRUE;
}
