var MIN_DAYS_IN_FUTURE = 3;	// How many days in the future is the earliest date that's allowed?

////////////////////////////////////////////////////////////////////////
// Class to store all of the available flights, and expose methods to query them as necessary
////////////////////////////////////////////////////////////////////////
function FlightSet() {

	this._allFlights = new Array();

	// Method: addFlight()
	// Register a new set of flights - intended to be used direct from data in spreadsheet
	// datePairsList: comma-separated list of dash-separated month-space-year date range pairs
	// durationsList: comma-separated list of available durations (number of nights)
	this.addFlight = function (arrAirName, arrAirCode, depAirName, depAirCode, datePairsList, durationsList, resortCode ) {

		var startDate = new Date(),
			endDate = new Date();
			
		// Add a new flight for each set of dates
		datePairsArray = String(datePairsList).split(/\s*,\s*/);
		var MONTH_INDEX = 0;
		var YEAR_INDEX = 1;
		
		for (var i = 0; i < datePairsArray.length; i++) {
		
			if ( datePairsArray[i] != "" ) {
				var datePair = datePairsArray[i].split(/\s?-\s?/);

				startDate.setDate(1);	// Prevents overflow causing month to roll over
				startDate.setYear( parseInt( datePair[0].split(" ")[YEAR_INDEX], 10 ) );
				startDate.setMonth( parseInt( datePair[0].split(" ")[MONTH_INDEX], 10 ) - 1 );	// Subtract 1 - months are numbered from 0

				endDate.setDate(1);	// Prevents overflow causing month to roll over
				endDate.setYear( parseInt( datePair[1].split(' ')[YEAR_INDEX], 10 ) );
				endDate.setMonth( parseInt( datePair[1].split(' ')[MONTH_INDEX], 10 ) );	// Don't subtract 1, we actually want to set next month...
				endDate.setDate(0);	// ...then roll back a day, to the end of the month we actually want

				this._allFlights.push(new Flight(new Airport(arrAirCode, arrAirName, resortCode ), new Airport(depAirCode, depAirName), startDate, endDate, String(durationsList).split(/,/)));
			}
		}
	}

	// Method: getDestinations()
	// Return all available destination airports
	this.getDestinations = function() {

		var destinations =  new Array(),
			found;

		// Add all of the available destinations to our output
		for (var i = 0; i < this._allFlights.length; i++) {
			found = false;
			// Have we already added this destination to our output?
			for (var j = 0; j < destinations.length; j++) {
				if (this._allFlights[i].arrAir.code == destinations[j].code && this._allFlights[i].arrAir.name == destinations[j].name) {
					found = true;
					break;
				}
			}
			// If not yet added, do so
			if (!found) {
				destinations.push(this._allFlights[i].arrAir);
			}
		}
		return destinations;
	}

	// Method: getAvailableDepartures()
	// Return an array of available departure airports for the given destination, date and duration
	this.getAvailableDepartures = function (arrAirCode) {

		var departures = new Array(),
			found;

		// Loop over all flights, and add this departure point if the flight matches the parameters
		for (var i = 0; i < this._allFlights.length; i++) {
			if (this._allFlights[i].arrAir.code == arrAirCode) {
				found = false;
				// Have we already added this departure to our output?
				for (var j = 0; j < departures.length; j++) {
					if (this._allFlights[i].depAir.code == departures[j].code && this._allFlights[i].depAir.name == departures[j].name) {
						found = true;
						break;
					}
				}
				// If not yet added, do so
				if (!found) {
					departures.push(this._allFlights[i].depAir);
				}
			}
		}
		return departures;

	}

	// Method: getAvailableDates()
	// Return an array of date ranges where flights between these airports are available
	// This is an array of 2-element arrays of dates
	this.getAvailableDates = function (arrAirCode, depAirCode) {
		var availableMonths = new Array(),
			dateRanges = new Array;

		// Initially, flag all our month numbers as unavailable (from now to 36 months ahead)
		for (var i = monthNumber(); i < monthNumber() + 36; i++) {
			availableMonths[i] = false;
		}

		// Loop over all the flights, finding the flights that match this route
		for (var i = 0; i < this._allFlights.length; i++) {
			if (this._allFlights[i].arrAir.code == arrAirCode && this._allFlights[i].depAir.code == depAirCode) {
				// Flag all the month numbers for this flight as available
				for (var j = monthNumber(this._allFlights[i].startDate); j <= monthNumber(this._allFlights[i].endDate); j++) {
					availableMonths[j] = true;
				}
			}
		}

		var i = monthNumber(),
			start = new Date(),
			end = new Date();

		// Now, loop over our array of flagged dates, converting to ranges
		while (i < monthNumber() + 36) {
			// Skip initial unavailable months
			while (i < monthNumber() + 36 && !availableMonths[i]) i++;
			// Check we haven't run past the end of our allowed dates
			if (i < monthNumber() + 36) {
				// Current i is the first date in this range
				start = new Date(monthNumberToDate(i))
				// Move to end of this available range
				i++
				while (i < monthNumber() + 36 && availableMonths[i]) i++;
				// Current i-1 is the last date in this range
				end = new Date(monthNumberToDate(i-1));
				end.setMonth(end.getMonth() + 1); end.setDate(0); // Go to end of month
				// Append the range we just found to the return value
				dateRanges.push(new Array(start, end));
			}
		}

		// Return our result
		return dateRanges;
	}

	// Method: getAvailableDurations()
	// Return an array of allowed durations (no. of nights) for selected airport and date
	this.getAvailableDurations = function (arrAirCode, depAirCode, date) {

		var durationAllowed = new Array(29),	// We store flags in here as to whether that duration is allowed or not; adjust the size of this array if longer durations allowed (note that this has a zero duration in it too, so array dimension should be maxdays+1)
			durations = new Array();

		// Initially, flag all durations as not allowed
		for (var i = 0; i < durationAllowed.length; i++) {
			durationAllowed[i] = false;
		}

		// Find all flights matching selected arrival and date
		for (var i = 0; i < this._allFlights.length; i++) {
			if (this._allFlights[i].arrAir.code == arrAirCode && this._allFlights[i].depAir.code == depAirCode && this._allFlights[i].isAvailableOn(date)) {
				for (var j = 0; j < this._allFlights[i].durations.length; j++) {
					durationAllowed[parseInt(this._allFlights[i].durations[j])] = true;
				}
			}
		}

		// Build an array containing all of the durations we just flagged as allowed
		for (var i = 0; i < durationAllowed.length; i++) {
			if (durationAllowed[i]) {
				durations.push(i);
			}
		}
		return durations;
	}
}

