require("plan.nut")

/* A Road system contains the following information:
 * system[0] -> Road Type
 * system[1] -> Cargo Type
 * system[2] -> maximum speed
 * system[3] -> whether to avoid levelCrossings
 */
 
function GetRoadSystem(vehicle) {
	local roadType = AIVehicle.GetRoadType(vehicle)
	local cargoType = GetVehicleCargoType(vehicle)
	local avoidLevelCrossings = false
	local maxSpeed = AIEngine.GetMaxSpeed(AIVehicle.GetEngineType(vehicle))
	if (roadType == AIRoad.ROADTYPE_TRAM)
		avoidLevelCrossings = true
	return [roadType, cargoType, maxSpeed, avoidLevelCrossings]
}
 
function SelectRoadSystem(cargo) {
	local l = AIList()
	l.AddItem(AIRoad.ROADTYPE_ROAD, AIRoad.ROADTYPE_ROAD)
	l.AddItem(AIRoad.ROADTYPE_TRAM, AIRoad.ROADTYPE_TRAM)
	local roadspeeds = {}
	local cars = {}
	local maxSpeed = 0
	local maxCost = 0
	local totalCarCount = 0
	local avoidLevelCrossings = false
	//Filter only the available railtypes
	l.Valuate(AIRoad.IsRoadTypeAvailable);
	l.KeepValue(1);
	
	foreach (roadType, index in l) {
		local result = SelectRoadVehicles(cargo, roadType)
		cars[roadType] <- result
		roadspeeds[roadType] <- result[1]
		if (result[1] > maxSpeed)
			maxSpeed = result[1];
		totalCarCount += result[2]
		local cost = AIRoad.GetBuildCost(roadType, AIRoad.BT_ROAD)
		if (cost > maxCost)
			maxCost = cost
	}
	if (maxSpeed == 0) {
		LogError("No road cars could be found for " + AICargo.GetCargoLabel(cargo) + "!")
		return false
	}
	//Log("Max speed " + maxSpeed)
	local chances = {}
	local totalChance = 0;
	foreach (roadType, index in l) {
		local result = cars[roadType]
		if (result[1] < maxSpeed/3) {
			chances[roadType] <- 0; //we don't vehicles running this slowly (e.g. 40 km/h tracks)
		} else {
			local chance = result[1] * totalCarCount + result[2] * maxSpeed;
			local cost = AIRail.GetBuildCost(roadType, AIRail.BT_TRACK)
			chance += chance *2 * (maxCost - cost) / (maxCost*3)
			chances[roadType] <- chance
			totalChance += chance
		}
		//Log("Road type with speed " + result[1] + " has chance: " + chances[roadType]);
	}
	local fortuna = AIBase.RandRange(totalChance)
	local sum = 0;
	foreach (roadType, index in l) {
		sum += chances[roadType]
		if (sum > fortuna) {
			if (roadType == AIRoad.ROADTYPE_TRAM)
				avoidLevelCrossings = true
			return [roadType, cargo, maxSpeed, avoidLevelCrossings]
		}
	}
	Log("Something went terribly wrong in selecting a road type and vehicle set")
}

/* Select all Road vehicles  for a certain cargo and road type
 * Returned value contains the following information:
 * trains[0] -> AIEngine list containing vehicles
 * trains[1] -> Maximum speed of the vehicles
 * trains[2] -> Number of different available vehicles
 */
function SelectRoadVehicles(cargo, roadType) {
	
	local cars = AIEngineList(AIVehicle.VT_ROAD);
	cars.Valuate(AIEngine.IsBuildable); cars.KeepValue(1);
	cars.Valuate(AIEngine.GetRoadType); cars.KeepValue(roadType);	
	cars.Valuate(AIEngine.CanRefitCargo, cargo); cars.KeepValue(1);
	
	local maxcarspeed = 0;
	
	foreach (car, value in cars) {
		local maxSpeed = AIEngine.GetMaxSpeed(car)
		if (maxSpeed > maxcarspeed) {
			maxcarspeed = maxSpeed
		} else if (maxSpeed == 0) {
			maxcarspeed = 100000
		}
	}
	local minSpeed = maxcarspeed * 8 / 11
	if (maxcarspeed < 100000) {
		cars.Valuate(AIEngine.GetMaxSpeed); cars.KeepAboveValue(minSpeed);
	}
	return [cars, maxcarspeed, cars.Count()]
}

