-- Copyright Declaration
-- This file is part of the "Faction Commander Mod", a personal production of rgggclp.
-- You could copy, modify and distribute this file only if you keep this header intact as the originality of this file.
-- If you have questions, feel free to contact me at rggg_clp@163.com
-- 版权声明
-- 这个程序是“阵营指挥官”的一部分，它是Chen Lin Peng的个人作品
-- 你可以拷贝、修改和再次发布这个文件，不过你必须保留这个版权声明，它表示这个文件的来源，谢谢

class "FactionChanger"
function FactionChanger:__init()
	self.actorToFaction = {
		["actor_csky"]		= "csky",
		["actor_stalker"]	= "stalker",
		["actor_dolg"]		= "dolg",
		["actor_freedom"]	= "freedom",
		["actor_bandit"]	= "bandit",
		["actor"]			= "none",
	}
end

function FactionChanger:__finalize()
end

function FactionChanger:_check()
	return level.present() and db.actor ~= nil and db.actor:alive()
end

function FactionChanger:joinFaction( faction )
	if self:_check() then
		--self:setFactionGoodwill( 0 )
		sim_board.get_sim_board():set_actor_community( faction )
		self:setFactionGoodwill( 1000 )
	end
end

function FactionChanger:getFaction()
	if self:_check() then
		return self.actorToFaction[ db.actor:character_community() ]
	end
	return "none"
end

function FactionChanger:setFactionGoodwill( goodwill )
	if self:_check() then
		local faction = self.actorToFaction[ db.actor:character_community() ]
		if faction ~= "none" then
			db.actor:set_community_goodwill( faction, goodwill )
		end
	end
end

function FactionChanger:getFactionGoodwill()
	if self:_check() then
		local faction = self.actorToFaction[ db.actor:character_community() ]
		if faction ~= "none" then
			return db.actor:community_goodwill( faction, goodwill )
		end
	end
	return 0
end

factionChanger = FactionChanger()


--Faction Commander
function test()
end

function message( text, period )
	if period == nil then period = 10000 end
	news_manager.send_tip( db.actor, text, nil, nil, period )
end

function deepcopy( object )
    local lookup_table = {}
    local function _copy( object )
        if type( object ) ~= "table" then
            return object
        elseif lookup_table[ object ] then
            return lookup_table[ object ]
        end
        local new_table = {}
        lookup_table[ object ] = new_table
        for index, value in pairs( object ) do
            new_table[ _copy( index ) ] = _copy( value )
        end
        return setmetatable( new_table, getmetatable( object ) )
    end
    return _copy( object )
end

