--luacheck: ignore
local Map_functions = require 'tools.map_functions'
local Server = require 'utils.server'
local Get_noise = require 'utils.get_noise'
local Constants = require 'maps.journey.constants'
local Unique_modifiers = require 'maps.journey.unique_modifiers'
local math_sqrt = math.sqrt
local math_random = math.random
local math_floor = math.floor
local math_abs = math.abs

local Public = {}
local mixed_ores = {'copper-ore', 'iron-ore', 'stone', 'coal'}

local function clear_selectors(journey)
	for k, world_selector in pairs(journey.world_selectors) do
		for _, ID in pairs(world_selector.texts) do
			rendering.destroy(ID)
		end
		journey.world_selectors[k].texts = {}
		journey.world_selectors[k].activation_level = 0
	end
	for _, ID in pairs(journey.reroll_selector.texts) do
		rendering.destroy(ID)
	end
	journey.reroll_selector.texts = {}
	journey.reroll_selector.activation_level = 0
end

function Public.place_mixed_ore(event, journey)
	if math_random(1, 192) ~= 1 then return end
	local surface = event.surface	
	local x = event.area.left_top.x + math_random(0, 31)
	local y = event.area.left_top.y + math_random(0, 31)
	local base_amount = 1000 + math_sqrt(x ^ 2 + y ^ 2) * 5
	local richness = journey.mixed_ore_richness
	Map_functions.draw_rainbow_patch({x = x, y = y}, surface, math_random(17, 22), base_amount * richness + 100)
end

local function place_teleporter(journey, surface, position)
	local tiles = {}
	for x = -1, 0, 1 do
		for y = -1, 0, 1 do
			local position = {x = position.x + x, y = position.y + y}
			table.insert(tiles, {name = Constants.teleporter_tile, position = position})
		end
	end
	surface.set_tiles(tiles, false)	
	surface.create_entity({name = 'electric-beam-no-sound', position = position, source = {x = position.x - 1, y = position.y - 1}, target = {x = position.x + 1, y = position.y - 0.5}})
	surface.create_entity({name = 'electric-beam-no-sound', position = position, source = {x = position.x + 1, y = position.y - 1}, target = {x = position.x + 1, y = position.y + 1.5}})
	surface.create_entity({name = 'electric-beam-no-sound', position = position, source = {x = position.x + 1, y = position.y + 1}, target = {x = position.x - 1, y = position.y + 1.5}})
	surface.create_entity({name = 'electric-beam-no-sound', position = position, source = {x = position.x - 1, y = position.y + 1}, target = {x = position.x - 1, y = position.y - 0.5}})	
	surface.destroy_decoratives({area = {{position.x - 1, position.y - 1}, {position.x + 1, position.y + 1}}})
end

local function destroy_teleporter(journey, surface, position)
	local tiles = {}
	for x = -1, 0, 1 do
		for y = -1, 0, 1 do
			local position = {x = position.x + x, y = position.y + y}
			table.insert(tiles, {name = "lab-dark-1", position = position})
		end
	end
	surface.set_tiles(tiles, true)
	for _, e in pairs(surface.find_entities_filtered({name = "electric-beam-no-sound", area = {{position.x - 1, position.y - 1}, {position.x + 1, position.y + 1}}})) do
		e.destroy()
	end
end

local function drop_player_items(player)
	local character = player.character
	if not character then return end
	if not character.valid then return end
	
	player.clear_cursor()
	
	for i = 1, player.crafting_queue_size, 1 do
		if player.crafting_queue_size > 0 then
			player.cancel_crafting{index = 1, count = 99999999}
		end
	end
	
	local surface = player.surface
	local spill_blockage = surface.create_entity{name = "stone-furnace", position = player.position}	
	
	for _, define in pairs({defines.inventory.character_main, defines.inventory.character_guns, defines.inventory.character_ammo, defines.inventory.character_armor, defines.inventory.character_vehicle, defines.inventory.character_trash}) do
		local inventory = character.get_inventory(define)
		if inventory and inventory.valid then
			for i = 1, #inventory, 1 do
				local slot = inventory[i]
				if slot.valid and slot.valid_for_read then
					surface.spill_item_stack(player.position, slot, true, nil, false)
				end		
			end
			inventory.clear()
		end	
	end
	
	spill_blockage.destroy()
end

function Public.clear_player(player)
	local character = player.character
	if not character then return end
	if not character.valid then return end
	player.character.destroy()
	player.set_controller({type = defines.controllers.god})
	player.create_character()	
	player.clear_items_inside()	
end

local function remove_offline_players(maximum_age_in_hours)
	local maximum_age_in_ticks = maximum_age_in_hours * 216000
	local t = game.tick - maximum_age_in_ticks
	if t < 0 then return end
	local players_to_remove = {}
	for _, player in pairs(game.players) do
		if player.last_online < t then
			table.insert(players_to_remove, player)
		end
	end
	game.remove_offline_players(players_to_remove)
end

local function get_current_modifier_percentage(name, journey)	
	local mgs = game.surfaces.nauvis.map_gen_settings
	for _, autoplace in pairs({"iron-ore", "copper-ore", "uranium-ore", "coal", "stone", "crude-oil", "stone", "trees", "enemy-base"}) do
		if name == autoplace then return mgs.autoplace_controls[name].richness end
	end	
	if name == "cliff_settings" then return 40 / mgs.cliff_settings.cliff_elevation_interval end
	if name == "water" then	return mgs.water end	
	if name == "time_factor" then return game.map_settings.enemy_evolution.time_factor * 250000 end
	if name == "destroy_factor" then return game.map_settings.enemy_evolution.destroy_factor * 500 end
	if name == "pollution_factor" then return game.map_settings.enemy_evolution.pollution_factor * 1111000 end	
	if name == "expansion_cooldown" then return (game.map_settings.enemy_expansion.min_expansion_cooldown / 144) * 0.01 end	
	if name == "technology_price_multiplier" then return game.difficulty_settings.technology_price_multiplier * 2 end
	if name == "enemy_attack_pollution_consumption_modifier" then return game.map_settings.pollution.enemy_attack_pollution_consumption_modifier end
	if name == "ageing" then return game.map_settings.pollution.ageing end
	if name == "diffusion_ratio" then return game.map_settings.pollution.diffusion_ratio * 50 end
	if name == "tree_durability" then return game.map_settings.pollution.pollution_restored_per_tree_damage * 0.1 end
	if name == "max_unit_group_size" then return game.map_settings.unit_group.max_unit_group_size * 0.005 end
	if name == "mixed_ore" then return journey.mixed_ore_richness end