class RoadVehicleBuyPlan extends Plan {
	roadSystem = null
	garage = null
	vehicle = null
	constructor(roadSystem, garage, data = null) {
		if (data) {
			this.roadSystem = data.roadSystem
			this.garage = data.garage
			this.vehicle = null
		} else {
			this.roadSystem = roadSystem
			this.garage = garage.location
			this.vehicle = null
		}
	}
	
	function getData() {
		return {roadSystem = this.roadSystem, garage = this.garage}
	}
	
	function Build(testmode = false) {
		if (this.garage == null) {
			LogError("No garage to buy the vehicle!")
			return false
		}
		LogTile("Buying a vehicle at ", garage)
		vehicle = BuyRoadVehicle(garage, roadSystem[1], roadSystem[0])
		if (vehicle == null && !testmode)
			vehicle = BuyRoadVehicle(garage, roadSystem[1], roadSystem[0])
		if (vehicle == null && !testmode)
			vehicle = BuyRoadVehicle(garage, roadSystem[1], roadSystem[0])
		if (vehicle == null) {
			LogError("Couldn't buy a vehicle.")
			return false
		}
		if (testmode) {
			AIVehicle.SellVehicle(vehicle)
			return true
		}
		//Log("Built vehicle has ID " + vehicle)
		return vehicle
	}
	
	function EstimateCosts() {
		local accountant = AIAccounting()
		local vehicle = Build(false)
		if (vehicle == false)
			return false
		local costs = accountant.GetCosts()
		Log ("vehicle costs estimation: " + costs + " id " + vehicle)
		if (!AIVehicle.SellVehicle(vehicle))
			LogWarning("Failed selling vehicle")
		return costs
	}
	
	function Undo() {
		AIVehicle.SellVehicle(vehicle)	
	}
}

/* Buy a Road vehicle in an existing garage (depot not in Test Mode!)
 * garage = (TileIndex) location of the garage
 * cargoType = type of cargo
 */
function BuyRoadVehicle(garage, cargoType, roadType) {
	
	//local vehicles = SelectRoadVehicles(cargoType, AIRoad.GetRoadType(garage))
	AIRoad.SetCurrentRoadType(roadType)
	local vehicles = SelectRoadVehicles(cargoType, AIRoad.GetCurrentRoadType())
	local cars = vehicles[0]
	//pick a random valid vehicle
	cars.Valuate(AIBase.RandRangeItem, 100)
	cars.Sort(AIList.SORT_BY_VALUE, true);
	if (!AIRoad.IsRoadDepotTile(garage)) {
		local list = AIDepotList(AITile.TRANSPORT_ROAD)
		for (local depot = list.Begin(); list.HasNext(); depot = list.Next()) {
			if (AIRoad.HasRoadType(depot, roadType)) {
				garage = depot
				break
			}
		}
	}
	local vehicle = AIVehicle.BuildVehicle(garage, cars.Begin())
	if (!AIVehicle.IsValidVehicle(vehicle)) {
		LogWarning("I cannot buy a vehicle: " + AIError.GetLastErrorString())
		return null
	}
	AIVehicle.RefitVehicle(vehicle, cargoType)
	//Check if the vehicle indeed carries the correct type of cargo
	local cargoCapacity = AIVehicle.GetCapacity(vehicle, cargoType)
	local list = AICargoList();
	for (local cargo = list.Begin(); list.HasNext(); cargo = list.Next()) {
		if (AIVehicle.GetCapacity(vehicle, cargo) > cargoCapacity) {
			LogWarning("The vehicle does not transport the right kind of cargo/passengers")
			AIVehicle.SellVehicle(vehicle);
			return null
		}
	}
	//Log("Succesfully bought a road vehicle with id " + vehicle)
	return vehicle
}

// A Plan to build tracks between two locations
class RoadPlan extends Plan {
	path1 = null
	tiles1 = null
	tiles2 = null
	from = null
	towards = null
	avoidArea = []
	counters = [1, 1] //tiles1, tiles2
	designSpeed = 160
	randomSeed = 0
	avoidLevelCrossings = false
	
