/* $Id$ */

#include "stdafx.h"
#include "openttd.h"
#include "debug.h"
#include "map.h"
#include "airport.h"
#include "macros.h"
#include "variables.h"
#include "airport_movement.h"
#include "date.h"

/* Uncomment this to print out a full report of the airport-structure
 * You should either use
 * - true: full-report, print out every state and choice with string-names
 * OR
 * - false: give a summarized report which only shows current and next position */
//#define DEBUG_AIRPORT false

static AirportFTAClass *CountryAirport;
static AirportFTAClass *CityAirport;
static AirportFTAClass *Oilrig;
static AirportFTAClass *Heliport;
static AirportFTAClass *MetropolitanAirport;
static AirportFTAClass *InternationalAirport;
static AirportFTAClass *CommuterAirport;
static AirportFTAClass *HeliDepot;
static AirportFTAClass *IntercontinentalAirport;
static AirportFTAClass *HeliStation;

static void AirportFTAClass_Constructor(AirportFTAClass *apc,
	const byte *terminals, const byte *helipads,
	const byte entry_point,  const byte acc_planes,
	const AirportFTAbuildup *apFA,
	const TileIndexDiffC *depots, const byte nof_depots,
	uint size_x, uint size_y
);
static void AirportFTAClass_Destructor(AirportFTAClass *apc);

static uint16 AirportGetNofElements(const AirportFTAbuildup *apFA);
static void AirportBuildAutomata(AirportFTAClass *apc, const AirportFTAbuildup *apFA);
static byte AirportGetTerminalCount(const byte *terminals, byte *groups);
static byte AirportTestFTA(const AirportFTAClass *apc);

#ifdef DEBUG_AIRPORT
static void AirportPrintOut(const AirportFTAClass *apc, bool full_report);
#endif /* DEBUG_AIRPORT */

void InitializeAirports(void)
{
	// country airport
	CountryAirport = malloc(sizeof(AirportFTAClass));

	AirportFTAClass_Constructor(
		CountryAirport,
		_airport_terminal_country,
		NULL,
		16,
		ALL,
		_airport_fta_country,
		_airport_depots_country,
		lengthof(_airport_depots_country),
		4, 3
	);

	// city airport
	CityAirport = malloc(sizeof(AirportFTAClass));

	AirportFTAClass_Constructor(
		CityAirport,
		_airport_terminal_city,
		NULL,
		19,
		ALL,
		_airport_fta_city,
		_airport_depots_city,
		lengthof(_airport_depots_city),
		6, 6
	);

	// metropolitan airport
	MetropolitanAirport = malloc(sizeof(AirportFTAClass));

	AirportFTAClass_Constructor(
		MetropolitanAirport,
		_airport_terminal_metropolitan,
		NULL,
		20,
		ALL,
		_airport_fta_metropolitan,
		_airport_depots_metropolitan,
		lengthof(_airport_depots_metropolitan),
		6, 6
	);

	// international airport
	InternationalAirport = (AirportFTAClass *)malloc(sizeof(AirportFTAClass));

	AirportFTAClass_Constructor(
		InternationalAirport,
		_airport_terminal_international,
		_airport_helipad_international,
		37,
		ALL,
		_airport_fta_international,
		_airport_depots_international,
		lengthof(_airport_depots_international),
		7, 7
	);

	// intercontintental airport
	IntercontinentalAirport = (AirportFTAClass *)malloc(sizeof(AirportFTAClass));

	AirportFTAClass_Constructor(
		IntercontinentalAirport,
		_airport_terminal_intercontinental,
		_airport_helipad_intercontinental,
		43,
		ALL,
		_airport_fta_intercontinental,
		_airport_depots_intercontinental,
		lengthof(_airport_depots_intercontinental),
		9,11
	);

	// heliport, oilrig
	Heliport = (AirportFTAClass *)malloc(sizeof(AirportFTAClass));

	AirportFTAClass_Constructor(
		Heliport,
		NULL,
		_airport_helipad_heliport_oilrig,
		7,
		HELICOPTERS_ONLY,
		_airport_fta_heliport_oilrig,
		NULL,
		0,
		1, 1
	);

	Oilrig = Heliport;  // exactly the same structure for heliport/oilrig, so share state machine

	// commuter airport
	CommuterAirport = malloc(sizeof(AirportFTAClass));

	AirportFTAClass_Constructor(
		CommuterAirport,
		_airport_terminal_commuter,
		_airport_helipad_commuter,
		22,
		ALL,
		_airport_fta_commuter,
		_airport_depots_commuter,
		lengthof(_airport_depots_commuter),
		5,4
	);

	// helidepot airport
	HeliDepot = malloc(sizeof(AirportFTAClass));

	AirportFTAClass_Constructor(
		HeliDepot,
		NULL,
		_airport_helipad_helidepot,
		4,
		HELICOPTERS_ONLY,
		_airport_fta_helidepot,
		_airport_depots_helidepot,
		lengthof(_airport_depots_helidepot),
		2,2
	);

	// helistation airport
	HeliStation = malloc(sizeof(AirportFTAClass));

	AirportFTAClass_Constructor(
		HeliStation,
		NULL,
		_airport_helipad_helistation,
		25,
		HELICOPTERS_ONLY,
		_airport_fta_helistation,
		_airport_depots_helistation,
		lengthof(_airport_depots_helistation),
		4,2
	);

}

