local checkluatype = SF.CheckLuaType
local registerprivilege = SF.Permissions.registerPrivilege
local IsValid = IsValid

-- Create permission types.
registerprivilege("particleEffect.attach", "Allow users to create particle effect", { client = {}, entities = {} })

local plyCount = SF.LimitObject(
	"particleeffects",
	"particle effects",
	16,
	"The number of created particle effects via Neostarfall per client at once"
)
SF.ResourceCounters.ParticleEffects = {
	icon = "icon16/asterisk_orange.png",
	count = function(ply)
		return plyCount:get(ply)
	end,
}

--- ParticleEffect library.
-- @name particleEffect
-- @class library
-- @libtbl particleef_library
SF.RegisterLibrary("particleEffect")

--- ParticleEffect type
-- @name ParticleEffect
-- @class type
-- @libtbl particleef_methods
SF.RegisterType("ParticleEffect", false, false)

return function(instance)
	local checkpermission = instance.player ~= SF.Superuser and SF.Permissions.check or function() end

	local particleef_library = instance.Libraries.particleEffect
	local particleef_methods = instance.Types.ParticleEffect.Methods

	local particle_meta, wrap, unwrap =
		instance.Types.ParticleEffect, instance.Types.ParticleEffect.Wrap, instance.Types.ParticleEffect.Unwrap
	local ent_meta, ewrap, eunwrap = instance.Types.Entity, instance.Types.Entity.Wrap, instance.Types.Entity.Unwrap
	local vec_meta, vwrap, vunwrap = instance.Types.Vector, instance.Types.Vector.Wrap, instance.Types.Vector.Unwrap

	local getent
	local vunwrap1
	local particleEffects = {}
	instance:AddHook("initialize", function()
		getent = instance.Types.Entity.GetEntity
		vunwrap1 = vec_meta.QuickUnwrap1
	end)

	instance:AddHook("deinitialize", function()
		-- Remove all
		for p in pairs(particleEffects) do
			if p:IsValid() then
				p:StopEmissionAndDestroyImmediately()
			end
			plyCount:free(instance.player, 1)
		end
	end)

	local function badParticle(flags) -- implemented for future use in case anything is found to be unfriendly.
		return false
	end

	local function checkValid(emitter)
		if not (emitter and emitter:IsValid()) then
			SF.Throw("ParticleEffect emitter is no longer valid.", 2)
		end
	end

	--- Attaches a particleEffect to an entity.
	-- @param Entity entity Entity to attach to
	-- @param string name Name of the particle effect
	-- @param number pattach PATTACH enum
	-- @param table options Table of options
	-- @return ParticleEffect ParticleEffect type.
	function particleef_library.attach(entity, name, pattach, options)
		checkpermission(instance, entity, "particleEffect.attach")

		checkluatype(name, TYPE_STRING)
		checkluatype(pattach, TYPE_NUMBER)
		checkluatype(options, TYPE_TABLE)

		local entity = getent(entity)

		if badParticle(name) then
			SF.Throw("Invalid particle effect path: " .. name, 2)
		end
		plyCount:use(instance.player, 1)

		local PEffect = entity:CreateParticleEffect(name, pattach, options)

		if not (PEffect and PEffect:IsValid()) then
			SF.Throw("Invalid particle effect system.", 2)
		end

		particleEffects[PEffect] = true

		return wrap(PEffect)
	end

	--- Gets if the particle effect is valid or not.
	-- @return boolean Is valid or not
	function particleef_methods:isValid()
		local uw = unwrap(self)

		return uw and uw:IsValid()
	end

	--- Starts emission of the particle effect.
	function particleef_methods:startEmission()
		local uw = unwrap(self)

		checkValid(uw)

		uw:StartEmission()
	end

	--- Stops emission of the particle effect.
	function particleef_methods:stopEmission()
		local uw = unwrap(self)

		checkValid(uw)

		uw:StopEmission()
	end

	--- Stops emission of the particle effect and destroys the object.
	function particleef_methods:destroy()
		local uw = unwrap(self)

		if uw and particleEffects[uw] then
			if uw:IsValid() then
				uw:StopEmissionAndDestroyImmediately()
			end
			particleEffects[uw] = nil
			plyCount:free(instance.player, 1)
		end
	end

	--- Restarts emission of the particle effect.
	function particleef_methods:restart()
		local uw = unwrap(self)

		checkValid(uw)

		uw:Restart()
	end

	--- Returns if the particle effect is finished
	-- @return boolean If the particle effect is finished
	function particleef_methods:isFinished()
		local uw = unwrap(self)

		if uw and uw:IsValid() then
			return uw:isFinished()
		end

		return true
	end

	--- Sets the sort origin for given particle effect system. This is used as a helper to determine which particles are in front of which.
	-- @param Vector origin Sort Origin
	function particleef_methods:setSortOrigin(origin)
		local uw = unwrap(self)

		checkValid(uw)

		uw:SetSortOrgin(vunwrap1(origin))
	end

	--- Sets a value for given control point.
	-- @param number id Control Point ID (0-63)
	-- @param Vector value Value
	function particleef_methods:setControlPoint(id, value)
		local uw = unwrap(self)

		checkluatype(id, TYPE_NUMBER)

		checkValid(uw)

		uw:SetControlPoint(id, vunwrap1(value))
	end

	--- Essentially makes child control point follow the parent entity.
	-- @param number id Child Control Point ID (0-63)
	-- @param Entity entity Entity parent
	function particleef_methods:setControlPointEntity(id, entity)
		local uw = unwrap(self)
		local entity = getent(entity)

		checkluatype(id, TYPE_NUMBER)

		checkValid(uw)

		uw:SetControlPointEntity(id, entity)
	end

	--- Sets the forward direction for given control point.
	-- @param number id Control Point ID (0-63)
	-- @param Vector fwd Forward vector
	function particleef_methods:setForwardVector(id, value)
		local uw = unwrap(self)

		checkluatype(id, TYPE_NUMBER)

		checkValid(uw)

		uw:SetControlPointForwardVector(id, vunwrap1(value))
	end

	--- Sets the right direction for given control point.
	-- @param number id Control Point ID (0-63)
	-- @param Vector right Right vector
	function particleef_methods:setRightVector(id, value)
		local uw = unwrap(self)

		checkluatype(id, TYPE_NUMBER)

		checkValid(uw)

		uw:SetControlPointRightVector(id, vunwrap1(value))
	end

	--- Sets the up direction for given control point.
	-- @param number id Control Point ID (0-63)
	-- @param Vector up Up vector
	function particleef_methods:setUpVector(id, value)
		local uw = unwrap(self)

		checkluatype(id, TYPE_NUMBER)

		checkValid(uw)

		uw:SetControlPointUpVector(id, vunwrap1(value))
	end

	--- Sets the parent for given control point.
	-- @param number id Child Control Point ID (0-63)
	-- @param number parentid Parent control point ID (0-63)
	function particleef_methods:setControlPointParent(id, parentid)
		local uw = unwrap(self)

		checkluatype(id, TYPE_NUMBER)
		checkluatype(parentid, TYPE_NUMBER)

		checkValid(uw)

		uw:SetControlPointParent(id, parentid)
	end
end