end

local function delete_nauvis_chunks(journey)
	local surface = game.surfaces.nauvis	
	if not journey.nauvis_chunk_positions then
		journey.nauvis_chunk_positions = {}
		for chunk in surface.get_chunks() do table.insert(journey.nauvis_chunk_positions, {chunk.x, chunk.y}) end
		journey.size_of_nauvis_chunk_positions = #journey.nauvis_chunk_positions
		for _, e in pairs(surface.find_entities_filtered{type = "radar"}) do e.destroy() end
		for _, player in pairs(game.players) do				
			local button = player.gui.top.add({type = "sprite-button", name = "chunk_progress", caption = ""})
			button.style.font = "heading-1"
			button.style.font_color = {222, 222, 222}
			button.style.minimal_height = 38
			button.style.minimal_width = 240
			button.style.padding = -2		
		end		
	end
	
	if journey.size_of_nauvis_chunk_positions == 0 then return end
	
	for c = 1, 12, 1 do
		local chunk_position = journey.nauvis_chunk_positions[journey.size_of_nauvis_chunk_positions]
		if chunk_position then
			surface.delete_chunk(chunk_position)
			journey.size_of_nauvis_chunk_positions = journey.size_of_nauvis_chunk_positions - 1
		else
			break
		end	
	end	

	local caption = "Deleting Chunks.. " .. journey.size_of_nauvis_chunk_positions
	for _, player in pairs(game.connected_players) do
		if player.gui.top.chunk_progress then player.gui.top.chunk_progress.caption = caption end
	end
	return true	
end

function Public.mothership_message_queue(journey)
	local text = journey.mothership_messages[1]
	if not text then return end
	if text ~= "" then
		text = "[font=default-game][color=200,200,200]" .. text .. "[/color][/font]"
		text = "[font=heading-1][color=255,155,155]<Mothership> [/color][/font]" .. text
		game.print(text)
	end
	table.remove(journey.mothership_messages, 1)
end

function Public.deny_building(event)
    local entity = event.created_entity
    if not entity.valid then return end
	if entity.surface.name ~= "mothership" then return end
	if Constants.build_type_whitelist[entity.type] then
		entity.destructible = false
		return 
	end
	entity.die() 
end

function Public.register_built_silo(event, journey)
    local entity = event.created_entity
    if not entity.valid then return end
	if entity.surface.index ~= 1 then return end
	if entity.type ~= "rocket-silo" then return end
	entity.auto_launch = false
	table.insert(journey.rocket_silos, entity)
end

function Public.draw_gui(journey)
	local surface = game.surfaces.nauvis
	local mgs = surface.map_gen_settings	
	local caption = "World " .. journey.world_number .. " | " .. Constants.unique_world_traits[journey.world_trait][1]	
	local tooltip = Constants.unique_world_traits[journey.world_trait][2] .. "\n\n"
	
	for k, v in pairs(Constants.modifiers) do
		tooltip = tooltip .. v[3] .. " - " .. math.round(get_current_modifier_percentage(k, journey) * 100, 1) .. "%\n"
	end
	
	tooltip = tooltip .. "\nCapsules:\n"
	local c = 0
	for k, v in pairs(journey.bonus_goods) do
		tooltip = tooltip .. v .. "x " .. k .. "    "
		c = c + 1
		if c % 2 == 0 then tooltip = tooltip .. "\n" end	
	end
	
	for _, player in pairs(game.connected_players) do	
		if not player.gui.top.journey_button then
			local element = player.gui.top.add({type = "sprite-button", name = "journey_button", caption = ""})
			element.style.font = "heading-1"
			element.style.font_color = {222, 222, 222}
			element.style.minimal_height = 38
			element.style.minimal_width = 250
			element.style.padding = -2
		end
		local gui = player.gui.top.journey_button
		gui.caption = caption
		gui.tooltip = tooltip
	end
	
	local fuel_requirement = journey.mothership_cargo_space["uranium-fuel-cell"]
	local value
	if fuel_requirement == 0 then
		value = 1
	else
		value = journey.mothership_cargo["uranium-fuel-cell"] / fuel_requirement
	end	
	local tooltip = "Fuel requirement to advance to next world:\n" .. fuel_requirement .. "\n\nNuclear Fuel Cells in Mothership Cargo:\n" .. journey.mothership_cargo["uranium-fuel-cell"]
	
	for _, player in pairs(game.connected_players) do	
		if not player.gui.top.journey_fuel then
			local frame = player.gui.top.add({type = 'frame', name = 'journey_fuel'})
			frame.style.padding = 0
			local element = frame.add({type = 'sprite', sprite = 'item/uranium-fuel-cell', name = "journey_fuel_sprite"})
			element.style.minimal_width = 32
			element.style.minimal_height = 32
			element.style.maximal_width = 32
			element.style.maximal_height = 32
			element.style.margin = 0
			element.style.padding = 0
			local element = frame.add({type = 'progressbar', name = 'journey_fuel_progressbar', value = 0})		
			element.style.minimal_width = 100
			element.style.maximal_width = 100
			element.style.top_margin = 12
			element.style.right_margin = 12
		end
		local gui = player.gui.top.journey_fuel
		gui.tooltip = tooltip
		local gui = player.gui.top.journey_fuel.journey_fuel_sprite
		gui.tooltip = tooltip
		local gui = player.gui.top.journey_fuel.journey_fuel_progressbar
		gui.value = value
		gui.tooltip = tooltip
	end
	
	local max_sattelites = journey.mothership_cargo_space["satellite"]
	local value = journey.mothership_cargo["satellite"] / max_sattelites
	local tooltip = "Room for satellites in Mothership cargo:\n" .. max_sattelites .. "\n\nSatellites stored:\n" .. journey.mothership_cargo["satellite"]
	
	for _, player in pairs(game.connected_players) do	
		if not player.gui.top.journey_satellites then
			local frame = player.gui.top.add({type = 'frame', name = 'journey_satellites'})
			frame.style.left_margin = 4
			frame.style.padding = 0
			local element = frame.add({type = 'sprite', sprite = 'item/satellite', name = "journey_satellites_sprite"})
			element.style.minimal_width = 32
			element.style.minimal_height = 32
			element.style.maximal_width = 32
			element.style.maximal_height = 32
			element.style.margin = 0
			element.style.padding = 0
			local element = frame.add({type = 'progressbar', name = 'journey_satellites_progressbar', value = 0})		
			element.style.minimal_width = 100
			element.style.maximal_width = 100
			element.style.top_margin = 12
			element.style.right_margin = 12
		end
		local gui = player.gui.top.journey_satellites
		gui.tooltip = tooltip
		local gui = player.gui.top.journey_satellites.journey_satellites_sprite
		gui.tooltip = tooltip
		local gui = player.gui.top.journey_satellites.journey_satellites_progressbar
		gui.value = value
		gui.tooltip = tooltip
	end