void UnInitializeAirports(void)
{
	AirportFTAClass_Destructor(CountryAirport);
	AirportFTAClass_Destructor(CityAirport);
	AirportFTAClass_Destructor(Heliport);
	AirportFTAClass_Destructor(MetropolitanAirport);
	AirportFTAClass_Destructor(InternationalAirport);
	AirportFTAClass_Destructor(CommuterAirport);
	AirportFTAClass_Destructor(HeliDepot);
	AirportFTAClass_Destructor(IntercontinentalAirport);
	AirportFTAClass_Destructor(HeliStation);
}

static void AirportFTAClass_Constructor(AirportFTAClass *apc,
	const byte *terminals, const byte *helipads,
	const byte entry_point, const byte acc_planes,
	const AirportFTAbuildup *apFA,
	const TileIndexDiffC *depots, const byte nof_depots,
	uint size_x, uint size_y
)
{
	byte nofterminals, nofhelipads;
	byte nofterminalgroups, nofhelipadgroups;

	apc->size_x = size_x;
	apc->size_y = size_y;

	/* Set up the terminal and helipad count for an airport.
	 * TODO: If there are more than 10 terminals or 4 helipads, internal variables
	 * need to be changed, so don't allow that for now */
	nofterminals = AirportGetTerminalCount(terminals, &nofterminalgroups);
	if (nofterminals > MAX_TERMINALS) {
		DEBUG(misc, 0) ("[Ap] Currently only maximum of %d terminals are supported (you wanted %d)", MAX_TERMINALS, nofterminals);
		assert(nofterminals <= MAX_TERMINALS);
	}
	apc->terminals = terminals;

	nofhelipads = AirportGetTerminalCount(helipads, &nofhelipadgroups);
	if (nofhelipads > MAX_HELIPADS) {
		DEBUG(misc, 0) ("[Ap] Currently only maximum of %d helipads are supported (you wanted %d)", MAX_HELIPADS, nofhelipads);
		assert(nofhelipads <= MAX_HELIPADS);
	}
	apc->helipads = helipads;

	/* Get the number of elements from the source table. We also double check this
	 * with the entry point which must be within bounds and use this information
	 * later on to build and validate the state machine */
	apc->nofelements = AirportGetNofElements(apFA);
	if (entry_point >= apc->nofelements) {
		DEBUG(misc, 0) ("[Ap] Entry (%d) must be within the airport (maximum %d)", entry_point, apc->nofelements);
		assert(entry_point < apc->nofelements);
	}

	apc->acc_planes     = acc_planes;
	apc->entry_point    = entry_point;
	apc->airport_depots = depots;
	apc->nof_depots     = nof_depots;

	/* Build the state machine itself */
	AirportBuildAutomata(apc, apFA);
	DEBUG(misc, 1) ("[Ap] #count %3d; #term %2d (%dgrp); #helipad %2d (%dgrp); entry %3d",
		apc->nofelements, nofterminals, nofterminalgroups, nofhelipads, nofhelipadgroups, apc->entry_point);

	/* Test if everything went allright. This is only a rude static test checking
	 * the symantic correctness. By no means does passing the test mean that the
	 * airport is working correctly or will not deadlock for example */
	{ byte ret = AirportTestFTA(apc);
		if (ret != MAX_ELEMENTS) DEBUG(misc, 0) ("[Ap] ERROR with element: %d", ret - 1);
		assert(ret == MAX_ELEMENTS);
	}

#ifdef DEBUG_AIRPORT
	AirportPrintOut(apc, DEBUG_AIRPORT);
#endif
}

