/* supply.c 1.0 Sep-7-1998 - implementation of the (new) supply system.
 * 
 * Copyright (C) 1998 Sami Perttu.
 * Send bug reports to perttu@cc.helsinki.fi or the Xconq mailing list.
 * 
 * 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.
 * 
 * Module interface:
 * 
 * run_supply();
 * 	Run the supply algorithm for all sides. Called right after
 *	run_economy() from run2.c.
 * run_supply_side(side);
 * 	Run the supply algorithm for a specified side. Should be used in
 * 	sequential games to resolve supply at the beginning of each player's
 * 	turn.
 * boolean = supply_system_in_use();
 * 	Can be called by interfaces to find out whether the supply system is
 * 	used for a game or not. If not, then "supply flow" and "supply
 * 	connectedness" shouldn't be displayed for units.
 * compute_supply_zones(side, material);
 * 	Compute supply potentials for a specified material. On return, the
 * 	layer tmp1 will contain the potentials. This function is meant to be
 *	called by interfaces.
 * compute_unit_supply_zone(side, unit, material);
 * 	Same as above, but compute only the supply zone resulting from the
 *	given unit's supply-potential property.
 * 
 * A sixth function might be added later:
 * 
 * run_indep_supply();
 * 	Run supply for the independent side. Independent supply sources could
 *	provide supply to non-independent units as well, depending on some
 *	as-yet-unimplemented GDL tables.
 * 
 * Everything else is declared static. No initialization calls need to be
 * made: the supply system takes care of that automatically.
 * 
 * Some terrain optimizations (for_all_border_types, for_all_connection_types)
 * should find their way to init.c and world.h.
 * 
 * One supply calculation pass for a side consists of several phases:
 * 
 * 1. Initialize temporary layers.
 * 2. Find all units with a positive supply-potential property.
 * 3. Expand supply lines in descending order of supply zone potential.
 * 4. Establish supply zones with a flood-fill algorithm, calculating net
 *    material balance in each zone.
 * 5. For each zone: distribute supplies, ignoring any remnants. Then sort
 *    units and distribute the remainder. Ties are broken randomly.
 * 
 * One equivalence class of materials is processed in one supply calculation
 * pass. An equivalence class consists of all material types whose parameters
 * concerning the establishment of supply zones are identical.
 * 
 * Read the docs (?) for more info.
 */

/* qsort() */
#include <stdlib.h>

/* memcpy() */
#include <string.h>

#include "conq.h"

#define INITIAL_WORKSPACE	1024
#define MAX_FLOW		16384
#define MAX_CONN		25600

/* These should be moved to world.h */
#define for_all_border_types(t) for((t) = firstbtype; ((t) = btypear[t]) >= 0;)
#define for_all_connection_types(t) for((t) = firstctype; ((t) = ctypear[t]) >= 0;)

/* Accessor macro */
#define um_clip_in_threshold(u, m) umclipinthreshold[nummtypes * (u) + (m)]

typedef struct heap_node_struct {
    int pot;		/* supply potential */
    short x, y;		/* cell coordinates */
} heap_node;

typedef struct zone_node_struct {
    short re;		/* zone potential, material remainder or supply weight;
			 * used as a sort key in the latter two cases */
    short uw;		/* the worth of one unit of material in s_flow */
    Unit *unit;		/* the unit */
} zone_node;

typedef struct material_stats_struct {
    int mtype;		/* material type */
    long supply;	/* (net) supply */
    long demand;	/* (net) demand */
    long weight;	/* supply weight */
    long wdemand;	/* weighted (net) demand */
    struct material_stats_struct *next;		/* next material in this class */
    struct material_stats_struct *next_class;	/* next equivalence class */
} material_stats;

/* Copying the whole structure (~8 bytes) is a bit wasteful, but it's way
 * simpler than working with a pointer-based heap. */
#define heap_node_copy(dst, src) \
{ (dst) -> pot = (src) -> pot; (dst) -> x = (src) -> x; (dst) -> y = (src) -> y; }