end

local function is_mothership(position)
	if math.abs(position.x) > Constants.mothership_radius then return false end
	if math.abs(position.y) > Constants.mothership_radius then return false end
	local p = {x = position.x, y = position.y}
	if p.x > 0 then p.x = p.x + 1 end
	if p.y > 0 then p.y = p.y + 1 end
	local d = math.sqrt(p.x ^ 2 + p.y ^ 2)
	if d < Constants.mothership_radius then
		return true
	end	
end

function Public.on_mothership_chunk_generated(event)	
	local left_top = event.area.left_top
	local surface = event.surface
	local seed = surface.map_gen_settings.seed
	local tiles = {}
	for x = 0, 31, 1 do
		for y = 0, 31, 1 do
			local position = {x = left_top.x + x, y = left_top.y + y}
			if is_mothership(position) then			
				table.insert(tiles, {name = "black-refined-concrete", position = position})
			else
				table.insert(tiles, {name = "out-of-map", position = position})
			end		
		end
	end
	surface.set_tiles(tiles, true)
end

function Public.hard_reset(journey)
	if game.surfaces.mothership and game.surfaces.mothership.valid then
		game.delete_surface(game.surfaces.mothership)
	end
	
	game.forces.enemy.character_inventory_slots_bonus = 9999
	
	game.map_settings.enemy_expansion.enabled = true
	game.map_settings.enemy_expansion.max_expansion_distance = 20
	game.map_settings.enemy_expansion.settler_group_min_size = 5
	game.map_settings.enemy_expansion.settler_group_max_size = 50
	game.map_settings.enemy_expansion.min_expansion_cooldown = 14400
	game.map_settings.enemy_expansion.max_expansion_cooldown = 216000

	game.map_settings.pollution.enabled = true
	game.map_settings.pollution.ageing = 1
	game.map_settings.pollution.diffusion_ratio = 0.02
	game.map_settings.pollution.enemy_attack_pollution_consumption_modifier = 1
	game.map_settings.pollution.min_pollution_to_damage_trees = 60
	game.map_settings.pollution.pollution_restored_per_tree_damage = 10
	
	game.map_settings.unit_group.max_unit_group_size = 200

	game.difficulty_settings.technology_price_multiplier = 0.5

	game.map_settings.enemy_evolution.time_factor = 0.000004
	game.map_settings.enemy_evolution.destroy_factor = 0.002
	game.map_settings.enemy_evolution.pollution_factor = 0.0000009
	
	local surface = game.surfaces[1]
    local mgs = surface.map_gen_settings
    mgs.water = 1
	mgs.starting_area = 1
    mgs.cliff_settings = {cliff_elevation_interval = 40, cliff_elevation_0 = 10}
    mgs.autoplace_controls = {
		["coal"] = {frequency = 1, size = 1, richness = 1},
		["stone"] = {frequency = 1, size = 1, richness = 1},
		["copper-ore"] = {frequency = 1, size = 1, richness = 1},
		["iron-ore"] = {frequency = 1, size = 1, richness = 1},
		["uranium-ore"] = {frequency = 1, size = 1, richness = 1},
		["crude-oil"] = {frequency = 1, size = 1, richness = 1},
		["trees"] = {frequency = 1, size = 1, richness = 1},
		["enemy-base"] = {frequency = 1, size = 1, richness = 1},
    }
    surface.map_gen_settings = mgs
    surface.clear(true)
	surface.daytime = math.random(1, 100) * 0.01
	
	if journey.world_selectors and journey.world_selectors[1].border then
		for k, world_selector in pairs(journey.world_selectors) do
			for _, ID in pairs(world_selector.rectangles) do
				rendering.destroy(ID)
			end
			rendering.destroy(world_selector.border)
		end	
	end
	
	journey.world_selectors = {}
	journey.reroll_selector = {activation_level = 0}
	for i = 1, 3, 1 do journey.world_selectors[i] = {activation_level = 0, texts = {}} end	
	journey.mothership_speed = 0.5
	journey.characters_in_mothership = 0
	journey.world_color_filters = {}
	journey.mixed_ore_richness = 1
	journey.mothership_messages = {}
	journey.mothership_cargo = {}
	journey.mothership_cargo["uranium-fuel-cell"] = 10
	journey.mothership_cargo["satellite"] = 1
	journey.mothership_cargo_space = {
		["satellite"] = 1,
		["uranium-fuel-cell"] = 0,
	}
	journey.bonus_goods = {}
	journey.nauvis_chunk_positions = nil	
	journey.world_number = 0
	journey.world_trait = "lush"
	journey.game_state = "create_mothership"
end

function Public.create_mothership(journey)
	local surface = game.create_surface("mothership", Constants.mothership_gen_settings)
	surface.request_to_generate_chunks({x = 0, y = 0}, 6)
	surface.force_generate_chunk_requests()
	surface.freeze_daytime = true
	journey.game_state = "draw_mothership"
end

