﻿-- chunkname: @scripts/extension_systems/buff/minion_buff_extension.lua

require("scripts/extension_systems/buff/buff_extension_base")

local Ailment = require("scripts/utilities/ailment")
local BuffExtensionInterface = require("scripts/extension_systems/buff/buff_extension_interface")
local BuffTemplates = require("scripts/settings/buff/buff_templates")
local BuffExtensionBase = require("scripts/extension_systems/buff/buff_extension_base")
local FixedFrame = require("scripts/utilities/fixed_frame")
local MinionBuffExtension = class("MinionBuffExtension", "BuffExtensionBase")

MinionBuffExtension.UPDATE_DISABLED_BY_DEFAULT = true

MinionBuffExtension.init = function (self, extension_init_context, unit, extension_init_data, game_object_data_or_game_session, nil_or_game_object_id)
	self._owner_system = extension_init_context.owner_system
	self._active_material_vector_effects = {}
	self._current_material_vector_effect = {
		priority = 0,
	}

	MinionBuffExtension.super.init(self, extension_init_context, unit, extension_init_data, game_object_data_or_game_session, nil_or_game_object_id, false)
end

local buffs_to_sync = {}

MinionBuffExtension.hot_join_sync = function (self, unit, sender, channel)
	if self._buff_context.is_local_unit then
		return
	end

	local network_lookup_buff_templates = NetworkLookup.buff_templates

	table.clear(buffs_to_sync)

	local buffs = self._buffs_by_index

	for index, buff_instance in pairs(buffs) do
		if not buffs_to_sync[buff_instance] then
			buffs_to_sync[buff_instance] = {}
		end

		local local_indexes = buffs_to_sync[buff_instance]

		local_indexes[#local_indexes + 1] = index
	end

	local game_object_id = self._game_object_id

	for buff_instance, index_array in pairs(buffs_to_sync) do
		local template = buff_instance:template()
		local template_name = template.name
		local buff_template_id = network_lookup_buff_templates[template_name]
		local optional_lerp_value = buff_instance:buff_lerp_value()
		local num_index_array = #index_array

		if num_index_array == 1 then
			local index = index_array[1]

			RPC.rpc_add_buff(channel, game_object_id, buff_template_id, index, optional_lerp_value, nil, nil, false)
		else
			RPC.rpc_add_buff_with_stacks(channel, game_object_id, buff_template_id, index_array, optional_lerp_value, nil, nil, false)
		end
	end
end

MinionBuffExtension.game_object_initialized = function (self, game_session, game_object_id)
	self._game_object_id = game_object_id

	local buffs_added_before_game_object_creation = self._buffs_added_before_game_object_creation

	if buffs_added_before_game_object_creation then
		for i = 1, #buffs_added_before_game_object_creation do
			local buff_added_before_game_object_creation = buffs_added_before_game_object_creation[i]
			local buff_template_id = buff_added_before_game_object_creation.buff_template_id
			local index = buff_added_before_game_object_creation.index
			local optional_lerp_value = buff_added_before_game_object_creation.optional_lerp_value
			local optional_slot_id, optional_parent_buff_template_id
			local from_talent = false

			Managers.state.game_session:send_rpc_clients("rpc_add_buff", game_object_id, buff_template_id, index, optional_lerp_value, optional_slot_id, optional_parent_buff_template_id, from_talent)
		end

		self._buffs_added_before_game_object_creation = nil
	end
end

MinionBuffExtension.update = function (self, unit, dt, t)
	self:_update_buffs(dt, t)
	self:_move_looping_sfx_sources(unit)
	self:_update_proc_events(t)
	self:_update_stat_buffs_and_keywords(t)
end

local base_request_proc_event_param_table = BuffExtensionBase.request_proc_event_param_table

MinionBuffExtension.request_proc_event_param_table = function (self)
	if not self._update_enabled then
		return nil
	end

	return base_request_proc_event_param_table(self)
end

MinionBuffExtension._on_add_buff = function (self, buff_instance)
	if not self._update_enabled and #self._buffs == 0 then
		self._update_enabled = true

		self._owner_system:enable_update_function(self.__class_name, "update", self._unit, self)
	end
end

MinionBuffExtension._on_remove_buff = function (self, buff_instance)
	if self._update_enabled and #self._buffs == 1 then
		self._update_enabled = false

		self._owner_system:disable_update_function(self.__class_name, "update", self._unit, self)
	end
end

MinionBuffExtension._on_remove_buff_stack = function (self, buff_instance, previous_stack_count)
	local template = buff_instance:template()
	local minion_effects = template.minion_effects

	if minion_effects then
		local stack_node_effects = minion_effects.stack_node_effects

		if stack_node_effects then
			local template_name = template.name
			local current_stack_count = buff_instance:stack_count()

			self:_check_stack_node_effects(template_name, stack_node_effects, current_stack_count, previous_stack_count)
		end

		local stack_material_vectors = minion_effects.stack_material_vectors

		if stack_material_vectors then
			local current_stack_count = buff_instance:stack_count()
			local material_vector = stack_material_vectors[math.min(current_stack_count, #stack_material_vectors)]

			if material_vector then
				self:_start_material_vector_effect(self._unit, material_vector, template)
			end
		end
	end

	MinionBuffExtension.super._on_remove_buff_stack(self, buff_instance, previous_stack_count)
end

MinionBuffExtension._on_add_buff_stack = function (self, buff_instance, previous_stack_count)
	local template = buff_instance:template()
	local minion_effects = template.minion_effects

	if minion_effects then
		local stack_node_effects = minion_effects.stack_node_effects

		if stack_node_effects then
			local template_name = template.name
			local node_effects_priotity = minion_effects.node_effects_priotity
			local current_stack_count = buff_instance:stack_count()

			self:_check_stack_node_effects(template_name, stack_node_effects, current_stack_count, previous_stack_count, node_effects_priotity)
		end

		local stack_material_vectors = minion_effects.stack_material_vectors

		if stack_material_vectors then
			local current_stack_count = buff_instance:stack_count()
			local material_vector = stack_material_vectors[math.min(current_stack_count, #stack_material_vectors)]

			if material_vector then
				self:_start_material_vector_effect(self._unit, material_vector, template)
			end
		end
	end

	MinionBuffExtension.super._on_add_buff_stack(self, buff_instance, previous_stack_count)
end

MinionBuffExtension._post_on_remove_buff = function (self, buff_instance)
	local t = FixedFrame.get_latest_fixed_time()

	self:_update_proc_events(t)
	self:_update_stat_buffs_and_keywords(t, true)
end

MinionBuffExtension.add_proc_event = function (self, event, params)
	local buff_context = self._buff_context

	if buff_context.is_local_unit then
		params.is_local_proc_event = true
	end

	MinionBuffExtension.super.add_proc_event(self, event, params)
end

MinionBuffExtension.add_internally_controlled_buff = function (self, template_name, t, ...)
	local is_local_unit = self._buff_context.is_local_unit

	if not self._is_server and not is_local_unit then
		return
	end

	local template = BuffTemplates[template_name]
	local can_add_internally_controlled_buff = self:_can_add_internally_controlled_buff(template, t)

	if can_add_internally_controlled_buff then
		if is_local_unit then
			self:_add_buff(template, t, ...)
		else
			self:_add_rpc_synced_buff(template, t, ...)
		end
	end
end

MinionBuffExtension.add_externally_controlled_buff = function (self, template_name, t, ...)
	local client_tried_adding_rpc_buff = false
	local is_local_unit = self._buff_context.is_local_unit

	if not self._is_server and not is_local_unit then
		client_tried_adding_rpc_buff = true
	end

	if client_tried_adding_rpc_buff then
		return client_tried_adding_rpc_buff
	end

	local template = BuffTemplates[template_name]
	local should_be_muted = not self:_check_keywords(template)
	local local_index

	if should_be_muted then
		local_index = self:_next_local_index()
		self._muted_external_buffs[local_index] = template
	elseif is_local_unit then
		local_index = self:_add_buff(template, t, ...)
	else
		local_index = self:_add_rpc_synced_buff(template, t, ...)
	end

	return client_tried_adding_rpc_buff, local_index
end

MinionBuffExtension.remove_externally_controlled_buff = function (self, local_index)
	local muted_external_buffs = self._muted_external_buffs

	if muted_external_buffs[local_index] then
		muted_external_buffs[local_index] = nil

		return
	end

	local is_local_unit = self._buff_context.is_local_unit

	if is_local_unit then
		return
	end

	local buff_instance = self._buffs_by_index[local_index]

	if is_local_unit then
		self:_remove_buff(local_index)
	elseif self._is_server then
		self:_remove_rpc_synced_buff(local_index)
	end
end

MinionBuffExtension._remove_internally_controlled_buff = function (self, local_index)
	local buff_instance = self._buffs_by_index[local_index]

	if self._buff_context.is_local_unit then
		self:_remove_buff(local_index)
	elseif self._is_server then
		self:_remove_rpc_synced_buff(local_index)
	end
end

MinionBuffExtension._add_rpc_synced_buff = function (self, template, t, ...)
	local index = self:_add_buff(template, t, ...)
	local game_object_id = self._game_object_id
	local template_name = template.name
	local buff_template_id = NetworkLookup.buff_templates[template_name]
	local buff_instance = self._buffs_by_index[index]
	local optional_lerp_value = buff_instance:buff_lerp_value()

	if game_object_id then
		Managers.state.game_session:send_rpc_clients("rpc_add_buff", game_object_id, buff_template_id, index, optional_lerp_value, nil, nil, false)
	else
		local buff_added_before_game_object_creation = {
			from_talent = false,
			buff_template_id = buff_template_id,
			index = index,
			optional_lerp_value = optional_lerp_value,
		}
		local buffs_added_before_game_object_creation = self._buffs_added_before_game_object_creation

		buffs_added_before_game_object_creation[#buffs_added_before_game_object_creation + 1] = buff_added_before_game_object_creation
	end

	return index
end

MinionBuffExtension._remove_rpc_synced_buff = function (self, index)
	local game_object_id = self._game_object_id

	self:_remove_buff(index)
	Managers.state.game_session:send_rpc_clients("rpc_remove_buff", game_object_id, index)
end

MinionBuffExtension._set_proc_active_start_time = function (self, index, activation_time, skip_send_active_time_rpc)
	if self._is_server then
		if skip_send_active_time_rpc then
			return
		end

		local activation_frame = activation_time / self._fixed_time_step
		local game_object_id = self._game_object_id

		Managers.state.game_session:send_rpc_clients("rpc_buff_proc_set_active_time", game_object_id, index, activation_frame)
	else
		local buffs_by_index = self._buffs_by_index
		local buff_instance = buffs_by_index[index]

		if buff_instance and buff_instance.set_active_start_time then
			buff_instance:set_active_start_time(activation_time)
		end
	end
end

MinionBuffExtension._start_fx = function (self, index, template)
	MinionBuffExtension.super._start_fx(self, index, template)

	local minion_effects = template.minion_effects

	if minion_effects then
		local unit = self._unit
		local ailment_effect = minion_effects.ailment_effect

		if ailment_effect then
			local visual_loadout_extension = ScriptUnit.extension(unit, "visual_loadout_system")
			local existing_ailment_effect = visual_loadout_extension:ailment_effect()

			if not existing_ailment_effect then
				local include_children = true

				Ailment.play_ailment_effect_template(unit, ailment_effect, include_children)
				visual_loadout_extension:set_ailment_effect(ailment_effect)
			end
		end

		local node_effects = minion_effects.node_effects
		local node_effects_priotity = minion_effects.node_effects_priotity

		if node_effects then
			local template_name = template.name

			self:_start_node_effects(template_name, node_effects, node_effects_priotity)
		end

		local material_vector = minion_effects.material_vector

		if material_vector then
			self:_start_material_vector_effect(unit, material_vector, template)
		end

		if self._is_server then
			local effect_template = minion_effects.effect_template

			if effect_template then
				local fx_system = Managers.state.extension:system("fx_system")
				local effect_template_id = fx_system:start_template_effect(effect_template, unit)

				self._effect_template_id = effect_template_id
			end
		end
	end
end

MinionBuffExtension._stop_fx = function (self, index, template)
	local minion_effects = template.minion_effects

	if minion_effects then
		local minion_node_effects = minion_effects.node_effects

		if minion_node_effects then
			local template_name = template.name

			self:_stop_node_effects(template_name, minion_node_effects)
		end

		local material_vector = minion_effects.material_vector

		if material_vector then
			self:_stop_material_vector_effect(self.unit, material_vector, template)
		end

		if self._is_server then
			local effect_template = minion_effects.effect_template
			local fx_system = Managers.state.extension:system("fx_system")

			if effect_template and self._effect_template_id and fx_system:has_running_global_effect_id(self._effect_template_id) then
				fx_system:stop_template_effect(self._effect_template_id)
			end
		end
	end

	MinionBuffExtension.super._stop_fx(self, index, template)
end

MinionBuffExtension._start_material_vector_effect = function (self, unit, material_vector, buff_template)
	local buff_template_name = buff_template.name
	local material_vector_name = material_vector.name
	local value = material_vector.value
	local priority = material_vector.priority or 1
	local current_material_vector_effect = self._current_material_vector_effect

	if current_material_vector_effect.priority and priority >= current_material_vector_effect.priority then
		Unit.set_vector3_for_materials(unit, material_vector_name, Vector3(value[1], value[2], value[3]), true)
		self:_update_current_material_vector_effect(material_vector_name, value, priority, buff_template_name)
	end

	local material_vector_active_effects = self:_get_material_vector_active_effects(material_vector_name)
	local existing_effect_for_template = material_vector_active_effects.effects[buff_template_name] ~= nil

	if existing_effect_for_template then
		material_vector_active_effects.effects[buff_template_name].value = value
	else
		material_vector_active_effects.effects[buff_template_name] = {
			priority = priority,
			name = material_vector_name,
			value = value,
		}
		material_vector_active_effects.ref_count = (material_vector_active_effects.ref_count or 0) + 1
	end
end

MinionBuffExtension._stop_material_vector_effect = function (self, unit, material_vector, buff_template)
	local buff_template_name = buff_template.name
	local material_vector_name = material_vector.name
	local active_material_vector_effects = self._active_material_vector_effects
	local active_effects = active_material_vector_effects[material_vector_name]

	active_effects.effects[buff_template_name] = nil
	active_effects.ref_count = (active_effects.ref_count or 0) - 1

	local is_current_material_vector_effect = self:_is_current_material_vector_effect(material_vector_name, buff_template_name)

	if is_current_material_vector_effect then
		Unit.set_vector3_for_materials(self._unit, material_vector_name, Vector3(0, 0, 0), true)
		self:_update_current_material_vector_effect(nil, nil, 0, nil)

		if active_effects.ref_count > 0 then
			self:_start_existing_effect_for_material_vector(material_vector_name, buff_template_name)
		else
			active_material_vector_effects[material_vector_name] = nil
		end
	end
end

MinionBuffExtension._start_existing_effect_for_material_vector = function (self, material_vector_name, buff_template_name)
	local active_material_vector_effects = self._active_material_vector_effects
	local active_effects = active_material_vector_effects[material_vector_name]
	local target_effect, highest_priority = nil, 0

	for _, effect in pairs(active_effects.effects) do
		if highest_priority < effect.priority then
			target_effect = effect
			highest_priority = effect.priority
		end
	end

	if target_effect then
		Unit.set_vector3_for_materials(self._unit, material_vector_name, Vector3(target_effect.value[1], target_effect.value[2], target_effect.value[3]), true)
		self:_update_current_material_vector_effect(material_vector_name, target_effect.value, target_effect.priority, buff_template_name)
	end
end

MinionBuffExtension._update_current_material_vector_effect = function (self, material_vector_name, value, priority, buff_template_name)
	local current_material_vector_effect = self._current_material_vector_effect

	current_material_vector_effect.material_vector_name = material_vector_name
	current_material_vector_effect.value = value
	current_material_vector_effect.priority = priority
	current_material_vector_effect.buff_template_name = buff_template_name
end

MinionBuffExtension._is_current_material_vector_effect = function (self, material_vector_name, buff_template_name)
	local current_material_vector_effect = self._current_material_vector_effect

	return current_material_vector_effect.material_vector_name == material_vector_name and current_material_vector_effect.buff_template_name == buff_template_name
end

MinionBuffExtension._get_material_vector_active_effects = function (self, name)
	local active_material_vector_effects = self._active_material_vector_effects

	if not active_material_vector_effects[name] then
		active_material_vector_effects[name] = {
			ref_count = 0,
			effects = {},
		}
	end

	return active_material_vector_effects[name]
end

implements(MinionBuffExtension, BuffExtensionInterface)

return MinionBuffExtension