/* Static protos. */
static void init_supply_system PARAMS((void));
static void init_tmp_layers PARAMS((void));
static void process_supply_class PARAMS((Side *side, material_stats *mclass));
static void add_unit_heap PARAMS((Unit *unit, int m));
static void run_supply_lines PARAMS((Side *side, int snum, int m));
static void establish_supply_zone PARAMS((Side *side, int x, int y, material_stats *mclass));
static void process_unit_supply PARAMS((Unit *unit, material_stats *mclass, int pot));
static int calculate_supply_demand PARAMS((Unit *unit, int m, material_stats *res, int pot, int rec_stats));
static void heap_init PARAMS((void));
static void heap_insert PARAMS((int pot, short x, short y));
static int heap_deque PARAMS((heap_node *hn));
static void zone_init PARAMS((void));
static void zone_insert PARAMS((Unit *unit, int wd));
static void zone_sort PARAMS((void));
static void zone_shuffle PARAMS((void));
static int compare_zone_nodes PARAMS((const void *a, const void *b));
static void extend_workspace PARAMS((int semantic_bytes));
static void optimize_terrain PARAMS((void));

static int supply_initialized = FALSE;

/* Equivalence classes of materials. */
static int mclass_count;
static material_stats *mstats = NULL;
static material_stats *first_mstat;

/* Clipped supply-in-thresholds (should modify tables directly??) */
static short *umclipinthreshold;

/* Net importance array */
static int *neti;

/* Workspace for sorting and the heap. */
static void *ws;
static int ws_size = 0;

/* These point to the same region as ws. */
static zone_node *zone_ptr;
static int zone_size;
static int zone_entries;
static heap_node *heap_ptr;
static int heap_size;
static int heap_entries;

/* For terrain macros (see supply.h). */
static int numbtypes, numctypes;
static int firstbtype, firstctype;
static int *btypear, *ctypear;

/* Emergency hack. */
#if 0
#undef Dprintf
#define Dprintf printf
#endif

void
run_supply(void)
{
    Side *side;

    /* Run the algorithm for all sides in the game (except the independent
     * side). */
    for_all_sides(side) {
	run_supply_side(side);
    }
}

void
run_supply_side(side)
Side *side;
{
    Unit *unit;
    material_stats *mclass;

    /* Make sure we're set up. */
    init_supply_system();

    /* Do we need to run at all? */
    if(!mclass_count) return;
    
    /* Initialize a_unit variables. */
    for_all_side_units(side, unit) {
	/* If no materials are important to a unit, it is always considered to
	 * have optimal supply status. */
	if(neti[unit -> type]) {
	    unit -> s_flow = unit -> s_conn = 0;
	} else {
	    unit -> s_flow = MAX_FLOW;
	    unit -> s_conn = MAX_CONN;
	}
    }

    Dprintf("Running supply for side %d.\n", side_number(side));
    
    /* Run the algorithm for all equivalence classes of materials. */
    mclass = first_mstat;
    do {
	process_supply_class(side, mclass);
    } while(mclass = mclass -> next_class);
}

int
supply_system_in_use(void)
{
    init_supply_system();

    return mclass_count ? TRUE : FALSE;
}

void
compute_supply_zones(side, m)
Side *side;
int m;
{
    Unit *unit;

    init_supply_system();
    if(!mclass_count) return;

    init_tmp_layers();

    heap_init();
    for_all_side_units(side, unit) add_unit_heap(unit, m);

    run_supply_lines(side, side_number(side), m);
}

void
compute_unit_supply_zone(side, unit, m)
Side *side;
Unit *unit;
int m;
{
    init_supply_system();
    if(!mclass_count) return;

    init_tmp_layers();

    heap_init();
    add_unit_heap(unit, m);
    
    run_supply_lines(side, side_number(side), m);
}

/* init_supply_system() determines if, and how, the supply algorithm will
 * be used. */
