/*
 * This file is part of OpenTTD.
 * OpenTTD 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, version 2.
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see .
 */
/** @file tracerestrict.cpp Main file for Trace Restrict */
#include "stdafx.h"
#include "tracerestrict.h"
#include "train.h"
#include "core/bitmath_func.hpp"
#include "core/pool_func.hpp"
#include "command_func.h"
#include "company_func.h"
#include "viewport_func.h"
#include "window_func.h"
#include "order_base.h"
#include "cargotype.h"
#include "group.h"
#include "string_func.h"
#include "pathfinder/yapf/yapf_cache.h"
#include "scope_info.h"
#include "vehicle_func.h"
#include "date_func.h"
#include 
#include 
#include "safeguards.h"
/** @file
 *
 * Trace Restrict Data Storage Model Notes:
 *
 * Signals may have 0, 1 or 2 trace restrict programs attached to them,
 * up to one for each track. Two-way signals share the same program.
 *
 * The mapping between signals and programs is defined in terms of
 * TraceRestrictRefId to TraceRestrictProgramID,
 * where TraceRestrictRefId is formed of the tile index and track,
 * and TraceRestrictProgramID is an index into the program pool.
 *
 * If one or more mappings exist for a given signal tile, bit 12 of M3 will be set to 1.
 * This is updated whenever mappings are added/removed for that tile. This is to avoid
 * needing to do a mapping lookup for the common case where there is no trace restrict
 * program mapping for the given tile.
 *
 * Programs in the program pool are refcounted based on the number of mappings which exist.
 * When this falls to 0, the program is deleted from the pool.
 * If a program has a refcount greater than 1, it is a shared program.
 *
 * In all cases, an empty program is evaluated the same as the absence of a program.
 * Therefore it is not necessary to store mappings to empty unshared programs.
 * Any editing action which would otherwise result in a mapping to an empty program
 * which has no other references, instead removes the mapping.
 * This is not done for shared programs as this would delete the shared aspect whenever
 * the program became empty.
 *
 * Special case: In the case where an empty program with refcount 2 has one of its
 * mappings removed, the other mapping is left pointing to an empty unshared program.
 * This other mapping is then removed by performing a linear search of the mappings,
 * and removing the reference to that program ID.
 */
TraceRestrictProgramPool _tracerestrictprogram_pool("TraceRestrictProgram");
INSTANTIATE_POOL_METHODS(TraceRestrictProgram)
TraceRestrictSlotPool _tracerestrictslot_pool("TraceRestrictSlot");
INSTANTIATE_POOL_METHODS(TraceRestrictSlot)
TraceRestrictCounterPool _tracerestrictcounter_pool("TraceRestrictCounter");
INSTANTIATE_POOL_METHODS(TraceRestrictCounter)
std::vector TraceRestrictSlot::veh_temporarily_added;
std::vector TraceRestrictSlot::veh_temporarily_removed;
/**
 * TraceRestrictRefId --> TraceRestrictProgramID (Pool ID) mapping
 * The indirection is mainly to enable shared programs
 * TODO: use a more efficient container/indirection mechanism
 */
TraceRestrictMapping _tracerestrictprogram_mapping;
/**
 * List of pre-defined pathfinder penalty values
 * This is indexed by TraceRestrictPathfinderPenaltyPresetIndex
 */
const uint16 _tracerestrict_pathfinder_penalty_preset_values[] = {
	500,
	2000,
	8000,
};
static_assert(lengthof(_tracerestrict_pathfinder_penalty_preset_values) == TRPPPI_END);
/**
 * This should be used when all pools have been or are immediately about to be also cleared
 * Calling this at other times will leave dangling refcounts
 */
void ClearTraceRestrictMapping() {
	_tracerestrictprogram_mapping.clear();
}
/**
 * Flags used for the program execution condition stack
 * Each 'if' pushes onto the stack
 * Each 'end if' pops from the stack
 * Elif/orif/else may modify the stack top
 */
enum TraceRestrictCondStackFlags {
	TRCSF_DONE_IF         = 1<<0,       ///< The if/elif/else is "done", future elif/else branches will not be executed
	TRCSF_SEEN_ELSE       = 1<<1,       ///< An else branch has been seen already, error if another is seen afterwards
	TRCSF_ACTIVE          = 1<<2,       ///< The condition is currently active
	TRCSF_PARENT_INACTIVE = 1<<3,       ///< The parent condition is not active, thus this condition is also not active
};
DECLARE_ENUM_AS_BIT_SET(TraceRestrictCondStackFlags)
/**
 * Helper function to handle condition stack manipulatoin
 */
static void HandleCondition(std::vector &condstack, TraceRestrictCondFlags condflags, bool value)
{
	if (condflags & TRCF_OR) {
		assert(!condstack.empty());
		if (condstack.back() & TRCSF_ACTIVE) {
			// leave TRCSF_ACTIVE set
			return;
		}
	}
	if (condflags & (TRCF_OR | TRCF_ELSE)) {
		assert(!condstack.empty());
		if (condstack.back() & (TRCSF_DONE_IF | TRCSF_PARENT_INACTIVE)) {
			condstack.back() &= ~TRCSF_ACTIVE;
			return;
		}
	} else {
		if (!condstack.empty() && !(condstack.back() & TRCSF_ACTIVE)) {
			//this is a 'nested if', the 'parent if' is not active
			condstack.push_back(TRCSF_PARENT_INACTIVE);
			return;
		}
		condstack.push_back(static_cast(0));
	}
	if (value) {
		condstack.back() |= TRCSF_DONE_IF | TRCSF_ACTIVE;
	} else {
		condstack.back() &= ~TRCSF_ACTIVE;
	}
}
/**
 * Integer condition testing
 * Test value op condvalue
 */
