fai 'recruitment.fai'
		# ===== Some general functions used later ==== #

def sumarize_values( input_map )
	sum(
		values(input_map)
	);

def lowest_value( input_map )
	choose( input_map, -value ).value;

def highest_value( input_map )
	choose( input_map, value ).value;

def change_numbers_to_percents( input_map )
	map( input_map, 
		(value*100) / highest_value(input_map) 
	);

def add_number_to_values( input_map, number )
	map( input_map, 
		value + number 
	);

#make sure we have only positive values in a map #

def make_positive_only( input_map )
	if( lowest_value( input_map ) < 0, 
		add_number_to_values( 
			input_map, 
			abs(lowest_value(input_map))
		), 
		input_map 
	);

def sumarize_maps_values( map_A, map_B )
	map( map_A + map_B, 
		value + map_A[key]
	);

		#===== Evaluation how good unit is on a map ===== #

# ==1==  evaluate average defense #
#gives map where key is id of terrain, and value is number of hexes of that terrain on a gamemap#

def locations_map(ai*) 
	tomap( 
		map( 
			map.terrain, 
			id 
		) 
	);

#filters terrain map, min_percent it minimal % of map that terrain has to occupy to be considered in later evaluations# 

def filtered_locations_map(ai*, min_percent)
	filter( 
		locations_map(ai) , 
		value >  (map.w * map.h) / ( 100 / min_percent ) 
	);

#returns average defense of the unit #

def unit_average_defense(ai*, unit, terrain_min_percent )
	sum( 
		values( 
			map(
				filtered_locations_map(ai,terrain_min_percent), 
				defense_on( 
					unit, 
					find(map.terrain,id=key).loc
				) *
				value
			) 
		) 
	) / 
	sum(
		values(
			filtered_locations_map(
				ai,
				terrain_min_percent
			)
		)
	);

#returns map of units from units_list and their average defense (more -> better) #

def map_units_average_defense(ai*, units_list, terrain_min_percent) 
	tomap(
		map( 
			units_list, 
			id 
		), 
		map( 
			units_list, 'unit', 
			unit_average_defense(
				ai, 
				unit, 
				terrain_min_percent 
			) 
		) 
	);

def units_average_defense(ai*, units_list, terrain_min_percent)
	sum(
		map( 
			units_list, 'unit', 
			unit_average_defense(
				ai, 
				unit, 
				terrain_min_percent 
			) 
		) 
	) /
	size(
		units_list
	);


# ==2== TODO: evaluate unit effectivness based on movement points and movement cost#


		#===== Evaluation of game state - how good is ai doing ===== #

# ==1== based on unit cost and current amount of gold #
# returns number between 0 and 100 where: 0 we are losing, 100 we are winning #

def units_cost_gold_eval(ai*)
	min(
		[
			(
				(
					sum(
						map(
							allies, 'team', 
							sum(
								map(
									units_of_side[team], 
									cost
								)
							) + 
							teams[team].gold 
						)
					) * 
					100
				) / 
				sum(
					map(
						enemies, 'team', 
						sum(
							map(
								units_of_side[team], 
								cost
							)
						) + 
						max(
							[teams[team].gold, 0]
						) 
					)
				)
			) / 2,
			100
		]
	);

# ==2== based on village possession #

def count_allied_villages(ai*)
	sum(
		map(
			allies, 'side', 
			size(
				villages_of_side[side]
			)
		)
	);

def count_enemy_villages(ai*)
	sum(
		map(
			enemies, 'side', 
			size(
				villages_of_side[side]
			)
		)
	);

# multipled by 1000 to increase precision #

def village_evaluation(ai*)
	(
		50000 + 
		(
			count_allied_villages(ai) - 
			count_enemy_villages(ai)
		) * 
		(
			50000 /
			size(
				villages
			)
		)
	) /
	1000;

# ==3== combined 2 of the above #

def eval(ai*)
	( 
		units_cost_gold_eval(ai) + 
		village_evaluation(ai)
	) / 
	2;

		#===== Evaluation how effective my units are against the enemy ===== #