static void
init_supply_system(void)
{
    material_stats *ms, *ps = NULL, *pc = NULL;
    int mad[MAXMTYPES];
    int m, m2, i, u, cond, t;

    if(supply_initialized) return; supply_initialized = TRUE;

    Dprintf("Initializing the supply system.\n");

    mclass_count = 0;

    /* Exit immediately if there are no material types. */
    if(!nummtypes) return;
    
    /* for_all_border_types, for_all_connection_types */
    optimize_terrain();
    
    /* Allocate space for material information. */
    mstats = (material_stats *)xmalloc(nummtypes * sizeof(material_stats));

    /* Clip supply-in-threshold. */
    umclipinthreshold = (short *)xmalloc(nummtypes * numutypes * sizeof(short));
    
    for_all_unit_types(u) for_all_material_types(m) {
	um_clip_in_threshold(u, m) = min(um_supply_in_threshold(u, m), um_storage_x(u, m));
    }
    
    /* Initialize the net importance array. */
    neti = (int *)xmalloc(numutypes * sizeof(int));
    
    for_all_unit_types(u) {
	neti[u] = 0;
	for_all_material_types(m) neti[u] += um_supply_importance(u, m);
    }
    
    /* Analyze supply zone parameters and set up equivalence classes for
     * materials. */
    ms = mstats;
    
    for(i = 0; i < MAXMTYPES; ++i) mad[i] = FALSE;
    
    for_all_material_types(m) {
	
	/* Already added? */
	if(mad[m]) continue;
	
	Dprintf("Analyzing material %d.\n", m);

	/* Find out if this material type is used at all in the context of the
	 * supply system. */
	cond = FALSE;
	
	for_all_unit_types(u) {
	    if(um_supply_potential(u, m) > 0) { cond = TRUE; break; }
	}
	
	if(!cond) Dprintf("Supply system not needed with this material.\n");

	/* If not, skip to the next material. */
	if(!cond) continue;
	
	/* Establish a new equivalence class. */
	++mclass_count;
	ms -> next       = NULL;
	ms -> mtype      = m;
	ms -> next_class = pc;
	mad[m]           = TRUE;
	ps               = ms++;
	
	Dprintf("New equivalence class established.\n");
	
	/* Find any matching materials and add them to the class. */
	for_all_material_types(m2) {

	    if(mad[m2]) continue;
	    
	    cond = TRUE;
	    
	    for_all_unit_types(u) {
		if(um_supply_potential(u, m)
		      != um_supply_potential(u, m2)
		   || um_supply_interdiction_at_for_material(u, m)
		      != um_supply_interdiction_at_for_material(u, m2)
		   || um_supply_interdiction_adjacent_for_material(u, m)
		      != um_supply_interdiction_adjacent_for_material(u, m2)) {
		    cond = FALSE;
		    break;
		}
	    }
	    
	    if(cond) {
		for_all_terrain_types(t) {
		    if(tm_supply_deterioration(t, m)
		          != tm_supply_deterioration(t, m2)
		       || tm_supply_enemy_interdiction(t, m)
		          != tm_supply_enemy_interdiction(t, m2)
		       || tm_supply_neutral_interdiction(t, m)
		          != tm_supply_neutral_interdiction(t, m2)) {
			cond = FALSE;
			break;
		    }
		}
	    }
	    
	    if(cond) {
		/* All parameters relating to the establishment of supply
		 * zones are identical. */
		ms -> next       = ps;
		ms -> mtype      = m2;
		ms -> next_class = pc;
		mad[m2]          = TRUE;
		ps               = ms++;
		
		Dprintf("Material %d added to the class.\n", m2);
	    }
	}

	/* Link this class to the next. */
	pc = ps;
    }

    /* Link to the first material_stats structure. */
    first_mstat = ps;
    
    Dprintf("Initialization complete. Memory allocated = %d bytes\n",
	    (size_t)ms - (size_t)mstats + nummtypes * numutypes * sizeof(short)
	    + numutypes * sizeof(int));

    /* Allocate workspace if needed. */
    if(mclass_count) extend_workspace(0);

    /* Finally, trim memory taken by material_stats. */
    (void)realloc(mstats, (size_t)ms - (size_t)mstats);
}

static void
init_tmp_layers(void)
{
    int x, y;
    
    Dprintf("Initializing scratch layers.\n");

    /* Make sure we have the necessary scratch layers. */
    allocate_area_scratch(2);

    /* Initialize the scratch layers. tmp2 is lazily evaluated.
     * 
     * tmp1 = supply potentials
     * tmp2 = interdiction by enemy/neutral control/units */
    for_all_cells(x, y) {
	set_tmp1_at(x, y, 0);
	set_tmp2_at(x, y, -1);	/* -1 = indeterminate */
    }
}