function Public.draw_mothership(journey)
	local surface = game.surfaces.mothership
	
	local positions = {}
	for x = Constants.mothership_radius * -1, Constants.mothership_radius, 1 do
		for y = Constants.mothership_radius * -1, Constants.mothership_radius, 1 do
			local position = {x = x, y = y}
			if is_mothership(position) then table.insert(positions, position) end
		end
	end
	
	table.shuffle_table(positions)
	
	for _, position in pairs(positions) do	
		if surface.count_tiles_filtered({area = {{position.x - 1, position.y - 1}, {position.x + 2, position.y + 2}}, name = "out-of-map"}) > 0 then
			local e = surface.create_entity({name = "stone-wall", position = position, force = "player"})
			e.destructible = false
			e.minable = false
		end
		if surface.count_tiles_filtered({area = {{position.x - 1, position.y - 1}, {position.x + 2, position.y + 2}}, name = "lab-dark-1"}) < 4 then
			surface.set_tiles({{name = "lab-dark-1", position = position}}, true)
		end					
	end

	for _, tile in pairs(surface.find_tiles_filtered({area = {{Constants.mothership_teleporter_position.x - 2, Constants.mothership_teleporter_position.y - 2}, {Constants.mothership_teleporter_position.x + 2, Constants.mothership_teleporter_position.y + 2}}})) do
		surface.set_tiles({{name = "lab-dark-1", position = tile.position}}, true)
	end

	for k, area in pairs(Constants.world_selector_areas) do
		journey.world_selectors[k].rectangles = {}				
		local position = area.left_top
		local rectangle = rendering.draw_rectangle {
			width = 1,
			filled=true,
			surface = surface,
			left_top = position,
			right_bottom = {position.x + Constants.world_selector_width, position.y + Constants.world_selector_height},
			color = Constants.world_selector_colors[k],
			draw_on_ground = true,
			only_in_alt_mode = false
		}
		table.insert(journey.world_selectors[k].rectangles, rectangle)		
		journey.world_selectors[k].border = rendering.draw_rectangle {
			width = 8,
			filled=false,
			surface = surface,
			left_top = position,
			right_bottom = {position.x + Constants.world_selector_width, position.y + Constants.world_selector_height},
			color = {r = 100, g = 100, b = 100, a = 255},
			draw_on_ground = true,
			only_in_alt_mode = false
		}
	end

	journey.reroll_selector.rectangle = rendering.draw_rectangle {
		width = 8,
		filled=true,
		surface = surface,
		left_top = Constants.reroll_selector_area.left_top,
		right_bottom = Constants.reroll_selector_area.right_bottom,
		color = Constants.reroll_selector_area_color,
		draw_on_ground = true,
		only_in_alt_mode = false
	}
	journey.reroll_selector.border = rendering.draw_rectangle {
		width = 8,
		filled=false,
		surface = surface,
		left_top = Constants.reroll_selector_area.left_top,
		right_bottom = Constants.reroll_selector_area.right_bottom,
		color = {r = 100, g = 100, b = 100, a = 255},
		draw_on_ground = true,
		only_in_alt_mode = false
	}	

	for k, item_name in pairs({"arithmetic-combinator", "constant-combinator", "decider-combinator", "programmable-speaker", "red-wire", "green-wire", "small-lamp", "substation", "pipe", "gate", "stone-wall", "transport-belt"}) do
		local e = surface.create_entity({name = 'infinity-chest', position = {-7 + k, Constants.mothership_radius - 3}, force = 'player'})
		e.set_infinity_container_filter(1, {name = item_name, count = game.item_prototypes[item_name].stack_size})
		e.minable = false
		e.destructible = false
		e.operable = false
		local e = surface.create_entity({name = "express-loader", position = {-7 + k, Constants.mothership_radius - 4}, force = "player"})
		e.minable = false
		e.destructible = false
		e.direction = 4
	end
		
	for m = -1, 1, 2 do
		local e = surface.create_entity({name = "electric-energy-interface", position = {11 * m, Constants.mothership_radius - 4}, force = "player"})	
		e.minable = false
		e.destructible = false
		local e = surface.create_entity({name = "substation", position = {9 * m, Constants.mothership_radius - 4}, force = "player"})
		e.minable = false
		e.destructible = false
	end
	
	for m = -1, 1, 2 do
		local x = Constants.mothership_radius - 3
		if m > 0 then x = x - 1 end
		local y = Constants.mothership_radius * 0.5 - 7		
		local e = surface.create_entity({name = "artillery-turret", position = {x * m, y}, force = "player"})
		e.direction = 4
		e.minable = false
		e.destructible = false
		e.operable = false
		local e = surface.create_entity({name = "burner-inserter", position = {(x - 1) * m, y}, force = "player"})
		e.direction = 4 + m * 2
		e.rotatable = false
		e.minable = false
		e.destructible = false
		e.operable = false
		local e = surface.create_entity({name = 'infinity-chest', position = {(x - 2) * m, y}, force = 'player'})
		e.set_infinity_container_filter(1, {name = "solid-fuel", count = 50})
		e.set_infinity_container_filter(2, {name = "artillery-shell", count = 1})
		e.minable = false
		e.destructible = false
		e.operable = false
	end
	
	for _ = 1, 3, 1 do
		local e = surface.create_entity({name = "compilatron", position = Constants.mothership_teleporter_position, force = "player"})
		e.destructible = false
	end

	Public.draw_gui(journey)	
	surface.daytime = 0.5

	journey.game_state = "set_world_selectors"
end

function Public.teleport_players_to_mothership(journey)
	local surface = game.surfaces.mothership
	for _, player in pairs(game.connected_players) do
		if player.surface.name ~= "mothership" then
			Public.clear_player(player)
			player.teleport(surface.find_non_colliding_position("character", {0,0}, 32, 0.5), surface)
			journey.characters_in_mothership = journey.characters_in_mothership + 1
			table.insert(journey.mothership_messages, "Welcome home " .. player.name .. "!")
			return
		end
	end
end

local function get_activation_level(surface, area)
	local player_count_in_area = surface.count_entities_filtered({area = area, name = "character"})	
	local player_count_for_max_activation = #game.connected_players * 0.66	
	local level = player_count_in_area / player_count_for_max_activation	
	level = math.round(level, 2)
	return level
end

