-- Terrain for Biter Battles -- by MewMew
local event = require 'utils.event' 
local math_random = math.random
local simplex_noise = require 'utils.simplex_noise'.d2
local create_tile_chain = require "functions.create_tile_chain"
local spawn_circle_size = 28
local ores = {"copper-ore", "iron-ore", "stone", "coal"}	
	
local function shuffle(tbl)
	local size = #tbl
		for i = size, 1, -1 do
			local rand = math_random(size)
			tbl[i], tbl[rand] = tbl[rand], tbl[i]
		end
	return tbl
end	
	
local function get_noise(name, pos)	
	local seed = game.surfaces[1].map_gen_settings.seed
	local noise_seed_add = 25000
	if name == 1 then
		local noise = {}
		noise[1] = simplex_noise(pos.x * 0.005, pos.y * 0.005, seed)
		seed = seed + noise_seed_add
		noise[2] = simplex_noise(pos.x * 0.05, pos.y * 0.05, seed)
		seed = seed + noise_seed_add
		local noise = noise[1] + noise[2] * 0.1
		return noise
	end
	if name == 2 then
		local noise = {}
		noise[1] = simplex_noise(pos.x * 0.011, pos.y * 0.011, seed)
		seed = seed + noise_seed_add
		noise[2] = simplex_noise(pos.x * 0.08, pos.y * 0.08, seed)
		seed = seed + noise_seed_add
		local noise = noise[1] + noise[2] * 0.2
		return noise
	end
	if name == 3 then
		local noise = {}
		noise[1] = simplex_noise(pos.x * 0.02, pos.y * 0.02, seed)
		seed = seed + noise_seed_add
		noise[2] = simplex_noise(pos.x * 0.08, pos.y * 0.08, seed)
		seed = seed + noise_seed_add
		local noise = noise[1] + noise[2] * 0.1
		return noise
	end
end

local function draw_smoothed_out_ore_circle(position, name, surface, radius, richness)
	if not position then return end
	if not name then return end
	if not surface then return end
	if not radius then return end
	if not richness then return end
	local math_random = math.random	
	local noise_seed_add = 25000	
	local richness_part = richness / radius
	for y = radius * -2, radius * 2, 1 do
		for x = radius * -2, radius * 2, 1 do
			local pos = {x = x + position.x, y = y + position.y}
			local seed = game.surfaces[1].map_gen_settings.seed
			local noise_1 = simplex_noise(pos.x * 0.08, pos.y * 0.08, seed)
			seed = seed + noise_seed_add
			local noise_2 = simplex_noise(pos.x * 0.15, pos.y * 0.15, seed)
			local noise = noise_1 + noise_2 * 0.2
			local distance_to_center = math.sqrt(x^2 + y^2)						
			local a = richness - richness_part * distance_to_center
			if distance_to_center + ((1 + noise) * 3) < radius and a > 1 then			
				if surface.can_place_entity({name = name, position = pos, amount = a}) then
					surface.create_entity{name = name, position = pos, amount = a}									
				end
			end			
		end
	end
end

local function generate_horizontal_river(surface, pos)
	if pos.y < -32 then return false end
	if pos.y > -3 and pos.x > -3 and pos.x < 3 then return false end
	--if -14 < pos.y + (get_noise(1, pos) * 5) then return true end
	if -12 < pos.y + (get_noise(3, pos) * 6) then return true end
	return false	
end

