enum Direction {
	invalid, SW, SE, NE, NW
}
const PASS = 0
const CARGO = 1
const RAIL = 2
const ROAD = 3
const SHIP = 4

class RailwAI extends AIController 
{
	function Start();
	// Persistent variables (that will be saved)
	idea = null //The idea that is currently being planned or executed
	lastYearlyCheck = 0 //save the year in which this company did a check on the network
	lastRegularCheck = 0 //save the day on which this company did a check on the network
	newCompany = true //will set to false when loading a game
	chances = [7, 7, 7, 7, 7] //Chances that we will use passengers / cargo / rail / road / ships
	inhabitantsPerBusStop = 500
	financialBuffer = 0
	
	//non-persistent variables
	myTiles = [] //Keep track of the tiles this company has ownership of
	vehicleLocations = null //Keep track of vehicle locations, to see if those are locked in somewhere
	
	//empty constructor
	constructor() {}
}

require("company.nut");
require("rail.nut");
require("pathfinder.nut");
require("roadpathfinder.nut");
require("log.nut");
require("util.nut");
require("world.nut");
require("station.nut");
require("depot.nut");
require("train.nut");
require("build.nut");
require("idea.nut");
require("idea_rail.nut");
require("idea_road.nut");
require("plan.nut");
require("finance.nut");
require("town.nut");
require("road.nut");
require("ship.nut");
require("garage.nut");
require("service.nut");
require("strategy.nut");
require("vehicle.nut");

function RailwAI::Save()
{
	local data = null
	if (this.idea)
		data = this.idea.getData()
	local table = {
		lastYearlyCheck = this.lastYearlyCheck
		lastRegularCheck = this.lastRegularCheck
		inhabitantsPerBusStop = this.inhabitantsPerBusStop
		financialBuffer = this.financialBuffer
		chances = this.chances
		idea = data
	};
	return table;
}

function RailwAI::Load(version, data)
{
	Log(" Loaded");
	this.newCompany = false
	this.lastYearlyCheck = data.rawget("lastYearlyCheck")
	this.lastRegularCheck = data.rawget("lastRegularCheck")
	this.inhabitantsPerBusStop = data.rawget("inhabitantsPerBusStop")
	this.financialBuffer = data.rawget("financialBuffer")
	this.chances = data.rawget("chances")
	local data = data.rawget("idea")
	if (data) {
		this.idea = Idea.createIdeaFromSaved(data)
	}
}

function RailwAI::PerformChecks() {
	//Do the regular (fast) stuff
	local now = AIDate.GetCurrentDate()
	local currentMonth = AIDate.GetMonth(now)
	local currentYear = AIDate.GetYear(now)
	local ret = true
	if (currentMonth != this.lastRegularCheck) {
		 ret = PerformRegularChecks()
		 this.lastRegularCheck = currentMonth
	}
	//No yearly checks if we don't have multiple vehicles yet
	if (currentYear == this.lastYearlyCheck) return ret
	//Don't always do the checks when we reach this function
	if (AIBase.RandRange(10) < 5) return ret
	//Do what we should do approximately every year
	if (AIVehicleList().Count() > 1) {
		ScanMap()
	}
	this.PerformYearlyChecks()
	this.lastYearlyCheck = currentYear
	return ret
}

function RailwAI::PerformRegularChecks() {
	this.RedistributeVehicles()
	this.SetMaxAllowedLoan()
	local ret = this.CheckServices() //will return if everything is ok
	this.SetMinLoan()
	IncreaseLoan()
	this.CheckNonMovingVehicles()
	this.ConnectRailBridges()
	this.BuildHQ()
	return ret
}

/* checks that will be done every year
 * The AI can fix improper roads, enhance some routes, sell vehicles that do not earn money
 */
function RailwAI::PerformYearlyChecks() {
	this.IncreaseLoan()
	this.HandleLevelCrossings()
	this.HandleLocks()
	this.CheckUnprofitableVehicles()
	ConnectAllDepots()
	this.RemoveUnservedStations()
	this.SetMinLoan()
}

