
//An Idea to build a truck service (cargo via Road)
class TruckServiceIdea extends Idea {
	roadSystem = null;
	industryA = null
	industryB = null
	isTown = false
	constructor(roadSystem, industryA, industryB, isTown = false, data = null) {
		if (data) {
			this.roadSystem = data.misc[0]
			this.industryA = data.misc[1]
			this.industryB = data.misc[2]
			this.isTown = data.misc[3]
			this.results = data.results
			this.plans = []
			this.plans.append(BusStationPlan(data.plans[0], null, null))
			this.plans.append(BusStationPlan(data.plans[1], null, null))
			this.plans.append(GaragePlan(data.plans[2], null, null))
			this.plans.append(RoadPlan(null, null, null, data.plans[3]))
			this.plans.append(RoadPlan(null, null, null, data.plans[4]))
			this.plans.append(RoadVehicleBuyPlan(null, null, data.plans[5]))
			this.plans.append(RoadPlan(null, null, null, data.plans[6]))
			this.plans.append(RoadPlan(null, null, null, data.plans[7]))
		} else {
			this.plans = []
			this.results = []
			if (isTown)
				LogWarning("Creating a " + AICargo.GetCargoLabel(roadSystem[1]) + " Truck Service Idea: " + AIIndustry.GetName(industryA) + " to " + AITown.GetName(industryB))
			else
				LogWarning("Creating a " + AICargo.GetCargoLabel(roadSystem[1]) + " Truck Service Idea: " + AIIndustry.GetName(industryA) + " to " + AIIndustry.GetName(industryB))
			this.roadSystem = roadSystem
			this.industryA = industryA
			this.industryB = industryB
			this.isTown = isTown
			AIRoad.SetCurrentRoadType(roadSystem[0])
			local plan
			plan = BusStationPlan(null, AIIndustry.GetLocation(industryA), roadSystem, true)
			this.plans.append(plan)
			if (isTown)
				plan = BusStationPlan(null, AITown.GetLocation(industryB), roadSystem, false)
			else
				plan = BusStationPlan(null, AIIndustry.GetLocation(industryB), roadSystem, false)
			this.plans.append(plan)
			if (!this.plans[0].location || !this.plans[1].location) {
				this.plans = []
				return
			}
			plan = GaragePlan(null, this.plans[0].location, roadSystem)
			this.plans.append(plan)
			RailwAI.SetMaxAllowedLoan()
			if (this.plans[0].frontTile && this.plans[1].frontTile && this.plans[2].location && plan.Build(true)) {
				plan = RoadPlan(roadSystem, this.plans[0].getData(), this.plans[1].getData())
				this.plans.append(plan)
				if (plan.path1) {
					plan = RoadPlan(roadSystem, this.plans[0].getData(), this.plans[2].getData())
					this.plans.append(plan)
					if (plan.path1) {
						plan = RoadVehicleBuyPlan(roadSystem, this.plans[2])
						this.plans.append(plan)
						plan = RoadPlan(roadSystem, this.plans[0].getData(), this.plans[0].getData())
						this.plans.append(plan)
						plan = RoadPlan(roadSystem, this.plans[1].getData(), this.plans[1].getData())
						this.plans.append(plan)
						if (!this.plans[6].path1 || !this.plans[7].path1) {
							this.plans = []
							LogWarning("Could not plan to build roads around the industry")
						}
					} else {
						this.plans = []
						LogWarning("Could not plan to build roads towards the garage")
					}
				 } else {
					this.plans = []
					LogWarning("Could not plan to build roads")
				}
			} else {
				LogWarning("Could not plan both road stations or the garage")
				this.plans = []
			}
		}
	}
	
	function getType() {
		return "TruckServiceIdea"
	}
	
	function getMiscData() {
		return [roadSystem, industryA, industryB, isTown]
	}
	