	constructor(roadsystem, station1, station2, data = null) {
		if (data) {
			this.tiles1 = data.tiles1
			this.tiles2 = data.tiles2
			this.from = data.from
			this.towards = data.towards
			this.avoidArea = data.avoidArea
			this.counters = data.counters
			this.designSpeed = data.designSpeed
			this.randomSeed = data.randomSeed
			this.avoidLevelCrossings = data.avoidLevelCrossings
		} else {
			this.from = station1
			this.towards = station2
			this.path1 = null
			this.tiles1 = null
			this.tiles2 = null
			this.counters = [1, 1]
			this.designSpeed = roadsystem[1]
			this.avoidLevelCrossings = roadsystem[3]
			randomSeed = AIBase.RandRange(1000)
			//Randomly do pathfinding the other way around
			if (AIBase.RandRange(10) < 5) {
				this.from = this.towards
				this.towards = station1
			}
			this.path1 = FindPath(this.from, this.towards)
			if (!this.path1) {
				LogError("Failed finding a suitable path to build roads")
				return false
			}
			this.tiles1 = GetTilesOn(this.path1)
			this.tiles2 = null //TODO, cars should be able to turn around at the destination
		}
	}
	
	function getData() {
		return {tiles1 = this.tiles1,
			tiles2 = this.tiles2,
			from = this.from,
			towards = this.towards,
			avoidArea = this.avoidArea,
			counters = this.counters,
			designSpeed = this.designSpeed,
			avoidLevelCrossings = this.avoidLevelCrossings,
			randomSeed = this.randomSeed}			
	}
	
		
	function Build(testmode = false) {
		if (!testmode) {
			//Re-plan roads to maybe find a better route. This usually goes fast
			this.path1 = FindPath(this.from, this.towards)
			if (!this.path1) {
				LogError("Failed finding a suitable path to build roads")
				return false
			}
			this.tiles1 = GetTilesOn(this.path1)
		}
		
		if (!BuildRoadOnTiles(this.tiles1, false, testmode, this.counters, 0)) {
			LogError("Building first route failed")
			return false
		}
		return true //success
	
	}
	
	function Undo() {
		//Removing roads is not necessary
	}
	
	
	function FindPath(from, to) {
		local pathfinder = Road();

		pathfinder.cost.max_bridge_length = 12;
		pathfinder.cost.max_tunnel_length = 8;
		
		local u = pathfinder.cost.tile;
		//pathfinder.cost.slope = 2*u;
		//pathfinder.cost.coast = 0.1*u;
		
		if (from.location == to.location)
			pathfinder.InitializePath([from.frontTile], [from.backTile], [from.location]);
		else
			pathfinder.InitializePath([from.location], [to.location]);
		
		if (this.avoidLevelCrossings) {
			local u = pathfinder.cost.tile;
			pathfinder.cost.level_crossing = 300*u;
		}
		
		//increase multiplier cost for long distances (or sometimes for short distances too)
		if (AIBase.RandRange(10) < AIMap.DistanceManhattan(from.location, to.location) / 10) {
			pathfinder.cost.multiplier = 1.6;
		}
		
		local path = false;
		LogWarning("Starting road pathfinder at " + RailwAI.GetTick())
		if (from.location == to.location) {
			LogTiles("path attempt from ", from.frontTile, " to ", to.backTile)
		} else {
			LogTiles("path attempt from ", from.location, " to ", to.location)
		}
		path = pathfinder.FindPath(20000);
		LogWarning("Ending pathfinder at " + RailwAI.GetTick())
		if (!path) {
			LogError("No path found :-(");
			return false
		}
		return path
	}
	