local function animate_selectors(journey)
	for k, world_selector in pairs(journey.world_selectors) do
		local activation_level = journey.world_selectors[k].activation_level
		if activation_level < 0.2 then activation_level = 0.2 end
		if activation_level > 1 then activation_level = 1 end
		for _, rectangle in pairs(world_selector.rectangles) do
			local color = Constants.world_selector_colors[k]
			rendering.set_color(rectangle, {r = color.r * activation_level, g = color.g * activation_level, b = color.b * activation_level, a = 255})
		end
	end
	local activation_level = journey.reroll_selector.activation_level
	if activation_level < 0.2 then activation_level = 0.2 end
	if activation_level > 1 then activation_level = 1 end	
	local color = Constants.reroll_selector_area_color
	rendering.set_color(journey.reroll_selector.rectangle, {r = color.r * activation_level, g = color.g * activation_level, b = color.b * activation_level, a = 255})
end

local function draw_background(journey, surface)
	if journey.characters_in_mothership == 0 then return end
	local speed = journey.mothership_speed
	for c = 1, 16 * speed, 1 do
		local position = Constants.particle_spawn_vectors[math.random(1, Constants.size_of_particle_spawn_vectors)]
		surface.create_entity({name = "shotgun-pellet", position = position, target = {position[1], position[2] + Constants.mothership_radius * 2}, speed = speed})
	end
	for c = 1, 16 * speed, 1 do
		local position = Constants.particle_spawn_vectors[math.random(1, Constants.size_of_particle_spawn_vectors)]
		surface.create_entity({name = "piercing-shotgun-pellet", position = position, target = {position[1], position[2] + Constants.mothership_radius * 2}, speed = speed})
	end
	for c = 1, 2 * speed, 1 do
		local position = Constants.particle_spawn_vectors[math.random(1, Constants.size_of_particle_spawn_vectors)]
		surface.create_entity({name = "cannon-projectile", position = position, target = {position[1], position[2] + Constants.mothership_radius * 2}, speed = speed})
	end
	for c = 1, 1 * speed, 1 do
		local position = Constants.particle_spawn_vectors[math.random(1, Constants.size_of_particle_spawn_vectors)]
		surface.create_entity({name = "uranium-cannon-projectile", position = position, target = {position[1], position[2] + Constants.mothership_radius * 2}, speed = speed})
	end
	if math.random(1, 32) == 1 then		
		local position = Constants.particle_spawn_vectors[math.random(1, Constants.size_of_particle_spawn_vectors)]
		surface.create_entity({name = "explosive-uranium-cannon-projectile", position = position, target = {position[1], position[2] + Constants.mothership_radius * 3}, speed = speed})	
	end	
	if math.random(1, 90) == 1 then		
		local position_x = math.random(64, 160)
		local position_y = math.random(64, 160)
		if math.random(1, 2) == 1 then position_x = position_x * -1 end
		if math.random(1, 2) == 1 then position_y = position_y * -1 end		
		surface.create_entity({name = "big-worm-turret", position = {position_x, position_y}, force = "enemy"})
	end
end

function Public.set_world_selectors(journey)
	local surface = game.surfaces.mothership
	
	local x = Constants.reroll_selector_area.left_top.x + 3.2
	journey.reroll_selector.texts = {
		rendering.draw_text{
			text = journey.mothership_cargo.satellite .. " x ",
			surface = surface,
			target = {x, Constants.reroll_selector_area.left_top.y - 1.5},
			color = {255, 255, 255, 255},
			scale = 1.5,
			font = "default-large-bold",
			alignment = "center",
			scale_with_zoom = false,
		},
		rendering.draw_sprite{
			sprite = "item/satellite",
			surface = surface,
			y_scale = 1.5,
			x_scale = 1.5,
			target = {x + 1.6, Constants.reroll_selector_area.left_top.y - 1},
		},
	}	
	
	local modifier_names = {}
	for k, _ in pairs(Constants.modifiers) do
		table.insert(modifier_names, k)
	end
	
	local bonus_goods_keys = {}
	for i = 1, #Constants.starter_goods_pool, 1 do
		table.insert(bonus_goods_keys, i)
	end
	
	local unique_world_traits = {}
	for k, _ in pairs(Constants.unique_world_traits) do
		table.insert(unique_world_traits, k)
	end
	table.shuffle_table(unique_world_traits)
	
	for k, world_selector in pairs(journey.world_selectors) do
		table.shuffle_table(bonus_goods_keys)
		table.shuffle_table(modifier_names)
		world_selector.modifiers = {}
		world_selector.bonus_goods = {}
		world_selector.world_trait = unique_world_traits[k]
		world_selector.fuel_requirement = math.random(25, 50)
		local position = Constants.world_selector_areas[k].left_top
		local texts = world_selector.texts
		local modifiers = world_selector.modifiers				
		local bonus_goods = world_selector.bonus_goods
		local y_modifier = -9.7
		
		for i = 1, 8, 1 do
			local modifier = modifier_names[i]
			local v = math.random(Constants.modifiers[modifier][1], Constants.modifiers[modifier][2])
			if i > 6 then v = v * -0.5 end			
			v = math.floor(v)
			modifiers[i] = {modifier, v}
		end

		table.insert(texts, rendering.draw_text{
			text = Constants.unique_world_traits[world_selector.world_trait][1],
			surface = surface,
			target = {position.x + Constants.world_selector_width * 0.5, position.y + y_modifier},
			color = {100, 0, 255, 255},
			scale = 1.25,
			font = "default-large-bold",
			alignment = "center",
			scale_with_zoom = false
		})	
		
		for k2, modifier in pairs(modifiers) do
			y_modifier = y_modifier + 0.8
			local text = ""
			if modifier[2] > 0 then text = text .. "+" end
			text = text .. modifier[2] .. "% "
			text = text .. Constants.modifiers[modifier[1]][3]
			
			local color
			if k2 < 7 then
				color = {200, 0, 0, 255}
			else
				color = {0, 200, 0, 255}
			end				
			
			table.insert(texts, rendering.draw_text{
				text = text,
				surface = surface,
				target = {position.x + Constants.world_selector_width * 0.5, position.y + y_modifier},
				color = color,
				scale = 1.25,
				font = "default-large",
				alignment = "center",
				scale_with_zoom = false
			})
		end
		
		y_modifier = y_modifier + 0.85
		table.insert(texts, rendering.draw_text{
			text = "Fuel requirement +" .. world_selector.fuel_requirement,
			surface = surface,
			target = {position.x + Constants.world_selector_width * 0.5, position.y + y_modifier},
			color = {155, 155, 0, 255},
			scale = 1.25,
			font = "default-large",
			alignment = "center",
			scale_with_zoom = false
		})
		table.insert(texts, rendering.draw_sprite{
				sprite  = "item/uranium-fuel-cell",
				surface = surface,
				target = {position.x + Constants.world_selector_width * 0.5 + 3.7, position.y + y_modifier + 0.5},
		})			
		
		for i = 1, 3, 1 do
			local key = bonus_goods_keys[i]
			local bonus_good = Constants.starter_goods_pool[key]
			bonus_goods[i] = {bonus_good[1], math.random(bonus_good[2], bonus_good[3])}
		end
				
		y_modifier = y_modifier + 1.1
		local x_modifier = -0.5	

		for k2, good in pairs(world_selector.bonus_goods) do
			local render_id = rendering.draw_text{
				text = "+" .. good[2],
				surface = surface,
				target = {position.x + x_modifier, position.y + y_modifier},
				color = {200, 200, 0, 255},
				scale = 1.25,
				font = "default-large",
				alignment = "center",
				scale_with_zoom = false
			}
			table.insert(texts, render_id)
			
			x_modifier = x_modifier + 0.95		
			if good[2] >= 10 then x_modifier = x_modifier + 0.18 end
			if good[2] >= 100 then x_modifier = x_modifier + 0.18 end

			local render_id = rendering.draw_sprite{
				sprite  = "item/" .. good[1],
				surface = surface,
				target = {position.x + x_modifier, position.y + 0.5 + y_modifier},
			}
			table.insert(texts, render_id)
			
			x_modifier = x_modifier + 1.70
		end
	end
	
	destroy_teleporter(journey, game.surfaces.nauvis, Constants.mothership_teleporter_position)
	destroy_teleporter(journey, surface, Constants.mothership_teleporter_position)
	
	Server.to_discord_embed("World " .. journey.world_number + 1 .. " selection has started!")
	
	journey.game_state = "delete_nauvis_chunks"