static void
process_supply_class(side, mclass)
Side *side;
material_stats *mclass;
{
    int x, y, t;
    int m, n;
    int snum;
    long nrem;
    Unit *unit;
    material_stats us, *ms;
    zone_node *zn;
    
    /* Initialize the heap. */
    heap_init();
    
    /* Set m to any material representing this equivalence class. */
    m = mclass -> mtype;
    
    Dprintf("Processing material class related to material %d.\n", m);

    /* Find out this side's number. */
    snum = side_number(side);

    /* Initialize temporary layers. */
    init_tmp_layers();
    
    Dprintf("Looking for applicable units.\n");
    
    /* Find all cells which contain a unit with a non-zero supply-potential and
     * insert them into the heap. */
    for_all_side_units(side, unit) {
	add_unit_heap(unit, m);
    }

    Dprintf("Now expanding supply zones.\n\n");

    run_supply_lines(side, snum, m);

    Dprintf("Complete. Establishing supply zones:\n");
    
    /* Layer tmp1 contains supply potentials for each cell. Now we need to find
     * the actual supply zones (continuous areas of non-zero supply potentials).
     */
    for_all_cells(x, y) if(tmp1_at(x, y)) {
	    
	ms = mclass;
	
	Dprintf("Supply zone found at (%d, %d). Processing:\n", x, y);
	
	do {
	    ms -> demand     = 0;
	    ms -> supply     = 0;
	    ms -> wdemand    = 0;
	} while(ms = ms -> next);
	
	zone_init();
	
	establish_supply_zone(side, x, y, mclass);

	/* Process the supply zone. We now know the net supply and demand of
	 * each material in the equivalence class, and have a list of all relevant
	 * units in the supply zone. Alas, we have to calculate unit-specific
	 * supply and demand anew, since storing them could potentially hog a lot
	 * of memory. */
	ms = mclass;

	do {
	    Dprintf("Net balance for material %d: supply=%d, demand=%d, weighted demand=%d.\n",
		    ms -> mtype, ms -> supply, ms -> demand, ms -> wdemand);

	    m = ms -> mtype;
	
	    /* Shuffle the zone entries. This is the most straightforward
	     * way of ensuring random distribution of any remainder
	     * materials. */
	    zone_shuffle();

	    if(!(ms -> supply && ms -> demand)) continue;
	    
	    Dprintf("There's a total of %d units in this supply zone.\n", zone_entries);
	    
	    if(ms -> supply >= ms -> demand) {
		    
		/* There's enough of material ms -> mtype for
		 * everyone.
		 */
		nrem = ms -> demand;
		
		for(zn = zone_ptr, n = zone_entries; n; --n, ++zn) {

		    (void)calculate_supply_demand(zn -> unit, m, &us, zn -> re, FALSE);
		
		    zn -> unit -> supply[m] += us.demand;
		
		    if(neti[zn -> unit -> type]) {
			zn -> unit -> s_flow +=
			  ((int)um_supply_importance(zn -> unit -> type, m) << 14)
			  / neti[zn -> unit -> type];
		    }

		    if(us.demand) 
		      Dprintf("Gave %d of material %d to (%d, %d).\n",
			      us.demand, m, zn -> unit -> x, zn -> unit -> y);

		    if(us.supply) {
			t        = (us.supply * ms -> demand) / ms -> supply;
			zn -> re = (us.supply * ms -> demand) % ms -> supply;
			/* Round down during the first pass. */
			zn -> unit -> supply[m] -= t;
			Dprintf("Took %d of material %d from (%d, %d).\n",
				t, m, zn -> unit -> x, zn -> unit -> y);
			nrem -= t;
		    } else zn -> re = 0;
		}


		if(nrem) {
		    /* Let's hope qsort() doesn't mess up the
		     * beautiful shuffling we did earlier... */
		    zone_sort();
		
		    for(zn = zone_ptr; nrem; --nrem, ++zn) {
			Dprintf("Took remainder from %d at (%d, %d) with %d.\n",
				zn -> unit -> type, zn -> unit -> x, zn -> unit -> y, zn -> re);
			zn -> unit -> supply[m]--;
		    }
		}
	    } else {
		/* There's a shortage of material ms -> mtype.
		 */
		nrem = ms -> supply;
		
		for(zn = zone_ptr, n = zone_entries; n; --n, ++zn) {
		    /* Some of this is stupid, will have to optimize later */
		    (void)calculate_supply_demand(zn -> unit, m, &us, zn -> re, FALSE);
		    zn -> uw = zn -> re;
		    zn -> re = us.weight;
		}

		/* Sort by supply weight. */
		zone_sort();
		
		for(zn = zone_ptr, n = zone_entries; n; --n, ++zn) {
			    
		    (void)calculate_supply_demand(zn -> unit, m, &us, zn -> uw, FALSE);
		    
		    zn -> unit -> supply[m] -= us.supply;
		
		    if(us.supply)
		      Dprintf("Took %d of material %d from (%d, %d).\n",
			      us.supply, m, zn -> unit -> x, zn -> unit -> y);

		    if(us.wdemand) {
			t = (us.wdemand * ms -> supply) / ms -> wdemand;
			
			if(t >= us.demand) {
			    if(neti[zn -> unit -> type]) {
				zn -> unit -> s_flow +=
				  ((int)um_supply_importance(zn -> unit -> type, m) << 14)
				  / neti[zn -> unit -> type];
			    }
			    zn -> unit -> supply[m] += us.demand;
			    zn -> re = 0;
			    zn -> uw = 0;
			    nrem -= t;
			    /* Update weighted demand. */
			    ms -> wdemand -= (t - us.demand) * us.weight;
			    Dprintf("Gave %d of material %d to (%d, %d).\n",
				    us.demand, m, zn -> unit -> x, zn -> unit -> y, 0);

			} else {
			
			    if(neti[zn -> unit -> type]) {
				zn -> uw = ((int)um_supply_importance(zn -> unit -> type, m) << 14) / neti[zn -> unit -> type];
				zn -> unit -> s_flow += (zn -> uw * t) / us.demand;
				zn -> uw /= us.demand;
			    } else zn -> uw = 0;
			
			    zn -> unit -> supply[m] += t;
			    zn -> re = (us.wdemand * ms -> supply) % ms -> wdemand;
			    nrem -= t;

			    Dprintf("Gave %d of material %d to (%d, %d); %d of weighted demand left.\n",
				    t, m, zn -> unit -> x, zn -> unit -> y, zn -> re);
			}
		    } else zn -> re = 0;
		}

		if(nrem) {
		    zone_sort();
		
		    for(zn = zone_ptr; nrem; --nrem, ++zn) {
			Dprintf("Distributed surplus to %d at (%d, %d).\n",
				zn -> unit -> type, zn -> unit -> x, zn -> unit -> y);
			zn -> unit -> supply[m]++;
			if(neti[zn -> unit -> type]) zn -> unit -> s_flow += zn -> uw;
		    }
		}
	    }
	} while(ms = ms -> next);

    }

    Dprintf("Finished processing this supply class.\n");
}

