/* $Id: engine.c 3524 2006-02-03 12:55:21Z peter1138 $ */

#include "stdafx.h"
#include "openttd.h"
#include "debug.h"
#include "functions.h"
#include "table/strings.h"
#include "engine.h"
#include "table/engines.h"
#include "gfx.h"
#include "player.h"
#include "command.h"
#include "vehicle.h"
#include "news.h"
#include "saveload.h"
#include "sprite.h"
#include "variables.h"
#include "train.h"
#include "newgrf_engine.h"

EngineInfo _engine_info[TOTAL_NUM_ENGINES];
RailVehicleInfo _rail_vehicle_info[NUM_TRAIN_ENGINES];
ShipVehicleInfo _ship_vehicle_info[NUM_SHIP_ENGINES];
AircraftVehicleInfo _aircraft_vehicle_info[NUM_AIRCRAFT_ENGINES];
RoadVehicleInfo _road_vehicle_info[NUM_ROAD_ENGINES];

enum {
	ENGINE_AVAILABLE = 1,
	ENGINE_INTRODUCING = 2,
	ENGINE_PREVIEWING = 4,
};

/** TRANSLATE FROM LOCAL CARGO TO GLOBAL CARGO ID'S.
 * This maps the per-landscape cargo ID's to globally unique cargo ID's usable ie. in
 * the custom GRF  files. It is basically just a transcribed table from TTDPatch's newgrf.txt.
 */
const CargoID _global_cargo_id[NUM_LANDSCAPE][NUM_CARGO] = {
	/* LT_NORMAL */ {GC_PASSENGERS, GC_COAL,  GC_MAIL, GC_OIL, GC_LIVESTOCK, GC_GOODS, GC_GRAIN, GC_WOOD, GC_IRON_ORE,    GC_STEEL,  GC_VALUABLES, GC_PAPER_TEMP},
	/* LT_HILLY */  {GC_PASSENGERS, GC_COAL,  GC_MAIL, GC_OIL, GC_LIVESTOCK, GC_GOODS, GC_GRAIN, GC_WOOD, GC_INVALID,     GC_PAPER,  GC_VALUABLES, GC_FOOD },
	/* LT_DESERT */ {GC_PASSENGERS, GC_RUBBER,GC_MAIL, GC_OIL, GC_FRUIT,     GC_GOODS, GC_GRAIN, GC_WOOD, GC_COPPER_ORE,  GC_WATER,  GC_VALUABLES, GC_FOOD },
	/* LT_CANDY */  {GC_PASSENGERS, GC_SUGAR, GC_MAIL, GC_TOYS,GC_BATTERIES, GC_CANDY, GC_TOFFEE,GC_COLA, GC_COTTON_CANDY,GC_BUBBLES,GC_PLASTIC,   GC_FIZZY_DRINKS },
	/**
	 * - GC_INVALID (255) means that  cargo is not available for that climate
	 * - GC_PAPER_TEMP (27) is paper in  temperate climate in TTDPatch
	 * Following can  be renumbered:
	 * - GC_DEFAULT (29) is the defa ult cargo for the purpose of spritesets
	 * - GC_PURCHASE (30) is the purchase list image (the equivalent of 0xff) for the purpose of spritesets
	 */
};

/** BEGIN --- TRANSLATE FROM GLOBAL CARGO TO LOCAL CARGO ID'S **/
/** Map global cargo ID's to local-cargo ID's */
const CargoID _local_cargo_id_ctype[NUM_GLOBAL_CID] = {
	CT_PASSENGERS,CT_COAL,   CT_MAIL,        CT_OIL,      CT_LIVESTOCK,CT_GOODS,  CT_GRAIN,      CT_WOOD,         /*  0- 7 */
	CT_IRON_ORE,  CT_STEEL,  CT_VALUABLES,   CT_PAPER,    CT_FOOD,     CT_FRUIT,  CT_COPPER_ORE, CT_WATER,        /*  8-15 */
	CT_RUBBER,    CT_SUGAR,  CT_TOYS,        CT_BATTERIES,CT_CANDY,    CT_TOFFEE, CT_COLA,       CT_COTTON_CANDY, /* 16-23 */
	CT_BUBBLES,   CT_PLASTIC,CT_FIZZY_DRINKS,CT_PAPER     /* unsup. */,CT_HILLY_UNUSED,                           /* 24-28 */
	CT_INVALID,   CT_INVALID                                                                                      /* 29-30 */
};