	function Execute(fromSave = false) {
		AIRoad.SetCurrentRoadType(roadSystem[0])
		if (!Perform(fromSave)) {
			LogError("Idea could not be executed")
			return false
		}
		local breakdownsEnabled = (AIGameSettings.GetValue("difficulty.vehicle_breakdowns") >= 1)
		local vehicle = this.results[5]
		//First order is go to garage where it was built. Is used for cloning the vehicle later
		if (breakdownsEnabled) {
			AIOrder.AppendOrder(vehicle, this.results[2].location, AIOrder.OF_SERVICE_IF_NEEDED)
		} else {
			AIOrder.AppendOrder(vehicle, this.results[2].location, AIOrder.OF_NONE)
		}
		//Go to the first station
		AIOrder.AppendOrder(vehicle, this.results[0]["location"], AIOrder.OF_NONE);
		AIOrder.SetOrderFlags(vehicle, 1, AIOrder.OF_FULL_LOAD_ANY)
		//Don't go to the garage
		AIOrder.InsertConditionalOrder(vehicle, 0, 1)
		AIOrder.SetOrderCondition(vehicle, 0, AIOrder.OC_RELIABILITY);
		AIOrder.SetOrderCompareFunction(vehicle, 0, AIOrder.CF_MORE_THAN)
		if (breakdownsEnabled) {
			AIOrder.SetOrderCompareValue(vehicle, 0, 70);
		} else {
			AIOrder.SetOrderCompareValue(vehicle, 0, 0);
		}
		
		// Go to the second stop
		AIOrder.AppendOrder(vehicle, this.results[1]["location"], AIOrder.OF_NONE);
		
		local group = AIGroup.CreateGroup(AIVehicle.GetVehicleType(vehicle))
		AIGroup.MoveVehicle(group, vehicle)
		
		AIVehicle.StartStopVehicle(vehicle);
		return true
	}
}

//An Idea to build a bus service
class BusServiceIdea extends Idea {
	roadSystem = null;
	townA = null
	townB = null
	constructor(roadSystem, townA, townB, data = null) {
		if (data) {
			this.roadSystem = data.misc[0]
			this.townA = data.misc[1]
			this.townB = data.misc[2]
			this.results = data.results
			this.plans = []
			this.plans.append(BusStationPlan(data.plans[0], null, null))
			this.plans.append(BusStationPlan(data.plans[1], null, null))
			this.plans.append(GaragePlan(data.plans[2], null, null))
			this.plans.append(RoadPlan(null, null, null, data.plans[3]))
			this.plans.append(RoadPlan(null, null, null, data.plans[4]))
			this.plans.append(RoadVehicleBuyPlan(null, null, data.plans[5]))
			this.plans.append(RoadPlan(null, null, null, data.plans[6]))
			this.plans.append(RoadPlan(null, null, null, data.plans[7]))
		} else {
			this.plans = []
			this.results = []
			if (townA == townB) return //this can happen, it wouldn't be profitable
			LogWarning("Creating a Bus Service Idea: " + AITown.GetName(townA) + " to " + AITown.GetName(townB))
			this.roadSystem = roadSystem
			this.townA = townA
			this.townB = townB
			AIRoad.SetCurrentRoadType(roadSystem[0])
			local plan
			plan = BusStationPlan(null, AITown.GetLocation(townA), roadSystem, true)
			this.plans.append(plan)
			plan = BusStationPlan(null, AITown.GetLocation(townB), roadSystem, false)
			this.plans.append(plan)
			if (!this.plans[0].location || !this.plans[1].location) {
				this.plans = []
				return
			}
			plan = GaragePlan(null, this.plans[0].location, roadSystem)
			this.plans.append(plan)
			RailwAI.SetMaxAllowedLoan()
			if (this.plans[0].frontTile && this.plans[1].frontTile && this.plans[2].location && plan.Build(true)) {
				plan = RoadPlan(roadSystem, this.plans[0].getData(), this.plans[1].getData())
				this.plans.append(plan)
				if (plan.path1) {
					plan = RoadPlan(roadSystem, this.plans[0].getData(), this.plans[2].getData())
					this.plans.append(plan)
					if (plan.path1) {
						plan = RoadVehicleBuyPlan(roadSystem, this.plans[2])
						this.plans.append(plan)
						plan = RoadPlan(roadSystem, this.plans[0].getData(), this.plans[0].getData())
						this.plans.append(plan)
						plan = RoadPlan(roadSystem, this.plans[1].getData(), this.plans[1].getData())
						this.plans.append(plan)
						if (!this.plans[6].path1 || !this.plans[7].path1) {
							this.plans = []
							LogWarning("Could not plan to build roads within the city")
						}
					} else {
						this.plans = []
						LogWarning("Could not plan to build roads towards the garage")
					}
				 } else {
					this.plans = []
					LogWarning("Could not plan to build roads")
				}
			} else {
				LogWarning("Could not plan both road stations or the garage")
				this.plans = []
			}
		}
	}
	
	function getType() {
		return "BusServiceIdea"
	}
	
	function getMiscData() {
		return [roadSystem, townA, townB]
	}
	