local function generate_circle_spawn(event)
	if event.area.left_top.y < -160 then return end
	if event.area.left_top.x < -160 then return end
	if event.area.left_top.x > 160 then return end	
	local surface = event.surface		
	local left_top_x = event.area.left_top.x
	local left_top_y = event.area.left_top.y
	for x = 0, 31, 1 do
		for y = 0, 31, 1 do
			local pos = {x = left_top_x + x, y = left_top_y + y}
			local distance_to_center = math.sqrt(pos.x ^ 2 + pos.y ^ 2)
			
			local tile = false
			if distance_to_center < spawn_circle_size then
				tile = "deepwater"
				if math_random(1, 48) == 1 then surface.create_entity({name = "fish", position = pos}) end
			end						
			if distance_to_center < 9.5 then tile = "refined-concrete" end
			if distance_to_center < 7 then tile = "sand-1" end					
			if tile then surface.set_tiles({{name = tile, position = pos}}, true) end
			
			if surface.can_place_entity({name = "stone-wall", position = pos}) then
				local noise = get_noise(2, pos) * 15
				local r = 115
				if distance_to_center + noise < r and distance_to_center + noise > r - 1.5 then
					surface.create_entity({name = "stone-wall", position = pos, force = "north"})
				end
				if distance_to_center + noise < r - 4 and distance_to_center + noise > r - 6 then
					if math_random(1,128) == 1 then
						if surface.can_place_entity({name = "gun-turret", position = pos}) then
							surface.create_entity({name = "gun-turret", position = pos, force = "north"})
						end
					end
				end
			end
			
		end
	end
end

local function generate_silos(event)
	if global.rocket_silo then return end
	if event.area.left_top.y < -128 then return end
	if event.area.left_top.x < -128 then return end
	if event.area.left_top.x > 128 then return end
	global.rocket_silo = {}
	local surface = event.surface
	global.rocket_silo["north"] =surface.create_entity({
		name = "rocket-silo",
		position = surface.find_non_colliding_position("rocket-silo", {0,-64}, 128, 1),
		force = "north"
	})
	global.rocket_silo["north"].minable = false
	
	for i = 1, 32, 1 do
		create_tile_chain(surface, {name = "stone-path", position = global.rocket_silo["north"].position}, 32, 10)
	end		
end

local function generate_river(event)
	if event.area.left_top.y < -32 then return end
	local surface = event.surface
	local left_top_x = event.area.left_top.x
	local left_top_y = event.area.left_top.y
	for x = 0, 31, 1 do
		for y = 0, 31, 1 do
			local pos = {x = left_top_x + x, y = left_top_y + y}
			local distance_to_center = math.sqrt(pos.x ^ 2 + pos.y ^ 2)
			if generate_horizontal_river(surface, pos) then
				surface.set_tiles({{name = "deepwater", position = pos}})
				if math_random(1, 64) == 1 then surface.create_entity({name = "fish", position = pos}) end
			end			
		end
	end
end

local function rainbow_ore_and_ponds(event)
	local surface = event.surface
	local left_top_x = event.area.left_top.x
	local left_top_y = event.area.left_top.y
	for x = 0, 31, 1 do
		for y = 0, 31, 1 do
			local pos = {x = left_top_x + x, y = left_top_y + y}
			if surface.can_place_entity({name = "iron-ore", position = pos}) then
				local noise = get_noise(1, pos)
				if noise > 0.81 then
					local amount = math_random(1500, 2000) + math.sqrt(pos.x ^ 2 + pos.y ^ 2) * noise * 4
					local i = math.ceil(math.abs(noise * 30)) % 4
					if i == 0 then i = 4 end
					surface.create_entity({name = ores[i], position = pos, amount = amount}) 
				end
				if noise < -0.85 then
					if noise < -0.91 then 
						surface.set_tiles({{name = "deepwater", position = pos}})
					else
						surface.set_tiles({{name = "water", position = pos}})
					end					
					if math_random(1, 48) == 1 then surface.create_entity({name = "fish", position = pos}) end
				end
			end
		end
	end
end