static void AirportFTAClass_Destructor(AirportFTAClass *apc)
{
	int i;
	AirportFTA *current, *next;

	for (i = 0; i < apc->nofelements; i++) {
		current = apc->layout[i].next;
		while (current != NULL) {
			next = current->next;
			free(current);
			current = next;
		};
	}
	free(apc->layout);
	free(apc);
}

/** Get the number of elements of a source Airport state automata
 * Since it is actually just a big array of AirportFTA types, we only
 * know one element from the other by differing 'position' identifiers */
static uint16 AirportGetNofElements(const AirportFTAbuildup *apFA)
{
	int i;
	uint16 nofelements = 0;
	int temp = apFA[0].position;

	for (i = 0; i < MAX_ELEMENTS; i++) {
		if (temp != apFA[i].position) {
			nofelements++;
			temp = apFA[i].position;
		}
		if (apFA[i].position == MAX_ELEMENTS) break;
	}
	return nofelements;
}

/* We calculate the terminal/helipod count based on the data passed to us
 * This data (terminals) contains an index as a first element as to how many
 * groups there are, and then the number of terminals for each group */
static byte AirportGetTerminalCount(const byte *terminals, byte *groups)
{
	byte i;
	byte nof_terminals = 0;
	*groups = 0;

	if (terminals != NULL) {
		i = terminals[0];
		*groups = i;
		while (i-- > 0) {
			terminals++;
			assert(*terminals != 0); // no empty groups please
			nof_terminals += *terminals;
		}
	}
	return nof_terminals;
}

static void AirportBuildAutomata(AirportFTAClass *apc, const AirportFTAbuildup *apFA)
{
	AirportFTA *current;
	AirportFTA *FAutomata = malloc(sizeof(AirportFTA) * apc->nofelements);
	uint16 internalcounter = 0;
	uint16 i;

	apc->layout = FAutomata;
	for (i = 0; i < apc->nofelements; i++) {
		current = &apc->layout[i];
		current->position      = apFA[internalcounter].position;
		current->heading       = apFA[internalcounter].heading;
		current->block         = apFA[internalcounter].block;
		current->next_position = apFA[internalcounter].next;

		// outgoing nodes from the same position, create linked list
		while (current->position == apFA[internalcounter + 1].position) {
			AirportFTA *newNode = malloc(sizeof(AirportFTA));

			newNode->position      = apFA[internalcounter + 1].position;
			newNode->heading       = apFA[internalcounter + 1].heading;
			newNode->block         = apFA[internalcounter + 1].block;
			newNode->next_position = apFA[internalcounter + 1].next;
			// create link
			current->next = newNode;
			current = current->next;
			internalcounter++;
		} // while
		current->next = NULL;
		internalcounter++;
	}
}

static byte AirportTestFTA(const AirportFTAClass *apc)
{
	byte position, i, next_position;
	AirportFTA *current, *first;
	next_position = 0;

	for (i = 0; i < apc->nofelements; i++) {
		position = apc->layout[i].position;
		if (position != next_position) return i;
		current = first = &apc->layout[i];

		for (; current != NULL; current = current->next) {
			/* A heading must always be valid. The only exceptions are
			 * - multiple choices as start, identified by a special value of 255
			 * - terminal group which is identified by a special value of 255 */
			if (current->heading > MAX_HEADINGS) {
				if (current->heading != 255) return i;
				if (current == first && current->next == NULL) return i;
				if (current != first && current->next_position > apc->terminals[0]) return i;
			}

			/* If there is only one choice, it must be at the end */
			if (current->heading == 0 && current->next != NULL) return i;
			/* Obviously the elements of the linked list must have the same identifier */
			if (position != current->position) return i;
			/* A next position must be within bounds */
			if (current->next_position >= apc->nofelements) return i;
		}
		next_position++;
	}
	return MAX_ELEMENTS;
}