#define MC(cargo) (1 << cargo)
/** Bitmasked value where the global cargo ID is available in landscape
 * 0: LT_NORMAL, 1: LT_HILLY, 2: LT_DESERT, 3: LT_CANDY */
const uint32 _landscape_global_cargo_mask[NUM_LANDSCAPE] =
{ /* LT_NORMAL: temperate */
	MC(GC_PASSENGERS)|MC(GC_COAL)|MC(GC_MAIL)|MC(GC_OIL)|MC(GC_LIVESTOCK)|MC(GC_GOODS)|MC(GC_GRAIN)|MC(GC_WOOD)|
	MC(GC_IRON_ORE)|MC(GC_STEEL)|MC(GC_VALUABLES),
	/* LT_HILLY: arctic */
	MC(GC_PASSENGERS)|MC(GC_COAL)|MC(GC_MAIL)|MC(GC_OIL)|MC(GC_LIVESTOCK)|MC(GC_GOODS)|
	MC(GC_GRAIN)|MC(GC_WOOD)|MC(GC_VALUABLES)|MC(GC_PAPER)|MC(GC_FOOD),
	/* LT_DESERT: rainforest/desert */
	MC(GC_PASSENGERS)|MC(GC_MAIL)|MC(GC_OIL)|MC(GC_GOODS)|MC(GC_GRAIN)|MC(GC_WOOD)|
	MC(GC_VALUABLES)|MC(GC_FOOD)|MC(GC_FRUIT)|MC(GC_COPPER_ORE)|MC(GC_WATER)|MC(GC_RUBBER),
	/* LT_CANDY: toyland */
	MC(GC_PASSENGERS)|MC(GC_MAIL)|MC(GC_SUGAR)|MC(GC_TOYS)|MC(GC_BATTERIES)|MC(GC_CANDY)|
	MC(GC_TOFFEE)|MC(GC_COLA)|MC(GC_COTTON_CANDY)|MC(GC_BUBBLES)|MC(GC_PLASTIC)|MC(GC_FIZZY_DRINKS)
};
/** END   --- TRANSLATE FROM GLOBAL CARGO TO LOCAL CARGO ID'S **/

/** Bitmasked values of what type of cargo is refittable for the given vehicle-type.
 * This coupled with the landscape information (_landscape_global_cargo_mask) gives
 * us exactly what is refittable and what is not */
