const RAIL_STATION_RADIUS = 4;
const PLATFORMS = 2

/* A station site site contains the following information:
 * site[0] -> location the platforms are built (North end)
 * site[1] -> direction of the platforms (NE_SW or NW_SE)
 * site[2] -> the cargo value of this station site
 * site[3] -> Exit 1 (Vector Coordinates)
 * site[4] -> Exit 2 (Vector Coordinates)
 * site[5] -> Location of the depot at this station
 * site[6] -> Number of platforms
 * site[7] -> Platform length
 * site[8] -> Name of the site/station
 */

/* Build a station at a found site
 * Returns the updated site (probably containing depots)
 */
function BuildStation(site) {
}

/* Cleanup everything we've built to have a station on this site
	Can be called if something went terribly wrong
	*/
function RemoveStation(site) {
}

function GetDataOfStationAt(tile) {
	return GetDataOfStation(AIStation.GetStationID(tile))
}

function GetDataOfBusStopAt(tile) {
	return GetDataOfBusStop(AIStation.GetStationID(tile))
}

function IsTerminusTile(tile) {
	return IsTerminus(AIStation.GetStationID(tile))
}

function IsTerminus(station) {
	local location = AIStation.GetLocation(station)
	local orientation = AIRail.GetRailStationDirection(location)
	local numPlatforms = GetNumPlatforms(location)
	local platformLength = GetPlatformLength(location)
	local exit1 = null
	local exit2 = null
	if (orientation == AIRail.RAILTRACK_NW_SE) {
		exit1 = VC.VectorCoordinates(location + dXY(0, -1), Direction.NW)
		exit2 = VC.VectorCoordinates(location + dXY(numPlatforms - 1, platformLength), Direction.SE)
	} else {
		exit1 = VC.VectorCoordinates(location + dXY(-1, numPlatforms - 1), Direction.NE)
		exit2 = VC.VectorCoordinates(location + dXY(platformLength, 0), Direction.SW)
	}
	if (!AIRail.IsRailTile(VC.GetTile(exit1, [0,0]))) {
		return true
	}
	if (!AIRail.IsRailTile(VC.GetTile(exit2, [0,0]))) {
		return true
	}
	return false
}

function GetDataOfBusStop(station) {
	local location = AIStation.GetLocation(station)
	local frontTile = null
	local backTile = null
	foreach (delta in [dXY(0,1), dXY(0,-1), dXY(1,0), dXY(-1,0)]) {
		if (AIRoad.AreRoadTilesConnected(location, location + delta)) {
			if (frontTile == null)
				frontTile = location + delta
			else
				backTile = location + delta
		}
	}
	local cargo = null //do not care about this either, any cargo can be fine
	return {location = location,
			frontTile = frontTile,
			backTile = backTile,
			cargo = cargo}
}

function GetDataOfStation(station) {
	local location = AIStation.GetLocation(station)
	local orientation = AIRail.GetRailStationDirection(location)
	local numPlatforms = GetNumPlatforms(location)
	local platformLength = GetPlatformLength(location)
	local depot = null // do not care about this
	local exit1 = null
	local exit2 = null
	local sizeX = GetStationSizeX(location)
	local sizeY = GetStationSizeY(location)
	local cargo = null //do not care about this either, any cargo can be fine
	LogTile("location", location)
	Log("orientation" + orientation)
	Log("numPlatforms" + numPlatforms)
	Log("platformLength" + platformLength)
	Log("sizeX" + sizeX)
	Log("sizeY" + sizeY)
	if (orientation == AIRail.RAILTRACK_NW_SE) {
		exit1 = VC.VectorCoordinates(location + dXY(0, -1), Direction.NW)
		exit2 = VC.VectorCoordinates(location + dXY(numPlatforms - 1, platformLength), Direction.SE)
	} else {
		exit1 = VC.VectorCoordinates(location + dXY(-1, numPlatforms - 1), Direction.NE)
		exit2 = VC.VectorCoordinates(location + dXY(platformLength, 0), Direction.SW)
	}
	if (!AIRail.IsRailTile(VC.GetTile(exit1, [0,0])))
		exit1 = null
	if (!AIRail.IsRailTile(VC.GetTile(exit2, [0,0])))
		exit2 = null
	if (exit1 == null) {
		if (exit2 == null) return null
		exit1 = exit2
		exit2 == null
	}
	local rightTrack = (numPlatforms-1)/ 2
	exit1["location"] = VC.GetTile(exit1, [-rightTrack, 2])
	if (exit2 != null) {
		exit2["location"] = VC.GetTile(exit2, [-rightTrack, 2])
		if (AIRail.IsRailTile(VC.GetTile(exit1, [0,0]))) {
			local exitA = exit1
			exit1 = exit2
			exit2 = exitA
		}
	}					
	return {location = location,
		orientation = orientation,
		numPlatforms = numPlatforms,
		platformLength = platformLength,
		depot = depot,
		exit1 = exit1,
		exit2 = exit2,
		sizeX = sizeX,
		sizeY = sizeY,
		cargo = cargo}
}


