/* $Id$ */

/** @file cargopacket.cpp Implementation of the cargo packets */

#include "stdafx.h"
#include "openttd.h"
#include "station.h"
#include "cargopacket.h"
#include "saveload.h"

/** Cache for speeding up lookups in AllocateRaw */
static uint _first_free_cargo_packet_index;

/**
 * Called if a new block is added to the station-pool
 */
static void CargoPacketPoolNewBlock(uint cpart_item)
{
	/* We don't use FOR_ALL here, because FOR_ALL skips invalid items.
	 *  TODO - This is just a temporary stage, this will be removed. */
	for (CargoPacket *cp = GetCargoPacket(cpart_item); cp != NULL; cp = (cp->index + 1U < GetCargoPacketPoolSize()) ? GetCargoPacket(cp->index + 1U) : NULL) cp->index = cpart_item++;
}

static void CargoPacketPoolCleanBlock(uint cpart_item, uint end_item)
{
	for (uint i = cpart_item; i <= end_item; i++) {
		CargoPacket *cp = GetCargoPacket(i);
		if (cp->IsValid()) cp->~CargoPacket();
	}
}

/* Initialize the cargopacket-pool */
DEFINE_OLD_POOL(CargoPacket, CargoPacket, CargoPacketPoolNewBlock, CargoPacketPoolCleanBlock)

void InitializeCargoPackets()
{
	_first_free_cargo_packet_index = 0;
	/* Clean the cargo packet pool and create 1 block in it */
	CleanPool(&_CargoPacket_pool);
	AddBlockToPool(&_CargoPacket_pool);

	/* Check whether our &cargolist == &cargolist.packets "hack" works */
	CargoList::AssertOnWrongPacketOffset();
}

CargoPacket::CargoPacket(StationID source, uint16 count)
{
	if (source != INVALID_STATION) assert(count != 0);

	this->source          = source;
	this->source_xy       = (source != INVALID_STATION) ? GetStation(source)->xy : 0;
	this->loaded_at_xy    = this->source_xy;

	this->count           = count;
	this->days_in_transit = 0;
	this->feeder_share    = 0;
	this->paid_for        = false;
}

CargoPacket::~CargoPacket()
{
	if (this->index < _first_free_cargo_packet_index) _first_free_cargo_packet_index = this->index;
	this->count = 0;
}

bool CargoPacket::SameSource(CargoPacket *cp)
{
	return this->source_xy == cp->source_xy && this->days_in_transit == cp->days_in_transit && this->paid_for == cp->paid_for;
}

void *CargoPacket::operator new(size_t size)
{
	CargoPacket *cp = AllocateRaw();
	return cp;
}

void *CargoPacket::operator new(size_t size, CargoPacket::ID cp_idx)
{
	if (!AddBlockIfNeeded(&_CargoPacket_pool, cp_idx))
		error("CargoPackets: failed loading savegame: too many cargo packets");

	CargoPacket *cp = GetCargoPacket(cp_idx);
	return cp;
}

void CargoPacket::operator delete(void *p)
{
}

void CargoPacket::operator delete(void *p, CargoPacket::ID cp_idx)
{
}

/*static*/ CargoPacket *CargoPacket::AllocateRaw()
{
	CargoPacket *cp = NULL;

	/* We don't use FOR_ALL here, because FOR_ALL skips invalid items.
	 * TODO - This is just a temporary stage, this will be removed. */
	for (cp = GetCargoPacket(_first_free_cargo_packet_index); cp != NULL; cp = (cp->index + 1U < GetCargoPacketPoolSize()) ? GetCargoPacket(cp->index + 1U) : NULL) {
		if (!cp->IsValid()) {
			CargoPacket::ID index = cp->index;

			memset(cp, 0, sizeof(CargoPacket));
			cp->index = index;
			_first_free_cargo_packet_index = cp->index;
			return cp;
		}
	}

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

	error("CargoPackets: too many cargo packets");
}

static const SaveLoad _cargopacket_desc[] = {
	SLE_VAR(CargoPacket, source,          SLE_UINT16),
	SLE_VAR(CargoPacket, source_xy,       SLE_UINT32),
	SLE_VAR(CargoPacket, loaded_at_xy,    SLE_UINT32),
	SLE_VAR(CargoPacket, count,           SLE_UINT16),
	SLE_VAR(CargoPacket, days_in_transit, SLE_UINT8),
	SLE_VAR(CargoPacket, feeder_share,    SLE_INT64),
	SLE_VAR(CargoPacket, paid_for,        SLE_BOOL),

	SLE_END()
};

static void Save_CAPA()
{
	CargoPacket *cp;

	FOR_ALL_CARGOPACKETS(cp) {
		SlSetArrayIndex(cp->index);
		SlObject(cp, _cargopacket_desc);
	}
}

static void Load_CAPA()
{
	int index;

	while ((index = SlIterateArray()) != -1) {
		if (!AddBlockIfNeeded(&_CargoPacket_pool, index)) {
			error("CargoPackets: failed loading savegame: too many cargo packets");
		}

		CargoPacket *cp = GetCargoPacket(index);
		SlObject(cp, _cargopacket_desc);
	}
}

