//An Idea contains a list of plans, that can be performed
//If the plans are executed succesfully, the list of results is filled with existing instances
class Idea {
	plans = []
	results = []
	
	constructor(data = null) {
		LogWarning("Creating a plan without content is just rubbish")
	}
	
	function createIdeaFromSaved(data) {
		Log("I was busy with a: " + data.type)
		if (data.type == "TrainServiceIdea") {
			return TrainServiceIdea(null, null, null, data)
		} else if (data.type == "CargoTrainServiceIdea") {
			return CargoTrainServiceIdea(null, null, null, data)
		} else if (data.type == "TrainServiceExtendIdea") {
			return TrainServiceExtendIdea(null, data)
		} else if (data.type == "BusServiceIdea") {
			return BusServiceIdea(null, null, null, data)
		} else if (data.type == "TruckServiceIdea") {
			return TruckServiceIdea(null, null, null, false, data)
		}  else if (data.type == "BusServiceExtendIdea") {
			return BusServiceExtendIdea(null, data)
		}   else if (data.type == "RoadDetourIdea") {
			return RoadDetourIdea(null, null, data)
		} 
		return null
	}
	
	function getType() {
		return "Idea"
	}
	
	function getMiscData() {
		return ""
	}
	
	function getData() {
		Log("Getting data from an idea")
		local savePlans = []
		foreach (plan in this.plans) {
			savePlans.append(plan.getData())
			//Log("Plan")
			//local item = plan.getData()
			//LogWarning(item)
			//foreach (key, a in item)
			//	Log(key + ":" + a)
		}
		//LogWarning(this.getType())
		//LogError("saveplans")
		//foreach (item in savePlans) {
		//	LogWarning(item)
		//	foreach (key, a in item)
		//		Log(key + ":" + a)
		//}
		//LogError("results")
		//foreach (item in this.results) {
		//	LogWarning(item)
		//}
		//LogError("misc data")
		//foreach (item in this.getMiscData()) {
		//	LogWarning(item)
		//}
		local table = {type = this.getType(),
				plans = savePlans,
				results = this.results,
				misc = this.getMiscData()
			}
			
		return table
	}
	
	//Execute the idea. It could be more than just performing the plans
	//For example, we want to assign vehicle orders
	function Execute(fromSave = false) {
		RailwAI.SetMaxAllowedLoan()
		Perform(fromSave)
		RailwAI.SetMinLoan()
	}
	
	//Test the idea for feasibility in test mode
	function CheckFeasibility() {
		local costs = 0
		foreach (plan in this.plans) {
			local add = plan.EstimateCosts()
			if (add == false) {
				LogWarning("A plan was indicated as not feasible")
				return false
			}
			costs += add
		}
		Log("Estimated costs of my idea: " + costs)
		if (costs + GetFinancialBuffer() > AICompany.GetBankBalance(AICompany.COMPANY_SELF)) {
			LogWarning("I do not have enough money to make my dreams come true")
			return false
		}
		return true
	}
	
	//perform our plans, first check feasibility
	//If test mode succeeds, really perform the plans
	function Perform(fromSave = false) {
		if (!fromSave && !CheckFeasibility()) {
			foreach (plan in this.plans) {
				plan.Cancel()
			}
			return false
		}
		local accountant = AIAccounting();
		Log("Entering executive mode for this idea")
		local numOfPlans = this.plans.len()
		for (local i = this.results.len(); i < numOfPlans; i++) {
			local plan = this.plans[i]
			local result = plan.Build(false)
			if (result == false || result == null) {
				LogError("result: " + result)
				LogError("Something went wrong while executing a plan. Undoing.")
				LogWarning(AIError.GetLastErrorString())
				for (local j = 0; j < i; j++) {
					plan = this.plans[j]
					plan.Undo()
				}
				return false
			}
			this.results.append(result)
		}
		
		Log("Actual costs: " + accountant.GetCosts())
		return true
	}
}