local function generate_potential_spawn_ore(event)
	if event.area.left_top.y < -128 then return end
	if event.area.left_top.x < -128 then return end
	if event.area.left_top.x > 128 then return end
	local pos = {x = event.area.left_top.x + 16, y = event.area.left_top.y + 16}
	local area = {{pos.x - 64, pos.y - 64}, {pos.x + 64, pos.y + 64}}
	local surface = event.surface
	
	local ores = {"iron-ore", "coal"}
	ores = shuffle(ores)
	
	if event.area.left_top.y == -96 and event.area.left_top.x == -32 then		
		if surface.count_entities_filtered({name = ores[1], area = area}) < 40 then
			draw_smoothed_out_ore_circle(pos, ores[1], surface, 13, math_random(1500, 2500)) 
		end
	end
	
	if event.area.left_top.y == -96 and event.area.left_top.x == 0 then
		if surface.count_entities_filtered({name = ores[2], area = area}) < 40 then
			draw_smoothed_out_ore_circle(pos, ores[2], surface, 13, math_random(1500, 2500)) 
		end
	end
end

local function on_chunk_generated(event)
	if event.area.left_top.y >= 0 then return end
	local surface = event.surface
	if surface.name ~= "biter_battles" then return end		 	
	
	for _, e in pairs(surface.find_entities_filtered({area = event.area, force = "enemy"})) do
		surface.create_entity({name = e.name, position = e.position, force = "north_biters", direction = e.direction})
		e.destroy()
	end
	
	rainbow_ore_and_ponds(event)
	generate_river(event)
	generate_circle_spawn(event)		
	generate_potential_spawn_ore(event)
	generate_silos(event)
	
	if event.area.left_top.y == -160 and event.area.left_top.x == -160 then
		local area = {{-10,-10},{10,10}}
		for _, e in pairs(surface.find_entities_filtered({area = area})) do
			if e.name ~= "player" then e.destroy() end
		end
		surface.destroy_decoratives({area = area})
	end
end

--Landfill Restriction
local function restrict_landfill(surface, inventory, tiles)
	for _, t in pairs(tiles) do
		local distance_to_center = math.sqrt(t.position.x ^ 2 + t.position.y ^ 2)
		local check_position = t.position
		if check_position.y > 0 then check_position = {x = check_position.x * -1, y = (check_position.y * -1) - 1} end
		if generate_horizontal_river(surface, check_position) or distance_to_center < spawn_circle_size then																			
			surface.set_tiles({{name = t.old_tile.name, position = t.position}}, true)
			inventory.insert({name = "landfill", count = 1})
		end				
	end	
end
local function on_player_built_tile(event)
	local player = game.players[event.player_index]
	restrict_landfill(player.surface, player, event.tiles)
end
local function on_robot_built_tile(event)
	restrict_landfill(event.robot.surface, event.robot.get_inventory(defines.inventory.robot_cargo), event.tiles)	
end

--Construction Robot Restriction
local function on_robot_built_entity(event)
	local deny_building = false
	local force_name = event.robot.force.name
	if force_name == "north" then
		if event.created_entity.position.y >= -10 then deny_building = true end
	end
	if force_name == "player" then
		if event.created_entity.position.y <= 10 then deny_building = true end
	end	
	if not deny_building then return end
	local inventory = event.robot.get_inventory(defines.inventory.robot_cargo)
	inventory.insert({name = event.created_entity.name, count = 1})
	event.robot.surface.create_entity({name = "explosion", position = event.created_entity.position})
	event.created_entity.destroy()			
end

--Prevent Players from damaging the Rocket Silo
local function on_entity_damaged(event)	
	if event.cause then
		if event.cause.type == "unit" then return end		 
	end
	if event.entity.name ~= "rocket-silo" then return end		
	event.entity.health = event.entity.health + event.final_damage_amount
end

local function on_marked_for_deconstruction(event)
	if event.entity.name == "fish" then event.entity.cancel_deconstruction(game.players[event.player_index].force.name) end
end

event.add(defines.events.on_marked_for_deconstruction, on_marked_for_deconstruction)
event.add(defines.events.on_entity_damaged, on_entity_damaged)
event.add(defines.events.on_robot_built_entity, on_robot_built_entity)
event.add(defines.events.on_robot_built_tile, on_robot_built_tile)
event.add(defines.events.on_player_built_tile, on_player_built_tile)
event.add(defines.events.on_chunk_generated, on_chunk_generated)