extern const ChunkHandler _cargopacket_chunk_handlers[] = {
	{ 'CAPA', Save_CAPA, Load_CAPA, CH_ARRAY | CH_LAST},
};

/*
 *
 * Cargo list implementation
 *
 */

/* static */ void CargoList::AssertOnWrongPacketOffset()
{
	CargoList cl;
	if ((void*)&cl != (void*)cl.Packets()) NOT_REACHED();
}


CargoList::~CargoList()
{
	while (!packets.empty()) {
		delete packets.front();
		packets.pop_front();
	}
}

const CargoList::List *CargoList::Packets() const
{
	return &packets;
}

void CargoList::AgeCargo()
{
	if (empty) return;

	uint dit = 0;
	for (List::const_iterator it = packets.begin(); it != packets.end(); it++) {
		if ((*it)->days_in_transit != 0xFF) (*it)->days_in_transit++;
		dit += (*it)->days_in_transit * (*it)->count;
	}
	days_in_transit = dit / count;
}

bool CargoList::Empty() const
{
	return empty;
}

uint CargoList::Count() const
{
	return count;
}

bool CargoList::UnpaidCargo() const
{
	return unpaid_cargo;
}

Money CargoList::FeederShare() const
{
	return feeder_share;
}

StationID CargoList::Source() const
{
	return source;
}

uint CargoList::DaysInTransit() const
{
	return days_in_transit;
}

void CargoList::Append(CargoPacket *cp)
{
	assert(cp != NULL);
	assert(cp->IsValid());

	for (List::iterator it = packets.begin(); it != packets.end(); it++) {
		if ((*it)->SameSource(cp) && (*it)->count + cp->count <= 65535) {
			(*it)->count        += cp->count;
			(*it)->feeder_share += cp->feeder_share;
			delete cp;

			InvalidateCache();
			return;
		}
	}

	/* The packet could not be merged with another one */
	packets.push_back(cp);
	InvalidateCache();
}


void CargoList::Truncate(uint count)
{
	for (List::iterator it = packets.begin(); it != packets.end(); it++) {
		uint local_count = (*it)->count;
		if (local_count <= count) {
			count -= local_count;
			continue;
		}

		(*it)->count = count;
		count = 0;
	}

	while (!packets.empty()) {
		CargoPacket *cp = packets.back();
		if (cp->count != 0) break;
		delete cp;
		packets.pop_back();
	}

	InvalidateCache();
}

bool CargoList::MoveTo(CargoList *dest, uint count, CargoList::MoveToAction mta, uint data)
{
	assert(mta == MTA_FINAL_DELIVERY || dest != NULL);
	CargoList tmp;

	while (!packets.empty() && count > 0) {
		CargoPacket *cp = *packets.begin();
		if (cp->count <= count) {
			/* Can move the complete packet */
			packets.remove(cp);
			switch (mta) {
				case MTA_FINAL_DELIVERY:
					if (cp->source == data) {
						tmp.Append(cp);
					} else {
						count -= cp->count;
						delete cp;
					}
					break;
				case MTA_CARGO_LOAD:
					cp->loaded_at_xy = data;
					/* When cargo is moved into another vehicle you have *always* paid for it */
					cp->paid_for     = false;
					/* FALL THROUGH */
				case MTA_OTHER:
					count -= cp->count;
					dest->packets.push_back(cp);
					break;
			}
		} else {
			/* Can move only part of the packet, so split it into two pieces */
			if (mta != MTA_FINAL_DELIVERY) {
				CargoPacket *cp_new = new CargoPacket();
				cp_new->source          = cp->source;
				cp_new->source_xy       = cp->source_xy;
				cp_new->loaded_at_xy    = (mta == MTA_CARGO_LOAD) ? data : cp->loaded_at_xy;

				cp_new->days_in_transit = cp->days_in_transit;
				cp_new->feeder_share    = cp->feeder_share / count;
				/* When cargo is moved into another vehicle you have *always* paid for it */
				cp_new->paid_for        = (mta == MTA_CARGO_LOAD) ? false : cp->paid_for;

				cp_new->count = count;
				dest->packets.push_back(cp_new);

				cp->feeder_share /= cp->count - count;
			}
			cp->count -= count;

			count = 0;
		}
	}

	bool remaining = !packets.empty();

	if (mta == MTA_FINAL_DELIVERY && !tmp.Empty()) {
		/* There are some packets that could not be delivered at the station, put them back */
		tmp.MoveTo(this, MAX_UVALUE(uint));
		tmp.packets.clear();
	}

	if (dest != NULL) dest->InvalidateCache();
	InvalidateCache();

	return remaining;
}

void CargoList::InvalidateCache()
{
	empty = packets.empty();
	count = 0;
	unpaid_cargo = false;
	feeder_share = 0;
	source = INVALID_STATION;
	days_in_transit = 0;

	if (empty) return;

	uint dit = 0;
	for (List::const_iterator it = packets.begin(); it != packets.end(); it++) {
		count        += (*it)->count;
		unpaid_cargo |= !(*it)->paid_for;
		dit          += (*it)->days_in_transit * (*it)->count;
		feeder_share += (*it)->feeder_share;
	}
	days_in_transit = dit / count;
	source = (*packets.begin())->source;
}