//A plan to build a train station in a town
class TrainStationPlan extends Plan {
	location = null
	orientation = null
	value = 0
	exit1 = null
	exit2 = null
	numPlatforms = 0
	platformLength = 0
	depot = null
	name = "Train Station"
	sizeX = 0
	sizeY = 0
	cargo = null
	
	constructor(data, place, trainSystem, terminus = false, prevStation = null, producing = false, orientations = [AIRail.RAILTRACK_NW_SE, AIRail.RAILTRACK_NE_SW]) {
		if (data) {
			this.location = data.location,
			this.orientation = data.orientation,
			this.numPlatforms = data.numPlatforms,
			this.platformLength = data.platformLength,
			this.depot = data.depot,
			this.exit1 = data.exit1,
			this.exit2 = data.exit2,
			this.sizeX = data.sizeX,
			this.sizeY = data.sizeY,
			this.cargo = data.cargo
		} else {
			this.cargo = trainSystem[1]
			this.platformLength = trainSystem[2]
			this.numPlatforms = terminus?PLATFORMS:(PLATFORMS+1)
			this.location = null
			this.exit1 = null
			this.exit2 = null
			this.depot = null
			if (this.platformLength <= this.numPlatforms) {
				if (this.platformLength < 3)
					this.platformLength = this.numPlatforms + 1
				else
					this.numPlatforms = this.platformLength - 1
					
			}
			LogTile("Finding a station location near", place)
			foreach (orientation in orientations) {
				local area = AITileList()
				
				if (orientation == AIRail.RAILTRACK_NW_SE) {
					this.sizeX = this.numPlatforms
					this.sizeY = this.platformLength
				} else {
					this.sizeX = this.platformLength
					this.sizeY = this.numPlatforms
				}
				
				SafeAddRectangle(area, place + dXY(-this.sizeX/2, -this.sizeY/2), 18)
				//this.platformLength+(terminus?3:6)
				//The station tiles must be empty
				area.Valuate(AITile.IsBuildableRectangle, this.sizeX, this.sizeY)
				area.KeepValue(1)
				//The station tiles must be flat
				area.Valuate(IsFlatRectangle, this.sizeX, this.sizeY)
				area.KeepValue(1)
				//The station should be somewhere near the city center
				area.Valuate(AITile.GetDistanceManhattanToTile, place + dXY(-this.sizeX/2, -this.sizeY/2))
				area.KeepBottom(10+AIBase.RandRange(9)*10)
				//The more passengers we can provide, the better it is
				if (producing) {
					area.Valuate(AITile.GetCargoProduction, cargo, this.sizeX, this.sizeY, 4)
					area.KeepAboveValue(0)
				} else {
					area.Valuate(AITile.GetCargoAcceptance, cargo, this.sizeX, this.sizeY, 4)
					area.KeepAboveValue(7)
				}
				if (AICargo.HasCargoClass(cargo, AICargo.CC_PASSENGERS)) {
					area.Valuate(AITile.GetCargoAcceptance, cargo, this.sizeX, this.sizeY, 4)
					area.KeepAboveValue(15)
				}
				//if (cargo == GetCargoID(AICargo.CC_PASSENGERS))
				//
				
				local loc = area.Begin()
				local value = area.GetValue(loc)
				/*if (producing)
					Log("Cargo delivery: " + AITile.GetCargoProduction(loc, cargo, this.sizeX, this.sizeY, 4))
				else
					Log("Cargo acceptance: " + AITile.GetCargoAcceptance(loc, cargo, this.sizeX, this.sizeY, 4))*/
				while (!area.IsEnd() && (this.location == null || value > this.value)) { //cargo acceptance, we want to maximize this
					local exit1 = null
					local exit2 = null
					if (orientation == AIRail.RAILTRACK_NW_SE) {
						exit1 = VC.VectorCoordinates(loc + dXY(0, -1), Direction.NW)
						exit2 = VC.VectorCoordinates(loc + dXY(this.numPlatforms - 1, this.platformLength), Direction.SE)
					} else {
						exit1 = VC.VectorCoordinates(loc + dXY(-1, this.numPlatforms - 1), Direction.NE)
						exit2 = VC.VectorCoordinates(loc + dXY(this.platformLength, 0), Direction.SW)
					}
					if (!IsBuildableExit(exit1, this.numPlatforms)) {
						if (terminus && IsBuildableExit(exit2, this.numPlatforms)) {
							exit1 = exit2;
							exit2 = null
						} else {
							exit1 = null
						}
					} else if (!IsBuildableExit(exit2, this.numPlatforms)) {
						exit2 = null
						if (!terminus)
							exit1 = null
					} else if (terminus) {
						exit2 = null
					}
					if (exit1 != null) {
						this.location = loc
						this.orientation = orientation
						this.value = value
						this.exit1 = exit1
						this.exit2 = exit2
						local rightTrack = (this.numPlatforms-1)/ 2
							this.exit1["location"] = VC.GetTile(this.exit1, [-rightTrack, 2])
						/*if (exit2 != null && terminus) {
							if (prevStation == null) {
								this.exit2 = null
							} else if (AIMap.DistanceManhattan(prevStation, this.exit2["location"]) <  AIMap.DistanceManhattan(prevStation, this.exit1["location"])) {
								this.exit1 = exit2
								this.exit2 = null
							}
						} else*/
						if (exit2 != null) {
							this.exit2["location"] = VC.GetTile(this.exit2, [-rightTrack, 2])
							if (prevStation != null) {
								if (AIMap.DistanceManhattan(prevStation, this.exit1["location"]) <  AIMap.DistanceManhattan(prevStation, this.exit2["location"])) {
									this.exit2 = exit1;
									this.exit1 = exit2;
								}
							}
						} else if (terminus && exit2 == null && prevStation) {
							this.exit2 = exit1
							this.exit1 = null
						}
					}
					loc = area.Next()
					value = area.GetValue(loc)
				}
			}
		}
		if (this.location == null)
			LogWarning("No location found for " + name)
		else
			LogTile("location of " + name + ": ", this.location)
	}
	