	function Execute(fromSave = false) {
		AIRoad.SetCurrentRoadType(roadSystem[0])
		if (!Perform(fromSave)) {
			LogError("Idea could not be executed")
			return false
		}
		local breakdownsEnabled = (AIGameSettings.GetValue("difficulty.vehicle_breakdowns") >= 1)
		local vehicle = this.results[5]
		//First order is go to garage where it was built. Is used for cloning the bus later
		if (breakdownsEnabled) {
			AIOrder.AppendOrder(vehicle, this.results[2].location, AIOrder.OF_SERVICE_IF_NEEDED);
		} else {
			AIOrder.AppendOrder(vehicle, this.results[2].location, AIOrder.OF_NONE);
		}
		//Go to the first station
		//AIOrder.AppendOrder(vehicle, this.results[0]["location"], AIOrder.OF_NONE);
		AIOrder.AppendOrder(vehicle, this.results[0]["location"], AIOrder.OF_FULL_LOAD_ANY);
		//Don't go to the garage
		AIOrder.InsertConditionalOrder(vehicle, 0, 1)
		AIOrder.SetOrderCondition(vehicle, 0, AIOrder.OC_RELIABILITY)
		AIOrder.SetOrderCompareFunction(vehicle, 0, AIOrder.CF_MORE_THAN)
		if (breakdownsEnabled) {
			AIOrder.SetOrderCompareValue(vehicle, 0, 70)
		} else {
			AIOrder.SetOrderCompareValue(vehicle, 0, 0)
		}
		
		// Do a round trip if the vehicle is exactly 1% full, then we have a tourist!
		AIOrder.AppendConditionalOrder(vehicle, 2);
		AIOrder.SetOrderCondition(vehicle, 3, AIOrder.OC_LOAD_PERCENTAGE);
		AIOrder.SetOrderCompareFunction(vehicle, 3, AIOrder.CF_LESS_THAN)
		AIOrder.SetOrderCompareValue(vehicle, 10, 1);
		// Go to the second bus stop
		AIOrder.AppendOrder(vehicle, this.results[1]["location"], AIOrder.OF_NONE)
		
		local group = AIGroup.CreateGroup(AIVehicle.GetVehicleType(vehicle))
		AIGroup.MoveVehicle(group, vehicle)
		
		AIVehicle.StartStopVehicle(vehicle);
		return true
	}
}

//An Idea to extend a bus service
class BusServiceExtendIdea extends Idea {
	roadSystem = null
	currentEnd = null
	vehicle = null
	constructor(vehicle, data = null) {
		if (data) {
			this.roadSystem = data.misc[0]
			this.currentEnd = data.misc[1]
			this.vehicle = data.misc[2]
			this.results = data.results
			this.plans = []
			this.plans.append(BusStationPlan(data.plans[0], null, null))
			this.plans.append(RoadPlan(null, null, null, data.plans[1]))
		} else {
			this.plans = []
			this.results = []
			this.vehicle = vehicle
			this.roadSystem = GetRoadSystem(vehicle)
			AIRoad.SetCurrentRoadType(roadSystem[0])
			
			local stops = []
			for (local i = 0; i < AIOrder.GetOrderCount(vehicle); i++) {
				if (AIOrder.IsGotoStationOrder(vehicle, i)) {
					local dest = AIOrder.GetOrderDestination(vehicle, i)
					local check = true
					for (local j = 0; j < stops.len(); j++) {
						if (stops[j] == dest)
							check = false
					}
					if (check)
						stops.append(dest)
				}
			}
			
			local startLocation = stops[0]
			local currentEndLocation = stops[stops.len()-1]
			if (stops.len() > 2) return //route is already long enough
			currentEnd = GetDataOfBusStopAt(currentEndLocation)
			
			local allTowns = AITownList();
			allTowns.Valuate(AITown.GetDistanceManhattanToTile, currentEndLocation); allTowns.KeepAboveValue(15);
			allTowns.Valuate(AITown.GetDistanceManhattanToTile, currentEndLocation); allTowns.KeepBelowValue(40+AIBase.RandRange(64));
			allTowns.Valuate(AITown.IsTownFurtherFromAthanFromB, startLocation, currentEndLocation); allTowns.KeepAboveValue(0);

			if (allTowns.Count() < 1) {
				return
				LogWarning("Couldn't find any town to extend the bus service to")
			}
			allTowns.RemoveTop(AIBase.RandRange(allTowns.Count()))
			local town = allTowns.Begin()
			Log("Trying to extend a bus service towards " + AITown.GetName(town))
			
			local plan = BusStationPlan(null, AITown.GetLocation(town), roadSystem, false)
			this.plans.append(plan)
		
			if (this.plans[0].location) {
				plan = RoadPlan(roadSystem, currentEnd, this.plans[0].getData())
				plans.append(plan)
				if (!plan.path1) {
					this.plans = []
					LogWarning("Could not plan to build roads")
					return
				}
				plan = RoadPlan(roadSystem, this.plans[0].getData(), this.plans[0].getData())
				this.plans.append(plan)
				if (!plan.path1) {
					this.plans = []
					LogWarning("Could not plan to build roads")
					return
				}
			} else {
				LogWarning("Could not plan new bus station")
				this.plans = []
			}
		}
	}
	