const uint32 _default_refitmasks[NUM_VEHICLE_TYPES] = {
	/* Trains */
	MC(GC_PASSENGERS)|MC(GC_COAL)|MC(GC_MAIL)|MC(GC_LIVESTOCK)|MC(GC_GOODS)|MC(GC_GRAIN)|MC(GC_WOOD)|MC(GC_IRON_ORE)|
	MC(GC_STEEL)|MC(GC_VALUABLES)|MC(GC_PAPER)|MC(GC_FOOD)|MC(GC_FRUIT)|MC(GC_COPPER_ORE)|MC(GC_WATER)|MC(GC_SUGAR)|
	MC(GC_TOYS)|MC(GC_CANDY)|MC(GC_TOFFEE)|MC(GC_COLA)|MC(GC_COTTON_CANDY)|MC(GC_BUBBLES)|MC(GC_PLASTIC)|MC(GC_FIZZY_DRINKS),
	/* Road vehicles (not refittable by default) */
	0,
	/* Ships */
	MC(GC_COAL)|MC(GC_MAIL)|MC(GC_LIVESTOCK)|MC(GC_GOODS)|MC(GC_GRAIN)|MC(GC_WOOD)|MC(GC_IRON_ORE)|MC(GC_STEEL)|MC(GC_VALUABLES)|
	MC(GC_PAPER)|MC(GC_FOOD)|MC(GC_FRUIT)|MC(GC_COPPER_ORE)|MC(GC_WATER)|MC(GC_RUBBER)|MC(GC_SUGAR)|MC(GC_TOYS)|MC(GC_BATTERIES)|
	MC(GC_CANDY)|MC(GC_TOFFEE)|MC(GC_COLA)|MC(GC_COTTON_CANDY)|MC(GC_BUBBLES)|MC(GC_PLASTIC)|MC(GC_FIZZY_DRINKS),
	/* Aircraft */
	MC(GC_PASSENGERS)|MC(GC_MAIL)|MC(GC_GOODS)|MC(GC_VALUABLES)|MC(GC_FOOD)|MC(GC_FRUIT)|MC(GC_SUGAR)|MC(GC_TOYS)|
	MC(GC_BATTERIES)|MC(GC_CANDY)|MC(GC_TOFFEE)|MC(GC_COLA)|MC(GC_COTTON_CANDY)|MC(GC_BUBBLES)|MC(GC_PLASTIC)|MC(GC_FIZZY_DRINKS),
	/* Special/Disaster */
	0,0
};

/**
 * Bitmask of classes for cargo types.
 */
const uint32 cargo_classes[16] = {
	/* Passengers */ MC(GC_PASSENGERS),
	/* Mail       */ MC(GC_MAIL),
	/* Express    */ MC(GC_GOODS)|MC(GC_FOOD)|MC(GC_CANDY),
	/* Armoured   */ MC(GC_VALUABLES),
	/* Bulk       */ MC(GC_COAL)|MC(GC_GRAIN)|MC(GC_IRON_ORE)|MC(GC_COPPER_ORE)|MC(GC_FRUIT)|MC(GC_SUGAR)|MC(GC_TOFFEE)|MC(GC_COTTON_CANDY),
	/* Piece      */ MC(GC_LIVESTOCK)|MC(GC_WOOD)|MC(GC_STEEL)|MC(GC_PAPER)|MC(GC_TOYS)|MC(GC_BATTERIES)|MC(GC_BUBBLES)|MC(GC_FIZZY_DRINKS),
	/* Liquids    */ MC(GC_OIL)|MC(GC_WATER)|MC(GC_RUBBER)|MC(GC_COLA)|MC(GC_PLASTIC),
	/* Chilled    */ MC(GC_FOOD)|MC(GC_FRUIT),
	/* Undefined  */ 0, 0, 0, 0, 0, 0, 0, 0
};
#undef MC

void ShowEnginePreviewWindow(EngineID engine);

void DeleteCustomEngineNames(void)
{
	uint i;
	StringID old;

	for (i = 0; i != TOTAL_NUM_ENGINES; i++) {
		old = _engine_name_strings[i];
		_engine_name_strings[i] = i + STR_8000_KIRBY_PAUL_TANK_STEAM;
		DeleteName(old);
	}

	_vehicle_design_names &= ~1;
}

void LoadCustomEngineNames(void)
{
	// XXX: not done */
	DEBUG(misc, 1) ("LoadCustomEngineNames: not done");
}

static void SetupEngineNames(void)
{
	StringID *name;

	for (name = _engine_name_strings; name != endof(_engine_name_strings); name++)
		*name = STR_SV_EMPTY;

	DeleteCustomEngineNames();
	LoadCustomEngineNames();
}