static void
add_unit_heap(unit, m)
Unit *unit;
int m;
{
    Unit *tr;
    int ss;
    
    /* Get the basic potential. */
    ss  = um_supply_potential(unit -> type, m);

    if(!ss) return;

    /* Effect of terrain on supply potential. */
    ss *= ut_supply_potential_terrain_effect(unit -> type,
					     terrain_at(unit -> x, unit -> y));

    /* Effect of occupancy on supply potential. */
    if(tr = unit -> transport) {
	/* This improves accuracy but could conceivably overflow?? */
	ss *= uu_occupant_supply_potential(unit -> type, tr -> type);
	ss /= 10000;
    } else ss /= 100;

    Dprintf("Potential %d, modified to %d, found in a unit of type %d at (%d, %d).\n",
	    um_supply_potential(unit -> type, m), ss, unit -> type, unit -> x, unit -> y);

    /* Add to the heap unless a previously processed unit in the same cell
     * had a higher supply potential (only the maximum counts). */

    if(ss > tmp1_at(unit -> x, unit -> y)) {
	set_tmp1_at(unit -> x, unit -> y, ss);
	heap_insert(ss, unit -> x, unit -> y);
    }
}

static void
run_supply_lines(side, snum, m)
Side *side;
int snum, m;
{
    heap_node hn;
    int x, y, tt, td, t2, cnum, cc, nx, ny, pot;
    int dir, dir2, ss;
    Unit *unit;
    
    /* Expand supply lines in order of descending supply zone potential. The
     * ordering is necessary to avoid pathological cases.
     */
    while(heap_deque(&hn)) {

	Dprintf("Potential %d at (%d, %d).\n", hn.pot, hn.x, hn.y);
	
	/* Has this cell already been reached from a stronger supply line? */
	if(tmp1_at(hn.x, hn.y) > hn.pot) continue;
	
	/* Attempt to run supply to adjacent cells. */
	for_all_directions(dir) {

	    if(point_in_dir(hn.x, hn.y, dir, &x, &y)) {

		tt = terrain_at(x, y);		/* cell type */
		ss = tmp1_at(x, y);		/* previous supply potential (or 0) */

		/* Evaluate enemy/neutral control/interdiction */
		if(tmp2_at(x, y) < 0) {

		    /* Check if nobody/another side is controlling the cell. */
		    if(area.controlside && (cnum = control_side_at(x, y)) != snum) {
			if(cnum == NOCONTROL) t2 = tm_supply_neutral_interdiction(tt, m);
			else if(!side -> trusts || !side -> trusts[cnum]) {
			    /* Should use reciprocal relation instead in the test? TODO? */
			    t2 = tm_supply_enemy_interdiction(tt, m);
			} else t2 = 0;
		    } else t2 = 0;

		    /* Enemy interdiction in the cell. */
		    for_all_stack(x, y, unit) {
			if(unit -> side != side 
			   && (!side -> trusts || !side -> trusts[side_number(side)])) {
			    t2 += (ut_supply_interdiction_at(unit -> type, tt) *
				   um_supply_interdiction_at_for_material(unit -> type, m)) / 100;
			}
		    }

		    /* Interdiction from adjacent cells. */
		    for_all_directions(dir2) if(point_in_dir(x, y, dir2, &nx, &ny)) {
			for_all_stack(nx, ny, unit) {
			    if(unit -> side != side && (!side -> trusts || !side -> trusts[side_number(side)])) {
				t2 += (ut_supply_interdiction_adjacent(unit -> type, tt) *
				       um_supply_interdiction_adjacent_for_material(unit -> type, m)) / 100;
			    }
			}
		    }

		    set_tmp2_at(x, y, t2);
		}
	    
		pot = hn.pot - tmp2_at(x, y);
		
		/* Stop here if we aren't stronger than all previous supply
		 * attempts. */
		if(pot <= ss) continue;
		
		/* Calculate deterioration from cell and border terrain. */
		td = tm_supply_deterioration(tt, m);
		
		for_all_border_types(tt) {
		    if(border_at(hn.x, hn.y, dir, tt)) td += tm_supply_deterioration(tt, m);
		}
		
		/* Find the cheapest connection type. Connection terrain is
		 * assumed to "run over" any border terrain. */
		cc = td;
		for_all_connection_types(tt) {
		    if(connection_at(hn.x, hn.y, dir, tt)
		       && tm_supply_deterioration(tt, m) < cc) {
			cc = tm_supply_deterioration(tt, m);
		    }
		}

		/* Compute the final potential and compare. */
		pot -= min(cc, td);
		if(pot <= ss) continue;

		/* OK, add this cell to the heap. */
		set_tmp1_at(x, y, pot);
		heap_insert(pot, x, y);
	    }
	}
    }
}