end

function Public.delete_nauvis_chunks(journey)
	local surface = game.surfaces.mothership
	Public.teleport_players_to_mothership(journey)
	draw_background(journey, surface)
	if delete_nauvis_chunks(journey) then return end	
	for _, player in pairs(game.players) do
		if player.gui.top.chunk_progress then player.gui.top.chunk_progress.destroy() end
	end
	
	journey.game_state = "mothership_world_selection"
end

function Public.reroll_worlds(journey)
	local surface = game.surfaces.mothership
	Public.teleport_players_to_mothership(journey)
	draw_background(journey, surface)
	animate_selectors(journey)	
	local reroll_selector_activation_level = get_activation_level(surface, Constants.reroll_selector_area)
	journey.reroll_selector.activation_level = reroll_selector_activation_level
	for i = 1, 3, 1 do
		local activation_level = get_activation_level(surface, Constants.world_selector_areas[i])
		journey.world_selectors[i].activation_level = activation_level
	end	
	if reroll_selector_activation_level > 1 then
		journey.mothership_speed = journey.mothership_speed + 0.025
		if journey.mothership_speed > 4 then
			journey.mothership_speed = 4
			clear_selectors(journey)
			journey.mothership_cargo.satellite = journey.mothership_cargo.satellite - 1
			Public.draw_gui(journey)
			table.insert(journey.mothership_messages, "New lands have been discovered!")
			journey.game_state = "set_world_selectors"
		end
	else	
		journey.mothership_speed = journey.mothership_speed - 0.25
		if journey.mothership_speed < 0.35 then
			table.insert(journey.mothership_messages, "Aborting..")
			journey.game_state = "mothership_world_selection"
			journey.mothership_speed = 0.35 
		end
	end
end

function Public.mothership_world_selection(journey)
	Public.teleport_players_to_mothership(journey)

	local surface = game.surfaces.mothership
	local daytime = surface.daytime
	daytime = daytime - 0.025	
	if daytime < 0 then daytime = 0 end
	surface.daytime = daytime

	local reroll_selector_activation_level = get_activation_level(surface, Constants.reroll_selector_area)
	journey.reroll_selector.activation_level = reroll_selector_activation_level
	
	journey.selected_world = false
	for i = 1, 3, 1 do
		local activation_level = get_activation_level(surface, Constants.world_selector_areas[i])
		journey.world_selectors[i].activation_level = activation_level
		if activation_level > 1 then
			journey.selected_world = i 
		end
	end
	
	if reroll_selector_activation_level > 1 and journey.mothership_speed == 0.35 and journey.mothership_cargo.satellite > 0 then
		journey.game_state = "reroll_worlds"
		table.insert(journey.mothership_messages, "Dispatching satellite..")
		return
	end
	
	if journey.selected_world then
		if not journey.mothership_advancing_to_world then
			table.insert(journey.mothership_messages, "Advancing to selected world.")
			journey.mothership_advancing_to_world = game.tick + math.random(60 * 45, 60 * 75)
		else
			local seconds_left = math.floor((journey.mothership_advancing_to_world - game.tick) / 60)
			if seconds_left <= 0 then
				journey.mothership_advancing_to_world = false
				table.insert(journey.mothership_messages, "Arriving at targeted destination!")
				journey.game_state = "mothership_arrives_at_world"
				return
			end
			if seconds_left % 15 == 0 then table.insert(journey.mothership_messages, "Estimated arrival in " .. seconds_left .. " seconds.") end
		end
		
		journey.mothership_speed = journey.mothership_speed + 0.1
		if journey.mothership_speed > 4 then journey.mothership_speed = 4 end
	else
		if journey.mothership_advancing_to_world then
			table.insert(journey.mothership_messages, "Aborting travelling sequence.")
			journey.mothership_advancing_to_world = false
		end	
		journey.mothership_speed = journey.mothership_speed - 0.25
		if journey.mothership_speed < 0.35 then journey.mothership_speed = 0.35 end
	end
			
	draw_background(journey, surface)
	animate_selectors(journey)
end

