-- Exposed API:

local Rain = {}

Rain.CollisionMode = CollisionMode

function Rain:Enable(tweenInfo)
	
	if tweenInfo ~= nil and typeof(tweenInfo) ~= "TweenInfo" then
		error("bad argument #1 to 'Enable' (TweenInfo expected, got " .. typeof(tweenInfo) .. ")", 2)
	end
	
	disconnectLoop() -- Just in case :Enable(..) is called multiple times on accident
	
	Emitter.RainStraight.Enabled = true
	Emitter.RainTopDown.Enabled = true
	Emitter.Parent = workspace.CurrentCamera
	
	for i = 1, RAIN_SPLASH_NUM do
		splashAttachments[i].Parent = workspace.Terrain
		rainAttachments[i].Parent = workspace.Terrain
	end
	
	if RunService:IsRunning() then -- don't need sound in studio preview, it won't work anyway
		SoundGroup.Parent = game:GetService("SoundService")
	end
	
	connectLoop()
	
	if tweenInfo then
		TweenService:Create(GlobalModifier, tweenInfo, {Value = 0}):Play()
	else
		GlobalModifier.Value = 0
	end
	
	if not Sound.Playing then
		Sound:Play()
		Sound.TimePosition = math.random()*Sound.TimeLength
	end
	
	disabled = false
	
end

function Rain:Disable(tweenInfo)
	
	if tweenInfo ~= nil and typeof(tweenInfo) ~= "TweenInfo" then
		error("bad argument #1 to 'Disable' (TweenInfo expected, got " .. typeof(tweenInfo) .. ")", 2)
	end
	
	if tweenInfo then
		local tween = TweenService:Create(GlobalModifier, tweenInfo, {Value = 1})
		tween.Completed:connect(function(state)
			if state == Enum.PlaybackState.Completed then
				-- Only disable the rain completely once the visual effects are faded out
				disable()
			end
			tween:Destroy()
		end)
		tween:Play()
		-- Start tweening out sound now as well
		disableSound(tweenInfo)
	else
		GlobalModifier.Value = 1
		disable()
	end
	
	disabled = true
	
end

function Rain:SetColor(value, tweenInfo)
		
	if typeof(value) ~= "Color3" then
		error("bad argument #1 to 'SetColor' (Color3 expected, got " .. typeof(value) .. ")", 2)
	elseif tweenInfo ~= nil and typeof(tweenInfo) ~= "TweenInfo" then
		error("bad argument #2 to 'SetColor' (TweenInfo expected, got " .. typeof(tweenInfo) .. ")", 2)
	end
	
	if tweenInfo then
		TweenService:Create(Color, tweenInfo, {Value = value}):Play()
	else
		Color.Value = value
	end
	
end

local function makeRatioSetter(methodName, valueObject)
	-- Shorthand because most of the remaining property setters are very similar
	return function(_, value, tweenInfo)
		
		if typeof(value) ~= "number" then
			error("bad argument #1 to '" .. methodName .. "' (number expected, got " .. typeof(value) .. ")", 2)
		elseif tweenInfo ~= nil and typeof(tweenInfo) ~= "TweenInfo" then
			error("bad argument #2 to '" .. methodName .. "' (TweenInfo expected, got " .. typeof(tweenInfo) .. ")", 2)
		end
		
		value = math.clamp(value, 0, 1)
		
		if tweenInfo then
			TweenService:Create(valueObject, tweenInfo, {Value = value}):Play()
		else
			valueObject.Value = value
		end
		
	end
end

Rain.SetTransparency = makeRatioSetter("SetTransparency", Transparency)
Rain.SetSpeedRatio = makeRatioSetter("SetSpeedRatio", SpeedRatio)
Rain.SetIntensityRatio = makeRatioSetter("SetIntensityRatio", IntensityRatio)
Rain.SetLightEmission = makeRatioSetter("SetLightEmission", LightEmission)
Rain.SetLightInfluence = makeRatioSetter("SetLightInfluence", LightInfluence)

function Rain:SetVolume(volume, tweenInfo)
	
	if typeof(volume) ~= "number" then
		error("bad argument #1 to 'SetVolume' (number expected, got " .. typeof(volume) .. ")", 2)
	elseif tweenInfo ~= nil and typeof(tweenInfo) ~= "TweenInfo" then
		error("bad argument #2 to 'SetVolume' (TweenInfo expected, got " .. typeof(tweenInfo) .. ")", 2)
	end
	
	if tweenInfo then
		TweenService:Create(SoundGroup, tweenInfo, {Volume = volume}):Play()
	else
		SoundGroup.Volume = volume
	end
	
end

