-- Artefacts movement module for DAO

-- Shuffle table, Fisher-Yates shuffle with preserving original table
local function shuffle(t)
   local s = {}
   for i = 1, #t do s[i] = t[i] end
   for i = #t, 2, -1 do
     local j = math.random(i)
     s[i], s[j] = s[j], s[i]
   end
   return s
end

local function vec_to_table(vec)
	return {
		x = vec.x,
		y = vec.y,
		z = vec.z
	}
end

local function table_to_vec(vec)
	return vector():set(vec.x, vec.y, vec.z)
end

local function distance_to_xz_sqr(a, b)
	if a.distance_to_xz_sqr then
		return a:distance_to_xz_sqr(b)
	end

	local va = b.x - a.x
	local vb = b.z - a.z
	return va * va + vb * vb
end

local function table_nexter(t)
    local nexter = {}
    nexter.t = t
    nexter.current = nil
    nexter.next = function(self)
    	if self.current ~= nil and not self.t[self.current] then
            self.current = nil
        end
        self.current = next(self.t, self.current)
        if self.current == nil then
            self.current = next(self.t, self.current)
        end
        return self.current, self.t[self.current]
    end
    return nexter
end

artefact_ids_to_way = table_nexter({})

spawn_artefact_on_smart = drx_da_main.spawn_artefact_on_smart
drx_da_main.spawn_artefact_on_smart = function(level_file, smart_name, picked_artefact, level_name)
	local id = spawn_artefact_on_smart(level_file, smart_name, picked_artefact, level_name)
	if not id then return end

	-- 50% chance for way generation
	if math.random() < 0.5 then
		generate_ways_for_id(id, smart_name, picked_artefact, level_name)
	end
	return id
end

clean_artefacts_on_level = drx_da_main.clean_artefacts_on_level
drx_da_main.clean_artefacts_on_level = function(level_name)
	clean_artefacts_on_level(level_name)
	for id, v in pairs(artefact_ids_to_way.t) do
		if v.level_name == level_name then
			artefact_ids_to_way.t[id] = nil
		end
	end
end

function generate_ways_for_id(id, smart_name, picked_artefact, level_name)
	if artefact_ids_to_way.t[id] then
		-- printf("artefact way is already generated for %s on %s in %s", id, smart_name, level_name)
		return
	end

	local anomalies = drx_da_main.updated_anomaly_levels[level_name] and drx_da_main.updated_anomaly_levels[level_name].anomalies_by_smart and drx_da_main.updated_anomaly_levels[level_name].anomalies_by_smart[smart_name]
	if not anomalies then
		-- printf("no anomalies are in db on smart for %s on %s in %s", id, smart_name, level_name)
		return
	end

	local se_objs = {}
	for id, _ in pairs(anomalies) do
		local se_obj = alife_object(id)
		if se_obj then
			se_objs[#se_objs + 1] = se_obj
		end
	end

	if is_empty(se_objs) then
		-- printf("no anomalies se_objs found on smart for %s on %s in %s", id, smart_name, level_name)
		return
	end

	artefact_ids_to_way.t[id] = {
		points = {},
		current_point_index = 1,
		smart_name = smart_name,
		level_name = level_name,
		update_time = 0
	}
	local points = artefact_ids_to_way.t[id].points
	se_objs = shuffle(se_objs)
	for i, se_obj in ipairs(se_objs) do
		points[#points + 1] = vec_to_table(se_obj.position)
		-- printf("adding point for %s on %s in %s: %s,%s,%s", id, smart_name, level_name, round_idp(se_obj.position.x, 2), round_idp(se_obj.position.y, 2), round_idp(se_obj.position.z, 2))
	end
end

function move_artefact(obj, t)
	local id = obj:id()
	local pos = obj:position()
	local point = t.points[t.current_point_index]
	if not point then
		-- printf("update artefact %s, point not found by index %s", id, t.current_point_index)
		point = t.points[1]
		t.current_point_index = 1
	end
	point = table_to_vec(point)
	if distance_to_xz_sqr(pos, point) < 2 then
		t.current_point_index = t.current_point_index + 1
		-- printf("update artefact %s, artefact near current point, new point index %s", id, t.current_point_index)
		point = t.points[t.current_point_index]
		if not point then
			-- printf("update artefact %s, point not found by index %s", id, t.current_point_index)
			point = t.points[1]
			t.current_point_index = 1
		end
	end
	point = table_to_vec(point)

	local lvid = obj:level_vertex_id()
	local lvid_pos = level.vertex_position(lvid)
	point.y = lvid_pos.y + 0.1
	local dir = vector():set(point):sub(pos):normalize()
	-- demonized_geometry_ray.VisualizeRay(pos, point, nil, 500)

	-- obj:set_const_force(vector():set(dir.x, dir.y, dir.z), 100, 1000)
	local ph = obj:get_physics_shell()
	if ph then
		local mass = obj:mass()
		dir.y = dir.y + 1
		dir:mul(mass)
		ph:apply_force(dir.x * 150, dir.y * 250, dir.z * 150)
		obj:set_const_force(vector():set(dir.x, dir.y / 5, dir.z), 2.5, 1000)
		-- printf("move artefact %s, index %s", id, t.current_point_index)
	end
end

local tg = 0
function update_artefacts()
	if time_global() == tg then return end
	tg = time_global()

	local id, t = artefact_ids_to_way:next()
	if not id then return end
	
	local obj = level.object_by_id(id)
	if not obj then
		-- printf("no artefact obj by %s", id)
		return
	end

	if time_global() < (t.update_time or 0) then return end
	t.update_time = time_global() + math.random(500, 1500)

	move_artefact(obj, t)
end

function save_state(m_data)
	m_data.dao_artefact_movement = artefact_ids_to_way
end

function load_state(m_data)
	if m_data.dao_artefact_movement then
		artefact_ids_to_way = m_data.dao_artefact_movement
	end
end

function actor_on_item_take(obj)
	if artefact_ids_to_way.t[obj:id()] then
		artefact_ids_to_way.t[obj:id()] = nil
	end
end

function npc_on_item_take(npc, obj)
	actor_on_item_take(obj)
end

-- Next actor tick enabling
function actor_on_first_update()
	CreateTimeEvent("drx_da_main_artefacts_movement", 0, 0, function()
		AddUniqueCall(update_artefacts)
		return true
	end)
end

function on_game_start()
	RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
	RegisterScriptCallback("save_state", save_state)
	RegisterScriptCallback("load_state", load_state)
	RegisterScriptCallback("actor_on_item_take", actor_on_item_take)
	RegisterScriptCallback("npc_on_item_take", npc_on_item_take)
end

function refresh_artefacts()
	drx_da_main.clean_artefacts_on_level(level.name())
	drx_da_main.spawn_artefacts_on_level(level.name())
end