/* Establish a supply zone recursively. */
static void
establish_supply_zone(side, x, y, mclass)
Side *side;
int x, y;
material_stats *mclass;
{
    Unit *unit;
    int dir, x1, y1, pot;

    pot = tmp1_at(x, y);
    set_tmp1_at(x, y, 0);

    for_all_stack(x, y, unit) {
	if(unit -> side == side) process_unit_supply(unit, mclass, pot);
    }

    for_all_directions(dir) {
	if(point_in_dir(x, y, dir, &x1, &y1) && tmp1_at(x1, y1)) {
	    (void)establish_supply_zone(side, x1, y1, mclass);
	}
    }
}

static void
process_unit_supply(unit, mclass, pot)
Unit *unit;
material_stats *mclass;
int pot;
{
    Unit *occ;
    material_stats *ms;
    material_stats res;
    int sd = FALSE;

    /* This is a convenient place to process any occupants. */
    for_all_occupants(unit, occ) process_unit_supply(occ, mclass, pot);

    Dprintf("Processing supply for a unit of type %d at (%d, %d) with potential %d;\n",
	    unit -> type, unit -> x, unit -> y, pot);

    ms = mclass;

    /* Now the unit. */
    do {

	if(calculate_supply_demand(unit, ms -> mtype, &res, pot, TRUE)) {

	    sd = TRUE;
	    
	    Dprintf("Material %d status: supply=%d, demand=%d, weighted demand=%d.\n",
		    ms -> mtype, res.supply, res.demand, res.wdemand);
	    
	    ms -> supply  += res.supply;
	    ms -> demand  += res.demand;
	    ms -> wdemand += res.wdemand;
	}

    } while(ms = ms -> next);
    
    if(sd) {
	/* Link up. */
	zone_insert(unit, pot);
    }
}