function deepCompare( object1, object2 )
    local function _compare( object1, object2 )
		if type( object1 ) ~= type( object2 ) then
			return false
		end
        if type( object1 ) ~= "table" then
			if type( object1 ) ~= "userdata" then
				return object1 == object2
			else
				return false
			end
		else
			if( #object1 ~= #object2 ) then
				return false
			end
			for index, value in pairs( object1 ) do
				if not _compare( value, object2[ index ] ) then
					return false
				end
			end
		end
        return true
    end
    return _compare( object1, object2 )
end

function tableMinus( table1, table2 )
	local function find( table1, table2 )
		for i, value1 in pairs( table1 ) do
			for j, value2 in pairs( table2 ) do
				if value1 == value2 then
					return i, j
				end
			end
		end
		return nil, nil
	end
	table.sort( table1 )
	table.sort( table2 )
	local index1, index2 = true, true
	while index1 ~= nil and index2 ~= nil do
		index1, index2 = find( table1, table2 )
		if index1 ~= nil then
			table.remove( table1, index1 )
		end
	end
	while #table2 > 0 do
		table.remove( table2, next( table2 ) )
	end
end

function trim( tab )
	table.sort( tab )
	local index1 = next( tab )
	local index2 = next( tab, index1 )
	while 	index1 ~= nil and
			index2 ~= nil do
		if tab[ index1 ] == tab[ index2 ] then
			table.remove( tab, index2 )
		else
			index1 = index2
		end
		index2 = next( tab, index1 )
	end
end

function smartDistance( smartID1, smartID2 )
	if smartID1 == "anywhere" or smartID1 == "anywhere" then
		return 0
	end
	local v1 = getSmartObjectByID( smartID1 ).m_game_vertex_id
	local v2 = getSmartObjectByID( smartID2 ).m_game_vertex_id
	return utils.graph_distance( v1, v2 )
end 
function getSmartObjectByName( smart_name )
	local board = sim_board.get_sim_board()
	if( board.smarts_by_names == nil ) then return nil end
	return  board.smarts_by_names[ smart_name ]
end
function getSmartObjectByID( smart_id )
	local board = sim_board.get_sim_board()
	if 	board.smarts ~= nil and
		smart_id ~= nil and
		board.smarts[ smart_id ] ~= nil then
		return board.smarts[ smart_id ].smrt
	end
	return nil
end
function getSmartNameTranslation( smart_obj )
	if( smart_obj == nil ) then return "nil" end
	local translation = smart_names.get_smart_terrain_name( smart_obj )
	local levelName = alife():level_name( game_graph():vertex( smart_obj.m_game_vertex_id ):level_id() )
	return levelName.."."..translation
end
function getSmartNameTranslationByID( smart_id )
	return getSmartNameTranslation( getSmartObjectByID( smart_id ) )
end
function getSmartName( smart_obj )
	return smart_obj:name()
end
function getSmartNameByID( smart_id )
	return getSmartName( getSmartObjectByID( smart_id ) )
end
function getSmartCapacity( smart_id )
	if smart_id == "anywhere" then return 0 end
	return getSmartObjectByID( smart_id ).squad_capacity
end
function getFactionByName( name )
	return sim_board.get_sim_board().players[ name ]
end
function getAllFaction()
	return sim_board.get_sim_board().players
end

local pathPrintStatistic = {}
function printPath( squadObject, path, threshold )
	if squadObject.player_id ~= "stalker" then
		return
	end
	
	if( #path < 1 )then return end
	if( pathPrintStatistic[ squadObject.squad_id ] == nil ) then
		pathPrintStatistic[ squadObject.squad_id ] = { lastPrintTime = 0, printTimes = 0 }
	end
	pathPrintStatistic[ squadObject.squad_id ].printTimes = pathPrintStatistic[ squadObject.squad_id ].printTimes + 1
	local curTime = time_global()
	local period = 5000
	if( math.abs( curTime - pathPrintStatistic[ squadObject.squad_id ].lastPrintTime ) < period ) then
		return
	else
		pathPrintStatistic[ squadObject.squad_id ].lastPrintTime = curTime
	end
	local text = squadObject.squad_id.." : "..tostring( threshold ).."  "
	for i, info in pairs( path ) do
		text = text..getSmartNameTranslationByID( info.id ).." : "..tostring( info.value )
		if( i ~= #path ) then
			text = text.."  ->  "
		end
	end
	--error_log( text ) 
	--message( text )
end

function getSpawnProportion( spawnNumber )
	if( spawnNumber < 10 ) then
		return 1
	elseif( spawnNumber < 30 ) then
		return 0.33333
	elseif( spawnNumber < 60 ) then
		return 0.16666
	elseif( spawnNumber < 100 ) then
		return 0.1
	elseif( spawnNumber < 200 ) then
		return 0.05
	else
		return 0.025
	end
end


class "CommanderAI"
function CommanderAI:__init( commander )
	self.commander = commander
	self.enemyTable = {
		["csky"]		= "renegade",
		["stalker"]		= "bandit",
		["dolg"]		= "freedom",
		["freedom"]		= "dolg",
		["bandit"]		= "stalker",
		["renegade"]	= "csky",
	}
end
function CommanderAI:__finalize()
end

function CommanderAI:_calculate( attackThreshold, magnitude )
	local enemyFactionName = self.enemyTable[ self.commander.faction.player_name ]
	if enemyFactionName ~= nil then
		local relativePower = self.commander.faction:get_relative_power()
		local enemyFaction = getFactionByName( enemyFactionName )
		local enemyCommander = enemyFaction.commander
		local enemyOrder = enemyCommander:getOrder()
		local enemyMagnitude = enemyCommander:getMagnitude()
		if enemyOrder == "attack" and
			relativePower < attackThreshold then
			self.commander:giveOrder( "defend" )
			self.commander:setMagnitude( enemyMagnitude )
			return
		end
		if relativePower < attackThreshold then
			self.commander:giveOrder( "expansion" )
			self.commander:setMagnitude( magnitude )
		else
			self.commander:giveOrder( "attack" )
			self.commander:setMagnitude( magnitude )
		end
	else
		self.commander:giveOrder( "expansion" )
		self.commander:setMagnitude( magnitude )
	end
end

class "AI_Disable" ( CommanderAI )
function AI_Disable:__init( commander ) super( commander )
end
function AI_Disable:__finalize()
end
function AI_Disable:calculate()
	self.commander:giveOrder( "default" )
	self.commander:setMagnitude( "green" )
end

class "AI_Normal" ( CommanderAI )
function AI_Normal:__init( commander ) super( commander )
end
function AI_Normal:__finalize()
end
function AI_Normal:calculate()
	CommanderAI._calculate( self, 0.75, "green" )
end

class "AI_Hard" ( CommanderAI )
function AI_Hard:__init( commander ) super( commander )
end
function AI_Hard:__finalize()
end
function AI_Hard:calculate()
	CommanderAI._calculate( self, 0.6, "yellow" )
end

class "AI_Insane" ( CommanderAI )
function AI_Insane:__init( commander ) super( commander )
end
function AI_Insane:__finalize()
end
function AI_Insane:calculate()
	CommanderAI._calculate( self, 0.45, "red" )
end


class "Commander"
function Commander:__init( faction )
	self.faction = faction
	self.board = sim_board.get_sim_board()
	self.attackMagnitude = "green"
	self.order = "default"
	self.ai = "disable"
	self.aiObject = nil
	self.target = nil
	self.lastUpdateTime = 0
	self.updateTimeInterval = 30000
end

function Commander:__finalize()
end

function Commander:getSquads()
	local result = {}
	for i, squadObject in pairs( self.faction.squads ) do
		if squadObject ~= nil then
			table.insert( result, squadObject.squad_id )
		end
	end
	table.sort( result )
	return result
end

function Commander:update( immediate )
	if not ( level.present() and db.actor ~= nil ) then
		return false
	end
	if not( immediate or 
		math.abs( time_global() - self.lastUpdateTime ) > self.updateTimeInterval ) then
		return false
	else
		self.lastUpdateTime = time_global()
	end
	--error_log( self.faction.player_name.." commander: Order: "..tostring(self.order).." : "..self.attackMagnitude )

	if self.aiObject == nil then
		self.ai = "disable"
		self.aiObject = AI_Disable( self )
	end
	if self.faction.community_player == false and
		self.order ~= "cancel" then
		self.aiObject:calculate()
	end
	
	local squads = self:getSquads()
	if self.target == nil then
		self.order = "default"
		self.target = DefaultTarget( self, self.attackMagnitude )
	end
	local assignedBuffer = {}
	self.target:update( squads, self.attackMagnitude, assignedBuffer )
	
	--error_log( self.faction.player_name.." commander: end" )
	--error_log( "" )
end

function Commander:setAI( ai )
	if ai ~= nil and
		self.ai ~= ai then
		self.ai = ai
		if self.ai == "disable" then
			self.aiObject = AI_Disable( self )
		elseif self.ai == "normal" then
			self.aiObject = AI_Normal( self )
		elseif self.ai == "hard" then
			self.aiObject = AI_Hard( self )
		elseif self.ai == "insane" then
			self.aiObject = AI_Insane( self )
		end
	end
end

function Commander:getAI()
	return self.ai
end

function Commander:setMagnitude( magnitude )
	if magnitude ~= nil and
		self.attackMagnitude ~= magnitude then
		self.attackMagnitude = magnitude
	end
end

function Commander:getMagnitude()
	return self.attackMagnitude
end

function Commander:getOrder()
	return self.order
end

function Commander:giveOrder( order )
	if order ~= nil and
		self.order ~= order then
		self.order = order
		if self.order == "attack" then
			self.target = Attack( self, self.attackMagnitude )
		elseif self.order == "defend" then
			self.target = Defend( self, self.attackMagnitude )
		elseif self.order == "assemble" then
			self.target = Assemble( self, self.attackMagnitude )
		elseif self.order == "expansion" then
			self.target = Expansion( self, self.attackMagnitude )
		elseif self.order == "cancel" then
			self.target = Cancel( self, self.attackMagnitude )
		elseif self.order == "default" then
			self.target = DefaultTarget( self, self.attackMagnitude )
		end
	end
end

function Commander:_getSquadObjectByID( squadUniqueID )
	for i, squadObject in pairs( self.faction.squads ) do
		if 	squadObject ~= nil and
			squadUniqueID == squadObject.squad_id then
			return squadObject
		end
	end
	error_log( "Commander:_getSquadObjectByID, squadObject == nil, id:"..tostring( squadUniqueID ) )
	return nil
end

function Commander:unregisterSquad( id )
	self.faction.squad_target_cache[ id ] = nil
end

function Commander:registerSquad( id, target_id )
	self.faction.squad_target_cache[ id ] = target_id
end

function Commander:_getSquadInfo( id )
	local squadObject = self:_getSquadObjectByID( id )
	local nextPosition, position, action, combatStage, destination
	if( squadObject.current_action ~= nil ) then
		action = squadObject.current_action.name
	end
	local next_id = nil
	if( squadObject.current_action ~= nil and squadObject.current_action.name == "attack_point" ) then
		next_id  = squadObject.current_action.dest_smrt_id
		combatStage = getSmartObjectByID( next_id  ).combat_manager:get_squad_stage( squadObject )
		destination = getSmartNameTranslationByID( squadObject.assigned_target_smart_id )
	else
		destination = ""
	end
	if( next_id  ~= nil ) then
		nextPosition = getSmartNameTranslationByID( next_id  )
	end
	if( squadObject.smart_id ~= nil ) then
		position = getSmartNameTranslationByID( squadObject.smart_id )
	end

	return { position = tostring( position ),
			id = tostring( id ),
			destination = tostring( destination ),
			action = tostring( action ),
			nextPosition = tostring( nextPosition ),
			combatStage = tostring( combatStage ) }
end

function Commander:_getOrderInfo( order )
	if order:getDestination() == "anywhere" then
		return { }
	end
	local smart = getSmartObjectByID( order:getDestination() )
	local number = self.board:get_smart_squad_quantity( smart, self.faction ) + self.board:get_smart_population( smart )
	return { destination = self.faction.player_name.."@"..getSmartNameTranslationByID( order:getDestination() ),
			name = order.name,
			number = tostring( order:getSquadsNumber() )	}
end

function Commander:printOrders( orders )
	local statistic = {}
	for i, ord in pairs( orders ) do
		if ord:getDestination() ~= "anywhere" then
			table.insert( statistic, self:_getOrderInfo( ord ) )
		end
	end
	table.sort( statistic, function( a, b ) return a.destination < b.destination end )
	for i, ord in pairs( statistic ) do
		message( ord.name.." -> "..ord.destination.."    "..ord.number )
	end
end

function Commander:printSquads( squads )
	local statistic = {}
	for i, squadObject in pairs( self.faction.squads ) do
		if( squadObject ~= nil ) then
			local info = self:_getSquadInfo( squadObject.squad_id )
			table.insert( statistic, info )
		end
	end
	table.sort( statistic, function( a, b ) return a.id < b.id end )
	for i, info in pairs( statistic ) do
		local text = self.faction.player_name..":"..info.id.."@"..info.position
		if info.destination ~= "" then
			text = text.." -> "..info.destination
		end
		if( info.action == "attack_point" ) then
			text = text.."     "..info.action.." -> "..info.nextPosition.." : "..info.combatStage
		end
		message( text )
	end
end

function Commander:printTarget( target )
	self:printOrders( target.orders )
end

function Commander:printAllTarget()
	if self.target ~= nil then
		self:printTarget( self.target )
	end
	if self.target.defaultTarget ~= nil then
		self:printTarget( self.target.defaultTarget )
	end
end

function Commander:printAllSquads()
	self:printSquads( self:getSquads() )
end

function Commander:printAllResourceSmart()
	local list = {}
	for smart_id, smart_obj in pairs( self.board.smarts ) do
		if 	smart_obj.smrt.sim_type == "resource" then
			table.insert( list, smart_id )
		end
	end
	for i, id in pairs( list ) do
		message( getSmartNameTranslationByID( id ) )
	end
end


class "Order"
function Order:__init( target, maxAssignedNumber )
	if maxAssignedNumber == nil then
		maxAssignedNumber = 9999
	end
	self.target = target
	self.commander = target.commander
	self.board = self.commander.board
	self.maxAssignedNumber = maxAssignedNumber
	self.assignedSquads = {}
end

function Order:__finalize()
end

function Order:isEqualTo( order )
	return self.destination == order:getDestination()
end

function Order:insertSquad( id )
	table.insert( self.assignedSquads, id )
	table.sort( self.assignedSquads )
end

function Order:getSquadsNumber()
	return #self.assignedSquads
end

function Order:getMaxAssignedNumber()
	return self.maxAssignedNumber
end


class "StayOrder" ( Order )
function StayOrder:__init( target, maxAssignedNumber ) super( target, maxAssignedNumber )
	self.destination = "anywhere"
	self.name = "Stay"
end

function StayOrder:__finalize()
	self:stopOrder()
end

function StayOrder:getDestination()
	return self.destination
end

function StayOrder:executeOrder()
	--if #self.assignedSquads > 0 then
		--local text = "StayOrder:      "..tostring(#self.assignedSquads).." squads: "..table.concat( self.assignedSquads, ", " )
		--error_log( text )
	--end

	for i, id in pairs( self.assignedSquads ) do
		self.commander:unregisterSquad( id )
		local squadObject = self.commander:_getSquadObjectByID( id )
		if squadObject ~= nil then
			local action = squadObject.current_action
			if action ~= nil and action.name == "attack_point" then
				local destSmart = getSmartObjectByID( action.dest_smrt_id )
				destSmart.combat_manager:unregister_squad( squadObject )
			end
			squadObject:update()
		end
	end
end

function StayOrder:stopOrder()
	self:executeOrder()
end


class "MoveOrder" ( Order )
function MoveOrder:__init( target, smartID, maxAssignedNumber ) super( target, maxAssignedNumber )
	self.name = "MoveTo"
	self.destination = smartID
end

function MoveOrder:__finalize()
end

function MoveOrder:getDestination()
	return self.destination
end

function MoveOrder:_clearSquadStagnation( id )
	local squadObject = self.commander:_getSquadObjectByID( id )
	local action = squadObject.current_action
	if( action ~= nil and action.name == "attack_point" ) then
		local destSmart = getSmartObjectByID( action.dest_smrt_id )
		local stage = destSmart.combat_manager:get_squad_stage( squadObject )
		if 	utils.graph_distance(
				alife():actor().m_game_vertex_id,
				alife():object(squadObject.commander_id).m_game_vertex_id ) > 125 then
			destSmart.combat_manager:request_squad_stage( squadObject, "move" )
		end
	end
end

function MoveOrder:executeOrder()
	--if #self.assignedSquads > 0 then
		--local text = "MoveOrder: "..getSmartNameTranslationByID( self.destination )
		--text = text.."        "..tostring(#self.assignedSquads).." squads: "..table.concat( self.assignedSquads, ", " )
		--error_log( text )
	--end

	for i, id in pairs( self.assignedSquads ) do
		self.commander:registerSquad( id, self.destination )
		local squadObject = self.commander:_getSquadObjectByID( id )
		if squadObject ~= nil then
			squadObject:update()
		end
		self:_clearSquadStagnation( id )
	end
end

function MoveOrder:stopOrder()
end



class "Assigner"
function Assigner:__init( target )
	self.target = target
	self.commander = target.commander
end

function Assigner:__finalize()
end

function Assigner:assign( orders, squads, assignedSquadsRecord )
	local deleted = self:_getScriptedSquads( squads )
	for i, id in pairs( deleted ) do
		table.insert( assignedSquadsRecord, id )
	end
	tableMinus( squads, deleted )
	
	deleted = self:_getBusySquads( squads )
	for i, id in pairs( deleted ) do
		table.insert( assignedSquadsRecord, id )
	end
	tableMinus( squads, deleted )
end

function Assigner:_isSquadScripted( id )
	local squadObject = self.commander:_getSquadObjectByID( id )
	if squadObject.name ~= "sim_squad_generic" then
		return true
	end
	return false
end

function Assigner:_getScriptedSquads( squads )
	local result = {}
	for i, id in pairs( squads ) do
		if self:_isSquadScripted( id ) then
			table.insert( result, id )
		end
	end
	return result
end

function Assigner:_sortSquadsByDistance( squads, destination )
	if destination == "anywhere" then
		return
	end
	local function compareDistance( a, b )
		local startPositionA = self.commander:_getSquadObjectByID( a ).smart_id
		local startPositionB = self.commander:_getSquadObjectByID( b ).smart_id
		local dA = smartDistance( startPositionA, destination )
		local dB = smartDistance( startPositionB, destination )
		return dA < dB
	end
	table.sort( squads, compareDistance )
end

function Assigner:_sortOrdersByDistance( orders, id )
	local squadObject = self.commander:_getSquadObjectByID( id )
	local startPosition = squadObject.smart_id
	local function compareDistance( a, b )
		local dA = smartDistance( a:getDestination(), startPosition )
		local dB = smartDistance( b:getDestination(), startPosition )
		return dA < dB
	end
	table.sort( orders, compareDistance )
end

function Assigner:_getTargetOfSquad( id )
	local squadObject = self.commander:_getSquadObjectByID( id )
	local action = squadObject.current_action
	if action ~= nil then
		if action.name == "stay_point" then
			return squadObject.smart_id
		end
		--elseif action.name == "attack_point" then
		--	return squadObject.assigned_target_smart_id
		--end
	end
	return squadObject.smart_id
end

function Assigner:_getSquadsOfTarget( squads, targetSmartID )
	if targetSmartID == "anywhere" then
		return {}
	end
	local squadList = {}
	for i, id in pairs( squads ) do
		if( self:_getTargetOfSquad( id ) == targetSmartID ) then
			table.insert( squadList, id )
		end
	end
	return squadList
end

function Assigner:_getBusySquads( squads )
	local result = {}
	for i, id in pairs( squads ) do
		local squadObject = self.commander:_getSquadObjectByID( id )
		if squadObject.delayed_attack_task ~= nil or
			(self.random_tasks ~= nil and
			(self.random_tasks[ 0 ] ~= nil or
			self.random_tasks[ 1 ] ~= nil or
			self.random_tasks[ 2 ] ~= nil or
			self.random_tasks[ 3 ] ~= nil)) then
			table.insert( result, id )
		end
	end
	return result
end


class "BlankAssigner" ( Assigner )
function BlankAssigner:__init( target ) super( target )
end

function BlankAssigner:__finalize()
end

function BlankAssigner:assign( orders, squads, assignedSquadsRecord )
end


class "KeepAssigner" ( Assigner )
function KeepAssigner:__init( target, maxCapacity ) super( target )
	if maxCapacity == nil then
		maxCapacity = 9999
	end
	self.maxCapacity = maxCapacity
end

function KeepAssigner:__finalize()
end

function KeepAssigner:assign( orders, squads, assignedSquadsRecord )
	Assigner.assign( self, orders, squads, assignedSquadsRecord )
	local deletedSquads = {}
	for i, ord in pairs( orders ) do	
		local destination = ord:getDestination()
		local alreadyAssignedSquads = Assigner._getSquadsOfTarget( self, assignedSquadsRecord, destination )
		local keepableSquads = Assigner._getSquadsOfTarget( self, squads, destination )
		local smartCapacity = math.min( getSmartCapacity( destination ), self.maxCapacity )
		local capacity = smartCapacity - #alreadyAssignedSquads - ord:getSquadsNumber()
		for j = 1, math.min( capacity, #keepableSquads, ord:getMaxAssignedNumber() ) do
			--ord:insertKeepSquad( keepableSquads[ j ] )
			ord:insertSquad( keepableSquads[ j ] )
			table.insert( deletedSquads, keepableSquads[ j ]  )
			table.insert( assignedSquadsRecord, keepableSquads[ j ]  )
		end
		tableMinus( squads, deletedSquads )
	end
end


class "KeepOneAssigner" ( Assigner )
function KeepOneAssigner:__init( target ) super( target )
end

function KeepOneAssigner:__finalize()
end

function KeepOneAssigner:assign( orders, squads, assignedSquadsRecord )
	local kAssigner = KeepAssigner( self.target, 1 )
	kAssigner:assign( orders, squads, assignedSquadsRecord )
end


class "KeepAllAssigner" ( Assigner )
function KeepAllAssigner:__init( target ) super( target )
end

function KeepAllAssigner:__finalize()
end

function KeepAllAssigner:assign( orders, squads, assignedSquadsRecord )
	local kAssigner = KeepAssigner( self.target, 9999 )
	kAssigner:assign( orders, squads, assignedSquadsRecord )
end


class "RestrictedAssigner" ( Assigner )
function RestrictedAssigner:__init( target, restrictedNumber ) super( target )
	--error_log( "capacity: "..tostring(restrictedNumber) )
	if restrictedNumber == nil then
		restrictedNumber = 0
	end
	self.restrictedNumber = restrictedNumber
end

function RestrictedAssigner:__finalize()
end

function RestrictedAssigner:assign( orders, squads, assignedSquadsRecord )
	local deletedSquads = {}
	local maxNumber = math.min( self.restrictedNumber, #squads )
	for i = 1, maxNumber do
		Assigner._sortOrdersByDistance( self, orders, squads[ i ] )
		for j, ord in pairs( orders ) do
			local destination = ord:getDestination()
			local alreadyAssignedSquads = Assigner._getSquadsOfTarget( self, assignedSquadsRecord, destination )
			local capacity = ord:getMaxAssignedNumber() - #alreadyAssignedSquads - ord:getSquadsNumber()
			if 	capacity > 0 then
				ord:insertSquad( squads[ i ] )
				table.insert( deletedSquads, squads[ i ] )
				table.insert( assignedSquadsRecord, squads[ i ] )
				break
			end
		end
	end
	tableMinus( squads, deletedSquads )
end


class "KeepOneRestrictedAssigner" ( Assigner )
function KeepOneRestrictedAssigner:__init( target, expansionFactor ) super( target )
	if expansionFactor == nil then
		expansionFactor = 0
	end
	self.expansionFactor = expansionFactor
end

function KeepOneRestrictedAssigner:__finalize()
end

function KeepOneRestrictedAssigner:assign( orders, squads, assignedSquadsRecord )
	local koAssigner = KeepOneAssigner( self.target )
	koAssigner:assign( orders, squads, assignedSquadsRecord )

	local totalSquadsNumber = #squads + #assignedSquadsRecord
	local restrictedNumber = totalSquadsNumber * self.expansionFactor - #assignedSquadsRecord
	local rAssigner = RestrictedAssigner( self.target, restrictedNumber )
	rAssigner:assign( orders, squads, assignedSquadsRecord )
end


class "KeepAllRestrictedAssigner" ( Assigner )
function KeepAllRestrictedAssigner:__init( target, expansionFactor ) super( target )
	if expansionFactor == nil then
		expansionFactor = 0
	end
	self.expansionFactor = expansionFactor
end

function KeepAllRestrictedAssigner:__finalize()
end

function KeepAllRestrictedAssigner:assign( orders, squads, assignedSquadsRecord )
	local kaAssigner = KeepAllAssigner( self.target )
	kaAssigner:assign( orders, squads, assignedSquadsRecord )
	
	local totalSquadsNumber = #squads + #assignedSquadsRecord
	local restrictedNumber = totalSquadsNumber * self.expansionFactor - #assignedSquadsRecord
	local rAssigner = RestrictedAssigner( self.target, restrictedNumber )
	rAssigner:assign( orders, squads, assignedSquadsRecord )
end



class "Target"
function Target:__init( commander, magnitude )
	self.orders = {}
	self.squadsAssigned = {}		--squads assigned
	self.squadsBeforeAssigned = {}	--squads befor assigned
	self.magnitude = magnitude
	self.commander = commander
	self.faction = commander.faction
	self.board = commander.board
	self.name = "Target"
	self.magnitudeComplement = {
		["green"]	= "green_noexp",
		["yellow"]	= "yellow_noexp",
		["red"]		= "red_noexp",	}
		
	self.forbiddenSmart = { 
		"mar_smart_terrain_csky_respawn",
		"mar_smart_terrain_csky_base",
		"mil_smart_terrain_2_10",
	}
end

function Target:__finalize()
end

function Target:_sortOrders( orders)
	table.sort( orders, function( a, b ) return a:getDestination() < b:getDestination() end )
end

function Target:_getTargetsFromPrecondition( level )
	if( level < 1 ) then return {} end
	local targets = {}
	for i, smartName in pairs( self.faction.expansion_level[ level ].precondition_target ) do
		local smart_obj = self.board.smarts_by_names[ smartName ]
		table.insert( targets, smart_obj.id )
	end
	for i, smartName in pairs( self.faction.expansion_level[ level ].precondition_target_optional ) do
		local smart_obj = self.board.smarts_by_names[ smartName ]
		table.insert( targets, smart_obj.id )
	end
	for i, smartName in pairs( self.faction.expansion_level[ level ].precondition_target_eliminate ) do
		local smart_obj = self.board.smarts_by_names[ smartName ]
		table.insert( targets, smart_obj.id )
	end
	return targets
end

function Target:_filter( targets )
	local deleted = {}
	for i, name in pairs( self.forbiddenSmart ) do
		local object = getSmartObjectByName( name )
		table.insert( deleted, object.id )
	end
	tableMinus( targets, deleted )
end

function Target:update( squads, magnitude, assignedBuffer )
	--error_log( "----------start----------"..self.name.." Update:" )

	local needReassign = false
	local orders = self:getOrders()
	self:_sortOrders( orders )
	if #orders == #self.orders then
		for i = 1, #self.orders do
			if not self.orders[ i ]:isEqualTo( orders[ i ] ) then
				needReassign = true
				--error_log( "not self.orders[ i ]:isEqualTo( orders[ i ] )" )
				break;
			end
		end
	else
		--error_log( "#orders ~= #self.orders" )
		needReassign = true
	end
	if magnitude ~= self.magnitude then
		--error_log( "magnitude ~= self.magnitude" )
		self.magnitude = magnitude
		needReassign = true
	end
	--error_log( "tag5" )
	if	deepCompare( squads, self.squadsBeforeAssigned ) ~= true then
		--error_log( "deepCompare( squads, self.squadsBeforeAssigned ) ~= true" )
		self.squadsBeforeAssigned = deepcopy( squads )
		needReassign = true
	end
	if needReassign == true then
		self.orders = deepcopy( orders )
		--error_log( "needReassign == true" )
		--error_log( "ordersNumber = "..tostring( #self.orders ) )
		--error_log( "squadsTotalNumber = "..tostring( #squads ) )
		local assigner = self:getAssigner()
		assigner:assign( self.orders, squads, assignedBuffer )
		self.squadsAssigned = deepcopy( assignedBuffer )
		--error_log( "unAssignedNumber = "..tostring( #squads ) )
	else
		--error_log( "needReassign == false" )
		local deleted = deepcopy( self.squadsAssigned )
		tableMinus( squads, deleted )
		assignedBuffer = deepcopy( self.squadsAssigned )
	end
	self:_sortOrders( self.orders )
	for i, ord in pairs( self.orders ) do
		ord:executeOrder()	--maybe outdate
	end
	
	--error_log( "----------end---------- " )
end


class "Assemble" ( Target )
function Assemble:__init( commander, magnitude ) super( commander, magnitude )
	self.name = "Assemble"
	self.defaultTarget = DefaultTarget( commander, self.magnitudeComplement[ magnitude ] )
end

function Assemble:_getCommanderSmartTerrain()
	local statistic = {}
	for smart_id, smart_obj in pairs( self.board.smarts ) do
		local smart = smart_obj.smrt
		if sim_board.is_point_avail( smart, self.faction.player_name ) then
			local distance = utils.graph_distance( 
				alife():actor().m_game_vertex_id, 
				smart.m_game_vertex_id )
			table.insert( statistic, { smart_id = smart_id, distance = distance } )
		end
	end
	table.sort( statistic, function( a, b ) return a.distance < b.distance end )
	message( getSmartNameTranslationByID( statistic[ 1 ].smart_id ) )
	return statistic[ 1 ].smart_id
end

function Assemble:getAssigner()
	return KeepOneRestrictedAssigner( self, 1 )
end

function Assemble:getOrders()
	local smartID = self:_getCommanderSmartTerrain()
	return { MoveOrder( self, smartID ) }
end

function Assemble:update( squads, magnitude, assignedBuffer )
	self.defaultTarget:update( squads, self.magnitudeComplement[ magnitude ], assignedBuffer )
	Target.update( self, squads, magnitude, assignedBuffer )
end


class "Attack" ( Target )
function Attack:__init( commander, magnitude ) super( commander, magnitude )
	self.name = "Attack"
	self.defaultTarget = DefaultTarget( commander, self.magnitudeComplement[ magnitude ] )
end

function Attack:getAssigner()
	if self.magnitude == "green" then
		return KeepOneRestrictedAssigner( self, 0.8 )
	elseif self.magnitude == "yellow" then
		return KeepOneRestrictedAssigner( self, 1 )
	elseif self.magnitude == "red" then
		return KeepOneRestrictedAssigner( self, 1 )
	end
end

function Attack:_getAttackOrders( level )
	local orders = {}
	for i = 1, level - 1 do
		local destination = self:_getTargetsFromPrecondition( i, self.order )
		for i, smartID in pairs( destination ) do
			table.insert( orders, MoveOrder( self, smartID, 1 ) )
		end
	end
	local destination = self:_getTargetsFromPrecondition( level, self.order )
	for i, smartID in pairs( destination ) do
		table.insert( orders, MoveOrder( self, smartID ) )
	end
	return orders
end

function Attack:getOrders()
	return self:_getAttackOrders( self.faction.current_expansion_level )
end

function Attack:update( squads, magnitude, assignedBuffer )
	self.defaultTarget:update( squads, self.magnitudeComplement[ magnitude ], assignedBuffer )
	Target.update( self, squads, magnitude, assignedBuffer )
end


class "Defend" ( Target )
function Defend:__init( commander, magnitude ) super( commander, magnitude )
	self.name = "Defend"
	self.defaultTarget = DefaultTarget( commander, self.magnitudeComplement[ magnitude ] )
end

function Defend:getAssigner()
	if self.magnitude == "green" then
		return KeepOneRestrictedAssigner( self, 0.8 )
	elseif self.magnitude == "yellow" then
		return KeepOneRestrictedAssigner( self, 1 )
	elseif self.magnitude == "red" then
		return KeepOneRestrictedAssigner( self, 1 )
	end
end

function Defend:_getDefendOrders( level )
	local orders = {}
	if level < 2 then
		level = 2
	end
	for i = 1, level - 2 do
		local destination = self:_getTargetsFromPrecondition( i, self.order )
		for i, smartID in pairs( destination ) do
			table.insert( orders, MoveOrder( self, smartID, 1 ) )
		end
	end
	local destination = self:_getTargetsFromPrecondition( level - 1, self.order )
	for i, smartID in pairs( destination ) do
		table.insert( orders, MoveOrder( self, smartID ) )
	end
	return orders
end

function Defend:getOrders()
	return self:_getDefendOrders( self.faction.current_expansion_level )
end

function Defend:update( squads, magnitude, assignedBuffer )
	self.defaultTarget:update( squads, self.magnitudeComplement[ magnitude ], assignedBuffer )
	Target.update( self, squads, magnitude, assignedBuffer )
end


class "Cancel" ( Target )
function Cancel:__init( commander, magnitude ) super( commander, magnitude )
	self.name = "Cancel"
end

function Cancel:getAssigner()
	return KeepOneRestrictedAssigner( self, 1 )
end

function Cancel:getOrders()
	return { StayOrder( self ) }
end


class "DefaultTarget" ( Target )
function DefaultTarget:__init( commander, magnitude ) super( commander, magnitude )
	self.name = "DefaultTarget"
end

function DefaultTarget:_getDefaultTargets( targets )
	for smartID, value in pairs( self.faction.target_smart_value ) do
		if value ~= nil and 
			sim_board.is_point_avail( value.smart, self.faction.player_name ) then
			table.insert( targets, value.smart_id )
		end
	end
end

function DefaultTarget:getAssigner()
	if self.magnitude == "green" then
		return KeepAllRestrictedAssigner( self, 0.6 )
	elseif self.magnitude == "yellow" then
		return KeepOneRestrictedAssigner( self, 0.6 )
	elseif self.magnitude == "red" then
		return KeepOneRestrictedAssigner( self, 0.7 )
		
	elseif self.magnitude == "green_noexp" then
		return KeepAllAssigner( self )
	elseif self.magnitude == "yellow_noexp" then
		return KeepOneAssigner( self )
	elseif self.magnitude == "red_noexp" then
		return BlankAssigner( self )
	end
end

function DefaultTarget:getOrders()
	local orders = {}
	local targets = {}
	self:_getDefaultTargets( targets )
	trim( targets )
	Target._filter( self, targets )
	for i, smartID in pairs( targets ) do
		table.insert( orders, MoveOrder( self, smartID ) )
	end
	return orders
end


class "Expansion" ( Target )
function Expansion:__init( commander, magnitude ) super( commander, magnitude )
	self.name = "Expansion"
	self.magnitudeComplement = {
		["green"]	= "green_noexp",
		["yellow"]	= "yellow_noexp",
		["red"]		= "yellow_noexp",	}
	self.defaultTarget = DefaultTarget( commander, self.magnitudeComplement[ magnitude ] )
end

function Expansion:_getExpansionTarget( targets )
	for smart_id, smart_obj in pairs( self.board.smarts ) do
		if 	sim_board.is_point_avail( smart_obj.smrt, self.faction.player_name ) and
			( smart_obj.smrt.sim_type == "territory" or
			smart_obj.smrt.sim_type == "resource" ) and
			( smart_obj.smrt.player_name == "none" or 
			game_relations.is_factions_enemies( smart_obj.smrt.player_name, self.faction.player_name ) )then
			table.insert( targets, smart_id )
		end
	end
    for smart_id, smart_obj in pairs( self.board.spawn_smarts ) do
		local smart = getSmartObjectByID( smart_id )
		if sim_board.is_point_avail( smart, self.faction.player_name ) then
			local spawnPlayer = xr_logic.pick_section_from_condlist( db.actor_proxy, smart, smart_obj )
			if 	smart.player_name == "none" and 
				( spawnPlayer == "all" or spawnPlayer == self.faction.player_name ) then
				table.insert( targets, smart_id )
			elseif 	game_relations.is_factions_enemies( smart.player_name, self.faction.player_name ) and
					smart.player_name == spawnPlayer then
				table.insert( targets, smart_id )
			end
		end
	end
end

function Expansion:getAssigner()
	if self.magnitude == "green" then
		return KeepAllRestrictedAssigner( self, 0.7 )
	elseif self.magnitude == "yellow" then
		return KeepOneRestrictedAssigner( self, 0.85 )
	elseif self.magnitude == "red" then
		return KeepOneRestrictedAssigner( self, 1 )
	end
end

function Expansion:_setDefault( targets )
	for i, id in pairs( targets ) do
		local smart = getSmartObjectByID( id )
		if smart.player_name == self.faction.player_name then
			self.faction.brain:register_target_point( smart, false )
		end
	end
end

function Expansion:getOrders()
	local orders = {}
	local targets = {}
	--self.defaultTarget:_getDefaultTargets( targets )
	self:_getExpansionTarget( targets )
	trim( targets )
	Target._filter( self, targets )
	self:_setDefault( targets )
	for i, smartID in pairs( targets ) do
		table.insert( orders, MoveOrder( self, smartID, 1 ) )
	end
	return orders
end

function Expansion:update( squads, magnitude, assignedBuffer )
	self.defaultTarget:update( squads, self.magnitudeComplement[ magnitude ], assignedBuffer )
	Target.update( self, squads, magnitude, assignedBuffer )
end



class "Facade"
function Facade:__init()
	self.actorFactionList = {
		["actor_csky"]		= "csky",
		["actor_stalker"]	= "stalker",
		["actor_dolg"]		= "dolg",
		["actor_freedom"]	= "freedom",
		["actor_bandit"]	= "bandit",
		["actor"]			= "",
		["renegade"]		= "renegade",
		["army"]			= "army",
		["monster"]			= "monster",
		["zombied"]			= "zombied",
		["ecolog"]			= "ecolog",
		["monolith"]		= "monolith"
	}
end

function Facade:_getProtagonistCommander()
	if level.present() and db.actor ~= nil and db.actor:alive() then
		local name = self.actorFactionList[ db.actor:character_community() ]
		local faction = getFactionByName( name )
		if faction ~= nil then
			return faction.commander
		end
	end
	return nil
end

function Facade:giveOrder( order )
	local commander = self:_getProtagonistCommander()
	if commander ~= nil then
		commander:giveOrder( order )
		commander:update( true )
	end
end

function Facade:getOrder()
	local commander = self:_getProtagonistCommander()
	if commander ~= nil then
		return commander:getOrder()
	else
		return "default"
	end
end

function Facade:setMagnitude( magnitude )
	local commander = self:_getProtagonistCommander()
	if commander ~= nil then
		commander:setMagnitude( magnitude )
		commander:update( true )
	end
end

function Facade:getMagnitude()
	local commander = self:_getProtagonistCommander()
	if commander ~= nil then
		return commander:getMagnitude()
	else
		return "green"
	end
end

function Facade:setAI( ai )
	for name, faction in pairs( getAllFaction() ) do
		if faction.commander ~= nil and 
			faction.community_player == false then
			faction.commander:setAI( ai )
			faction.commander:update( true )
		end
	end
end

function Facade:getAI()
	for name, faction in pairs( getAllFaction() ) do
		if faction.commander ~= nil and 
			faction.community_player == false then
			return faction.commander:getAI()
		end
	end
	return "disable"
end

function Facade:printInfo()
	local commander = self:_getProtagonistCommander()
	if commander ~= nil then
		commander:printAllTarget()
		commander:printAllSquads()
	end
end

function Facade:summonSquad()
	local commander = self:_getProtagonistCommander()
	if commander ~= nil then
		local assemble = Assemble( commander, "green" )
		local smartid = assemble:_getCommanderSmartTerrain()
		local smart = getSmartObjectByID( smartid )
		local board = sim_board.get_sim_board()
		local squad = board:create_squad( 
			commander.faction.player_name,
			sim_squad_generic.sim_squad_generic,
			smart )
		board:enter_smart( squad, smartid )
		squad:update()
		for k,v in pairs( squad.squad_npc ) do
			local obj = alife():object( k )
			board:setup_squad_and_group( obj )
		end
		squad:refresh()
	end
end

function Facade:setTimeScale( scale )
	if level.present() and db.actor ~= nil and db.actor:alive() then
		if scale == "time_real" then
			level.set_time_factor( 1 )
		elseif scale == "time_fast" then
			level.set_time_factor( 10 )
		elseif scale == "time_very_fast" then
			level.set_time_factor( 2000 )
		end
	end
end

function Facade:getTimeScale()
	if level.present() and db.actor ~= nil and db.actor:alive() then
		local factor = level.get_time_factor()
		if factor <= 1 then
			return "time_real"
		elseif factor <= 10 then
			return "time_fast"
		else
			return "time_very_fast"
		end
	end
	return "time_fast"
end

facadeSinglton = Facade()