	function getMiscData() {
		return [roadSystem, currentEnd, vehicle]
	}
	
	function getType() {
		return "BusServiceExtendIdea"
	}
	
	function Execute(fromSave = false) {
		Log("Executing plan")
		AIRoad.SetCurrentRoadType(roadSystem[0])
	
		if (!Perform(fromSave)) {
			LogError("Idea could not be executed")
			return false
		}
		local position = null
		for (local i = 0; i < AIOrder.GetOrderCount(vehicle); i++) {
			if (AIOrder.IsGotoStationOrder(vehicle, i) && AIOrder.GetOrderDestination(vehicle, i) == currentEnd["location"]) {
				position = i
			}
		}
		if (!position) {
			LogError("Could not find the previous station in the list of orders of the vehicle!")
			AIOrder.InsertOrder(vehicle, AIOrder.GetOrderCount(vehicle)/2 + 2, this.results[0]["location"], AIOrder.OF_NONE);
			return vehicle
		}
		
		
		//Go to the new bus station
		AIOrder.SetOrderFlags(vehicle, position, AIOrder.OF_NONE)
		AIOrder.InsertOrder(vehicle, position+1, currentEnd["location"], AIOrder.OF_NONE);
		AIOrder.InsertOrder(vehicle, position+1, this.results[0]["location"], AIOrder.OF_NONE);
		
		//An empty bus should turn around at the next bus station.
		if (position < 5 && AIOrder.IsConditionalOrder(vehicle, 3)) {
			position -= 1
			AIOrder.InsertConditionalOrder(vehicle, position, position+3)
		} else {
			AIOrder.InsertConditionalOrder(vehicle, position, position+2)
		}
		AIOrder.SetOrderCondition(vehicle, position, AIOrder.OC_LOAD_PERCENTAGE)
		AIOrder.SetOrderCompareFunction(vehicle, position, AIOrder.CF_LESS_THAN)
		AIOrder.SetOrderCompareValue(vehicle, position, 1);
		
		return vehicle
	}
}


//An Idea to build a road around two tiles (probably a double track level crossing), then remove the road on these tiles
class RoadDetourIdea extends Idea {
	roadSystem = null;
	tileA = null
	tileB = null
	delta = null
	constructor(tileA, tileB, data = null) {
		if (data) {
			this.roadSystem = data.misc[0]
			this.tileA = data.misc[1]
			this.tileB = data.misc[2]
			this.delta = data.misc[3]
			this.results = data.results
			this.plans = []
			this.plans.append(RoadPlan(null, null, null, data.plans[0]))
		} else {
			this.plans = []
			this.results = []
			if (AIRoad.HasRoadType(tileA, AIRoad.ROADTYPE_TRAM)) {
				this.roadSystem = [AIRoad.ROADTYPE_TRAM, 0, 110, true] //Tram, 110 km/h, no cargo, avoid level crossings
				if (AIRoad.HasRoadType(tileA, AIRoad.ROADTYPE_ROAD) && AIBase.RandRange(10) < 4)
					this.roadSystem = [AIRoad.ROADTYPE_ROAD, 0, 110, true] //randomly go for removing the road instead
			} else
				this.roadSystem = [AIRoad.ROADTYPE_ROAD, 0, 110, true]
			this.tileA = tileA
			this.tileB = tileB
			this.delta = tileB - tileA
			local table = {
				location = tileA
				frontTile = tileB
				backTile = tileA - this.delta
			};
			AIRoad.SetCurrentRoadType(roadSystem[0])
			local plan = RoadPlan(roadSystem, table, table)
			this.plans.append(plan)
			if (!plan.path1) {
				this.plans = []
				LogWarning("Could not plan to build roads")
			}
		}
	}
	
	function getType() {
		return "RoadDetourIdea"
	}
	
	function getMiscData() {
		return [roadSystem, this.tileA, this.tileB, this.delta]
	}
	
	function Execute(fromSave = false) {
		AIRoad.SetCurrentRoadType(roadSystem[0])
		if (!Perform(fromSave)) {
			LogError("Idea could not be executed")
			return false
		}
		LogTile("removing road at a level crossing", this.tileA)
		AIRoad.RemoveRoad(this.tileA - delta, this.tileB + delta)
		return true
	}
}