static void AdjustAvailAircraft(void)
{
	uint16 date = _date;
	byte avail = 0;
	if (date >= 12784) avail |= 2; // big airport
	if (date < 14610 || _patches.always_small_airport) avail |= 1;  // small airport
	if (date >= 15706) avail |= 4; // enable heliport

	if (avail != _avail_aircraft) {
		_avail_aircraft = avail;
		InvalidateWindow(WC_BUILD_STATION, 0);
	}
}

static void CalcEngineReliability(Engine *e)
{
	uint age = e->age;

	if (age < e->duration_phase_1) {
		uint start = e->reliability_start;
		e->reliability = age * (e->reliability_max - start) / e->duration_phase_1 + start;
	} else if ((age -= e->duration_phase_1) < e->duration_phase_2) {
		e->reliability = e->reliability_max;
	} else if ((age -= e->duration_phase_2) < e->duration_phase_3) {
		uint max = e->reliability_max;
		e->reliability = (int)age * (int)(e->reliability_final - max) / e->duration_phase_3 + max;
	} else {
		// time's up for this engine
		// make it either available to all players (if never_expire_vehicles is enabled and if it was available earlier)
		// or disable this engine completely
		e->player_avail = (_patches.never_expire_vehicles && e->player_avail)? -1 : 0;
		e->reliability = e->reliability_final;
	}
}

void AddTypeToEngines(void)
{
	Engine* e = _engines;

	do e->type = VEH_Train;    while (++e < &_engines[ROAD_ENGINES_INDEX]);
	do e->type = VEH_Road;     while (++e < &_engines[SHIP_ENGINES_INDEX]);
	do e->type = VEH_Ship;     while (++e < &_engines[AIRCRAFT_ENGINES_INDEX]);
	do e->type = VEH_Aircraft; while (++e < &_engines[TOTAL_NUM_ENGINES]);
	do e->type = VEH_Special;  while (++e < endof(_engines));
}

void StartupEngines(void)
{
	Engine *e;
	const EngineInfo *ei;

	SetupEngineNames();

	for (e = _engines, ei = _engine_info; e != endof(_engines); e++, ei++) {
		uint32 r;

		e->age = 0;
		e->railtype = ei->railtype;
		e->flags = 0;
		e->player_avail = 0;

		// The magic value of 729 days below comes from the NewGRF spec. If the
		// base intro date is before 1922 then the random number of days is not
		// added.
		r = Random();
		e->intro_date = ei->base_intro <= 729 ? ei->base_intro : GB(r, 0, 9) + ei->base_intro;
		if (e->intro_date <= _date) {
			e->age = (_date - e->intro_date) >> 5;
			e->player_avail = (byte)-1;
			e->flags |= ENGINE_AVAILABLE;
		}

		e->reliability_start = GB(r, 16, 14) + 0x7AE0;
		r = Random();
		e->reliability_max   = GB(r,  0, 14) + 0xBFFF;
		e->reliability_final = GB(r, 16, 14) + 0x3FFF;

		r = Random();
		e->duration_phase_1 = GB(r, 0, 5) + 7;
		e->duration_phase_2 = GB(r, 5, 4) + ei->base_life * 12 - 96;
		e->duration_phase_3 = GB(r, 9, 7) + 120;

		e->reliability_spd_dec = (ei->unk2&0x7F) << 2;

		/* my invented flag for something that is a wagon */
		if (ei->unk2 & 0x80) {
			e->age = 0xFFFF;
		} else {
			CalcEngineReliability(e);
		}

		e->lifelength = ei->lifelength + _patches.extend_vehicle_life;

		// prevent certain engines from ever appearing.
		if (!HASBIT(ei->climates, _opt.landscape)) {
			e->flags |= ENGINE_AVAILABLE;
			e->player_avail = 0;
		}

		/* This sets up type for the engine
		   It is needed if you want to ask the engine what type it is
		   It should hopefully be the same as when you ask a vehicle what it is
		   but using this, you can ask what type an engine number is
		   even if it is not a vehicle (yet)*/
	}

	AdjustAvailAircraft();
}