	function Build(testmode = false) {
		LogTile("Building station " + name + " at ", this.location)
		if (AIRail.BuildNewGRFRailStation(this.location, this.orientation, this.numPlatforms, this.platformLength, AIStation.STATION_NEW,
			cargo, AIIndustryType.INDUSTRYTYPE_TOWN, AIIndustryType.INDUSTRYTYPE_TOWN, 100, true) == false && !IsAlreadyBuilt()) {
		//if (AIRail.BuildRailStation(this.location, this.orientation, this.numPlatforms, this.platformLength, AIStation.STATION_NEW) == false) {
			LogTile("Failed building a station at ", this.location)
			LogWarning(AIError.GetLastErrorString())
			if (this.depot != null) {
				RemoveDepot(this.depot);
				this.depot = null;
			}
			return false
		}
		
		local rightTrack = (this.numPlatforms-1)/ 2
		local leftTrack = rightTrack + 1
		
		foreach (exit in [this.exit1, this.exit2]) {
			if (exit != null) {
				exit["location"] = VC.GetTile(exit, [rightTrack, -2])
				for (local t = 0; t < this.numPlatforms; t++) {
					if (t == rightTrack || t == leftTrack) {
						if (!BuildRail(exit, [-t,-1], [-t,0], [-t,2]) && !IsAlreadyBuilt()) //continuous track
							return false
						//BuildPSignal(exit, [-t,3], [-t,2]);
					} else {
						if (!BuildRail(exit, [-t,-1], [-t,0], [-t,1]) && !IsAlreadyBuilt()) //short piece of track
							return false
					}
					BuildPSignal(exit, [-t,-1], [-t,0]);
					if (t < leftTrack) {
						if (!BuildRail(exit, [-t,0], [-t,1], [-t-1,2]) && !IsAlreadyBuilt()) //switch to left
							return false
					} else {
						if (!BuildRail(exit, [-t,0], [-t,1], [-t+1,2]) && !IsAlreadyBuilt()) //switch to right
							return false
					}
				}
				//Try to build a depot if there is none
				if (this.depot == null)
					this.depot = BuildDepot(exit, [0,1], [1,1])
				if (this.depot == null)
					this.depot = BuildDepot(exit, [1-numPlatforms,1], [-numPlatforms,1])
				if (this.depot == false) this.depot = null //not false
				//Update the exit tile to the end of the entrance/exit tracks, if building was not performed in test mode
				exit["location"] = VC.GetTile(exit, [-rightTrack, 2])
			}
		}
		if (this.depot && !testmode) ConnectDepot(this.depot)
		//Log("Succesfully built the train station")
		return this.getData()
	}
	