	function GetTilesOn(pathToFollow) {
		/*A tile from the resulting list contains an array with data 
		* tile[0] = the actual tile
		* tile[1] = * nothing
		* tile[2] = The direction of the tile
		*/
		local path = pathToFollow
		local tile = path.GetTile()
		local tiles = []
		local prevPath = path.GetParent()
		local prevTile = null
		local direction = 255
		if (prevPath) {
			prevTile = prevPath.GetTile()
			direction = Rail._dir(prevTile, tile, true)
		}
		prevTile = null
		while (path != null) {
			tile = path.GetTile()
			local newDirection = path.GetDirection()
			//In the begin, direction is zero (why?)
			//In the end, direction is 255 (why?)
			//Assume that we start and end with a straight piece of rail
			if (newDirection == 255 || newDirection == 0) newDirection = direction
			tiles.append([tile, false, newDirection])
			prevTile = tile
			direction = newDirection
			path = path.GetParent()
		}
		return tiles
	
	}

	
	//Build road on a list of Tiles (or repair parts if we cannot build those parts)
	//When not in testmode, we should update the counter in counters with index cIndex
	function BuildRoadOnTiles(path, backwards = false, testmode = false, counters = null, cIndex = 0) {
		local i = 1;
		//restore starting value if provided
		if (!testmode && counters) {
			i = counters[cIndex]
		}
		for (; i < path.len(); i++) {
			local tile = path[i][0]
			local prev = path[i-1][0]
			local direction = path[i][2]
			local succes = true
			local length = AIMap.DistanceManhattan(tile, prev)
			if (length > 1) {
				if (AITunnel.GetOtherTunnelEnd(prev) == tile) {
					if (!AITunnel.IsTunnelTile(tile) && !AITunnel.BuildTunnel(AIVehicle.VT_ROAD, prev) && !IsAlreadyBuilt()) {
						if (AIRoad.IsRoadTile(prev) && !AIRail.IsRailTile(prev))
							AITile.DemolishTile(prev)
						if (!AITunnel.IsTunnelTile(tile) && !AITunnel.BuildTunnel(AIVehicle.VT_ROAD, prev) && !IsAlreadyBuilt()) {
							LogError("Failed building tunnel");
							succes = false
							i--
						}
					}
				} else {
					local delta = (tile-prev)/length
					//If there is already a bridge, we don't need to do anything, the bridge will be converted
					if (!AIBridge.IsBridgeTile(tile) || !AIBridge.IsBridgeTile(prev)) {
						local bridge_list = AIBridgeList_Length(length + 1)
						bridge_list.Valuate(AIBridge.GetMaxSpeed)
						bridge_list.KeepAboveValue(designSpeed)
						if (bridge_list.Count() > 0) {
							bridge_list.Valuate(AIBridge.GetMaxSpeed)
							bridge_list.KeepBottom(4)
							bridge_list.RemoveTop(randomSeed % bridge_list.Count())
						} else {
							bridge_list = AIBridgeList_Length(length + 1)
							bridge_list.Valuate(AIBridge.GetMaxSpeed)
							bridge_list.Sort(AIAbstractList.SORT_BY_VALUE, false)
						}
						if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), prev, tile) && !IsAlreadyBuilt()) {
							//It might be a piece of road that we already put here, remove it then
							if (AIRoad.IsRoadTile(prev) && !AIRail.IsRailTile(prev))
								AITile.DemolishTile(prev);
							if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), prev, tile) && !IsAlreadyBuilt()) {
								LogError("Failed building bridge:")
								LogTile("Bridge tile start", prev)
								LogTile("Bridge tile end", tile)
								succes = false
								i--
								if (BuildTunnel(AIVehicle.VT_ROAD, prev, tile, direction, length, testmode)) {
									succes = true
									i++
								}
							}
						}
					}
					//Ensure that the bridge is connected!
					AIRoad.BuildRoad(prev, prev-delta)
					AIRoad.BuildRoad(tile, tile+delta)
				}
				i++
			} else { //no bridge or tunnel, but just adjacent road
				if (!AIRoad.BuildRoad(prev, tile) && !IsAlreadyBuilt()) {
					succes = false
					if (VehicleInTheWay()) {
						RailwAI.Sleep(50)
						if (AIRoad.BuildRoad(prev, tile)) {
							succes = true
						} else {
							RailwAI.Sleep(50)
							if (AIRoad.BuildRoad(prev, tile))
								succes = true
						}
					}

				}
			}
			if (!succes) {
				LogError("Failed building road: " + AIError.GetLastErrorString());
				return false;
			}				
		}
		//Log("Build Road on Tiles returning true")
		return true
	}
}