/* This function is called twice or thrice per unit per material. */
static int
calculate_supply_demand(unit, m, res, pot, rec_stats)
Unit *unit;
int m;
material_stats *res;
int pot, rec_stats;
{
    int s, t, u;
    int sd = FALSE;
    int capm;

    u = unit -> type;
    s = unit -> supply[m];
    
    /* Calculate any supply capacity deterioration. */
    t = um_supply_capacity_threshold(u, m);
    
    if(pot < t) {
	capm = (pot * 100 + (t - pot) * um_supply_capacity_deterioration(u, m)) / t;
    } else {
	capm = 100;
    }

    if(rec_stats && neti[u]) {
	/* Update connectedness. */
	unit -> s_conn += ((capm << 8) * (int)um_supply_importance(u, m)) / (int)neti[u];
    }

    /* Calculate supply. */
    t = um_supply_out_threshold(u, m);

    if(t >= 0 && s > t) {
	if(um_supply_out_max(u, m) == -1) res -> supply = s - t;
	else res -> supply = min((um_supply_out_max(u, m) * capm) / 100, s - t);

	if(res -> supply) sd = TRUE;
    } else {
	res -> supply  = 0;
    }

    /* Calculate demand. */
    t = um_clip_in_threshold(u, m);

    if(t > 0 && s < t) {

	if(um_supply_in_max(u, m) == -1) res -> demand = t - s;
	else res -> demand = min((um_supply_in_max(u, m) * capm) / 100, t - s);

	if(res -> demand) {
	    sd = TRUE;
	} else {
	    res -> wdemand = 0;
	    res -> weight  = 0;

	    /* Not sure about this... supply lines so bad that cannot receive
	     * anything, but inflow still at 100%? */
	    if(rec_stats && neti[u]) {
		unit -> s_flow += ((int)um_supply_importance(u, m) << 14) / neti[u];
	    }

	    return sd;
	}

	/* Calculate weighted demand. */
	if(um_supply_starve_weight(u, m) != um_supply_in_weight(u, m)) {
	    res -> weight  = (s * um_supply_in_weight(u, m) + (t - s) * um_supply_starve_weight(u, m)) / t;
	} else {
	    res -> weight  = um_supply_in_weight(u, m);
	}

	res -> wdemand = res -> demand * res -> weight;

    } else {
	res -> demand  = 0;
	res -> weight  = 0;
	res -> wdemand = 0;

	/* If didn't need any supplies, add to inflow anyway */
	if(t > 0 && rec_stats && neti[u]) {
	    unit -> s_flow += ((int)um_supply_importance(u, m) << 14) / neti[u];
	}
    }
    
    return sd;
}

static void
heap_init(void)
{
    heap_entries = 0;
}

static void

// --- CODEWARRIOR GIVES THE FOLLOWING COMPILE ERROR: -------------

//	Error   : identifier 'heap_insert(int, short, short)' redeclared
//	was declared as: 'void (int, short, short)'
//	now declared as: 'void (...)'

//	This happens ONLY for this function and not for any other in this file. Weird!
//	Declaring the formal parameters within the parenthesis solved the problem.

//	heap_insert(pot, x, y)
//	int pot;
//	short x, y;

	heap_insert(int pot, short x, short y)

// --------------------------------------------------

{
    int i, f;
    
    if(++heap_entries == heap_size) extend_workspace(heap_entries * sizeof(heap_node));

    i = heap_entries;
    f = i >> 1;

    while(f && pot > heap_ptr[f].pot) {
	heap_node_copy(&heap_ptr[i], &heap_ptr[f]);
	i   = f;
	f >>= 1;
    }

    heap_ptr[i].pot = pot;
    heap_ptr[i].x   = x;
    heap_ptr[i].y   = y;
}