//Scan the map and save what tiles are mine
function RailwAI::ScanMap() {
	this.myTiles = []
	Log("Scanning the complete map. This can take some time")
	local myCompany = AICompany.ResolveCompanyID(AICompany.COMPANY_SELF)
	local Xmax = AIMap.GetMapSizeX()
	local Ymax = AIMap.GetMapSizeY()
	for (local x = 1; x < Xmax; x++) {
		for (local y = 1; y < Ymax; y++) {
			local tile = AIMap.GetTileIndex(x,y)
			if (AITile.GetOwner(tile) == myCompany)
				this.myTiles.append(tile)
		}
	}
	Log("Completed scanning the map. I am the owner of " + this.myTiles.len() + " tiles")
}



function RailwAI::Start() {
	//Initializations of non-persistent variables
	this.vehicleLocations = AIList()
	this.myTiles = []
	
	//place to put debug output for testing things in a saved game
	LogTile(AITile.IsWaterTile(dXY(127,462)), dXY(127,462))
	LogTile(AITile.IsWaterTile(dXY(128,462)), dXY(128,462))
	LogTile(AIStation.GetStationID(dXY(243,467)), dXY(243,467), "stationcount")
	LogTile(AIStation.GetStationID(dXY(243,468)), dXY(243,468), "stationcount")

	
	//Found new company if this we are not loading from a savegame
	if (this.newCompany) {
		SetCompanyName();
		Log("A new company was founded! ");
		Log("I have money: " + GetBankBalance())
		//AICompany.SetLoanAmount(AICompany.GetMaxLoanAmount());
		//Log("The new company has decided to get as much debt als possible");
	}
	Log("This company is named " + AICompany.GetName(AICompany.ResolveCompanyID(AICompany.COMPANY_SELF)))

	if (AIGameSettings.GetValue("difficulty.vehicle_breakdowns") >= 1) {
		AICompany.SetAutoRenewMonths(0) //replace vehicles when they are too old
		AICompany.SetAutoRenewStatus(true);
		AICompany.SetAutoRenewMoney(5000);
	} else {
		AICompany.SetAutoRenewStatus(false);
	}
	
	
	//Check if we were executing a plan when loading a savegame
	if (this.idea) {
		this.idea.Execute(true)
	}
	
	while (true) {
		local vehicleList = AIVehicleList()
		Log("I have money: " + GetBankBalance())
		Log("I Have " + vehicleList.Count() + " vehicles")
		
		local health = PerformChecks()
		if (!health) {
			Log("Not all connections are well served")
		}
		
		if (RailwAI.GetSetting("use_trains") && RailwAI.GetSetting("use_pass") && MayExtendRailway()) {
			this.extendPassengerTrainService()
		
		}
		if (RailwAI.GetSetting("use_road") && RailwAI.GetSetting("use_pass") && MayCreateNewBusline()) {
			this.extendBusService()
		}
		if (RailwAI.GetSetting("use_road") && RailwAI.GetSetting("use_pass") && MayCreateNewBusline()) {
			this.startNewBusService()
		}
		
		if (RailwAI.GetSetting("use_cargo")) {
			this.startNewCargoService()
		}
		if (RailwAI.GetSetting("use_cargo")) {
			this.startNewCargoService()
		}
		this.PerformChecks()
		if (RailwAI.GetSetting("use_road") && RailwAI.GetSetting("use_pass") && MayCreateNewBusline()) {
			this.startNewBusService()
		}
		
		if (RailwAI.GetSetting("use_trains") && RailwAI.GetSetting("use_pass") && MayCreateNewRailway()) {
			this.startNewPassengerService()
		}
		
		if (RailwAI.GetSetting("use_cargo")) {
			this.startNewCargoService()
		}
		this.Sleep(AIBase.RandRange(300));
	
	}
}	

