require("plan.nut")
require("shippathfinder.nut")

/* A Ship system contains the following information:
 * system[0] -> nothing (reserved for Ship type)
 * system[1] -> Cargo Type
 * system[2] -> maximum speed
 * system[3] -> whether to avoid canals
 * system[4] -> Estimated transport capacity per month
 */
 
 function GetShipSystem(vehicle) {
	local cargoType = GetVehicleCargoType(vehicle)
	local maxSpeed = AIEngine.GetMaxSpeed(AIVehicle.GetEngineType(vehicle))
	local capacity = AIVehicle.GetCapacity(vehicle, cargoType)
	return [0, cargoType, maxSpeed, false, capacity]
}
 
function SelectShipSystem(cargo, transportPerMonth) {
	local l = AIList()
	l.AddItem(0,0) //default (only) boat type
	local maxSpeed = 0
	
	foreach (boatType, index in l) {
		local result = SelectShips(cargo, boatType, transportPerMonth)
		if (result[1] > maxSpeed)
			maxSpeed = result[1];
	}
	if (maxSpeed == 0) {
		LogError("No ships could be found for " + AICargo.GetCargoLabel(cargo) + "!")
		return false
	}
	//Log("Max speed " + maxSpeed)
	foreach (boatType, index in l) {
		return [boatType, cargo, maxSpeed, false, transportPerMonth] //There is only 1 boat type
	}
}

/* Select all Road vehicles  for a certain cargo and road type
 * Returned value contains the following information:
 * ships[0] -> AIEngine list containing vehicles
 * ships[1] -> Maximum speed of the vehicles
 * ships[2] -> Number of different available vehicles
 */
function SelectShips(cargo, boatType, transportPerMonth) {
	
	local ships = AIEngineList(AIVehicle.VT_WATER);
	ships.Valuate(AIEngine.IsBuildable); ships.KeepValue(1);
	ships.Valuate(AIEngine.CanRefitCargo, cargo); ships.KeepValue(1);
	ships.Valuate(AIEngine.GetCapacity); ships.KeepAboveValue(transportPerMonth);
	ships.Valuate(AIEngine.GetCapacity); ships.KeepBelowValue(transportPerMonth*6);
	
	if (ships.Count() == 0) {
		ships = AIEngineList(AIVehicle.VT_WATER);
		ships.Valuate(AIEngine.IsBuildable); ships.KeepValue(1);
		ships.Valuate(AIEngine.CanRefitCargo, cargo); ships.KeepValue(1);	
	}
	
	local maxshipspeed = 0;
	
	foreach (ship, value in ships) {
		local maxSpeed = AIEngine.GetMaxSpeed(ship)
		if (maxSpeed > maxshipspeed) {
			maxshipspeed = maxSpeed
		} else if (maxSpeed == 0) {
			maxshipspeed = 100000
		}
	}
	/*local minSpeed = maxshipspeed * 8 / 11
	if (maxshipspeed < 100000) {
		ships.Valuate(AIEngine.GetMaxSpeed); ships.KeepAboveValue(minSpeed);
	}*/
	return [ships, maxshipspeed, ships.Count()]
}

class ShipBuyPlan extends Plan {
	shipSystem = null
	garage = null
	vehicle = null
	constructor(shipSystem, garage, data = null) {
		if (data) {
			this.shipSystem = data.shipSystem
			this.garage = data.garage
			this.vehicle = null
		} else {
			this.shipSystem = shipSystem
			this.garage = garage.location
			this.vehicle = null
		}
	}
	
	function getData() {
		return {shipSystem = this.shipSystem, garage = this.garage}
	}
	