	function getData() {
		return {location = this.location,
			orientation = this.orientation,
			numPlatforms = this.numPlatforms,
			platformLength = this.platformLength,
			depot = this.depot,
			exit1 = this.exit1,
			exit2 = this.exit2,
			sizeX = this.sizeX,
			sizeY = this.sizeY,
			cargo = this.cargo}
	}
	
	//In testmode, the depot is built in Executive Mode. When cancelling plans, we thus should remove the depot
	function Cancel() {
		if (this.depot) {
			RemoveDepot(this.depot);
			this.depot = null;
		}	
	}
	
	function Undo() {
		if (this.orientation == AIRail.RAILTRACK_NW_SE) {
			AIRail.RemoveRailStationTileRectangle(this.location, this.location + dXY(this.numPlatforms, this.platformLength), false)
		} else {
			AIRail.RemoveRailStationTileRectangle(this.location, this.location + dXY(this.platformLength, this.platformLength), false)
		}
		if (this.depot) {
			RemoveDepot(this.depot);
			this.depot = null;
		}	
		local rightTrack = (this.numPlatforms-1)/ 2;
		local leftTrack = rightTrack + 1;
		foreach (exit in [this.exit1, this.exit2]) {
			if (exit != null) {
				exit["location"] = VC.GetTile(exit, [rightTrack, -2])
				for (local t = 0; t < this.numPlatforms; t++) {
					if (t == rightTrack || t == leftTrack) {
						RemoveRail(exit, [-t,-1], [-t,0], [-t,2]) //continuous track
					} else {
						RemoveRail(exit, [-t,-1], [-t,0], [-t,1]) //short piece of track
					}
					if (t < leftTrack) {
						RemoveRail(exit, [-t,0], [-t,1], [-t-1,2]) //switch to left
					} else {
						RemoveRail(exit, [-t,0], [-t,1], [-t+1,2]) //switch to right
					}
				}
			}
		}
	}
}

function ConnectAllStationExitTracks(exit) {
	for (local i = -3; i < 3; i++) {
		local tile = VC.GetTile(exit, [i, -1])
		local leftTile = VC.GetTile(exit, [i-1, -1])
		local rightTile = VC.GetTile(exit, [i+1, -1])
		if (AIRail.IsRailTile(tile) && AIRail.IsRailTile(leftTile) && AIRail.IsRailTile(rightTile) &&
			AIRail.GetRailType(leftTile) == AIRail.GetRailType(rightTile)) {
			AIRail.SetCurrentRailType(AIRail.GetRailType(leftTile))
			if (!AIRail.BuildRail(leftTile, tile, rightTile)) {
				if (!AIRail.BuildRail(leftTile, tile, rightTile)) {
					LogError("Couldn't build a rail track")
					LogTile("I tried to build rail at", tile)
				}
			}
		}
	}
	//local station = AIStation.GetStationID(VC.GetTile(exit, [0, -3]))
}

//A plan to build a bus or truck station in a town
class BusStationPlan extends Plan {
	location = null
	frontTile = null
	backTile = null
	vehicleType = null
	value = 0
	name = "Bus Station"
	cargo = null
	