static void AcceptEnginePreview(Engine *e, PlayerID player)
{
	Player *p = GetPlayer(player);

	assert(e->railtype < RAILTYPE_END);
	SETBIT(e->player_avail, player);
	SETBIT(p->avail_railtypes, e->railtype);

	e->preview_player = 0xFF;
	InvalidateWindowClasses(WC_BUILD_VEHICLE);
	InvalidateWindowClasses(WC_REPLACE_VEHICLE);
}

static PlayerID GetBestPlayer(PlayerID pp)
{
	const Player *p;
	int32 best_hist;
	PlayerID best_player;
	uint mask = 0;

	do {
		best_hist = -1;
		best_player = OWNER_SPECTATOR;
		FOR_ALL_PLAYERS(p) {
			if (p->is_active && p->block_preview == 0 && !HASBIT(mask, p->index) &&
					p->old_economy[0].performance_history > best_hist) {
				best_hist = p->old_economy[0].performance_history;
				best_player = p->index;
			}
		}

		if (best_player == OWNER_SPECTATOR) return OWNER_SPECTATOR;

		SETBIT(mask, best_player);
	} while (--pp != 0);

	return best_player;
}

void EnginesDailyLoop(void)
{
	EngineID i;

	if (_cur_year >= 130) return;

	for (i = 0; i != lengthof(_engines); i++) {
		Engine* e = &_engines[i];

		if (e->flags & ENGINE_INTRODUCING) {
			if (e->flags & ENGINE_PREVIEWING) {
				if (e->preview_player != 0xFF && !--e->preview_wait) {
					e->flags &= ~ENGINE_PREVIEWING;
					DeleteWindowById(WC_ENGINE_PREVIEW, i);
					e->preview_player++;
				}
 			} else if (e->preview_player != 0xFF) {
				PlayerID best_player = GetBestPlayer(e->preview_player);

				if (best_player == OWNER_SPECTATOR) {
					e->preview_player = 0xFF;
					continue;
				}

				if (!IS_HUMAN_PLAYER(best_player)) {
					/* XXX - TTDBUG: TTD has a bug here ???? */
					AcceptEnginePreview(e, best_player);
				} else {
					e->flags |= ENGINE_PREVIEWING;
					e->preview_wait = 20;
					if (IS_INTERACTIVE_PLAYER(best_player)) ShowEnginePreviewWindow(i);
				}
			}
		}
	}
}

/** Accept an engine prototype. XXX - it is possible that the top-player
 * changes while you are waiting to accept the offer? Then it becomes invalid
 * @param x,y unused
 * @param p1 engine-prototype offered
 * @param p2 unused
 */
int32 CmdWantEnginePreview(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
	Engine *e;

	if (!IsEngineIndex(p1)) return CMD_ERROR;
	e = GetEngine(p1);
	if (GetBestPlayer(e->preview_player) != _current_player) return CMD_ERROR;

	if (flags & DC_EXEC) AcceptEnginePreview(e, _current_player);

	return 0;
}

// Determine if an engine type is a wagon (and not a loco)
static bool IsWagon(EngineID index)
{
	return index < NUM_TRAIN_ENGINES && RailVehInfo(index)->flags & RVI_WAGON;
}