	function Build(testmode = false) {
		if (this.garage == null) {
			LogError("No dock to buy the ship!")
			return false
		}
		LogTile("Buying a ship at ", garage)
		vehicle = BuyShip(garage, shipSystem[1], shipSystem[4])
		if (vehicle == null && !testmode)
			vehicle = BuyShip(garage, shipSystem[1], shipSystem[4])
		if (vehicle == null && !testmode)
			vehicle = BuyShip(garage, shipSystem[1], shipSystem[4])
		if (vehicle == null) {
			LogError("Couldn't buy a ship.")
			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 ("Ship costs estimation: " + costs + " id " + vehicle)
		if (!AIVehicle.SellVehicle(vehicle))
			LogWarning("Failed selling vehicle")
		return costs
	}
	
	function Undo() {
		AIVehicle.SellVehicle(vehicle)	
	}
}

/* Buy a ship in an existing garage (depot not in Test Mode!)
 * garage = (TileIndex) location of the garage
 * cargoType = type of cargo
 */
function BuyShip(garage, cargoType, transportPerMonth) {
	local vehicles = SelectShips(cargoType, null, transportPerMonth)
	local ships = vehicles[0]
	//pick a random valid vehicle
	ships.Valuate(AIBase.RandRangeItem, 100)
	ships.Sort(AIList.SORT_BY_VALUE, true);
	if (!AIMarine.IsWaterDepotTile(garage)) {
		local depots = AIDepotList(AITile.TRANSPORT_WATER)
		garage = depots.Begin()
	}
	local vehicle = AIVehicle.BuildVehicle(garage, ships.Begin())
	if (!AIVehicle.IsValidVehicle(vehicle)) {
		LogWarning("I cannot buy a ship")
		LogWarning(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 ship 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 WaterPlan extends Plan {
	path1 = null
	tiles1 = null
	tiles2 = null
	from = null
	towards = null
	avoidArea = []
	counters = [1, 1] //tiles1, tiles2
	designSpeed = 160
	randomSeed = 0
	avoidCanals = false
	buoys = []
	rePlan = false
	
	constructor(shipSystem, station1, station2, refreshPlanning = false, 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.avoidCanals = data.avoidCanals
			this.buoys = data.buoys
			this.rePlan = data.rePlan
		} else {
			this.from = station1
			this.towards = station2
			this.path1 = null
			this.tiles1 = null
			this.tiles2 = null
			this.counters = [1, 1]
			this.buoys = []
			this.rePlan = refreshPlanning
			this.designSpeed = shipSystem[1]
			this.avoidCanals = shipSystem[3]
			randomSeed = AIBase.RandRange(1000)
			this.path1 = FindPath(this.from, this.towards)
			if (!this.path1) {
				LogError("Failed finding a suitable path to build canals")
				return false
			}
			this.tiles1 = GetTilesOn(this.path1)
			this.tiles2 = null //TODO, ships should be able to turn around at the destination
		}
	}
	
	function getData() {
		return {tiles1 = this.tiles1,
			tiles2 = this.tiles2,
			from = this.from,
			towards = this.towards,
			counters = this.counters,
			designSpeed = this.designSpeed,
			randomSeed = this.randomSeed,
			buoys = this.buoys,
			rePlan = this.rePlan}
	}
	
		
	function Build(testmode = false) {
		if (!testmode && this.rePlan) {
			this.path1 = FindPath(this.from, this.towards)
			if (!this.path1) {
				LogError("Failed finding a suitable path to build water")
				return false
			}
			this.tiles1 = GetTilesOn(this.path1)
		}
		
		if (!BuildWaterOnTiles(this.tiles1, false, testmode, this.counters, 0)) {
			LogError("Building first route failed")
			return false
		}
		return this.buoys //success
	
	}
	
	function Undo() {
		//Removing canals is not necessary
	}
	
	
	function FindPath(from, to) {
		local pathfinder = Ship();

		
		local u = pathfinder.cost.tile;
		//pathfinder.cost.slope = 2*u;
		//pathfinder.cost.coast = 0.1*u;
		
		local avoidTiles = []
		avoidTiles.append(from.location);
		foreach (tile in from.areaTiles) {
			avoidTiles.append(tile)
		}
		avoidTiles.append(from.secondTile);
		avoidTiles.append(to.location);
		foreach (tile in to.areaTiles) {
			avoidTiles.append(tile)
		}
		avoidTiles.append(to.secondTile);
		
		pathfinder.InitializePath(to.frontTile, from.frontTile, avoidTiles);
		
		
		local path = false;
		LogWarning("Starting water pathfinder at " + RailwAI.GetTick())
		LogTiles("path attempt from ", from.frontTile, " to ", to.frontTile)
		path = pathfinder.FindPath(40000);
		LogWarning("Ending pathfinder at " + RailwAI.GetTick())
		LogWarning("Starting water pathfinder2 at " + RailwAI.GetTick())
		LogTiles("path attempt from ", from.frontTile, " to ", to.frontTile)
		path = pathfinder.FindPath2(40000);
		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()
			local 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 BuildWaterOnTiles(path, backwards = false, testmode = false, counters = null, cIndex = 0) {
		local i = 1
		local s = 1
		//restore starting value if provided
		if (!testmode && counters) {
			i = counters[cIndex]
		}
		local lastBuoy = null
		for (; i < path.len(); i++) {
			local tile = path[i][0]
			local prev = path[i-1][0]
			local direction = path[i][2]
			local prev_direction = path[i-1][2]
			local succes = true
			s++
			if (!AITile.IsWaterTile(tile)) {
				if (AITile.GetSlope(tile) != AITile.SLOPE_FLAT) {
					if (lastBuoy == prev) {
						this.buoys.pop()
						s = 16
						AIMarine.RemoveBuoy(lastBuoy)
						lastBuoy = null
					}
					if (!AIMarine.BuildLock(tile)) {
						succes = false
					}
				} else if (AITile.GetMaxHeight(tile) > 0) {
					if (!AIMarine.BuildCanal(tile)) {
						succes = false
					}
				}
				if (!succes) {
					LogTile("Failed building something on", tile)
					LogError("Failed building water route: " + AIError.GetLastErrorString());
					return false;
				}	
			}
			if (!testmode) {
				if (AIMarine.IsBuoyTile(tile)) {
					this.buoys.append(tile)
					lastBuoy = tile
					s = 1
				}
				if (s >= 16 || (direction != prev_direction && s > 2 && (i < 16 || i > path.len()-16 || AITile.GetMaxHeight(tile) > 0))) {
					if (AIMarine.BuildBuoy(tile)) {
						this.buoys.append(tile)
						lastBuoy = tile
						s = 1
					} else {
						s = 16;
					}
				}
			}
		}
		//Log("Build Water on Tiles returning true")
		return true
	}
}