	constructor(data, place, roadSystem, producing = false) {
		if (data) {
			this.location = data.location,
			this.frontTile = data.frontTile,
			this.backTile = data.backTile,
			this.cargo = data.cargo
			this.vehicleType = data.vehicleType
		} else {
			this.cargo = roadSystem[1]
			this.vehicleType = AIRoad.GetRoadVehicleTypeForCargo(this.cargo)
			LogTile("Finding a station location near", place)
			local area = AITileList()
			SafeAddRectangle(area, place, 8)
			//The station tiles must be flat
			area.Valuate(IsFlatTile)
			area.KeepValue(1)
			if (this.vehicleType == AIRoad.ROADVEHTYPE_BUS) {
				//Find a suitable location on existing road
				area.Valuate(AIRoad.IsRoadTile)
				area.KeepValue(1)
				area.Valuate(AIRoad.GetNeighbourRoadCount)
				area.KeepBelowValue(3)
			} else {
				//Find an empty tile or road tile to build a truck station
				if (AICargo.GetTownEffect(this.cargo) != AICargo.TE_NONE && !producing) {
					area.Valuate(AIRoad.IsRoadTile)
					area.KeepValue(1)
				}
				area.Valuate(AIRoad.GetNeighbourRoadCount)
				area.KeepBelowValue(3)
			}
			area.Valuate(AIRoad.IsRoadStationTile)
			area.KeepValue(0)
			area.Valuate(AIRoad.IsRoadDepotTile)
			area.KeepValue(0)
			
			//The station should be somewhere near the requested location
			area.Valuate(AITile.GetDistanceManhattanToTile, place)
			area.KeepBottom(6+AIBase.RandRange(9)*6)
			//The more passengers/cargo we can provide, the better it is
			if (producing) {
				area.Valuate(AITile.GetCargoProduction, cargo, 1, 1, 3)
				area.KeepAboveValue(0)
			} else {
				area.Valuate(AITile.GetCargoAcceptance, cargo, 1, 1, 3)
				area.KeepAboveValue(7)
			}
			
			local loc = area.Begin()
			local value = area.GetValue(loc) *2 / (2 + NumStationsNear(loc, 3, cargo))
			//if (producing)
			//	Log("Cargo delivery: " + AITile.GetCargoProduction(loc, cargo, 1, 1, 3))
			//else
			//	Log("Cargo acceptance: " + AITile.GetCargoAcceptance(loc, cargo, 1, 1, 3))
			while (!area.IsEnd()) { 
				if (this.location == null || value > this.value) {//value = cargo acceptance, we want to maximize this
					if (((!HasAnyRoad(loc + dXY(0, 1)) && !HasAnyRoad(loc + dXY(0, -1))) || !AIRoad.IsRoadTile(loc)) && CanBuildRoadOn(loc + dXY(1,0)) && CanBuildRoadOn(loc + dXY(-1,0))) {
						this.location = loc
						this.frontTile = loc + dXY(1, 0)
						this.backTile = loc + dXY(-1, 0)
						this.value = value
					} else if (((!HasAnyRoad(loc + dXY(1, 0)) && !HasAnyRoad(loc + dXY(-1, 0))) || !AIRoad.IsRoadTile(loc)) && CanBuildRoadOn(loc + dXY(0,1)) && CanBuildRoadOn(loc + dXY(0,-1))) {
						this.location = loc
						this.frontTile = loc + dXY(0, 1)
						this.backTile = loc + dXY(0, -1)
						this.value = value
					} 
				}
				loc = area.Next()
				value = area.GetValue(loc) * 2 / (2 + NumStationsNear(loc, 3, cargo))
			}
		}
		if (this.location == null)
			LogWarning("No location found for " + name)
		else
			LogTile("location of " + name + ": ", this.location)
	}
	
	function Build(testmode = false) {
		LogTile("Building road station " + name + " at ", this.location)
		if (AIRoad.BuildDriveThroughRoadStation(this.location, this.frontTile, this.vehicleType, AIStation.STATION_JOIN_ADJACENT) == false && !IsAlreadyBuilt()) {
			if (AIRoad.BuildDriveThroughRoadStation(this.location, this.frontTile, this.vehicleType, AIStation.STATION_NEW) == false && !IsAlreadyBuilt()) {
				LogTile("Failed building a road station at ", this.location)
				LogWarning(AIError.GetLastErrorString())
				return false
			}
		}
		if (!AIRoad.BuildRoad(this.location, this.frontTile) && !IsAlreadyBuilt()) {
			LogTile("Failed building a road station road at ", this.frontTile)
			LogWarning(AIError.GetLastErrorString())
			return false
		}
		if (!AIRoad.BuildRoad(this.location, this.backTile) && !IsAlreadyBuilt()) {
			LogTile("Failed building a road station road at ", this.backTile)
			LogWarning(AIError.GetLastErrorString())
			return false
		}
		//Log("Succesfully built the road station")
		return this.getData()
	}
	
	function getData() {
		return {location = this.location,
			frontTile = this.frontTile,
			backTile = this.backTile,
			cargo = this.cargo,
			vehicleType = this.vehicleType}
	}
	
	function Cancel() {
	}
	
	function Undo() {
		AIRoad.RemoveRoadStation(this.location)
	}
	
}

// A plan to build a dock for ships
class DockPlan extends Plan {
	location = null
	frontTile = null
	moorTile = null
	secondTile = null
	areaTiles = []
	value = 0
	name = "Dock"
	cargo = null
	