static void NewVehicleAvailable(Engine *e)
{
	Vehicle *v;
	Player *p;
	EngineID index = e - _engines;

	// In case the player didn't build the vehicle during the intro period,
	// prevent that player from getting future intro periods for a while.
	if (e->flags & ENGINE_INTRODUCING) {
		FOR_ALL_PLAYERS(p) {
			uint block_preview = p->block_preview;

			if (!HASBIT(e->player_avail, p->index)) continue;

			/* We assume the user did NOT build it.. prove me wrong ;) */
			p->block_preview = 20;

			FOR_ALL_VEHICLES(v) {
				if (v->type == VEH_Train || v->type == VEH_Road || v->type == VEH_Ship ||
						(v->type == VEH_Aircraft && v->subtype <= 2)) {
					if (v->owner == p->index && v->engine_type == index) {
						/* The user did prove me wrong, so restore old value */
						p->block_preview = block_preview;
						break;
					}
				}
			}
		}
	}

	e->flags = (e->flags & ~ENGINE_INTRODUCING) | ENGINE_AVAILABLE;
	InvalidateWindowClasses(WC_BUILD_VEHICLE);
	InvalidateWindowClasses(WC_REPLACE_VEHICLE);

	// Now available for all players
	e->player_avail = (byte)-1;

	// Do not introduce new rail wagons
	if (IsWagon(index)) return;

	// make maglev / monorail available
	FOR_ALL_PLAYERS(p) {
		if (p->is_active) {
			assert(e->railtype < RAILTYPE_END);
			SETBIT(p->avail_railtypes, e->railtype);
		}
	}

	if (index < NUM_TRAIN_ENGINES) {
		AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_TRAINAVAIL), 0, 0);
	} else if (index < NUM_TRAIN_ENGINES + NUM_ROAD_ENGINES) {
		AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_ROADAVAIL), 0, 0);
	} else if (index < NUM_TRAIN_ENGINES + NUM_ROAD_ENGINES + NUM_SHIP_ENGINES) {
		AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_SHIPAVAIL), 0, 0);
	} else {
		AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_AIRCRAFTAVAIL), 0, 0);
	}
}

void EnginesMonthlyLoop(void)
{
	Engine *e;

	if (_cur_year < 130) {
		for (e = _engines; e != endof(_engines); e++) {
			// Age the vehicle
			if (e->flags & ENGINE_AVAILABLE && e->age != 0xFFFF) {
				e->age++;
				CalcEngineReliability(e);
			}

			if (!(e->flags & ENGINE_AVAILABLE) && (uint16)(_date - min(_date, 365)) >= e->intro_date) {
				// Introduce it to all players
				NewVehicleAvailable(e);
			} else if (!(e->flags & (ENGINE_AVAILABLE|ENGINE_INTRODUCING)) && _date >= e->intro_date) {
				// Introduction date has passed.. show introducing dialog to one player.
				e->flags |= ENGINE_INTRODUCING;

				// Do not introduce new rail wagons
				if (!IsWagon(e - _engines))
					e->preview_player = 1; // Give to the player with the highest rating.
			}
		}
	}
	AdjustAvailAircraft();
}

/** Rename an engine.
 * @param x,y unused
 * @param p1 engine ID to rename
 * @param p2 unused
 */
int32 CmdRenameEngine(int x, int y, uint32 flags, uint32 p1, uint32 p2)
{
	StringID str;

	if (!IsEngineIndex(p1) || _cmd_text[0] == '\0') return CMD_ERROR;

	str = AllocateNameUnique(_cmd_text, 0);
	if (str == 0) return CMD_ERROR;

	if (flags & DC_EXEC) {
		StringID old_str = _engine_name_strings[p1];
		_engine_name_strings[p1] = str;
		DeleteName(old_str);
		_vehicle_design_names |= 3;
		MarkWholeScreenDirty();
	} else {
		DeleteName(str);
	}

	return 0;
}


/*
 * returns true if an engine is valid, of the specified type, and buildable by
 * the current player, false otherwise
 *
 * engine = index of the engine to check
 * type   = the type the engine should be of (VEH_xxx)
 */
bool IsEngineBuildable(uint engine, byte type)
{
	const Engine *e;

	// check if it's an engine that is in the engine array
	if (!IsEngineIndex(engine)) return false;

	e = GetEngine(engine);

	// check if it's an engine of specified type
	if (e->type != type) return false;

	// check if it's available
	if (!HASBIT(e->player_avail, _current_player)) return false;

	return true;
}

/************************************************************************
 * Engine Replacement stuff
 ************************************************************************/

