-- This file is part of thesixthroc's Pirate Ship softmod, licensed under GPLv3 and stored at https://github.com/ComfyFactory/ComfyFactorio and https://github.com/danielmartin0/ComfyFactorio-Pirates.

local Memory = require('maps.pirates.memory')
local Math = require('maps.pirates.math')
local Common = require('maps.pirates.common')
local IslandsCommon = require('maps.pirates.surfaces.islands.common')
local GetNoise = require('utils.math.get_noise')
local BoatData = require('maps.pirates.structures.boats.sloop.data')
local Balance = require('maps.pirates.balance')
local ShopCovered = require('maps.pirates.shop.covered')
local Classes = require('maps.pirates.roles.classes')

local Public = {}
Public.Data = require('maps.pirates.surfaces.islands.cave.data')

local math_random = Math.random

-- Code imported from cave_miner_v2 scenario for cave generation

local function spawn_market(args, is_main)
	is_main = is_main or false
	local destination_data = Common.current_destination()
	--if not (destination_data and destination_data.dynamic_data and destination_data.dynamic_data.cave_miner) then return end
	if not (destination_data and destination_data.dynamic_data) then
		return
	end

	local offers
	if is_main then
		offers = ShopCovered.market_generate_coin_offers(6)

		if not destination_data.dynamic_data.offered_weapon_damage_upgrade then
			offers[#offers + 1] = {
				price = Balance.weapon_damage_upgrade_price(),
				offer = {
					type = 'nothing',
					effect_description = {
						'pirates.market_description_purchase_attack_upgrade',
						Balance.weapon_damage_upgrade_percentage(),
					},
				},
			}

			destination_data.dynamic_data.offered_weapon_damage_upgrade = true
		end

		if destination_data.static_params.class_for_sale then
			offers[#offers + 1] = {
				price = { { name = 'coin', count = Balance.class_cost(true) } },
				offer = {
					type = 'nothing',
					effect_description = {
						'pirates.market_description_purchase_class',
						Classes.display_form(destination_data.static_params.class_for_sale),
					},
				},
			}
		end
		offers[#offers + 1] = {
			price = { { name = 'coin', count = 200 } },
			offer = { type = 'give-item', item = 'small-lamp', count = 100 },
		}
	else
		-- This doesn't really prevent markets spawning near each other, since markets aren't spawned immediately for a given chunk, but it helps a bit
		local cave_miner = destination_data.dynamic_data.cave_miner
		if not cave_miner then
			-- this can be nil if cave island is first and run is launched using proposal
			-- should probably investigate this some time
			local message = 'ERROR: cave_miner is nil'
			if _DEBUG then
				game.print(message)
			else
				log(message)
			end

			return
		end
		local surface = cave_miner.cave_surface
		if not surface then
			return
		end

		local r = 64
		if
			surface.count_entities_filtered({
				name = 'market',
				area = { { args.p.x - r, args.p.y - r }, { args.p.x + r, args.p.y + r } },
			}) > 0
		then
			return
		end

		offers = ShopCovered.market_generate_coin_offers(4)
	end

	args.specials[#args.specials + 1] = { name = 'market', position = args.p, offers = offers }
end

local function place_rock(args)
	local a = math_random(-49, 49) * 0.01
	local b = math_random(-49, 49) * 0.01

	args.entities[#args.entities + 1] = IslandsCommon.random_rock_1({ x = args.p.x + a, y = args.p.y + b })
end

local function place_spawner(args)
	local memory = Memory.get_crew_memory()

	local name
	if math_random(1, 2) == 1 then
		name = 'biter-spawner'
	else
		name = 'spitter-spawner'
	end
	args.entities[#args.entities + 1] = { name = name, position = args.p, force = memory.enemy_force_name }
end

local function place_worm(args)
	local memory = Memory.get_crew_memory()
	local name = Common.get_random_worm_type(memory.evolution_factor)
	local force = memory.enemy_force_name
	args.entities[#args.entities + 1] = { name = name, position = args.p, force = force }
end

local biomes = {}

function biomes.oasis(args, noise)
	local seed = args.seed
	local position = args.p
	if noise > 0.83 then
		args.tiles[#args.tiles + 1] = { name = 'deepwater', position = args.p }
		Public.Data.spawn_fish(args)
		return
	end

	local noise_decoratives = GetNoise('decoratives', position, seed + 50000)
	args.tiles[#args.tiles + 1] = { name = 'grass-1', position = args.p }
	if math_random(1, 16) == 1 and Math.abs(noise_decoratives) > 0.17 then
		args.entities[#args.entities + 1] = { name = 'tree-04', position = args.p }
	end

	if math_random(1, 64) == 1 then
		args.entities[#args.entities + 1] =
			{ name = 'crude-oil', position = args.p, amount = Balance.pick_default_oil_amount() * 2 }
	end

	if noise < 0.73 then
		place_rock(args)
	end
end

function biomes.void(args)
	args.tiles[#args.tiles + 1] = { name = 'out-of-map', position = args.p }
end

function biomes.pond_cave(args, noise)
	local seed = args.seed
	local position = args.p
	local noise_2 = GetNoise('cm_ponds', position, seed)

	if Math.abs(noise_2) > 0.60 then
		args.tiles[#args.tiles + 1] = { name = 'water', position = args.p }
		Public.Data.spawn_fish(args)
		return
	end

	args.tiles[#args.tiles + 1] = { name = 'dirt-7', position = args.p }

	if math_random(1, 512) == 1 then
		args.specials[#args.specials + 1] = { name = 'chest', position = args.p }
		return
	end

	if Math.abs(noise_2) > 0.25 then
		if math_random(1, 64) == 1 then
			place_spawner(args)
		else
			place_rock(args)
		end

		return
	end

	if math_random(1, 32) == 1 then
		place_spawner(args)
		return
	end

	if noise > -0.53 then
		place_rock(args)
		return
	else
		if math_random(1, 1024) == 1 then
			spawn_market(args)
			return
		end
	end
end

-- Spawn refers to the "middle of the map" where the market is located
function biomes.spawn(args, square_distance)
	local seed = args.seed
	local position = args.p

	-- If coordinate iteration ever changes to xn instead of 0.5 + xn this will need to change
	if Math.abs(position.x - 0.5) < 0.1 and Math.abs(position.y - 0.5) < 0.1 then
		args.tiles[#args.tiles + 1] = { name = 'dirt-7', position = args.p }
		spawn_market(args, true)
		return
	end

	local noise = GetNoise('decoratives', position, seed)
	if Math.abs(noise) > 0.60 and square_distance < 900 and square_distance > 10 then
		args.tiles[#args.tiles + 1] = { name = 'water', position = args.p }
		Public.Data.spawn_fish(args)
		return
	end

	args.tiles[#args.tiles + 1] = { name = 'dirt-7', position = args.p }

	if square_distance > 100 then
		place_rock(args)
	end
end

function biomes.ocean(args, noise)
	if noise > 0.66 then
		args.tiles[#args.tiles + 1] = { name = 'deepwater', position = args.p }
		Public.Data.spawn_fish(args)
		return
	end
	if noise > 0.63 then
		args.tiles[#args.tiles + 1] = { name = 'water', position = args.p }
		Public.Data.spawn_fish(args)
		return
	end

	args.tiles[#args.tiles + 1] = { name = 'dirt-7', position = args.p }

	place_rock(args)
end

function biomes.worm_desert(args, noise)
	local seed = args.seed
	local position = args.p

	local i = Math.floor((GetNoise('decoratives', position, seed) * 8) % 3) + 1
	args.tiles[#args.tiles + 1] = { name = 'sand-' .. i, position = args.p }

	if noise > -0.65 then
		place_rock(args)
		return
	end

	if math_random(1, 64) == 1 then
		place_worm(args)
		return
	end

	if math_random(1, 32) == 1 then
		local n = GetNoise('decoratives', position, seed + 10000)
		if n > 0.2 then
			local trees = { 'dead-grey-trunk', 'dead-grey-trunk', 'dry-tree' }
			args.entities[#args.entities + 1] = { name = trees[math_random(1, 3)], position = args.p }
			return
		end
	end

	if math_random(1, 512) == 1 then
		args.specials[#args.specials + 1] = { name = 'chest', position = args.p }
	end
end

function biomes.cave(args, square_distance)
	local seed = args.seed
	local position = args.p

	local noise_cave_rivers1 = GetNoise('cave_rivers_2', position, seed + 100000)
	if Math.abs(noise_cave_rivers1) < 0.025 then
		local noise_cave_rivers2 = GetNoise('cave_rivers_3', position, seed + 200000)
		if noise_cave_rivers2 > 0 then
			args.tiles[#args.tiles + 1] = { name = 'water-shallow', position = args.p }
			Public.Data.spawn_fish(args)
			return
		end
	end

	local no_rocks_2 = GetNoise('no_rocks_2', position, seed)
	if no_rocks_2 > 0.7 then
		if no_rocks_2 > 0.73 then
			if math_random(1, 256) == 1 then
				spawn_market(args)
			end
		end
		args.tiles[#args.tiles + 1] = { name = 'dirt-' .. Math.floor(no_rocks_2 * 16) % 4 + 3, position = args.p }
		return
	end

	args.tiles[#args.tiles + 1] = { name = 'dirt-7', position = args.p }

	if Math.abs(no_rocks_2) < 0.05 then
		return
	end

	local noise_rock = GetNoise('small_caves', position, seed)

	if noise_rock < 0.6 then
		local ring1_range = 10
		local ring2_range = 20
		local ring1_radius = 150
		local ring2_radius = 300

		local ring1_start = (ring1_radius - ring1_range) * (ring1_radius - ring1_range)
		local ring1_end = (ring1_radius + ring1_range) * (ring1_radius + ring1_range)
		local ring2_start = (ring2_radius - ring2_range) * (ring2_radius - ring2_range)
		local ring2_end = (ring2_radius + ring2_range) * (ring2_radius + ring2_range)

		-- add nest obstacles in these rings on "main" wide cave roads
		if
			(square_distance > ring1_start and square_distance < ring1_end)
			or (square_distance > ring2_start and square_distance < ring2_end)
		then
			if math_random(1, 32) == 1 then
				if math_random(1, 3) == 1 then
					place_worm(args)
				else
					place_spawner(args)
				end
				return
			end

			if math_random(1, 512) == 1 then
				args.specials[#args.specials + 1] = { name = 'chest', position = args.p }
				return
			end

			if math_random(1, 16) == 1 then
				place_rock(args)
				return
			end
		end

		if math_random(1, 1024) == 1 then
			args.specials[#args.specials + 1] = { name = 'chest', position = args.p }
			return
		end

		place_rock(args)
		return
	end

	if square_distance < 4096 then
		return
	end

	if math_random(1, 8192) == 1 then
		spawn_market(args)
		return
	end

	if math_random(1, 16) == 1 then
		if math_random(1, 3) == 1 then
			place_worm(args)
			return
		else
			place_spawner(args)
			return
		end
	end
end

local function pick_biome(args)
	local position = args.p
	local d = position.x ^ 2 + position.y ^ 2

	local boat_height = Math.max(BoatData.height, 15) -- even if boat height is smaller, we need to be at least 10+ just so formulas below play out nicely
	local spawn_radius = boat_height + 15
	local entrance_radius = boat_height + 45
	local river_width = boat_height + 40

	-- Spawn location for market
	if d < spawn_radius ^ 2 then
		biomes.spawn(args, d)
		return
	end

	-- River upon which ship arrives + ship entrance
	if position.x < 0 and 2 * Math.abs(position.y) < river_width then
		biomes.void(args)
		return
	end

	-- Prevent cave expansion in west direction
	-- NOTE: although "river_width ^ 2 - entrance_radius ^ 2" should never be "< 0", it's a small safe check
	-- NOTE: the complex calculation here calculates wanted intersection of river and spawn area (or in other words line and circle intersection)
	if
		position.x < 0
		and -position.x + (river_width - Math.sqrt(Math.max(0, river_width ^ 2 - entrance_radius ^ 2)))
			> Math.abs(2 * position.y)
	then
		biomes.void(args)
		return
	end

	-- Actual cave generation below
	local cm_ocean = GetNoise('cm_ocean', position, args.seed + 100000)
	if cm_ocean > 0.6 then
		biomes.ocean(args, cm_ocean)
		return
	end

	local noise = GetNoise('cave_miner_01', position, args.seed)
	local abs_noise = Math.abs(noise)
	if abs_noise < 0.075 then
		biomes.cave(args, d)
		return
	end

	if abs_noise > 0.25 then
		noise = GetNoise('cave_rivers', position, args.seed)
		if noise > 0.72 then
			biomes.oasis(args, noise)
			return
		end
		if cm_ocean < -0.6 then
			biomes.worm_desert(args, cm_ocean)
			return
		end
		if noise < -0.5 then
			biomes.pond_cave(args, noise)
			return
		end
	end

	-- make 4 times as much narrow caves
	local position2 = { x = position.x * 2, y = position.y * 2 }
	noise = GetNoise('cave_miner_02', position2, args.seed)
	if Math.abs(noise) < 0.1 then
		biomes.cave(args, d)
		return
	end

	biomes.void(args)
end

function Public.terrain(args)
	local tiles_placed = #args.tiles
	pick_biome(args)

	-- fallback case that should never happen
	if #args.tiles == tiles_placed then
		-- game.print('no tile was placed!')
		args.tiles[#args.tiles + 1] = { name = 'dirt-7', position = args.p }
		return
	end
end

-- Finding a spot for structures is very hard (it might cause structures to spawn in weird locations, like ship)
-- function Public.chunk_structures(args)

-- end

-- Launching rocket in caves sounds silly (as well as hard to pick position for rocket)
-- function Public.generate_silo_setup_position(points_to_avoid)

-- end

return Public