function Public.mothership_arrives_at_world(journey)
	local surface = game.surfaces.mothership
	
	Public.teleport_players_to_mothership(journey)
	
	if journey.mothership_speed == 0.15 then
		for _ = 1, 16, 1 do table.insert(journey.mothership_messages, "") end
		table.insert(journey.mothership_messages, "[img=item/uranium-fuel-cell] Fuel cells depleted ;_;")
		for _ = 1, 16, 1 do table.insert(journey.mothership_messages, "") end
		table.insert(journey.mothership_messages, "Refuel via supply rocket required!")
	
		for i = 1, 3, 1 do
			journey.world_selectors[i].activation_level = 0
		end
		animate_selectors(journey)
			
		journey.game_state = "clear_modifiers"
	else
		journey.mothership_speed = journey.mothership_speed - 0.15
	end
	
	if journey.mothership_speed < 0.15 then 
		journey.mothership_speed = 0.15
	end
		
	draw_background(journey, surface)
end

function Public.clear_modifiers(journey)
	local unique_modifier = Unique_modifiers[journey.world_trait]
	local clear = unique_modifier.clear
	if clear then clear(journey) end
	
	local force = game.forces.player
	force.reset()
	force.reset_technologies()
	force.reset_technology_effects()
	for a = 1, 7, 1 do force.technologies['refined-flammables-' .. a].enabled = false end
	journey.game_state = "create_the_world"
end

function Public.create_the_world(journey)
	local surface = game.surfaces.nauvis
	local mgs = surface.map_gen_settings
	mgs.seed = math.random(1, 4294967295)
	mgs.terrain_segmentation = math.random(10, 20) * 0.1
	mgs.peaceful_mode = false
	
	local modifiers = journey.world_selectors[journey.selected_world].modifiers
	for _, modifier in pairs(modifiers) do
		local m = (100 + modifier[2]) * 0.01
		local name = modifier[1]
		for _, autoplace in pairs({"iron-ore", "copper-ore", "uranium-ore", "coal", "stone", "crude-oil"}) do
			if name == autoplace then
				mgs.autoplace_controls[name].richness = mgs.autoplace_controls[name].richness * m
				break
			end
		end
		if name == "mixed_ore" then
			journey.mixed_ore_richness = journey.mixed_ore_richness * m
		end
		for _, autoplace in pairs({"trees", "enemy-base"}) do
			if name == autoplace then
				for k, v in pairs(mgs.autoplace_controls[name]) do
					mgs.autoplace_controls[name][k] = mgs.autoplace_controls[name][k] * m
				end
				break
			end
		end
		if name == "cliff_settings" then
			--smaller value = more cliffs
			local m2 = (100 - modifier[2]) * 0.01
			mgs.cliff_settings.cliff_elevation_interval = mgs.cliff_settings.cliff_elevation_interval * m2
			mgs.cliff_settings.cliff_elevation_0 = mgs.cliff_settings.cliff_elevation_0 * m2
		end
		if name == "water" then			
			mgs.water = mgs.water * m
		end
		for _, evo in pairs({"time_factor", "destroy_factor", "pollution_factor"}) do
			if name == evo then
				game.map_settings.enemy_evolution[name] = game.map_settings.enemy_evolution[name] * m
				break
			end
		end
		if name == "expansion_cooldown" then			
			game.map_settings.enemy_expansion.min_expansion_cooldown = game.map_settings.enemy_expansion.min_expansion_cooldown * m
			game.map_settings.enemy_expansion.max_expansion_cooldown = game.map_settings.enemy_expansion.max_expansion_cooldown * m
		end
		if name == "technology_price_multiplier" then			
			game.difficulty_settings.technology_price_multiplier = game.difficulty_settings.technology_price_multiplier * m
		end
		if name == "enemy_attack_pollution_consumption_modifier" then			
			game.map_settings.pollution.enemy_attack_pollution_consumption_modifier = game.map_settings.pollution.enemy_attack_pollution_consumption_modifier * m
		end
		if name == "ageing" then			
			game.map_settings.pollution.ageing = game.map_settings.pollution.ageing * m
		end
		if name == "diffusion_ratio" then			
			game.map_settings.pollution.diffusion_ratio = game.map_settings.pollution.diffusion_ratio * m
		end
		if name == "tree_durability" then
			game.map_settings.pollution.min_pollution_to_damage_trees = game.map_settings.pollution.min_pollution_to_damage_trees * m
			game.map_settings.pollution.pollution_restored_per_tree_damage = game.map_settings.pollution.pollution_restored_per_tree_damage * m
		end
		if name == "max_unit_group_size" then
			game.map_settings.unit_group.max_unit_group_size = game.map_settings.unit_group.max_unit_group_size * m
		end	
	end

	surface.map_gen_settings = mgs
    surface.clear(false)
	
	journey.world_trait = journey.world_selectors[journey.selected_world].world_trait
	journey.nauvis_chunk_positions = nil
	journey.rocket_silos = {}
	journey.mothership_cargo["uranium-fuel-cell"] = 0
	journey.world_number = journey.world_number + 1
	journey.mothership_cargo_space["satellite"] = math_floor(journey.world_number * 0.334) + 1
	journey.mothership_cargo_space["uranium-fuel-cell"] = journey.mothership_cargo_space["uranium-fuel-cell"] + journey.world_selectors[journey.selected_world].fuel_requirement
	
	game.forces.enemy.reset_evolution()

	for _, good in pairs(journey.world_selectors[journey.selected_world].bonus_goods) do
		if journey.bonus_goods[good[1]] then
			journey.bonus_goods[good[1]] = journey.bonus_goods[good[1]] + good[2]
		else
			journey.bonus_goods[good[1]] = good[2]
		end	
	end
	journey.goods_to_dispatch = {}
	for k, v in pairs(journey.bonus_goods) do table.insert(journey.goods_to_dispatch, {k, v}) end
	table.shuffle_table(journey.goods_to_dispatch)

	journey.game_state = "wipe_offline_players"
end

function Public.wipe_offline_players(journey)
	remove_offline_players(168)
	for _, player in pairs(game.players) do
		if not player.connected then
			player.force = game.forces.enemy
		end
	end
	journey.game_state = "set_unique_modifiers"
end

function Public.set_unique_modifiers(journey)
	local unique_modifier = Unique_modifiers[journey.world_trait]
	local on_world_start = unique_modifier.on_world_start
	if on_world_start then on_world_start(journey) end
	Public.draw_gui(journey)
	journey.game_state = "place_teleporter_into_world"
end