////////////////////////////////////////////////////////////////////////
// Class that represents a set of flights (available over a continuous date range for a given set of durations)
////////////////////////////////////////////////////////////////////////
function Flight(arrAirport, depAirport, startDate, endDate, durationsArray) {

	// Init the properties
	this.arrAir = arrAirport;
	this.depAir = depAirport;
	this.startDate = (new Date(startDate) < new Date()?new Date():new Date(startDate));	// If start date is in the past, set to today
	this.endDate = new Date(endDate);
	this.durations = durationsArray;

	// Init the time parts of the date range
	this.startDate.setHours(0);this.startDate.setMinutes(0);this.startDate.setSeconds(0);this.startDate.setMilliseconds(0);
	this.endDate.setHours(23);this.endDate.setMinutes(59);this.endDate.setSeconds(59);this.endDate.setMilliseconds(999);

	// Method: isAvailableOn()
	// Find out if this flight is available on a given date
	this.isAvailableOn = function (dateToCheck) {
		return (dateToCheck >= this.startDate && dateToCheck <= this.endDate);
	}

	// Method: isAvailableFor()
	// Find out if this flight is available for a given duration
	this.isAvailableFor = function (duration) {

		for (var i = 0; i < this.durations.length; i++) {
			// Durations are ordered lowest to highest, so we can exit as soon as the value we find is >= than the one we're looking for
			if (parseInt(this.durations[i]) >= parseInt(duration)) {
				return (parseInt(this.durations[i]) == parseInt(duration));
			}
		}
		return false;
	}

}

////////////////////////////////////////////////////////////////////////
// Class to represent an airport
// Args:	airportCode: 3 letter code (required)
//			airportName: Name of airport (optional)
// Props:	code: 3-letter airport code
//			name: name of airport
// Meths:	nameLookup(airportCode): Look up airport name from 3-letter code
////////////////////////////////////////////////////////////////////////
function Airport(airportCode, airportName, resortCode ) {
	this.code = airportCode;
	this.name = airportName;
	this.resortCode = resortCode ? resortCode : null;
}


////////////////////////////////////////////////////////////////////////
// Extend Date class with a function to add days to any date
////////////////////////////////////////////////////////////////////////
Date.prototype.addDays = function(num) {
	var d = new Date(this);
	d.setDate(d.getDate() + num);
	return d;
}

////////////////////////////////////////////////////////////////////////
// Extend Array class with a push function for ie5 ;fix OPW 15.11.03
////////////////////////////////////////////////////////////////////////
Array.prototype.push = function(val) {
	this[this.length] = val;
}

function AssertJSLoadedOk() {
	return true;
}