	constructor(data, place, industry, shipSystem, producing = false) {
		if (data) {
			this.location = data.location,
			this.frontTile = data.frontTile,
			this.moorTile = data.moorTile,
			this.secondTile = data.secondTile,
			this.areaTiles = data.areaTiles,
			this.cargo = data.cargo
		} else {
			cargo = shipSystem[1]
			if (industry) {
				place = AIIndustry.GetLocation(industry)
				if (AIIndustry.HasDock(industry)) {
					this.location = AIIndustry.GetDockLocation(industry)
					for (local i = 1; i < 5; i++) {
						if (AITile.IsWaterTile(this.location+dXY(i,0)) && AITile.IsWaterTile(this.location+dXY(i+1,0))) {
							this.moorTile = this.location+dXY(i,0)
							this.frontTile = this.location+dXY(i+1,0)
							this.secondTile = this.location
							this.areaTiles = [this.location]
							this.value = 100
							LogTile(AIIndustry.GetName(industry) + " has a dock on", this.location)
							return
						}
					}
					//We cannot use the dock
					LogError(AIIndustry.GetName(industry) + " has a dock, but I don't know how to use it")
					this.location = null
				}
			}
			
			//LogTile("Finding a dock location near", place)
			local area = AITileList()
			SafeAddRectangle(area, place, 8)
			//The station tiles must be non flat
			//the tile should be a sloped land tile
			area.Valuate(AITile.GetSlope)
			area.RemoveValue(AITile.SLOPE_FLAT)
			area.Valuate(AITile.IsWaterTile)
			area.KeepValue(0)
			area.Valuate(AITile.IsBuildable)
			area.KeepValue(1)
			
			if (area.Count() == 0) {
				area = AITileList()
				SafeAddRectangle(area, place, 8)
				area.Valuate(AITile.IsWaterTile)
				area.KeepValue(0)
				area.Valuate(AITile.IsBuildable)
				area.KeepValue(1)
			}
			//The station should be somewhere near the requested location
			area.Valuate(AITile.GetDistanceManhattanToTile, place)
			area.KeepBottom(6+AIBase.RandRange(9)*6)
			//The more passengers/cargo we can provide, the better it is
			if (producing) {
				area.Valuate(AITile.GetCargoProduction, cargo, 1, 1, 5)
				area.KeepAboveValue(0)
			} else {
				area.Valuate(AITile.GetCargoAcceptance, cargo, 1, 1, 5)
				area.KeepAboveValue(7)
			}
			
			local loc = area.Begin()
			local value = area.GetValue(loc)
			//if (producing)
			//	Log("Cargo delivery: " + AITile.GetCargoProduction(loc, cargo, 1, 1, 5))
			//else
			//	Log("Cargo acceptance: " + AITile.GetCargoAcceptance(loc, cargo, 1, 1, 5))
			while (!area.IsEnd() && (this.location == null || value > this.value)) { //cargo acceptance, we want to maximize this
				local slope = AITile.GetSlope(loc)
				local delta = false
				if (slope == AITile.SLOPE_NW) {
					delta = dXY(0,1)
				} else if (slope == AITile.SLOPE_NE) {
					delta = dXY(1,0)
				} else if (slope == AITile.SLOPE_SW) {
					delta = dXY(-1,0)
				} else if (slope == AITile.SLOPE_SE) {
					delta = dXY(0,-1)
				} else if (slope == AITile.SLOPE_FLAT && AITile.GetMinHeight(loc) > 0 && AIBase.RandRange(10) < 9) {
					if (AITile.GetSlope(loc+dXY(0,-1)) == AITile.SLOPE_FLAT && AITile.IsBuildable(loc+dXY(0,-1)) &&
						AITile.IsBuildable(loc+dXY(1,-1)) && AITile.IsBuildable(loc+dXY(-1,-1)) &&
						AITile.IsBuildable(loc+dXY(1,0)) && AITile.IsBuildable(loc+dXY(-1,0))) {
						delta = dXY(0,1)
					}
				}
				if (delta) {
					local st = loc + delta
					local mt = st + delta
					local ft = mt + delta
					//There must be enough flat land in front of the harbour, to avoid building docks in front of the harbour
					if ((AITile.IsBuildable(st) || AITile.IsWaterTile(st)) && (AITile.IsBuildable(mt) || AITile.IsWaterTile(mt)) &&
						AITile.GetSlope(mt) == AITile.SLOPE_FLAT && (AITile.IsBuildable(ft) || AITile.IsWaterTile(ft)) &&
						AITile.GetSlope(mt+dXY(0,1)) == AITile.SLOPE_FLAT  && AITile.GetSlope(mt+dXY(0,-1)) == AITile.SLOPE_FLAT &&
						AITile.GetSlope(mt+dXY(1,0)) == AITile.SLOPE_FLAT  && AITile.GetSlope(mt+dXY(-1,0)) == AITile.SLOPE_FLAT ) {
						this.location = loc
						this.moorTile = mt
						this.frontTile = ft
						if (slope == AITile.SLOPE_FLAT) {
							this.areaTiles = [st, loc+dXY(-1,0), loc+dXY(1,0), loc+dXY(-1,-1), loc+dXY(1, -1), loc+dXY(0,-1)]
						} else {
							this.areaTiles = [st]
						}
						this.secondTile = st
						this.value = value
						//LogTile("val" + value, loc)
					}
				}
				loc = area.Next()
				value = area.GetValue(loc)
			}
		}
		if (this.location == null)
			LogWarning("No location found for " + name)
		else
			LogTile("location of " + name + ": ", this.location)
	}
	