function Public.place_teleporter_into_world(journey)
	local surface = game.surfaces.nauvis
	surface.request_to_generate_chunks({x = 0, y = 0}, 3)
	surface.force_generate_chunk_requests()
	place_teleporter(journey, surface, Constants.mothership_teleporter_position)
	journey.game_state = "make_it_night"
end

function Public.make_it_night(journey)
	draw_background(journey, game.surfaces.mothership)
	local surface = game.surfaces.mothership
	local daytime = surface.daytime
	daytime = daytime + 0.02
	surface.daytime = daytime
	if daytime > 0.5 then		
		clear_selectors(journey)		
		game.reset_time_played()
		place_teleporter(journey, surface, Constants.mothership_teleporter_position)
		table.insert(journey.mothership_messages, "Teleporter deployed. [gps=" .. Constants.mothership_teleporter_position.x .. "," .. Constants.mothership_teleporter_position.y .. ",mothership]")	
		journey.game_state = "dispatch_goods" 
	end
end

function Public.dispatch_goods(journey)
	draw_background(journey, game.surfaces.mothership)

	if journey.characters_in_mothership == #game.connected_players then return end	

	local goods_to_dispatch = journey.goods_to_dispatch
	local size_of_goods_to_dispatch = #goods_to_dispatch
	if size_of_goods_to_dispatch == 0 then
		for _ = 1, 30, 1 do table.insert(journey.mothership_messages, "") end
		table.insert(journey.mothership_messages, "Capsule storage depleted.")
		for _ = 1, 30, 1 do table.insert(journey.mothership_messages, "") end
		table.insert(journey.mothership_messages, "Good luck on your adventure! ^.^")
		journey.game_state = "world"
		return
	end

	if journey.dispatch_beacon and journey.dispatch_beacon.valid then return end
	
	local surface = game.surfaces.nauvis
	
	if journey.dispatch_beacon_position then
		local good = goods_to_dispatch[journey.dispatch_key]	
		surface.spill_item_stack(journey.dispatch_beacon_position, {name = good[1], count = good[2]}, true, nil, false)
		table.remove(journey.goods_to_dispatch, journey.dispatch_key)
		journey.dispatch_beacon = nil
		journey.dispatch_beacon_position = nil
		journey.dispatch_key = nil
		return
	end
	
	local chunk = surface.get_random_chunk()
	if math.abs(chunk.x) > 4 or math.abs(chunk.y) > 4 then return end
	
	local position = {x = chunk.x * 32 + math.random(0, 31), y = chunk.y * 32 + math.random(0, 31)}
	position = surface.find_non_colliding_position("rocket-silo", position, 32, 1)
	if not position then return end
	
	journey.dispatch_beacon = surface.create_entity({name = "stone-wall", position = position, force = "neutral"})
	journey.dispatch_beacon.minable = false
	journey.dispatch_beacon_position = {x = position.x, y = position.y}
	journey.dispatch_key = math.random(1, size_of_goods_to_dispatch)
	
	local good = goods_to_dispatch[journey.dispatch_key]	
	table.insert(journey.mothership_messages, "Capsule containing " .. good[2] .. "x [img=item/" .. good[1] .. "] dispatched. [gps=" .. position.x .. "," .. position.y .. ",nauvis]")
	
	surface.create_entity({name = "artillery-projectile", position = {x = position.x - 256 + math.random(0, 512), y = position.y - 256}, target = position, speed = 0.2})
end

function Public.world(journey)
	if journey.mothership_cargo["uranium-fuel-cell"] then
		if journey.mothership_cargo["uranium-fuel-cell"] >= journey.mothership_cargo_space["uranium-fuel-cell"] then
			table.insert(journey.mothership_messages, "[img=item/uranium-fuel-cell] Refuel operation successful!! =^.^=")
			Server.to_discord_embed("Refuel operation complete!")
			journey.game_state = "mothership_waiting_for_players"
		end
	end
	draw_background(journey, game.surfaces.mothership)
	
	if game.tick % 1800 ~= 0 then return end	
	for k, silo in pairs(journey.rocket_silos) do
		if not silo or not silo.valid then
			table.remove(journey.rocket_silos, k)
			break
		end
		local inventory = silo.get_inventory(defines.inventory.rocket_silo_rocket)
		if inventory then
			local fuel_cells_required = journey.mothership_cargo_space["uranium-fuel-cell"] - journey.mothership_cargo["uranium-fuel-cell"]
			if fuel_cells_required > 50 then fuel_cells_required = 50 end
			if inventory.get_item_count('satellite') == 1 or inventory.get_item_count('uranium-fuel-cell') >= fuel_cells_required then		
				if silo.launch_rocket() then
					table.insert(journey.mothership_messages, "Launching rocket [gps=" .. silo.position.x .. "," .. silo.position.y .. ",nauvis]")
				end
			end		
		end	
	end
end 

function Public.mothership_waiting_for_players(journey)
	if journey.characters_in_mothership > #game.connected_players * 0.5 then
		journey.game_state = "set_world_selectors"
		return
	end

	if math.random(1, 2) == 1 then return end
	local tick = game.tick % 3600
	if tick == 0 then
		local messages = Constants.mothership_messages.waiting
		table.insert(journey.mothership_messages, messages[math.random(1, #messages)])
	end
end

function Public.teleporters(journey, player)
	if not player.character then return end
	if not player.character.valid then return end
	local surface = player.surface	
	if surface.get_tile(player.position).name ~= Constants.teleporter_tile then return end
	local base_position = {0,0}
	if surface.index == 1 then		
		drop_player_items(player)
		local position = game.surfaces.mothership.find_non_colliding_position("character", base_position, 32, 0.5)
		if position then
			player.teleport(position, game.surfaces.mothership)
		else
			player.teleport(base_position, game.surfaces.mothership)
		end
		journey.characters_in_mothership = journey.characters_in_mothership + 1
		return
	end
	if surface.name == "mothership" then
		Public.clear_player(player)
		local position = game.surfaces.nauvis.find_non_colliding_position("character", base_position, 32, 0.5)
		if position then
			player.teleport(position, game.surfaces.nauvis)
		else
			player.teleport(base_position, game.surfaces.nauvis)
		end
		
		journey.characters_in_mothership = journey.characters_in_mothership - 1
		return
	end
end

return Public