static void EngineRenewPoolNewBlock(uint start_item); /* Forward declare for initializer of _engine_renew_pool */
enum {
	ENGINE_RENEW_POOL_BLOCK_SIZE_BITS = 3,
	ENGINE_RENEW_POOL_MAX_BLOCKS      = 8000,
};

MemoryPool _engine_renew_pool = { "EngineRe", ENGINE_RENEW_POOL_MAX_BLOCKS, ENGINE_RENEW_POOL_BLOCK_SIZE_BITS, sizeof(EngineRenew), &EngineRenewPoolNewBlock, 0, 0, NULL };

static inline uint16 GetEngineRenewPoolSize(void)
{
	return _engine_renew_pool.total_items;
}

#define FOR_ALL_ENGINE_RENEWS_FROM(er, start) for (er = GetEngineRenew(start); er != NULL; er = (er->index + 1 < GetEngineRenewPoolSize()) ? GetEngineRenew(er->index + 1) : NULL)
#define FOR_ALL_ENGINE_RENEWS(er) FOR_ALL_ENGINE_RENEWS_FROM(er, 0)

static void EngineRenewPoolNewBlock(uint start_item)
{
	EngineRenew *er;

	FOR_ALL_ENGINE_RENEWS_FROM(er, start_item) {
		er->index = start_item++;
		er->from = INVALID_ENGINE;
	}
}


static EngineRenew *AllocateEngineRenew(void)
{
	EngineRenew *er;

	FOR_ALL_ENGINE_RENEWS(er) {
		if (er->from == INVALID_ENGINE) {
			er->to = INVALID_ENGINE;
			er->next = NULL;
			return er;
		}
	}

	/* Check if we can add a block to the pool */
	if (AddBlockToPool(&_engine_renew_pool)) return AllocateEngineRenew();

	return NULL;
}

/**
 * Retrieves the EngineRenew that specifies the replacement of the given
 * engine type from the given renewlist */
static EngineRenew *GetEngineReplacement(EngineRenewList erl, EngineID engine)
{
	EngineRenew* er = (EngineRenew*)erl; /* Fetch first element */
	while (er) {
		if (er->from == engine) return er;
		er = er->next;
	}
	return NULL;
}

void RemoveAllEngineReplacement(EngineRenewList* erl)
{
	EngineRenew* er = (EngineRenew*)(*erl); /* Fetch first element */
	while (er) {
		er->from = INVALID_ENGINE; /* "Deallocate" all elements */
		er = er->next;
	}
	*erl = NULL; /* Empty list */
}

EngineID EngineReplacement(EngineRenewList erl, EngineID engine)
{
	const EngineRenew *er = GetEngineReplacement(erl, engine);
	return er == NULL ? INVALID_ENGINE : er->to;
}

int32 AddEngineReplacement(EngineRenewList* erl, EngineID old_engine, EngineID new_engine, uint32 flags)
{
	EngineRenew *er;

	// Check if the old vehicle is already in the list
	er = GetEngineReplacement(*erl, old_engine);
	if (er != NULL) {
		if (flags & DC_EXEC) er->to = new_engine;
		return 0;
	}

	er = AllocateEngineRenew();
	if (er == NULL) return CMD_ERROR;

	if (flags & DC_EXEC) {
		er->from = old_engine;
		er->to = new_engine;
		er->next = (EngineRenew*)(*erl); /* Resolve the first element in the list */

		*erl = (EngineRenewList)er; /* Insert before the first element */
	}

	return 0;
}

int32 RemoveEngineReplacement(EngineRenewList* erl, EngineID engine, uint32 flags)
{
	EngineRenew* er = (EngineRenew*)(*erl); /* Start at the first element */
	EngineRenew* prev = NULL;

	while (er)
	{
		if (er->from == engine) {
			if (flags & DC_EXEC) {
				if (prev == NULL) { /* First element */
					(*erl) = (EngineRenewList)er->next; /* The second becomes the new first element */
				} else {
					prev->next = er->next; /* Cut this element out */
				}
				er->from = INVALID_ENGINE; /* Deallocate */
			}
			return 0;
		}
		prev = er;
		er = er->next; /* Look at next element */
	}

	return CMD_ERROR; /* Not found? */
}