//An Idea to build a truck service (cargo via Road)
class ShipServiceIdea extends Idea {
	shipSystem = null;
	industryA = null
	industryB = null
	constructor(shipSystem, industryA, industryB, data = null) {
		if (data) {
			this.shipSystem = data.misc[0]
			this.industryA = data.misc[1]
			this.industryB = data.misc[2]
			this.results = data.results
			this.plans = []
			this.plans.append(DockPlan(data.plans[0], null, null, null))
			this.plans.append(DockPlan(data.plans[1], null, null, null))
			this.plans.append(WaterGaragePlan(data.plans[2], null, null))
			this.plans.append(WaterPlan(null, null, null, null, data.plans[3]))
			this.plans.append(WaterPlan(null, null, null, null, data.plans[4]))
			this.plans.append(ShipBuyPlan(null, null, data.plans[5]))
		} else {
			this.plans = []
			this.results = []
			LogWarning("Creating a " + AICargo.GetCargoLabel(shipSystem[1]) + " Ship Service Idea: " + AIIndustry.GetName(industryA) + " to " + AIIndustry.GetName(industryB))
			this.shipSystem = shipSystem
			this.industryA = industryA
			this.industryB = industryB
			AIRoad.SetCurrentRoadType(shipSystem[0])
			local plan
			plan = DockPlan(null, AIIndustry.GetLocation(industryA), industryA, shipSystem, true)
			this.plans.append(plan)
			plan = DockPlan(null, AIIndustry.GetLocation(industryB), industryB, shipSystem, false)
			this.plans.append(plan)
			if (!this.plans[0].location || !this.plans[1].location) {
				this.plans = []
				return
			}
			
			plan = WaterGaragePlan(null, this.plans[0].location, shipSystem)
			this.plans.append(plan)
			if (this.plans[0].frontTile && this.plans[1].frontTile && this.plans[2].location) {
				plan = WaterPlan(shipSystem, this.plans[0].getData(), this.plans[1].getData(), false)
				this.plans.append(plan)
				if (plan.path1) {
					plan = WaterPlan(shipSystem, this.plans[2].getData(), this.plans[0].getData(), true)
					this.plans.append(plan)
					if (plan.path1) {
						plan = ShipBuyPlan(shipSystem, this.plans[2])
						this.plans.append(plan)
					} else {
						this.plans[2].Cancel()
						this.plans = []
						LogWarning("Could not plan to build water")
					}
				} else {
					this.plans[2].Cancel()
					this.plans = []
					LogWarning("Could not plan to build water")
				}
			} else {
				LogWarning("Could not plan both docks or the garage")
				this.plans = []
			}
		}
	}
	
	function getType() {
		return "ShipServiceIdea"
	}
	
	function getMiscData() {
		return [shipSystem, industryA, industryB]
	}
	
	function Execute(fromSave = false) {
		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 ship 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, 1, 1)
		AIOrder.SetOrderCondition(vehicle, 1, AIOrder.OC_RELIABILITY);
		AIOrder.SetOrderCompareFunction(vehicle, 1, AIOrder.CF_MORE_THAN);
		if (breakdownsEnabled) {
			AIOrder.SetOrderCompareValue(vehicle, 1, 70)
		} else {
			AIOrder.SetOrderCompareValue(vehicle, 1, 0)
		}
		//Route from garage to first station
		local waypoints = this.results[4]
		for (local i = 0; i < waypoints.len(); i++) {
			AIOrder.InsertOrder(vehicle, 2+i, waypoints[i], AIOrder.OF_NONE);
		}
		//route towards the second stop
		waypoints = this.results[3]
		for (local i = 0; i < waypoints.len(); i++) {
			AIOrder.AppendOrder(vehicle, waypoints[i], AIOrder.OF_NONE);
		}
		// Go to the second stop
		AIOrder.AppendOrder(vehicle, this.results[1]["location"], AIOrder.OF_NONE);
		//route from second stop to first stop
		for (local i = waypoints.len() - 1; i >= 0; i--) {
			AIOrder.AppendOrder(vehicle, waypoints[i], AIOrder.OF_NONE);
		}
		
		//move order "don't go to garage" to the end of the list
		AIOrder.MoveOrder(vehicle, 1, AIOrder.GetOrderCount(vehicle)-1)
		
		//route from first stop to garage (required for maintenance)
		local waypoints = this.results[4]
		for (local i = waypoints.len() - 1; i >= 0; i--) {
			AIOrder.AppendOrder(vehicle, waypoints[i], AIOrder.OF_NONE);
		}		
		
		local group = AIGroup.CreateGroup(AIVehicle.GetVehicleType(vehicle))
		AIGroup.MoveVehicle(group, vehicle)
		
		AIVehicle.StartStopVehicle(vehicle);
		return {shipSystem = shipSystem, stops = [this.results[0], this.results[1]], ships = []}	
	}
}