#==1==  basic evaluation #

# evaluate how much damage unit can inflict - how much damage unit can get#

def evaluate_attacker_against_opponents(ai*, unit, enemy_units)
	sum( 
		map( 
			enemy_units, 'enemy_unit', 
			sum(
				map(
					[
						max_possible_damage_with_retaliation( unit, enemy_unit )
					],
					max(
						[
							self[0] - self[2],
							self[1] - self[3]
						]
					)
				) 
			)
		)
	);

def evaluate_defender_against_opponents(ai*, unit, enemy_units)
	-sum( 
		map( 
			enemy_units, 'enemy_unit', 
			sum(
				map(
					[
						max_possible_damage_with_retaliation( enemy_unit, unit )
					],
					max(
						[
							self[0] - self[2],
							self[1] - self[3]
						]
					)
				)
			)
		)
	);

def units_hp_map(ai*, units)
	tomap( 
		map(
			units, 
			id
		), 
		map( 
			units, 
			hitpoints
		)
	);

#in case side's gold is more than 50, add units that side can recruit to the side's unit list#

def create_side_unit_list(ai*, side)
	if( 
		teams[side].gold > 50, 
		recruits_of_side[side], 
		[]
	) + 
	units_of_side[side];

def evaluate_my_recruits_as_attackers(ai*)
	tomap( 
		map(
			my_recruits, 
			id
		), 
		map(
			my_recruits, 'my_unit',
			sum(
				map(
					enemies, 'enemy_side',
					evaluate_attacker_against_opponents(
						ai,
						my_unit,
						create_side_unit_list(
							ai, 
							enemy_side
						)
					)
				)
			)
		)
	);

def evaluate_my_recruits_as_defenders(ai*)
	tomap(
		map(
			my_recruits, 
			id
		),
		map( 
			my_recruits, 'my_unit',
			sum(
				map(
					enemies, 'enemy_side',
					evaluate_defender_against_opponents(
						ai,
						my_unit,
						create_side_unit_list(
							ai,
							enemy_side
						)
					)
				)
			)
		)
	);


# === 2 ===  now consider abilities and weapon specials  #

def abilities_weights()
	['skirmisher' -> 15, 'ambush' -> 10, 'nightstalk' -> 20, 'regenerates' -> 20, 'healing' -> 15, 'curing' -> 10, 'leadership' -> 20, 'illumination' -> 15, 'teleport' -> 10, 'steadfast' -> 15 ];

def weapon_specials_weights()
	[ 'backstab' -> 20 ,'slow' -> 10 ,'berserk' -> 20 ,'stones' -> 60 ,'plague' -> 10 ,'marksman' -> 30 ,'magical' -> 40 ,'charge' -> 30 ,'drains' -> 30,  'firststrike' -> 10, 'poison' -> 25 ];

def apply_abilities_weights( units_map )
	map( 
		units_map, 
		value +
		(
			value * 
			max(
				map(
					get_unit_type( key ).abilities, 'ability',
					abilities_weights()[ability]
				)
			)
		) / 100
	);

def apply_weapon_specials_weights( units_map )
	map(
		units_map, 
		value +
		(
			value *
			max(
				map(
					get_unit_type( key ).attacks, 'attack',
					vars.weapon_specials[attack.special]
				)
			)
		) / 100
	);

# since undead units don't get random traits and are balanced with that, we need to lower their evaluation a bit to match fairly the living units #

def undead_malus( units_map )
	map( 
		units_map, 
		if(
			get_unit_type(key).undead,
			( value * 90 ) / 100,
			value 
		)
	);

# === 3 ===  pit 1 and 2 together  #

def evaluate_attackers(ai*)
	change_numbers_to_percents(
		apply_abilities_weights(
			apply_weapon_specials_weights(
				undead_malus( 
					make_positive_only(
						evaluate_my_recruits_as_attackers( ai )	
					)
				)
			)
		)
	);

