/*
 * 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 <http://www.gnu.org/licenses/>.
 */

/** @file tracerestrict_sl.cpp Code handling saving and loading of trace restrict programs */

#include "../stdafx.h"
#include "../tracerestrict.h"
#include "../strings_func.h"
#include "../string_func.h"
#include "saveload.h"
#include <vector>

static const SaveLoad _trace_restrict_mapping_desc[] = {
  SLE_VAR(TraceRestrictMappingItem, program_id, SLE_UINT32),
};

/**
 * Load mappings
 */
static void Load_TRRM()
{
	int index;
	while ((index = SlIterateArray()) != -1) {
		TraceRestrictMappingItem &item = _tracerestrictprogram_mapping[index];
		SlObject(&item, _trace_restrict_mapping_desc);
	}
}

/**
 * Save mappings
 */
static void Save_TRRM()
{
	for (TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.begin();
			iter != _tracerestrictprogram_mapping.end(); ++iter) {
		SlSetArrayIndex(iter->first);
		SlObject(&(iter->second), _trace_restrict_mapping_desc);
	}
}

/**
 * Load program pool
 */
static void Load_TRRP()
{
	int index;
	while ((index = SlIterateArray()) != -1) {
		TraceRestrictProgram *prog = new (index) TraceRestrictProgram();
		uint32_t prog_length = SlReadUint32();
		prog->items.resize(prog_length);
		if (prog_length > 0) SlArray(prog->items.data(), prog_length, SLE_UINT32);
		if (SlXvIsFeaturePresent(XSLFI_JOKERPP)) {
			for (size_t i = 0; i < prog->items.size(); i++) {
				TraceRestrictItem &item = prog->items[i]; // note this is a reference,
				if (GetTraceRestrictType(item) == 19 || GetTraceRestrictType(item) == 20) {
					SetTraceRestrictType(item, (TraceRestrictItemType)(GetTraceRestrictType(item) + 2));
				}
				if (IsTraceRestrictDoubleItem(item)) i++;
			}
		}
		if (SlXvIsFeatureMissing(XSLFI_TRACE_RESTRICT, 17)) {
			/* TRIT_SLOT subtype moved from cond op to combined aux and cond op field in version 17.
			 * Do this for all previous versions to avoid cases where it is unexpectedly present despite the version,
			 * e.g. in JokerPP and non-SLXI tracerestrict saves.
			 */
			for (size_t i = 0; i < prog->items.size(); i++) {
				TraceRestrictItem &item = prog->items[i]; // note this is a reference
				if (GetTraceRestrictType(item) == TRIT_SLOT) {
					TraceRestrictSlotSubtypeField subtype = static_cast<TraceRestrictSlotSubtypeField>(GetTraceRestrictCondOp(item));
					if (subtype == 7) {
						/* Was TRSCOF_ACQUIRE_TRY_ON_RESERVE */
						subtype = TRSCOF_ACQUIRE_TRY;
					}
					SetTraceRestrictCombinedAuxCondOpField(item, subtype);
				}
				if (IsTraceRestrictDoubleItem(item)) i++;
			}
		}
		CommandCost validation_result = prog->Validate();
		if (validation_result.Failed()) {
			char str[4096];
			char *strend = str + seprintf(str, lastof(str), "Trace restrict program %d: %s\nProgram dump:",
					index, GetStringPtr(validation_result.GetErrorMessage()));
			uint fail_offset = validation_result.HasResultData() ? validation_result.GetResultData() : UINT32_MAX;
			for (uint i = 0; i < (uint)prog->items.size(); i++) {
				if ((i % 3) == 0) {
					strend += seprintf(strend, lastof(str), "\n%4u:", i);
				}
				strend += seprintf(strend, lastof(str), (i == fail_offset) ? " [%08X]" : " %08X", prog->items[i]);
			}
			SlErrorCorrupt(str);
		}
	}
}

/**
 * Save program pool
 */
static void Save_TRRP()
{
	for (TraceRestrictProgram *prog : TraceRestrictProgram::Iterate()) {
		SlSetArrayIndex(prog->index);
		SlSetLength(4 + (prog->items.size() * 4));
		SlWriteUint32((uint32_t)prog->items.size());
		SlArray(prog->items.data(), prog->items.size(), SLE_UINT32);
	}
}