	function Build(testmode = false) {
		LogTile("Building a dock " + name + " at ", this.location)
		if (this.secondTile != this.location) {
			AIMarine.BuildCanal(this.secondTile)
			if (!AITile.IsWaterTile(this.secondTile) && !AIMarine.BuildCanal(this.secondTile) && !IsAlreadyBuilt()) {
				LogTile("Failed building a canal to place dock at ", this.secondTile)
				LogWarning(AIError.GetLastErrorString())
				return false
			}
		}
		if (!AITile.IsWaterTile(this.moorTile) && !AIMarine.BuildCanal(this.moorTile) && !IsAlreadyBuilt()) {
			LogTile("Failed building a canal to moor in front of the dock at ", this.moorTile)
			LogWarning(AIError.GetLastErrorString())
			return false
		}
		if (!AITile.IsWaterTile(this.frontTile) && !AIMarine.BuildCanal(this.frontTile) && !IsAlreadyBuilt()) {
			LogTile("Failed building a canal in front of the dock at ", this.frontTile)
			LogWarning(AIError.GetLastErrorString())
			return false
		}
		if (this.secondTile != this.location) {
			if (AITile.GetSlope(this.location) == AITile.SLOPE_FLAT) {
				AITile.RaiseTile(this.location, AITile.SLOPE_NW)
			}
			if (!testmode && AIMarine.BuildDock(this.location, AIStation.STATION_NEW) == false && !IsAlreadyBuilt()) {
				LogTile("Failed building a dock at ", this.location)
				LogWarning(AIError.GetLastErrorString())
				return false
			}
		}
		return this.getData()
	}
	
	function getData() {
		return {location = this.location,
			frontTile = this.frontTile,
			moorTile = this.moorTile,
			areaTiles = this.areaTiles,
			secondTile = this.secondTile,
			cargo = this.cargo}
	}
	
	function Cancel() {
	}
	
	function Undo() {
		AIMarine.RemoveLock(this.location)
	}
	
}


function TownSiteValue(town, sites) {
	if (sites[town])
		return sites[town][2]
	return 0
}


function IsFlatRectangle(location, sizeX, sizeY) {
	for (local x = 0; x < sizeX; x++) {
		for (local y = 0; y < sizeY; y++) {
			local tile = location + dXY(x, y);
			local slope = AITile.GetSlope(tile);
			if (slope != AITile.SLOPE_FLAT && slope != AITile.SLOPE_ELEVATED) {
				return false;
			}
		}
	}
	return true;
}

function IsFlatTile(tile) {
	local slope = AITile.GetSlope(tile)
	return (slope == AITile.SLOPE_FLAT || slope == AITile.SLOPE_ELEVATED)
}

function IsBuildableExit(exit, numPlatforms) {
	local tile;
	local slope;
	local rightTrack = (numPlatforms-1)/ 2;
	local leftTrack = rightTrack + 1;
	for (local t = 0; t < numPlatforms; t++) {
		local fmax = ((t == leftTrack || t == rightTrack)? 5 : 2);
		for (local f = 0; f < fmax; f++) {
			tile = VC.GetTile(exit, [-t,f]);
			if (!AITile.IsBuildable(tile))
				return false
			if (f == 1) {
				slope = AITile.GetSlope(tile);
				if (slope != AITile.SLOPE_FLAT && slope != AITile.SLOPE_ELEVATED)
					return false
			}
		}
	}
	return true
}