static bool TestCondition(int value, TraceRestrictCondOp condop, int condvalue)
{
	switch (condop) {
		case TRCO_IS:
			return value == condvalue;
		case TRCO_ISNOT:
			return value != condvalue;
		case TRCO_LT:
			return value < condvalue;
		case TRCO_LTE:
			return value <= condvalue;
		case TRCO_GT:
			return value > condvalue;
		case TRCO_GTE:
			return value >= condvalue;
		default:
			NOT_REACHED();
			return false;
	}
}
/**
 * Binary condition testing helper function
 */
static bool TestBinaryConditionCommon(TraceRestrictItem item, bool input)
{
	switch (GetTraceRestrictCondOp(item)) {
		case TRCO_IS:
			return input;
		case TRCO_ISNOT:
			return !input;
		default:
			NOT_REACHED();
			return false;
	}
}
/**
 * Test order condition
 * @p order may be nullptr
 */
static bool TestOrderCondition(const Order *order, TraceRestrictItem item)
{
	bool result = false;
	if (order) {
		DestinationID condvalue = GetTraceRestrictValue(item);
		switch (static_cast(GetTraceRestrictAuxField(item))) {
			case TROCAF_STATION:
				result = (order->IsType(OT_GOTO_STATION) || order->IsType(OT_LOADING_ADVANCE))
						&& order->GetDestination() == condvalue;
				break;
			case TROCAF_WAYPOINT:
				result = order->IsType(OT_GOTO_WAYPOINT) && order->GetDestination() == condvalue;
				break;
			case TROCAF_DEPOT:
				result = order->IsType(OT_GOTO_DEPOT) && order->GetDestination() == condvalue;
				break;
			default:
				NOT_REACHED();
		}
	}
	return TestBinaryConditionCommon(item, result);
}
/**
 * Test station condition
 */
static bool TestStationCondition(StationID station, TraceRestrictItem item)
{
	bool result = (GetTraceRestrictAuxField(item) == TROCAF_STATION) && (station == GetTraceRestrictValue(item));
	return TestBinaryConditionCommon(item, result);
}
/**
 * Execute program on train and store results in out
 * @p v may not be nullptr
 * @p out should be zero-initialised
 */