/** program length save header struct */
struct TraceRestrictSlotStub {
	uint32_t length;
};

static const SaveLoad _trace_restrict_slot_stub_desc[] = {
	SLE_VAR(TraceRestrictSlotStub, length, SLE_UINT32),
};

static const SaveLoad _trace_restrict_slot_desc[] = {
	SLE_VAR(TraceRestrictSlot, max_occupancy, SLE_UINT32),
	SLE_SSTR(TraceRestrictSlot, name, SLF_ALLOW_CONTROL),
	SLE_VAR(TraceRestrictSlot, owner, SLE_UINT8),
	SLE_CONDVAR_X(TraceRestrictSlot, vehicle_type, SLE_UINT8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_TRACE_RESTRICT, 13)),
};

/**
 * Load slot pool
 */
static void Load_TRRS()
{
	int index;
	TraceRestrictSlotStub stub;
	while ((index = SlIterateArray()) != -1) {
		TraceRestrictSlot *slot = new (index) TraceRestrictSlot();
		SlObject(slot, _trace_restrict_slot_desc);
		SlObject(&stub, _trace_restrict_slot_stub_desc);
		slot->occupants.resize(stub.length);
		if (stub.length) SlArray(slot->occupants.data(), stub.length, SLE_UINT32);
	}
	TraceRestrictSlot::RebuildVehicleIndex();
}

/**
 * Save a slot, used by SlAutolength
 */
static void RealSave_TRRS(TraceRestrictSlot *slot)
{
	SlObject(slot, _trace_restrict_slot_desc);
	TraceRestrictSlotStub stub;
	stub.length = (uint32_t)slot->occupants.size();
	SlObject(&stub, _trace_restrict_slot_stub_desc);
	if (stub.length) SlArray(slot->occupants.data(), stub.length, SLE_UINT32);
}

/**
 * Save slot pool
 */
static void Save_TRRS()
{
	for (TraceRestrictSlot *slot : TraceRestrictSlot::Iterate()) {
		SlSetArrayIndex(slot->index);
		SlAutolength((AutolengthProc*) RealSave_TRRS, slot);
	}
}

static const SaveLoad _trace_restrict_counter_desc[] = {
	SLE_VAR(TraceRestrictCounter, value, SLE_INT32),
	SLE_SSTR(TraceRestrictCounter, name, SLF_ALLOW_CONTROL),
	SLE_VAR(TraceRestrictCounter, owner, SLE_UINT8),
};

/**
 * Load counter pool
 */
static void Load_TRRC()
{
	int index;
	while ((index = SlIterateArray()) != -1) {
		TraceRestrictCounter *ctr = new (index) TraceRestrictCounter();
		SlObject(ctr, _trace_restrict_counter_desc);
	}
}

/**
 * Save counter pool
 */
static void Save_TRRC()
{
	for (TraceRestrictCounter *ctr : TraceRestrictCounter::Iterate()) {
		SlSetArrayIndex(ctr->index);
		SlObject(ctr, _trace_restrict_counter_desc);
	}
}

/**
 * Update program reference counts from just-loaded mapping
 */
void AfterLoadTraceRestrict()
{
	for (TraceRestrictMapping::iterator iter = _tracerestrictprogram_mapping.begin();
			iter != _tracerestrictprogram_mapping.end(); ++iter) {
		_tracerestrictprogram_pool.Get(iter->second.program_id)->IncrementRefCount(iter->first);
	}
}

extern const ChunkHandler trace_restrict_chunk_handlers[] = {
	{ 'TRRM', Save_TRRM, Load_TRRM, nullptr, nullptr, CH_SPARSE_ARRAY },    // Trace Restrict Mapping chunk
	{ 'TRRP', Save_TRRP, Load_TRRP, nullptr, nullptr, CH_ARRAY },           // Trace Restrict Mapping Program Pool chunk
	{ 'TRRS', Save_TRRS, Load_TRRS, nullptr, nullptr, CH_ARRAY },           // Trace Restrict Slot Pool chunk
	{ 'TRRC', Save_TRRC, Load_TRRC, nullptr, nullptr, CH_ARRAY },           // Trace Restrict Counter Pool chunk
};

extern const ChunkHandlerTable _trace_restrict_chunk_handlers(trace_restrict_chunk_handlers);