def evaluate_defenders(ai*)
	map(
		map(
			change_numbers_to_percents(
				apply_abilities_weights(
					apply_weapon_specials_weights(
						undead_malus( 
							make_positive_only(
								evaluate_my_recruits_as_defenders( ai )	
							)
						)
					)
				)
			),
			value / 3
		), 
		value +
		map(
			change_numbers_to_percents(
				units_hp_map(
					ai,
					my_recruits
				)
			),
		value * 2 / 3
		)[key]
	);

	#=========== we have evaluation of game state and units, time to combine them: ==========#
		#=========	recruitment_list_builder	=========#

def rlb_apply_eval(eval, attackers_map, defenders_map, offset)
	map(
		sumarize_maps_values(
			map(
				attackers_map, 
				value * (eval+offset)
			),
			map(
				defenders_map,
				value * ( (100 - eval) - offset )
			)
		),
		value / 100
	);

def rlb_remove_lowest( input_map, percent)
	filter(
		input_map, 
		value > 
		(
			(
				percent * sumarize_values(input_map)
			) / 100
		)
	);

def rlb_first_step(eval, attackers_map, defenders_map, offset)
	rlb_remove_lowest(
		rlb_apply_eval(
			eval,
			attackers_map,
			defenders_map,
			offset
		),
		10
	);

def recruitment_list_builder(eval, attackers_map, defenders_map)
	map(
		rlb_first_step(
			eval,
			attackers_map,
			defenders_map,
			40
		),
		(
			(
				value * 100
			) / 
			sumarize_values(
				rlb_first_step(
					eval,
					attackers_map,
					defenders_map,
					40
				)
			)
		)
	);

		# ========== choose the unit to recruit now ========= #

def unit_chooser(ai*,unit_map) 
	choose(
		keys(unit_map), 'unit_type',
		unit_map[unit_type] - 
		(
			(
				100 * 
				size(
					filter(
						my_units, 
						type = unit_type
					)
				)
			) / 
			size(my_units)
		)
	);

def unit_map_cost_filter(ai*, unit_map)
	filter( unit_map,
		get_unit_type(key).cost < my_side.gold
	);

		# ==========	Main formula section 	========== #

def initialize_weapon_specials_map(ai*)
	if(vars.weapon_specials_init != turn,
		[
			set_var('weapon_specials_init', turn),
			set_var('weapon_specials',
				map(
					weapon_specials_weights(),
					if( key = 'magical',
						units_average_defense(ai, enemy_units, 10),
						if( key = 'marksman',
							units_average_defense(ai, enemy_units, 10) - 10,
							value
						)
					)
				)
			)
		],
		[]
	);

def create_recruits_map(ai*)
	if(vars.recruits_map_creator != turn,
		[
			set_var('recruits_map_creator', turn ),
			set_var('recruits_map',
				debug_print('final unit evaluation: ', recruitment_list_builder(
					debug_print('current AI evaluation (0-100): ', eval(ai)),
					debug_print('attackers evaluation: ',evaluate_attackers(ai)),
					debug_print('defenders evaluation: ',evaluate_defenders(ai))
				))
			)
		],
		[]
	);

def castle_space(ai*)
	filter( map( castle_locs( my_leader.loc ), 'location', if( unit_at( location ), 1, 0 ) ), self = 0  ).size;

if(vars.turn_initialized = turn,
		if( my_leader.loc = nearest_keep( my_leader.loc ),
			if( 
				get_unit_type(
					choose(
						vars.recruits_map,
						-get_unit_type(key).cost
					).key
				).cost <
				my_side.gold,
				if( castle_space(self),
					recruit(debug_print(
						unit_chooser(
							self,
							unit_map_cost_filter(
								self,
								vars.recruits_map
							)
						)
					)),
					fallback()
				),
				fallback() 
			),
			if( my_leader.movement_left > 0,
				move( my_leader.loc, nearest_keep( my_leader.loc ) ),
				fallback()
			)
		),
	[set_var(debug_print('turn_initialized'), turn)] +
	initialize_weapon_specials_map(self) +
	create_recruits_map(self)
)

faiend