void TraceRestrictProgram::Execute(const Train* v, const TraceRestrictProgramInput &input, TraceRestrictProgramResult& out) const
{
	// static to avoid needing to re-alloc/resize on each execution
	static std::vector condstack;
	condstack.clear();
	byte have_previous_signal = 0;
	TileIndex previous_signal_tile[2];
	size_t size = this->items.size();
	for (size_t i = 0; i < size; i++) {
		TraceRestrictItem item = this->items[i];
		TraceRestrictItemType type = GetTraceRestrictType(item);
		if (IsTraceRestrictConditional(item)) {
			TraceRestrictCondFlags condflags = GetTraceRestrictCondFlags(item);
			TraceRestrictCondOp condop = GetTraceRestrictCondOp(item);
			if (type == TRIT_COND_ENDIF) {
				assert(!condstack.empty());
				if (condflags & TRCF_ELSE) {
					// else
					assert(!(condstack.back() & TRCSF_SEEN_ELSE));
					HandleCondition(condstack, condflags, true);
					condstack.back() |= TRCSF_SEEN_ELSE;
				} else {
					// end if
					condstack.pop_back();
				}
			} else {
				uint16 condvalue = GetTraceRestrictValue(item);
				bool result = false;
				switch(type) {
					case TRIT_COND_UNDEFINED:
						result = false;
						break;
					case TRIT_COND_TRAIN_LENGTH:
						result = TestCondition(CeilDiv(v->gcache.cached_total_length, TILE_SIZE), condop, condvalue);
						break;
					case TRIT_COND_MAX_SPEED:
						result = TestCondition(v->GetDisplayMaxSpeed(), condop, condvalue);
						break;
					case TRIT_COND_CURRENT_ORDER:
						result = TestOrderCondition(&(v->current_order), item);
						break;
					case TRIT_COND_NEXT_ORDER: {
						if (v->orders == nullptr) break;
						if (v->orders->GetNumOrders() == 0) break;
						const Order *current_order = v->GetOrder(v->cur_real_order_index);
						for (const Order *order = v->orders->GetNext(current_order); order != current_order; order = v->orders->GetNext(order)) {
							if (order->IsGotoOrder()) {
								result = TestOrderCondition(order, item);
								break;
							}
						}
						break;
					}
					case TRIT_COND_LAST_STATION:
						result = TestStationCondition(v->last_station_visited, item);
						break;
					case TRIT_COND_CARGO: {
						bool have_cargo = false;
						for (const Vehicle *v_iter = v; v_iter != nullptr; v_iter = v_iter->Next()) {
							if (v_iter->cargo_type == GetTraceRestrictValue(item) && v_iter->cargo_cap > 0) {
								have_cargo = true;
								break;
							}
						}
						result = TestBinaryConditionCommon(item, have_cargo);
						break;
					}
					case TRIT_COND_ENTRY_DIRECTION: {
						bool direction_match;
						switch (GetTraceRestrictValue(item)) {
							case TRNTSV_NE:
							case TRNTSV_SE:
							case TRNTSV_SW:
							case TRNTSV_NW:
								direction_match = (static_cast(GetTraceRestrictValue(item)) == TrackdirToExitdir(ReverseTrackdir(input.trackdir)));
								break;
							case TRDTSV_FRONT:
								direction_match = (IsTileType(input.tile, MP_RAILWAY) && HasSignalOnTrackdir(input.tile, input.trackdir)) || IsTileType(input.tile, MP_TUNNELBRIDGE);
								break;
							case TRDTSV_BACK:
								direction_match = IsTileType(input.tile, MP_RAILWAY) && !HasSignalOnTrackdir(input.tile, input.trackdir);
								break;
							case TRDTSV_TUNBRIDGE_ENTER:
								direction_match = IsTunnelBridgeSignalSimulationEntranceTile(input.tile) && TrackdirEntersTunnelBridge(input.tile, input.trackdir);
								break;
							case TRDTSV_TUNBRIDGE_EXIT:
								direction_match = IsTunnelBridgeSignalSimulationExitTile(input.tile) && TrackdirExitsTunnelBridge(input.tile, input.trackdir);
								break;
							default:
								NOT_REACHED();
								break;
						}
						result = TestBinaryConditionCommon(item, direction_match);
						break;
					}
					case TRIT_COND_PBS_ENTRY_SIGNAL: {
						// TRIT_COND_PBS_ENTRY_SIGNAL value type uses the next slot
						i++;
						TraceRestrictPBSEntrySignalAuxField mode = static_cast(GetTraceRestrictAuxField(item));
						assert(mode == TRPESAF_VEH_POS || mode == TRPESAF_RES_END);
						uint32_t signal_tile = this->items[i];
						if (!HasBit(have_previous_signal, mode)) {
							if (input.previous_signal_callback) {
								previous_signal_tile[mode] = input.previous_signal_callback(v, input.previous_signal_ptr, mode);
							} else {
								previous_signal_tile[mode] = INVALID_TILE;
							}
							SetBit(have_previous_signal, mode);
						}
						bool match = (signal_tile != INVALID_TILE)
								&& (previous_signal_tile[mode] == signal_tile);
						result = TestBinaryConditionCommon(item, match);
						break;
					}
					case TRIT_COND_TRAIN_GROUP: {
						result = TestBinaryConditionCommon(item, GroupIsInGroup(v->group_id, GetTraceRestrictValue(item)));
						break;
					}
					case TRIT_COND_TRAIN_IN_SLOT: {
						const TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item));
						result = TestBinaryConditionCommon(item, slot != nullptr && slot->IsOccupant(v->index));
						break;
					}
					case TRIT_COND_SLOT_OCCUPANCY: {
						// TRIT_COND_SLOT_OCCUPANCY value type uses the next slot
						i++;
						uint32_t value = this->items[i];
						const TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item));
						switch (static_cast(GetTraceRestrictAuxField(item))) {
							case TRSOCAF_OCCUPANTS:
								result = TestCondition(slot != nullptr ? (uint)slot->occupants.size() : 0, condop, value);
								break;
							case TRSOCAF_REMAINING:
								result = TestCondition(slot != nullptr ? slot->max_occupancy - (uint)slot->occupants.size() : 0, condop, value);
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					}
					case TRIT_COND_PHYS_PROP: {
						switch (static_cast(GetTraceRestrictAuxField(item))) {
							case TRPPCAF_WEIGHT:
								result = TestCondition(v->gcache.cached_weight, condop, condvalue);
								break;
							case TRPPCAF_POWER:
								result = TestCondition(v->gcache.cached_power, condop, condvalue);
								break;
							case TRPPCAF_MAX_TE:
								result = TestCondition(v->gcache.cached_max_te / 1000, condop, condvalue);
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					}
					case TRIT_COND_PHYS_RATIO: {
						switch (static_cast(GetTraceRestrictAuxField(item))) {
							case TRPPRCAF_POWER_WEIGHT:
								result = TestCondition(std::min(UINT16_MAX, (100 * v->gcache.cached_power) / std::max(1, v->gcache.cached_weight)), condop, condvalue);
								break;
							case TRPPRCAF_MAX_TE_WEIGHT:
								result = TestCondition(std::min(UINT16_MAX, (v->gcache.cached_max_te / 10) / std::max(1, v->gcache.cached_weight)), condop, condvalue);
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					}
					case TRIT_COND_TRAIN_OWNER: {
						result = TestBinaryConditionCommon(item, v->owner == condvalue);
						break;
					}
					case TRIT_COND_TRAIN_STATUS: {
						bool has_status = false;
						switch (static_cast(GetTraceRestrictValue(item))) {
							case TRTSVF_EMPTY:
								has_status = true;
								for (const Vehicle *v_iter = v; v_iter != nullptr; v_iter = v_iter->Next()) {
									if (v_iter->cargo.StoredCount() > 0) {
										has_status = false;
										break;
									}
								}
								break;
							case TRTSVF_FULL:
								has_status = true;
								for (const Vehicle *v_iter = v; v_iter != nullptr; v_iter = v_iter->Next()) {
									if (v_iter->cargo.StoredCount() < v_iter->cargo_cap) {
										has_status = false;
										break;
									}
								}
								break;
							case TRTSVF_BROKEN_DOWN:
								has_status = v->flags & VRF_IS_BROKEN;
								break;
							case TRTSVF_NEEDS_REPAIR:
								has_status = v->critical_breakdown_count > 0;
								break;
							case TRTSVF_REVERSING:
								has_status = v->reverse_distance > 0 || HasBit(v->flags, VRF_REVERSING);
								break;
							case TRTSVF_HEADING_TO_STATION_WAYPOINT:
								has_status = v->current_order.IsType(OT_GOTO_STATION) || v->current_order.IsType(OT_GOTO_WAYPOINT);
								break;
							case TRTSVF_HEADING_TO_DEPOT:
								has_status = v->current_order.IsType(OT_GOTO_DEPOT);
								break;
							case TRTSVF_LOADING: {
								extern const Order *_choose_train_track_saved_current_order;
								const Order *o = (_choose_train_track_saved_current_order != nullptr) ? _choose_train_track_saved_current_order : &(v->current_order);
								has_status = o->IsType(OT_LOADING) || o->IsType(OT_LOADING_ADVANCE);
								break;
							}
							case TRTSVF_WAITING:
								has_status = v->current_order.IsType(OT_WAITING);
								break;
							case TRTSVF_LOST:
								has_status = HasBit(v->vehicle_flags, VF_PATHFINDER_LOST);
								break;
							case TRTSVF_REQUIRES_SERVICE:
								has_status = v->NeedsServicing();
								break;
						}
						result = TestBinaryConditionCommon(item, has_status);
						break;
					}
					case TRIT_COND_LOAD_PERCENT: {
						result = TestCondition(CalcPercentVehicleFilled(v, nullptr), condop, condvalue);
						break;
					}
					case TRIT_COND_COUNTER_VALUE: {
						// TRVT_COUNTER_INDEX_INT value type uses the next slot
						i++;
						uint32_t value = this->items[i];
						const TraceRestrictCounter *ctr = TraceRestrictCounter::GetIfValid(GetTraceRestrictValue(item));
						result = TestCondition(ctr != nullptr ? ctr->value : 0, condop, value);
						break;
					}
					case TRIT_COND_TIME_DATE_VALUE: {
						// TRVT_TIME_DATE_INT value type uses the next slot
						i++;
						uint32_t value = this->items[i];
						result = TestCondition(GetTraceRestrictTimeDateValue(static_cast(GetTraceRestrictValue(item))), condop, value);
						break;
					}
					case TRIT_COND_RESERVED_TILES: {
						uint tiles_ahead = 0;
						if (v->lookahead != nullptr) {
							tiles_ahead = std::max(0, v->lookahead->reservation_end_position - v->lookahead->current_position) / TILE_SIZE;
						}
						result = TestCondition(tiles_ahead, condop, condvalue);
						break;
					}
					case TRIT_COND_CATEGORY: {
						switch (static_cast(GetTraceRestrictAuxField(item))) {
							case TRCCAF_ENGINE_CLASS: {
								EngineClass ec = (EngineClass)condvalue;
								result = (GetTraceRestrictCondOp(item) != TRCO_IS);
								for (const Train *u = v; u != nullptr; u = u->Next()) {
									/* Check if engine class present */
									if (u->IsEngine() && RailVehInfo(u->engine_type)->engclass == ec) {
										result = !result;
										break;
									}
								}
								break;
							}
							default:
								NOT_REACHED();
								break;
						}
						break;
					}
					default:
						NOT_REACHED();
				}
				HandleCondition(condstack, condflags, result);
			}
		} else {
			if (condstack.empty() || condstack.back() & TRCSF_ACTIVE) {
				switch(type) {
					case TRIT_PF_DENY:
						if (GetTraceRestrictValue(item)) {
							out.flags &= ~TRPRF_DENY;
						} else {
							out.flags |= TRPRF_DENY;
						}
						break;
					case TRIT_PF_PENALTY:
						switch (static_cast(GetTraceRestrictAuxField(item))) {
							case TRPPAF_VALUE:
								out.penalty += GetTraceRestrictValue(item);
								break;
							case TRPPAF_PRESET: {
								uint16 index = GetTraceRestrictValue(item);
								assert(index < TRPPPI_END);
								out.penalty += _tracerestrict_pathfinder_penalty_preset_values[index];
								break;
							}
							default:
								NOT_REACHED();
						}
						break;
					case TRIT_RESERVE_THROUGH:
						if (GetTraceRestrictValue(item)) {
							out.flags &= ~TRPRF_RESERVE_THROUGH;
						} else {
							out.flags |= TRPRF_RESERVE_THROUGH;
						}
						break;
					case TRIT_LONG_RESERVE:
						if (GetTraceRestrictValue(item)) {
							out.flags &= ~TRPRF_LONG_RESERVE;
						} else {
							out.flags |= TRPRF_LONG_RESERVE;
						}
						break;
					case TRIT_WAIT_AT_PBS:
						switch (static_cast(GetTraceRestrictValue(item))) {
							case TRWAPVF_WAIT_AT_PBS:
								out.flags |= TRPRF_WAIT_AT_PBS;
								break;
							case TRWAPVF_CANCEL_WAIT_AT_PBS:
								out.flags &= ~TRPRF_WAIT_AT_PBS;
								break;
							case TRWAPVF_PBS_RES_END_WAIT:
								out.flags |= TRPRF_PBS_RES_END_WAIT;
								break;
							case TRWAPVF_CANCEL_PBS_RES_END_WAIT:
								out.flags &= ~TRPRF_PBS_RES_END_WAIT;
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					case TRIT_SLOT: {
						if (!input.permitted_slot_operations) break;
						TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item));
						if (slot == nullptr || slot->vehicle_type != v->type) break;
						switch (static_cast(GetTraceRestrictCondOp(item))) {
							case TRSCOF_ACQUIRE_WAIT:
								if (input.permitted_slot_operations & TRPISP_ACQUIRE) {
									if (!slot->Occupy(v->index)) out.flags |= TRPRF_WAIT_AT_PBS;
								}
								break;
							case TRSCOF_ACQUIRE_TRY:
								if (input.permitted_slot_operations & TRPISP_ACQUIRE) slot->Occupy(v->index);
								break;
							case TRSCOF_RELEASE_BACK:
								if (input.permitted_slot_operations & TRPISP_RELEASE_BACK) slot->Vacate(v->index);
								break;
							case TRSCOF_RELEASE_FRONT:
								if (input.permitted_slot_operations & TRPISP_RELEASE_FRONT) slot->Vacate(v->index);
								break;
							case TRSCOF_PBS_RES_END_ACQ_WAIT:
								if (input.permitted_slot_operations & TRPISP_PBS_RES_END_ACQUIRE) {
									if (!slot->Occupy(v->index)) out.flags |= TRPRF_PBS_RES_END_WAIT;
								} else if (input.permitted_slot_operations & TRPISP_PBS_RES_END_ACQ_DRY) {
									if (this->actions_used_flags & TRPAUF_PBS_RES_END_SIMULATE) {
										if (!slot->OccupyDryRunUsingTemporaryState(v->index)) out.flags |= TRPRF_PBS_RES_END_WAIT;
									} else {
										if (!slot->OccupyDryRun(v->index)) out.flags |= TRPRF_PBS_RES_END_WAIT;
									}
								}
								break;
							case TRSCOF_PBS_RES_END_ACQ_TRY:
								if (input.permitted_slot_operations & TRPISP_PBS_RES_END_ACQUIRE) {
									slot->Occupy(v->index);
								} else if ((input.permitted_slot_operations & TRPISP_PBS_RES_END_ACQ_DRY) && (this->actions_used_flags & TRPAUF_PBS_RES_END_SIMULATE)) {
									slot->OccupyDryRunUsingTemporaryState(v->index);
								}
								break;
							case TRSCOF_PBS_RES_END_RELEASE:
								if (input.permitted_slot_operations & TRPISP_PBS_RES_END_RELEASE) {
									slot->Vacate(v->index);
								} else if ((input.permitted_slot_operations & TRPISP_PBS_RES_END_ACQ_DRY) && (this->actions_used_flags & TRPAUF_PBS_RES_END_SIMULATE)) {
									slot->VacateUsingTemporaryState(v->index);
								}
								break;
							case TRSCOF_ACQUIRE_TRY_ON_RESERVE:
								if (input.permitted_slot_operations & TRPISP_ACQUIRE_ON_RES) slot->Occupy(v->index);
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					}
					case TRIT_REVERSE:
						switch (static_cast(GetTraceRestrictValue(item))) {
							case TRRVF_REVERSE:
								out.flags |= TRPRF_REVERSE;
								break;
							case TRRVF_CANCEL_REVERSE:
								out.flags &= ~TRPRF_REVERSE;
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					case TRIT_SPEED_RESTRICTION: {
						out.speed_restriction = GetTraceRestrictValue(item);
						out.flags |= TRPRF_SPEED_RESTRICTION_SET;
						break;
					}
					case TRIT_NEWS_CONTROL:
						switch (static_cast(GetTraceRestrictValue(item))) {
							case TRNCF_TRAIN_NOT_STUCK:
								out.flags |= TRPRF_TRAIN_NOT_STUCK;
								break;
							case TRNCF_CANCEL_TRAIN_NOT_STUCK:
								out.flags &= ~TRPRF_TRAIN_NOT_STUCK;
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					case TRIT_COUNTER: {
						// TRVT_COUNTER_INDEX_INT value type uses the next slot
						i++;
						uint32_t value = this->items[i];
						if (!(input.permitted_slot_operations & TRPISP_CHANGE_COUNTER)) break;
						TraceRestrictCounter *ctr = TraceRestrictCounter::GetIfValid(GetTraceRestrictValue(item));
						if (ctr == nullptr) break;
						switch (static_cast(GetTraceRestrictCondOp(item))) {
							case TRCCOF_INCREASE:
								ctr->UpdateValue(ctr->value + value);
								break;
							case TRCCOF_DECREASE:
								ctr->UpdateValue(ctr->value - value);
								break;
							case TRCCOF_SET:
								ctr->UpdateValue(value);
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					}
					case TRIT_PF_PENALTY_CONTROL:
						switch (static_cast(GetTraceRestrictValue(item))) {
							case TRPPCF_NO_PBS_BACK_PENALTY:
								out.flags |= TRPRF_NO_PBS_BACK_PENALTY;
								break;
							case TRPPCF_CANCEL_NO_PBS_BACK_PENALTY:
								out.flags &= ~TRPRF_NO_PBS_BACK_PENALTY;
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					case TRIT_SPEED_ADAPTATION_CONTROL:
						switch (static_cast(GetTraceRestrictValue(item))) {
							case TRSACF_SPEED_ADAPT_EXEMPT:
								out.flags |= TRPRF_SPEED_ADAPT_EXEMPT;
								out.flags &= ~TRPRF_RM_SPEED_ADAPT_EXEMPT;
								break;
							case TRSACF_REMOVE_SPEED_ADAPT_EXEMPT:
								out.flags &= ~TRPRF_SPEED_ADAPT_EXEMPT;
								out.flags |= TRPRF_RM_SPEED_ADAPT_EXEMPT;
								break;
							default:
								NOT_REACHED();
								break;
						}
						break;
					default:
						NOT_REACHED();
				}
			} else {
				if (IsTraceRestrictDoubleItem(type)) i++;
			}
		}
	}
	if ((input.permitted_slot_operations & TRPISP_PBS_RES_END_ACQ_DRY) && (this->actions_used_flags & TRPAUF_PBS_RES_END_SIMULATE)) {
		TraceRestrictSlot::RevertTemporaryChanges(v->index);
	}
	assert(condstack.empty());
}
void TraceRestrictProgram::ClearRefIds()
{
	if (this->refcount > 4) free(this->ref_ids.ptr_ref_ids.buffer);
}
/**
 * Increment ref count, only use when creating a mapping
 */
void TraceRestrictProgram::IncrementRefCount(TraceRestrictRefId ref_id)
{
	if (this->refcount >= 4) {
		if (this->refcount == 4) {
			/* Transition from inline to allocated mode */
			TraceRestrictRefId *ptr = MallocT(8);
			MemCpyT(ptr, this->ref_ids.inline_ref_ids, 4);
			this->ref_ids.ptr_ref_ids.buffer = ptr;
			this->ref_ids.ptr_ref_ids.elem_capacity = 8;
		} else if (this->refcount == this->ref_ids.ptr_ref_ids.elem_capacity) {
			// grow buffer
			this->ref_ids.ptr_ref_ids.elem_capacity *= 2;
			this->ref_ids.ptr_ref_ids.buffer = ReallocT(this->ref_ids.ptr_ref_ids.buffer, this->ref_ids.ptr_ref_ids.elem_capacity);
		}
		this->ref_ids.ptr_ref_ids.buffer[this->refcount] = ref_id;
	} else {
		this->ref_ids.inline_ref_ids[this->refcount] = ref_id;
	}
	this->refcount++;
}
/**
 * Decrement ref count, only use when removing a mapping
 */
void TraceRestrictProgram::DecrementRefCount(TraceRestrictRefId ref_id) {
	assert(this->refcount > 0);
	if (this->refcount >= 2) {
		TraceRestrictRefId *data = this->GetRefIdsPtr();
		for (uint i = 0; i < this->refcount - 1; i++) {
			if (data[i] == ref_id) {
				data[i] = data[this->refcount - 1];
				break;
			}
		}
	}
	this->refcount--;
	if (this->refcount == 4) {
		/* Transition from allocated to inline mode */
		TraceRestrictRefId *ptr = this->ref_ids.ptr_ref_ids.buffer;
		MemCpyT(this->ref_ids.inline_ref_ids, ptr, 4);
		free(ptr);
	}
	if (this->refcount == 0) {
		extern const TraceRestrictProgram *_viewport_highlight_tracerestrict_program;
		if (_viewport_highlight_tracerestrict_program == this) {
			_viewport_highlight_tracerestrict_program = nullptr;
			InvalidateWindowClassesData(WC_TRACE_RESTRICT);
		}
		delete this;
	}
}
/**
 * Validate a instruction list
 * Returns successful result if program seems OK
 * This only validates that conditional nesting is correct,
 * and that all instructions have a known type, at present
 */
CommandCost TraceRestrictProgram::Validate(const std::vector &items, TraceRestrictProgramActionsUsedFlags &actions_used_flags) {
	// static to avoid needing to re-alloc/resize on each execution
	static std::vector condstack;
	condstack.clear();
	actions_used_flags = static_cast(0);
	static std::vector pbs_res_end_released_slots;
	pbs_res_end_released_slots.clear();
	static std::vector pbs_res_end_acquired_slots;
	pbs_res_end_acquired_slots.clear();
	size_t size = items.size();
	for (size_t i = 0; i < size; i++) {
		TraceRestrictItem item = items[i];
		TraceRestrictItemType type = GetTraceRestrictType(item);
		// check multi-word instructions
		if (IsTraceRestrictDoubleItem(item)) {
			i++;
			if (i >= size) {
				return_cmd_error(STR_TRACE_RESTRICT_ERROR_OFFSET_TOO_LARGE); // instruction ran off end
			}
		}
		if (IsTraceRestrictConditional(item)) {
			TraceRestrictCondFlags condflags = GetTraceRestrictCondFlags(item);
			if (type == TRIT_COND_ENDIF) {
				if (condstack.empty()) {
					return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_NO_IF); // else/endif with no starting if
				}
				if (condflags & TRCF_ELSE) {
					// else
					if (condstack.back() & TRCSF_SEEN_ELSE) {
						return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE); // Two else clauses
					}
					HandleCondition(condstack, condflags, true);
					condstack.back() |= TRCSF_SEEN_ELSE;
				} else {
					// end if
					condstack.pop_back();
				}
			} else {
				if (condflags & (TRCF_OR | TRCF_ELSE)) { // elif/orif
					if (condstack.empty()) {
						return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_ELIF_NO_IF); // Pre-empt assertions in HandleCondition
					}
					if (condstack.back() & TRCSF_SEEN_ELSE) {
						return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_DUP_ELSE); // else clause followed by elif/orif
					}
				}
				HandleCondition(condstack, condflags, true);
			}
			switch (GetTraceRestrictType(item)) {
				case TRIT_COND_ENDIF:
				case TRIT_COND_UNDEFINED:
				case TRIT_COND_TRAIN_LENGTH:
				case TRIT_COND_MAX_SPEED:
				case TRIT_COND_CURRENT_ORDER:
				case TRIT_COND_NEXT_ORDER:
				case TRIT_COND_LAST_STATION:
				case TRIT_COND_CARGO:
				case TRIT_COND_ENTRY_DIRECTION:
				case TRIT_COND_PBS_ENTRY_SIGNAL:
				case TRIT_COND_TRAIN_GROUP:
				case TRIT_COND_PHYS_PROP:
				case TRIT_COND_PHYS_RATIO:
				case TRIT_COND_TRAIN_OWNER:
				case TRIT_COND_TRAIN_STATUS:
				case TRIT_COND_LOAD_PERCENT:
				case TRIT_COND_COUNTER_VALUE:
				case TRIT_COND_TIME_DATE_VALUE:
				case TRIT_COND_RESERVED_TILES:
				case TRIT_COND_CATEGORY:
					break;
				case TRIT_COND_TRAIN_IN_SLOT:
				case TRIT_COND_SLOT_OCCUPANCY:
					if (find_index(pbs_res_end_released_slots, GetTraceRestrictValue(item)) >= 0 || find_index(pbs_res_end_acquired_slots, GetTraceRestrictValue(item)) >= 0) {
						actions_used_flags |= TRPAUF_PBS_RES_END_SIMULATE;
					}
					break;
				default:
					return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_UNKNOWN_INSTRUCTION);
			}
		} else {
			switch (GetTraceRestrictType(item)) {
				case TRIT_PF_DENY:
				case TRIT_PF_PENALTY:
					actions_used_flags |= TRPAUF_PF;
					break;
				case TRIT_RESERVE_THROUGH:
					actions_used_flags |= TRPAUF_RESERVE_THROUGH;
					if (GetTraceRestrictValue(item)) {
						actions_used_flags &= ~TRPAUF_RESERVE_THROUGH_ALWAYS;
					} else if (condstack.empty()) {
						actions_used_flags |= TRPAUF_RESERVE_THROUGH_ALWAYS;
					}
					break;
				case TRIT_LONG_RESERVE:
					actions_used_flags |= TRPAUF_LONG_RESERVE;
					break;
				case TRIT_WAIT_AT_PBS:
					switch (static_cast(GetTraceRestrictValue(item))) {
						case TRWAPVF_WAIT_AT_PBS:
						case TRWAPVF_CANCEL_WAIT_AT_PBS:
							actions_used_flags |= TRPAUF_WAIT_AT_PBS;
							break;
						case TRWAPVF_PBS_RES_END_WAIT:
						case TRWAPVF_CANCEL_PBS_RES_END_WAIT:
							actions_used_flags |= TRPAUF_PBS_RES_END_WAIT;
							break;
						default:
							NOT_REACHED();
							break;
					}
					break;
				case TRIT_SLOT:
					switch (static_cast(GetTraceRestrictCondOp(item))) {
						case TRSCOF_ACQUIRE_WAIT:
							actions_used_flags |= TRPAUF_SLOT_ACQUIRE | TRPAUF_WAIT_AT_PBS;
							break;
						case TRSCOF_ACQUIRE_TRY:
							actions_used_flags |= TRPAUF_SLOT_ACQUIRE;
							break;
						case TRSCOF_RELEASE_BACK:
							actions_used_flags |= TRPAUF_SLOT_RELEASE_BACK;
							break;
						case TRSCOF_RELEASE_FRONT:
							actions_used_flags |= TRPAUF_SLOT_RELEASE_FRONT;
							break;
						case TRSCOF_PBS_RES_END_ACQ_WAIT:
							actions_used_flags |= TRPAUF_PBS_RES_END_SLOT | TRPAUF_PBS_RES_END_WAIT;
							if (find_index(pbs_res_end_released_slots, GetTraceRestrictValue(item)) >= 0) actions_used_flags |= TRPAUF_PBS_RES_END_SIMULATE;
							include(pbs_res_end_acquired_slots, GetTraceRestrictValue(item));
							break;
						case TRSCOF_PBS_RES_END_ACQ_TRY:
							actions_used_flags |= TRPAUF_PBS_RES_END_SLOT;
							if (find_index(pbs_res_end_released_slots, GetTraceRestrictValue(item)) >= 0) actions_used_flags |= TRPAUF_PBS_RES_END_SIMULATE;
							include(pbs_res_end_acquired_slots, GetTraceRestrictValue(item));
							break;
						case TRSCOF_PBS_RES_END_RELEASE:
							actions_used_flags |= TRPAUF_PBS_RES_END_SLOT;
							include(pbs_res_end_released_slots, GetTraceRestrictValue(item));
							break;
						case TRSCOF_ACQUIRE_TRY_ON_RESERVE:
							actions_used_flags |= TRPAUF_SLOT_ACQUIRE_ON_RES;
							break;
						default:
							NOT_REACHED();
							break;
					}
					break;
				case TRIT_REVERSE:
					switch (static_cast(GetTraceRestrictValue(item))) {
						case TRRVF_REVERSE:
							actions_used_flags |= TRPAUF_REVERSE;
							break;
						case TRRVF_CANCEL_REVERSE:
							if (condstack.empty()) actions_used_flags &= ~TRPAUF_REVERSE;
							break;
						default:
							NOT_REACHED();
							break;
					}
					break;
				case TRIT_SPEED_RESTRICTION:
					actions_used_flags |= TRPAUF_SPEED_RESTRICTION;
					break;
				case TRIT_NEWS_CONTROL:
					actions_used_flags |= TRPAUF_TRAIN_NOT_STUCK;
					break;
				case TRIT_COUNTER:
					actions_used_flags |= TRPAUF_CHANGE_COUNTER;
					break;
				case TRIT_PF_PENALTY_CONTROL:
					actions_used_flags |= TRPAUF_NO_PBS_BACK_PENALTY;
					break;
				case TRIT_SPEED_ADAPTATION_CONTROL:
					actions_used_flags |= TRPAUF_SPEED_ADAPTATION;
					break;
				default:
					return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_UNKNOWN_INSTRUCTION);
			}
		}
	}
	if (!condstack.empty()) {
		return_cmd_error(STR_TRACE_RESTRICT_ERROR_VALIDATE_END_CONDSTACK);
	}
	return CommandCost();
}
/**
 * Convert an instruction index into an item array index
 */