function IsStationProperlyServed(location, cargo = null) {
	local station = AIStation.GetStationID(location)
	local cargoWaiting = 0
	local cargoRating  = 100
	if (cargo == null)
		cargo = AICargoList().Begin()
	if (AIStation.HasCargoRating(station, cargo)) {
		cargoRating = AIStation.GetCargoRating(station, cargo)
	}
	cargoWaiting = AIStation.GetCargoWaiting(station, cargo)
	
	return (cargoRating - cargoWaiting / 10 > 40)
}

function CargoValue(location, direction, from, to, cargo, radius, accept) {
	// check if any tile in the rectangle has >= 8 cargo acceptance/production
	local f = accept ? AITile.GetCargoAcceptance : AITile.GetCargoProduction;
	local coords = RelativeCoordinates(location, direction);
	for (local x = from[0]; x < to[0]; x++) {
		for (local y = from[1]; y < to[1]; y++) {
			local tile = VC.GetTile(coords, [x, y]);
			local v = f(tile, cargo, 1, 1, radius);
			if (v > 7) {
				//LogTile("cargo", tile);
				//Log(v);
				return 1;
			}
		}
	}
	return 0;
}

function CalculateCargoValue(location, direction, from, to, cargo, radius, accept) {
	return AITile.GetCargoAcceptance(tile, cargo, width, height, radius);
}

/* Get the length of the platforms of a certain station, positioned on a location
*/
function GetPlatformLength(location) {
	if (AIRail.GetRailStationDirection(location) == AIRail.RAILTRACK_NE_SW)
		return GetStationSizeX(location)
	else
		return GetStationSizeY(location)
}

/* Get the length of the platforms of a certain station, positioned on a location
*/
function GetNumPlatforms(location) {
	if (AIRail.GetRailStationDirection(location) == AIRail.RAILTRACK_NE_SW)
		return GetStationSizeY(location)
	else
		return GetStationSizeX(location)
}

function GetStationSizeX(location) {
	local size = 0
	local stationID = AIStation.GetStationID(location)
	//Maximum size is limited at 10
	for (local i = 0; i < 10; i++) {
		if (AIStation.GetStationID(location + dXY(i,0)) == stationID)
			size += 1
	}
	return size
}

function GetStationSizeY(location) {
	local size = 0
	local stationID = AIStation.GetStationID(location)
	//Maximum size is limited at 10
	for (local i = 0; i < 10; i++) {
		if (AIStation.GetStationID(location + dXY(0,i)) == stationID)
			size += 1
	}
	return size
}

function NumStationsNear(tile, range, cargo = null) {
	local stations = AIList()
	for (local x = -range; x <= range; x++) {
		for (local y = -range; y <= range; y++) {
			local id = AIStation.GetStationID(tile + dXY(x,y))
			if (IsAnyValidStation(id) && !stations.HasItem(id)) {
				if (cargo == null || StationTransportsCargo(id, cargo)) {
					stations.AddItem(id, id)
				}
			}
		}
	}
	return stations.Count()
}

function NumStationsInTown(town, cargo = null) {
	local stations = AIList()
	local tile = AITown.GetLocation(town)
	for (local x = -20; x <= 20; x++) {
		for (local y = -20; y <= 20; y++) {
			local id = AIStation.GetStationID(tile + dXY(x,y))
			if (IsAnyValidStation(id) && AITile.GetClosestTown(tile + dXY(x,y)) == town && !stations.HasItem(id)) {
				if (cargo == null || StationTransportsCargo(id, cargo)) {
					stations.AddItem(id, id)
				}
			}
		}
	}
	//Log("station count of " + AITown.GetName(town) + ":" +  stations.Count(), "stationcount")
	return stations.Count()
}

/*Function to check if a certain type of cargo will be served on this station
 *If a station is new, it won't have a transport rating yet. Then this function checks
 *all vehicles that have this station in their order list.
 */
function StationTransportsCargo(station, cargo) {
	if (!AIStation.IsValidStation(station)) {
		//station is not mine, assume other players are using it to transport this type of cargo
		return true
	}
	
	local list = AIVehicleList_Station(station)
	if (list.Count() == 0) {
		LogTile("no vehicles towards" + AIStation.GetName(station), AIStation.GetLocation(station))
		///This station should be removed
	}
	//A station that has a cargo rating and has vehicles going to this station is served
	//A station that has no cargo rating and no vehicles to go here yet, might be used for this cargo in near future
	if ((AIStation.HasCargoRating(station, cargo)) == (list.Count() > 0)) return true
	for (local vehicle = list.Begin(); list.HasNext(); vehicle = list.Next()) {
		if (GetVehicleCargoType(vehicle) == cargo) return true
	}
	return false
}