function RailwAI::extendPassengerTrainService() {
	Log("Can I extend a train route transporting passengers?")
	local groupList = AIGroupList()
	local everythingIsGood = true
	local numOfGroups = groupList.Count()
	for (local group = groupList.Begin(); groupList.HasNext(); group = groupList.Next()) {
		local vehicleType = AIGroup.GetVehicleType(group)
		local list = AIVehicleList_Group(group)
		local vehicle = list.Begin()
		local vehicleType = AIVehicle.GetVehicleType(vehicle)
		local cargoType = GetVehicleCargoType(vehicle)
		if (vehicleType == AIVehicle.VT_RAIL && AICargo.HasCargoClass(cargoType, AICargo.CC_PASSENGERS)) {
			idea = TrainServiceExtendIdea(vehicle)
			if (idea.plans.len() > 1) {
				local trainService = idea.Execute()
			}
			idea = null
			return true
		}
	}
}
function RailwAI::extendBusService() {
	Log("Can I extend a bus route?")
	local groupList = AIGroupList()
	local everythingIsGood = true
	local numOfGroups = groupList.Count()
	for (local group = groupList.Begin(); groupList.HasNext(); group = groupList.Next()) {
		local vehicleType = AIGroup.GetVehicleType(group)
		local list = AIVehicleList_Group(group)
		local vehicle = list.Begin()
		local vehicleType = AIVehicle.GetVehicleType(vehicle)
		local cargoType = GetVehicleCargoType(vehicle)
		if (vehicleType == AIVehicle.VT_ROAD && AICargo.HasCargoClass(cargoType, AICargo.CC_PASSENGERS)) {
			idea = BusServiceExtendIdea(vehicle)
			if (idea.plans.len() > 1) {
				local trainService = idea.Execute()
			}
			idea = null
			return true
		}
	}
}

function RailwAI::startNewPassengerService() {
	Log("Can I build a train route to transport passengers?")
	local trainsystem = SelectTrainSystem(GetCargoID(AICargo.CC_PASSENGERS))
	if (!trainsystem)
		return
	AIRail.SetCurrentRailType(trainsystem[0])
	
	//List all largest towns
	local towns = AITownList()
	towns.Valuate(AITown.GetPopulation)
	towns.KeepTop(towns.Count()/3+1)
	towns.Valuate(AITown.GetPopulation)
	towns.RemoveTop(AIBase.RandRange(towns.Count()))
	local townA = towns.Begin()
	
	towns = AITownList()
	towns.Valuate(AITown.GetDistanceManhattanToTile, AITown.GetLocation(townA))
	towns.KeepAboveValue(10)
	towns.Valuate(AITown.GetDistanceManhattanToTile, AITown.GetLocation(townA))
	towns.KeepBelowValue(120)
	towns.Valuate(AITown.GetPopulation)
	towns.KeepTop(towns.Count()/2+1)
	
	towns.RemoveTop(AIBase.RandRange(towns.Count()))
	local townB = towns.Begin()
	if (IsConnectedByLand(AITown.GetLocation(townA), AITown.GetLocation(townB))) {
		if (AITown.GetDistanceManhattanToTile(townA, AITown.GetLocation(townB)) < 30) {
			LogError("I should use buses between " + AITown.GetName(townA) + " and " + AITown.GetName(townB))
		} else {
			idea = TrainServiceIdea(trainsystem, townA, townB);
			if (idea.plans.len() > 2) {
				local trainService = idea.Execute()
				if (trainService) {
					//celebrate
				}
			}
			idea = null
		}
	} else {
		LogWarning("I shouldn't create a train route between " + AITown.GetName(townA) + " and " + AITown.GetName(townB))
	}
			
}