size_t TraceRestrictProgram::InstructionOffsetToArrayOffset(const std::vector &items, size_t offset)
{
	size_t output_offset = 0;
	size_t size = items.size();
	for (size_t i = 0; i < offset && output_offset < size; i++, output_offset++) {
		if (IsTraceRestrictDoubleItem(items[output_offset])) {
			output_offset++;
		}
	}
	return output_offset;
}
/**
 * Convert an item array index into an instruction index
 */
size_t TraceRestrictProgram::ArrayOffsetToInstructionOffset(const std::vector &items, size_t offset)
{
	size_t output_offset = 0;
	for (size_t i = 0; i < offset; i++, output_offset++) {
		if (IsTraceRestrictDoubleItem(items[i])) {
			i++;
		}
	}
	return output_offset;
}
/**
 * Set the value and aux field of @p item, as per the value type in @p value_type
 */
void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueType value_type)
{
	switch (value_type) {
		case TRVT_NONE:
		case TRVT_INT:
		case TRVT_DENY:
		case TRVT_SPEED:
		case TRVT_TILE_INDEX:
		case TRVT_RESERVE_THROUGH:
		case TRVT_LONG_RESERVE:
		case TRVT_WEIGHT:
		case TRVT_POWER:
		case TRVT_FORCE:
		case TRVT_POWER_WEIGHT_RATIO:
		case TRVT_FORCE_WEIGHT_RATIO:
		case TRVT_WAIT_AT_PBS:
		case TRVT_TRAIN_STATUS:
		case TRVT_REVERSE:
		case TRVT_PERCENT:
		case TRVT_NEWS_CONTROL:
		case TRVT_TIME_DATE_INT:
		case TRVT_ENGINE_CLASS:
		case TRVT_PF_PENALTY_CONTROL:
		case TRVT_SPEED_ADAPTATION_CONTROL:
			SetTraceRestrictValue(item, 0);
			if (!IsTraceRestrictTypeAuxSubtype(GetTraceRestrictType(item))) {
				SetTraceRestrictAuxField(item, 0);
			}
			break;
		case TRVT_ORDER:
			SetTraceRestrictValue(item, INVALID_STATION);
			SetTraceRestrictAuxField(item, TROCAF_STATION);
			break;
		case TRVT_CARGO_ID:
			assert(_standard_cargo_mask != 0);
			SetTraceRestrictValue(item, FindFirstBit(_standard_cargo_mask));
			SetTraceRestrictAuxField(item, 0);
			break;
		case TRVT_DIRECTION:
			SetTraceRestrictValue(item, TRDTSV_FRONT);
			SetTraceRestrictAuxField(item, 0);
			break;
		case TRVT_PF_PENALTY:
			SetTraceRestrictValue(item, TRPPPI_SMALL);
			SetTraceRestrictAuxField(item, TRPPAF_PRESET);
			break;
		case TRVT_GROUP_INDEX:
			SetTraceRestrictValue(item, INVALID_GROUP);
			SetTraceRestrictAuxField(item, 0);
			break;
		case TRVT_OWNER:
			SetTraceRestrictValue(item, INVALID_OWNER);
			SetTraceRestrictAuxField(item, 0);
			break;
		case TRVT_SLOT_INDEX:
			SetTraceRestrictValue(item, INVALID_TRACE_RESTRICT_SLOT_ID);
			SetTraceRestrictAuxField(item, 0);
			break;
		case TRVT_SLOT_INDEX_INT:
			SetTraceRestrictValue(item, INVALID_TRACE_RESTRICT_SLOT_ID);
			break;
		case TRVT_COUNTER_INDEX_INT:
			SetTraceRestrictValue(item, INVALID_TRACE_RESTRICT_COUNTER_ID);
			break;
		default:
			NOT_REACHED();
			break;
	}
}
/**
 * Set the type field of a TraceRestrictItem, and resets any other fields which are no longer valid/meaningful to sensible defaults
 */