static int
heap_deque(hn)
heap_node *hn;
{
    int i = 2, f = 1, epot;
    heap_node *enode;
    
    if(!heap_entries) return FALSE;
    
    enode = &heap_ptr[heap_entries];	/* Last entry in the heap
					 * (the first slot is empty). */
    epot  = enode -> pot;		/* Priority of the last entry. */

    /* Get the top of the heap. */
    heap_node_copy(hn, &heap_ptr[1]);
    
    /* Ripple the remaining entries up in the heap. */
    while(i < heap_entries) {
	if(heap_ptr[i].pot > heap_ptr[i + 1].pot) {
	    if(epot > heap_ptr[i].pot) {
		heap_node_copy(&heap_ptr[f], enode);
		--heap_entries;
		return TRUE;
	    }
	    heap_node_copy(&heap_ptr[f], &heap_ptr[i]);
	    f  = i;
	    i += i;
	} else {
	    if(epot > heap_ptr[i + 1].pot) {
		heap_node_copy(&heap_ptr[f], enode);
		--heap_entries;
		return TRUE;
	    }
	    heap_node_copy(&heap_ptr[f], &heap_ptr[i + 1]);
	    f = i + 1;
	    i = f + f;
	}
    }

    if(i == heap_entries && heap_ptr[i].pot > epot) {
	heap_node_copy(&heap_ptr[f], &heap_ptr[i]);
	heap_node_copy(&heap_ptr[i], enode);
    } else {
	heap_node_copy(&heap_ptr[f], enode);
    }

    --heap_entries;
    return TRUE;
}

static void
zone_init(void)
{
    zone_entries = 0;
}

static void
zone_insert(unit, re)
Unit *unit;
int re;
{
    if(zone_entries == zone_size) extend_workspace(zone_entries * sizeof(zone_node));
    
    zone_ptr[zone_entries].re   = re;
    zone_ptr[zone_entries].unit = unit;
    
    ++zone_entries;
}

/* Just use qsort() for now and hope it doesn't do anything insane. */
static void
zone_sort(void)
{
    qsort((void *)ws, zone_entries, sizeof(zone_node), compare_zone_nodes);
}

/* Randomize units' positions in the zone. */
static void
zone_shuffle(void)
{
    zone_node t, *zn1, *zn2;
    int n;
    
    for(n = zone_entries, zn1 = zone_ptr; n; --n, ++zn1) {
	
	zn2 = &zn1[xrandom(n)];
	
	t.re        = zn1 -> re;
	t.uw        = zn1 -> uw;
	t.unit      = zn1 -> unit;
	zn1 -> re   = zn2 -> re;
	zn1 -> uw   = zn2 -> uw;
	zn1 -> unit = zn2 -> unit;
	zn2 -> re   = t.re;
	zn2 -> uw   = t.uw;
	zn2 -> unit = t.unit;
    }
}

static int
compare_zone_nodes(a, b)
const void *a, *b;
{
    return (int)(((zone_node *)b) -> re - ((zone_node *)a) -> re);
}

static void
extend_workspace(semantic_bytes)
int semantic_bytes;
{
    void *new_ws;

    if(!ws_size) ws_size = INITIAL_WORKSPACE; else ws_size <<= 1;

    /* Should try realloc() here but who cares. */
    new_ws    = xmalloc(ws_size);

    if(semantic_bytes) memcpy(new_ws, ws, semantic_bytes);

    ws        = new_ws;
    zone_ptr  = (zone_node *)new_ws;
    heap_ptr  = (heap_node *)new_ws;

    /* Let's assume nothing about type sizes... */
    heap_size = ws_size / sizeof(heap_node);
    zone_size = ws_size / sizeof(zone_node) - 1;
}

/* Initialize stuff for macros for_all_border_types and for_all_connection_types.
 * This could/should be done in calculate_globals(). */
static void
optimize_terrain(void)
{
    int t, pb = -1, pc = -1;

    numbtypes = numctypes = 0;

    btypear = (int *)xmalloc(numttypes * sizeof(int));
    ctypear = (int *)xmalloc(numttypes * sizeof(int));
    
    for_all_terrain_types(t) {
	if(t_is_border(t)) {
	    if(pb < 0) firstbtype = t; else { btypear[pb] = t; pb = t; }
	} else if(t_is_connection(t)) {
	    if(pc < 0) firstctype = t; else { ctypear[pc] = t; pc = t; }
	}
    }

    if(pb < 0) btypear[pb = firstbtype = 0] = -1; else btypear[pb] = -1;
    if(pc < 0) ctypear[pc = firstctype = 0] = -1; else ctypear[pc] = -1;

    /* Pedantic */
    (void)realloc((void *)btypear, (pb + 1) * sizeof(int));
    (void)realloc((void *)ctypear, (pc + 1) * sizeof(int));
}