function RailwAI::startNewBusService() {
	Log("Can I build a train route to transport passengers?")
	local cargo = GetCargoID(AICargo.CC_PASSENGERS)
	local roadSystem = SelectRoadSystem(cargo)
	if (!roadSystem)
		return
	AIRoad.SetCurrentRoadType(roadSystem[0])
	
	//List all towns
	local towns = AITownList()
	towns.Valuate(AITown.GetPopulation)
	towns.KeepTop(towns.Count()/2+1)
	towns.Valuate(AITown.GetPopulation)
	towns.KeepAboveValue(this.inhabitantsPerBusStop)
	if (towns.Count() == 0) {
		this.AdjustInhabitantsPerBusStop(-50)
		return
	}
	towns.Valuate(AITown.GetPopulation)
	towns.RemoveTop(AIBase.RandRange(towns.Count()))
	local townA = towns.Begin()
	
	//maximum of 1 station per 500 inhabitants in this town (rounded down)
	if (AITown.GetPopulation(townA) / this.inhabitantsPerBusStop <= NumStationsInTown(townA, cargo)) {
		this.AdjustInhabitantsPerBusStop(-10)
		return
	}
	
	towns = AITownList()
	towns.Valuate(AITown.GetDistanceManhattanToTile, AITown.GetLocation(townA))
	towns.KeepAboveValue(10)
	towns.Valuate(AITown.GetDistanceManhattanToTile, AITown.GetLocation(townA))
	towns.KeepBelowValue(60)
	towns.Valuate(AITown.GetDistanceManhattanToTile, AITown.GetLocation(townA))
	//towns.KeepBottom(5)
	//towns.Valuate(AITown.GetPopulation)
	//towns.KeepTop(towns.Count()/2+3)
	
	towns.RemoveTop(AIBase.RandRange(towns.Count()))
	local townB = towns.Begin()
	//maximum of 1 station per 250 inhabitants in this town (rounded up)
	if (IsConnectedByLand(AITown.GetLocation(townA), AITown.GetLocation(townB)) &&
		AITown.GetPopulation(townB) * 2 / this.inhabitantsPerBusStop >= NumStationsInTown(townB, cargo)) {
		idea = BusServiceIdea(roadSystem, townA, townB);
		if (idea.plans.len() > 2) {
			local roadService = idea.Execute()
		}
		idea = null
		this.AdjustInhabitantsPerBusStop(10)
	} else {
		LogWarning("I shouldn't create a bus route between " + AITown.GetName(townA) + " and " + AITown.GetName(townB))
	}
			
}

	
function RailwAI::startNewCargoService() {
	Log("Can I build a route to transport cargo?")
	local cargoType = null
	local cargoSelected = false
	local trainsystem = null
	local roadsystem = null
	local shipsystem = null
	local industryA = null
	local industries = null
	for (local i = 0; i < 300 && !cargoSelected; i++) {
		local cargos = AICargoList()
		cargos.RemoveTop(AIBase.RandRange(cargos.Count()))
		cargoType = cargos.Begin()
		if (AICargo.HasCargoClass(cargoType, AICargo.CC_PASSENGERS) || AICargo.HasCargoClass(cargoType, AICargo.CC_MAIL)) continue; //we want real cargo
		
		industries = AIIndustryList_CargoProducing(cargoType)
		industries.Valuate(AIIndustry.GetLastMonthTransportedPercentage, cargoType)
		industries.KeepBelowValue(25)
		industries.Valuate(AIIndustry.GetLastMonthProduction, cargoType)
		industries.KeepAboveValue(5)
		industries.Valuate(AIIndustry.GetLastMonthProduction, cargoType)
		industries.KeepTop(industries.Count()/2+1)
		Log(AICargo.GetCargoLabel(cargoType) + " has " + industries.Count() + " industries producing")
		industries.RemoveTop(AIBase.RandRange(industries.Count()))
		if (industries.Count() > 0) {
			industryA = industries.Begin()
			if (AIIndustry.GetLastMonthTransportedPercentage(industryA, cargoType) < AIBase.RandRange(100)*(50+AIBase.RandRange(50))) {
				local transportPerMonth = AIIndustry.GetLastMonthProduction(industryA, cargoType) - AIIndustry.GetLastMonthTransported(industryA, cargoType)
				if (transportPerMonth > 15) {
					cargoSelected = true
					if (RailwAI.GetSetting("use_trains") && MayCreateNewRailway())
					trainsystem = SelectTrainSystem(cargoType)
					if (RailwAI.GetSetting("use_road") && MayCreateNewBusline())
					roadsystem = SelectTrainSystem(cargoType)
					if (RailwAI.GetSetting("use_ships") && MayCreateNewBusline())
					shipsystem = SelectShipSystem(cargoType, transportPerMonth)
					if (!trainsystem && !roadsystem && !shipsystem) return
					//set a legal railtype and roadtype
					if (trainsystem)
						AIRail.SetCurrentRailType(trainsystem[0])
					if (roadsystem)
						AIRoad.SetCurrentRoadType(roadsystem[0])
				}
			}
		}
	}
	if (!cargoSelected)
		return
	local locationB = null
	local nameB = ""
	local industryOrTownB = null
	local isTown = false
	if (AICargo.GetTownEffect(cargoType) == AICargo.TE_NONE || AICargo.GetTownEffect(cargoType) == AICargo.TE_WATER){ 
		industries = AIIndustryList_CargoAccepting(cargoType)
		/*industries.Valuate(AIIndustry.GetDistanceManhattanToTile, AIIndustry.GetLocation(industryA))
		industries.KeepAboveValue(20)
		industries.Valuate(AIIndustry.GetDistanceManhattanToTile, AIIndustry.GetLocation(industryA))
		industries.KeepBelowValue(180)*/
		Log(AICargo.GetCargoLabel(cargoType) + " has " + industries.Count() + " industries accepting")
		if (industries.Count() < 1)
			return
		industries.RemoveTop(AIBase.RandRange(industries.Count()))
		industryOrTownB = industries.Begin()
		locationB = AIIndustry.GetLocation(industryOrTownB)
		nameB = AIIndustry.GetName(industryOrTownB)
	} else {
		local towns = AITownList()
		towns.Valuate(AITown.GetPopulation)
		towns.KeepAboveValue(200+AIBase.RandRange(500))
		towns.Valuate(AITown.GetDistanceManhattanToTile, AIIndustry.GetLocation(industryA))
		towns.KeepAboveValue(10+AIBase.RandRange(80))
		towns.Valuate(AITown.GetDistanceManhattanToTile, AIIndustry.GetLocation(industryA))
		towns.KeepBelowValue(180+AIBase.RandRange(500))
		if (towns.Count() < 1)
			return
		towns.RemoveTop(AIBase.RandRange(towns.Count()))
		industryOrTownB = towns.Begin()
		locationB = AITown.GetLocation(industryOrTownB)
		nameB = AITown.GetName(industryOrTownB)
		isTown = true
	}
	if (IsConnectedByLand(AIIndustry.GetLocation(industryA), locationB)) {
		if (!trainsystem || AIIndustry.GetDistanceManhattanToTile(industryA, locationB) < 60) {
			if (roadsystem) {
				idea = TruckServiceIdea(roadsystem, industryA, industryOrTownB, isTown)
				if (idea.plans.len() > 2) {
					local trainService = idea.Execute()
				}
				idea = null
			}
		} else {
			idea = CargoTrainServiceIdea(trainsystem, industryA, industryOrTownB);
			if (idea.plans.len() > 2) {
				local trainService = idea.Execute()
			}
			idea = null
		}
	} else if (shipsystem) {
		LogWarning("The industries should not be connected via land: " + AIIndustry.GetName(industryA) + " and " + nameB)
		idea = ShipServiceIdea(shipsystem, industryA, industryOrTownB)
		if (idea.plans.len() > 2) {
			local trainService = idea.Execute()
		}
		idea = null
	}
}