function Rain:SetDirection(direction, tweenInfo)
	
	if typeof(direction) ~= "Vector3" then
		error("bad argument #1 to 'SetDirection' (Vector3 expected, got " .. typeof(direction) .. ")", 2)
	elseif tweenInfo ~= nil and typeof(tweenInfo) ~= "TweenInfo" then
		error("bad argument #2 to 'SetDirection' (TweenInfo expected, got " .. typeof(tweenInfo) .. ")", 2)
	end
	
	if not (direction.unit.magnitude > 0) then -- intentional statement formatting since NaN comparison
		warn("Attempt to set rain direction to a zero-length vector, falling back on default direction = (" .. tostring(RAIN_DEFAULT_DIRECTION) .. ")")
		direction = RAIN_DEFAULT_DIRECTION
	end
	
	if tweenInfo then
		TweenService:Create(RainDirection, tweenInfo, {Value = direction}):Play()
	else
		RainDirection.Value = direction
	end
	
end

function Rain:SetCeiling(ceiling)
	
	if ceiling ~= nil and typeof(ceiling) ~= "number" then
		error("bad argument #1 to 'SetCeiling' (number expected, got " .. typeof(ceiling) .. ")", 2)
	end
	
	currentCeiling = ceiling
	
end

function Rain:SetStraightTexture(asset)
	
	if typeof(asset) ~= "string" then
		error("bad argument #1 to 'SetStraightTexture' (string expected, got " .. typeof(asset) .. ")", 2)
	end
	
	Emitter.RainStraight.Texture = asset
	
	for _,v in pairs(rainAttachments) do
		v.RainStraight.Texture = asset
	end
	
end

function Rain:SetTopDownTexture(asset)
	
	if typeof(asset) ~= "string" then
		error("bad argument #1 to 'SetStraightTexture' (string expected, got " .. typeof(asset) .. ")", 2)
	end
	
	Emitter.RainTopDown.Texture = asset
	
	for _,v in pairs(rainAttachments) do
		v.RainTopDown.Texture = asset
	end
	
end

function Rain:SetSplashTexture(asset)
	
	if typeof(asset) ~= "string" then
		error("bad argument #1 to 'SetStraightTexture' (string expected, got " .. typeof(asset) .. ")", 2)
	end
	
	for _,v in pairs(splashAttachments) do
		v.RainSplash.Texture = asset
	end
	
end

function Rain:SetSoundId(asset)
	
	if typeof(asset) ~= "string" then
		error("bad argument #1 to 'SetSoundId' (string expected, got " .. typeof(asset) .. ")", 2)
	end
	
	Sound.SoundId = asset
	
end

function Rain:SetCollisionMode(mode, param)
	
	if mode == CollisionMode.None then
		
		-- Regular mode needs no white/blacklist or test function
		collisionList = nil
		collisionFunc = nil
		
	elseif mode == CollisionMode.Blacklist then
		
		if typeof(param) == "Instance" then
			-- Add Emitter anyway, since users will probably not expect collisions with emitter block regardless
			collisionList = {param, Emitter}
		elseif typeof(param) == "table" then
			for i = 1, #param do
				if typeof(param[i]) ~= "Instance" then
					error("bad argument #2 to 'SetCollisionMode' (blacklist contained a " .. typeof(param[i]) .. " on index " .. tostring(i) .. " which is not an Instance)", 2)
				end
			end
			collisionList = {Emitter} -- see above
			for i = 1, #param do
				table.insert(collisionList, param[i])
			end
		else
			error("bad argument #2 to 'SetCollisionMode (Instance or array of Instance expected, got " .. typeof(param) .. ")'", 2)
		end
		
		-- Blacklist does not need a test function
		collisionFunc = nil
		
	elseif mode == CollisionMode.Whitelist then
		
		if typeof(param) == "Instance" then
			collisionList = {param}
		elseif typeof(param) == "table" then
			for i = 1, #param do
				if typeof(param[i]) ~= "Instance" then
					error("bad argument #2 to 'SetCollisionMode' (whitelist contained a " .. typeof(param[i])  .. " on index " .. tostring(i) .. " which is not an Instance)", 2)
				end
			end
			collisionList = {}
			for i = 1, #param do
				table.insert(collisionList, param[i])
			end
		else
			error("bad argument #2 to 'SetCollisionMode (Instance or array of Instance expected, got " .. typeof(param) .. ")'", 2)
		end
		
		-- Whitelist does not need a test function
		collisionFunc = nil
		
	elseif mode == CollisionMode.Function then
		
		if typeof(param) ~= "function" then
			error("bad argument #2 to 'SetCollisionMode' (function expected, got " .. typeof(param) .. ")", 2)
		end
		
		-- Test function does not need a list
		collisionList = nil
		
		collisionFunc = param
		
	else
		error("bad argument #1 to 'SetCollisionMode (Rain.CollisionMode expected, got " .. typeof(param) .. ")'", 2)
	end
	
	collisionMode = mode
	raycast = raycastFunctions[mode]
	
end

return Rain