#ifdef DEBUG_AIRPORT
static const char* const _airport_heading_strings[] = {
	"TO_ALL",
	"HANGAR",
	"TERM1",
	"TERM2",
	"TERM3",
	"TERM4",
	"TERM5",
	"TERM6",
	"HELIPAD1",
	"HELIPAD2",
	"TAKEOFF",
	"STARTTAKEOFF",
	"ENDTAKEOFF",
	"HELITAKEOFF",
	"FLYING",
	"LANDING",
	"ENDLANDING",
	"HELILANDING",
	"HELIENDLANDING",
	"TERM7",
	"TERM8",
	"HELIPAD3",
	"HELIPAD4",
	"DUMMY" // extra heading for 255
};

static uint AirportBlockToString(uint32 block)
{
	uint i = 0;
	if (block & 0xffff0000) { block >>= 16; i += 16; }
	if (block & 0x0000ff00) { block >>=  8; i +=  8; }
	if (block & 0x000000f0) { block >>=  4; i +=  4; }
	if (block & 0x0000000c) { block >>=  2; i +=  2; }
	if (block & 0x00000002) { i += 1; }
	return i;
}

static void AirportPrintOut(const AirportFTAClass *apc, bool full_report)
{
	uint16 i;

	if (!full_report) printf("(P = Current Position; NP = Next Position)\n");

	for (i = 0; i < apc->nofelements; i++) {
		AirportFTA *current = &apc->layout[i];

		for (; current != NULL; current = current->next) {
			if (full_report) {
				byte heading = (current->heading == 255) ? MAX_HEADINGS + 1 : current->heading;
				printf("\tPos:%2d NPos:%2d Heading:%15s Block:%2d\n", current->position,
					    current->next_position, _airport_heading_strings[heading],
							AirportBlockToString(current->block));
			} else {
				printf("P:%2d NP:%2d", current->position, current->next_position);
			}
		}
		printf("\n");
	}
}
#endif

const AirportFTAClass *GetAirport(const byte airport_type)
{
	//FIXME -- AircraftNextAirportPos_and_Order -> Needs something nicer, don't like this code
	// needs constant change if more airports are added
	switch (airport_type) {
		default:               NOT_REACHED();
		case AT_SMALL:         return CountryAirport;
		case AT_LARGE:         return CityAirport;
		case AT_METROPOLITAN:  return MetropolitanAirport;
		case AT_HELIPORT:      return Heliport;
		case AT_OILRIG:        return Oilrig;
		case AT_INTERNATIONAL: return InternationalAirport;
		case AT_COMMUTER:      return CommuterAirport;
		case AT_HELIDEPOT:     return HeliDepot;
		case AT_INTERCON:      return IntercontinentalAirport;
		case AT_HELISTATION:   return HeliStation;
	}
}

const AirportMovingData *GetAirportMovingData(byte airport_type, byte position)
{
	assert(airport_type < lengthof(_airport_moving_datas));
	assert(position < GetAirport(airport_type)->nofelements);
	return &_airport_moving_datas[airport_type][position];
}

uint32 GetValidAirports(void)
{
	uint32 bytemask = _avail_aircraft; /// sets the first 3 bytes, 0 - 2, @see AdjustAvailAircraft()

	if (_cur_year >= 1980) SETBIT(bytemask, 3); // metropolitan airport
	if (_cur_year >= 1990) SETBIT(bytemask, 4); // international airport
	if (_cur_year >= 1983) SETBIT(bytemask, 5); // commuter airport
	if (_cur_year >= 1976) SETBIT(bytemask, 6); // helidepot
	if (_cur_year >= 2002) SETBIT(bytemask, 7); // intercontinental airport
	if (_cur_year >= 1980) SETBIT(bytemask, 8); // helistation
	return bytemask;
}