static const SaveLoad _engine_renew_desc[] = {
	SLE_VAR(EngineRenew, from, SLE_UINT16),
	SLE_VAR(EngineRenew, to,   SLE_UINT16),

	SLE_REF(EngineRenew, next, REF_ENGINE_RENEWS),

	SLE_END()
};

static void Save_ERNW(void)
{
	EngineRenew *er;

	FOR_ALL_ENGINE_RENEWS(er) {
		if (er->from != INVALID_ENGINE) {
			SlSetArrayIndex(er->index);
			SlObject(er, _engine_renew_desc);
		}
	}
}

static void Load_ERNW(void)
{
	int index;

	while ((index = SlIterateArray()) != -1) {
		EngineRenew *er;

		if (!AddBlockIfNeeded(&_engine_renew_pool, index))
			error("EngineRenews: failed loading savegame: too many EngineRenews");

		er = GetEngineRenew(index);
		SlObject(er, _engine_renew_desc);
	}
}

static const SaveLoad _engine_desc[] = {
	SLE_VAR(Engine,intro_date,						SLE_UINT16),
	SLE_VAR(Engine,age,										SLE_UINT16),
	SLE_VAR(Engine,reliability,						SLE_UINT16),
	SLE_VAR(Engine,reliability_spd_dec,		SLE_UINT16),
	SLE_VAR(Engine,reliability_start,			SLE_UINT16),
	SLE_VAR(Engine,reliability_max,				SLE_UINT16),
	SLE_VAR(Engine,reliability_final,			SLE_UINT16),
	SLE_VAR(Engine,duration_phase_1,			SLE_UINT16),
	SLE_VAR(Engine,duration_phase_2,			SLE_UINT16),
	SLE_VAR(Engine,duration_phase_3,			SLE_UINT16),

	SLE_VAR(Engine,lifelength,						SLE_UINT8),
	SLE_VAR(Engine,flags,									SLE_UINT8),
	SLE_VAR(Engine,preview_player,				SLE_UINT8),
	SLE_VAR(Engine,preview_wait,					SLE_UINT8),
	SLE_VAR(Engine,railtype,							SLE_UINT8),
	SLE_VAR(Engine,player_avail,					SLE_UINT8),

	// reserve extra space in savegame here. (currently 16 bytes)
	SLE_CONDARR(NullStruct,null,SLE_FILE_U64 | SLE_VAR_NULL, 2, 2, 255),

	SLE_END()
};

static void Save_ENGN(void)
{
	uint i;

	for (i = 0; i != lengthof(_engines); i++) {
		SlSetArrayIndex(i);
		SlObject(&_engines[i], _engine_desc);
	}
}

static void Load_ENGN(void)
{
	int index;
	while ((index = SlIterateArray()) != -1) {
		SlObject(GetEngine(index), _engine_desc);
	}
}

static void LoadSave_ENGS(void)
{
	SlArray(_engine_name_strings, lengthof(_engine_name_strings), SLE_STRINGID);
}

const ChunkHandler _engine_chunk_handlers[] = {
	{ 'ENGN', Save_ENGN,     Load_ENGN,     CH_ARRAY          },
	{ 'ENGS', LoadSave_ENGS, LoadSave_ENGS, CH_RIFF           },
	{ 'ERNW', Save_ERNW,     Load_ERNW,     CH_ARRAY | CH_LAST},
};

void InitializeEngines(void)
{
	/* Clean the engine renew pool and create 1 block in it */
	CleanPool(&_engine_renew_pool);
	AddBlockToPool(&_engine_renew_pool);
}