void SetTraceRestrictTypeAndNormalise(TraceRestrictItem &item, TraceRestrictItemType type, uint8 aux_data)
{
	if (item != 0) {
		assert(GetTraceRestrictType(item) != TRIT_NULL);
		assert(IsTraceRestrictConditional(item) == IsTraceRestrictTypeConditional(type));
	}
	assert(type != TRIT_NULL);
	TraceRestrictTypePropertySet old_properties = GetTraceRestrictTypeProperties(item);
	SetTraceRestrictType(item, type);
	if (IsTraceRestrictTypeAuxSubtype(type)) {
		SetTraceRestrictAuxField(item, aux_data);
	} else {
		assert(aux_data == 0);
	}
	TraceRestrictTypePropertySet new_properties = GetTraceRestrictTypeProperties(item);
	if (old_properties.cond_type != new_properties.cond_type ||
			old_properties.value_type != new_properties.value_type) {
		SetTraceRestrictCondOp(item, TRCO_IS);
		SetTraceRestrictValueDefault(item, new_properties.value_type);
	}
	if (new_properties.value_type == TRVT_SLOT_INDEX || new_properties.value_type == TRVT_SLOT_INDEX_INT) {
		if (!IsTraceRestrictTypeNonMatchingVehicleTypeSlot(GetTraceRestrictType(item))) {
			const TraceRestrictSlot *slot = TraceRestrictSlot::GetIfValid(GetTraceRestrictValue(item));
			if (slot != nullptr && slot->vehicle_type != VEH_TRAIN) SetTraceRestrictValue(item, INVALID_TRACE_RESTRICT_SLOT_ID);
		}
	}
	if (GetTraceRestrictType(item) == TRIT_COND_LAST_STATION && GetTraceRestrictAuxField(item) != TROCAF_STATION) {
		// if changing type from another order type to last visited station, reset value if not currently a station
		SetTraceRestrictValueDefault(item, TRVT_ORDER);
	}
}
/**
 * Sets the "signal has a trace restrict mapping" bit
 * This looks for mappings with that tile index
 */
void TraceRestrictSetIsSignalRestrictedBit(TileIndex t)
{
	// First mapping for this tile, or later
	TraceRestrictMapping::iterator lower_bound = _tracerestrictprogram_mapping.lower_bound(MakeTraceRestrictRefId(t, static_cast