-- stencil functions
local useStencils = render.SupportsPixelShaders_2_0() and render.SupportsVertexShaders_2_0()

local function defineCanvas(ref)
	render.UpdateScreenEffectTexture()
	render.ClearStencil()
	render.SetStencilEnable(true)
	render.SetStencilCompareFunction(STENCIL_ALWAYS)
	render.SetStencilPassOperation(STENCIL_REPLACE)
	render.SetStencilFailOperation(STENCIL_KEEP)
	render.SetStencilZFailOperation(STENCIL_REPLACE)
	render.SetStencilWriteMask(255)
	render.SetStencilTestMask(255)
	render.SetStencilReferenceValue(ref or 54)
end

local function drawOn()
	render.SetStencilCompareFunction(STENCIL_EQUAL)
end

local function stopCanvas()
	render.SetStencilEnable(false)
end

-- main draw functions
local CachedMaterials = {}

local DrawFunctions = {}

do -- Flat reticle, stays at center or moves with recoil
	local function ScreenScaleH(num)
		return num * (ScrH() / 480)
	end

	DrawFunctions[TFA.Enum.RETICLE_FLAT] = function(vm, ply, wep, SightElementTable)
		local ReticleMaterial = wep:GetStat("StencilSight_ReticleMaterial")
		if not ReticleMaterial then return end

		if type(ReticleMaterial) == "string" then
			CachedMaterials[ReticleMaterial] = CachedMaterials[ReticleMaterial] or Material(ReticleMaterial, "noclamp nocull smooth")
			ReticleMaterial = CachedMaterials[ReticleMaterial]
		end

		local ReticleSize = wep:GetStat("StencilSight_ReticleSize")
		if not ReticleSize then return end

		if wep:GetStat("StencilSight_ScaleReticleByScreenHeight", true) then
			ReticleSize = ScreenScaleH(ReticleSize)
		end

		if wep:GetStat("StencilSight_ScaleReticleByProgress", true) then
			ReticleSize = ReticleSize * wep.IronSightsProgress
		end

		local w, h = ScrW(), ScrH()

		local x, y = w * .5, h * .5
		if wep:GetStat("StencilSight_FollowRecoil", true) then
			x, y = TFA.LastCrosshairPosX or x, TFA.LastCrosshairPosY or y
		end

		local TargetColor = wep:GetStat("StencilSight_ReticleTint", color_white)

		if wep:GetStat("StencilSight_ReticleTintBySightColor", false) and IsValid(wep:GetOwner()) then
			local Owner = wep:GetOwner()

			local _GetNWVector = Owner.GetNW2Vector or Owner.GetNWVector

			local ColorVec = _GetNWVector(Owner, "TFAReticuleColor")

			if ColorVec then
				TargetColor = Color(ColorVec.x, ColorVec.y, ColorVec.z)
			end
		end

		if wep:GetStat("StencilSight_FadeReticleByProgress", false) then
			TargetColor = ColorAlpha(TargetColor, wep.IronSightsProgress * 255)
		end

		render.DepthRange(0.0, 0.1)
		cam.Start2D(0, 0, w, h)
			surface.SetMaterial(ReticleMaterial)
			surface.SetDrawColor(TargetColor)
			surface.DrawTexturedRect(x - ReticleSize * .5, y - ReticleSize * .5, ReticleSize, ReticleSize)
		cam.End2D()
		if not wep.UseHands then
			render.DepthRange(0.0, 1.0)
		end
	end
end

do -- Model reticle, for when you don't have an attach point
	if IsValid(TFA.SightReticleEnt) then
		TFA.SightReticleEnt:Remove()
		TFA.SightReticleEnt = nil
	end

	TFA.SightReticleEnt = ClientsideModel("models/error.mdl", RENDERGROUP_VIEWMODEL)
	TFA.SightReticleEnt:SetNoDraw(true)

	local SightReticleEnt = TFA.SightReticleEnt

	DrawFunctions[TFA.Enum.RETICLE_MODEL] = function(vm, ply, wep, SightElementTable)
		if not SightElementTable.reticle then return end

		local SightElementModel = SightElementTable.curmodel

		SightReticleEnt:SetModel(SightElementTable.reticle)
		if SightReticleEnt:GetModel() == "models/error.mdl" then return end

		local matrix = Matrix()
		matrix:Scale(SightElementTable.size)
		SightReticleEnt:EnableMatrix("RenderMultiply", matrix)

		if SightReticleEnt:GetParent() ~= SightElementModel then
			SightReticleEnt:SetParent(SightElementModel)
			SightReticleEnt:SetPos(SightElementModel:GetPos())
			SightReticleEnt:SetAngles(SightElementModel:GetAngles())

			if not SightReticleEnt:IsEffectActive(EF_BONEMERGE) then
				SightReticleEnt:AddEffects(EF_BONEMERGE)
				SightReticleEnt:AddEffects(EF_BONEMERGE_FASTCULL)
			end
		end

		if wep.ViewModelFlip then render.CullMode(MATERIAL_CULLMODE_CW) end
		if wep:GetStat("StencilSight_FadeReticleByProgress", false) then
			local oldBlend = render.GetBlend()

			render.SetBlend(wep.IronSightsProgress)
			SightReticleEnt:DrawModel()
			render.SetBlend(oldBlend)
		else
			SightReticleEnt:DrawModel()
		end
		if wep.ViewModelFlip then render.CullMode(MATERIAL_CULLMODE_CCW) end

		if wep:GetStat("StencilSight_EnableQuad") and bit.band(wep:GetStat("StencilSight_ReticleType")) ~= TFA.Enum.RETICLE_QUAD then
			DrawFunctions[TFA.Enum.RETICLE_QUAD](vm, ply, wep, SightElementTable)
		end
	end
end

do -- Quad/Attach reticle, TFA INS2 method
	local function GetTargetPosition(wep, SightElementTable)
		local TargetEntity = SightElementTable.curmodel
		if not IsValid(TargetEntity) then return end

		local Type = wep:GetStat("StencilSight_PositionType", TFA.Enum.SIGHTSPOS_ATTACH)

		local pos, ang

		if Type == TFA.Enum.SIGHTSPOS_ATTACH then
			local AttachmentID = wep:GetStat("StencilSight_ReticleAttachment")
			if not AttachmentID then return end

			if type(AttachmentID) == "string" then
				AttachmentID = TargetEntity:LookupAttachment(AttachmentID)
			end

			if not AttachmentID or AttachmentID <= 0 then return end

			local Attachment = TargetEntity:GetAttachment(AttachmentID)
			if not Attachment.Pos or not Attachment.Ang then return end

			pos, ang = Attachment.Pos, Attachment.Ang
		elseif Type == TFA.Enum.SIGHTSPOS_BONE then
			local BoneID = wep:GetStat("StencilSight_ReticleBone")

			if type(BoneID) == "string" then
				BoneID = TargetEntity:LookupBone(BoneID)
			end

			if not BoneID or BoneID < 0 then return end

			pos, ang = TargetEntity:GetBonePosition(BoneID)

			if pos == TargetEntity:GetPos() then
				pos = TargetEntity:GetBoneMatrix(BoneID):GetTranslation()
				ang = TargetEntity:GetBoneMatrix(BoneID):GetAngles()
			end
		else
			return
		end

		local OffsetPos = wep:GetStat("StencilSight_ReticleOffsetPos")
		if OffsetPos then
			pos = pos + ang:Right() * OffsetPos.x + ang:Forward() * OffsetPos.y + ang:Up() * OffsetPos.z
		end

		local OffsetAng = wep:GetStat("StencilSight_ReticleOffsetAng")
		if OffsetAng then
			ang:RotateAroundAxis(ang:Right(), OffsetAng.p)
			ang:RotateAroundAxis(ang:Up(), OffsetAng.y)
			ang:RotateAroundAxis(ang:Forward(), OffsetAng.r)
		end

		return pos, ang
	end

	DrawFunctions[TFA.Enum.RETICLE_QUAD] = function(vm, ply, wep, SightElementTable)
		local ReticleMaterial = wep:GetStat("StencilSight_ReticleMaterial")
		if not ReticleMaterial then return end

		if type(ReticleMaterial) == "string" then
			CachedMaterials[ReticleMaterial] = CachedMaterials[ReticleMaterial] or Material(ReticleMaterial, "noclamp nocull smooth")
			ReticleMaterial = CachedMaterials[ReticleMaterial]
		end

		local ReticleSize = wep:GetStat("StencilSight_ReticleSize")
		if not ReticleSize then return end

		if wep:GetStat("StencilSight_ScaleReticleByProgress", false) then
			ReticleSize = ReticleSize * wep.IronSightsProgress
		end

		local TargetColor = wep:GetStat("StencilSight_ReticleTint", color_white)

		if wep:GetStat("StencilSight_ReticleTintBySightColor", false) and IsValid(wep:GetOwner()) then
			local Owner = wep:GetOwner()

			local _GetNWVector = Owner.GetNW2Vector or Owner.GetNWVector

			local ColorVec = _GetNWVector(Owner, "TFAReticuleColor")

			if ColorVec then
				TargetColor = Color(ColorVec.x, ColorVec.y, ColorVec.z)
			end
		end

		if wep:GetStat("StencilSight_FadeReticleByProgress", false) then
			TargetColor = ColorAlpha(TargetColor, wep.IronSightsProgress * 255)
		end

		local p, a = GetTargetPosition(wep, SightElementTable)
		if not p or not a then return end

		render.OverrideDepthEnable(true, true)

		render.SetMaterial(ReticleMaterial)
		render.DrawQuadEasy(p, a:Forward() * -1, ReticleSize, ReticleSize, TargetColor, 180 + a.r * (wep.ViewModelFlip and 1 or -1))

		render.OverrideDepthEnable(false, false)
	end
end

-- hook logic
if IsValid(TFA.SightMaskEnt) then
	TFA.SightMaskEnt:Remove()
	TFA.SightMaskEnt = nil
end

TFA.SightMaskEnt = ClientsideModel("models/error.mdl", RENDERGROUP_VIEWMODEL)
TFA.SightMaskEnt:SetNoDraw(true)

local SightMaskEnt = TFA.SightMaskEnt

local function DrawSight(vm, ply, wep)
	if not IsValid(wep) or not wep.IsTFAWeapon then return end

	local shouldDraw = hook.Run("TFA_ShouldDrawStencilSight", wep)
	if shouldDraw == false then return end

	local wep2 = wep:GetTable()

	if wep2.TFA_IsDrawingStencilSights then return end
	wep2.TFA_IsDrawingStencilSights = true

	if not wep2.GetStat(wep, "StencilSight") then wep2.TFA_IsDrawingStencilSights = false return end
	if wep2.IronSightsProgress < wep2.GetStat(wep, "StencilSight_MinPercent", 0.05) then wep2.TFA_IsDrawingStencilSights = false return end

	local SightElementName = wep2.GetStat(wep, "StencilSight_VElement")
	if not SightElementName or not wep2.GetStat(wep, "VElements." .. SightElementName .. ".active") then wep2.TFA_IsDrawingStencilSights = false return end

	local SightElementTable = wep2.VElements[SightElementName]
	if not SightElementTable then wep2.TFA_IsDrawingStencilSights = false return end

	local SightElementModel = SightElementTable.curmodel
	if not IsValid(SightElementModel) then wep2.TFA_IsDrawingStencilSights = false return end

	if useStencils then
		defineCanvas()

		local SightMaskModel = SightElementModel

		if wep2.GetStat(wep, "StencilSight_UseMask", false) and SightElementTable.mask then
			SightMaskEnt:SetModel(SightElementTable.mask)

			if SightMaskEnt:GetModel() ~= "models/error.mdl" then
				SightMaskModel = SightMaskEnt

				local matrix = Matrix()
				matrix:Scale(SightElementTable.size)
				SightMaskEnt:EnableMatrix("RenderMultiply", matrix)

				if SightMaskEnt:GetParent() ~= SightElementModel then
					SightMaskEnt:SetParent(SightElementModel)
					SightMaskEnt:SetPos(SightElementModel:GetPos())
					SightMaskEnt:SetAngles(SightElementModel:GetAngles())

					if not SightMaskEnt:IsEffectActive(EF_BONEMERGE) then
						SightMaskEnt:AddEffects(EF_BONEMERGE)
						SightMaskEnt:AddEffects(EF_BONEMERGE_FASTCULL)
					end
				end
			end
		end

		if wep.ViewModelFlip then render.CullMode(MATERIAL_CULLMODE_CW) end
		local oldBlend = render.GetBlend()
		render.SetBlend(0)
			SightMaskModel:DrawModel()
		render.SetBlend(oldBlend)
		if wep.ViewModelFlip then render.CullMode(MATERIAL_CULLMODE_CCW) end

		drawOn()
	end

	local retValPre = wep2.CallAttFunc(wep, "PreDrawStencilSight", vm, ply, SightElementTable)
	if retValPre ~= true then
		retValPre = wep2.PreDrawStencilSight(wep, vm, ply, SightElementTable) or retValPre
	end

	if retValPre ~= false then
		local funcType = wep2.GetStat(wep, "StencilSight_ReticleType", TFA.Enum.RETICLE_FLAT)

		for _, retType in ipairs(TFA.Enum.RETICLE_DRAW_ORDER) do
			if bit.band(funcType, retType) == retType and DrawFunctions[retType] then
				ProtectedCall(function()
					DrawFunctions[retType](vm, ply, wep, SightElementTable)
				end)
			end
		end

		local retValPost = wep2.CallAttFunc(wep, "PostDrawStencilSight", vm, ply, SightElementTable)
		if retValPost ~= true then
			wep2.PostDrawStencilSight(wep, vm, ply, SightElementTable)
		end
	end

	if useStencils then
		stopCanvas()
	end

	wep2.TFA_IsDrawingStencilSights = false
end

hook.Add("PostDrawViewModel", "TFA_DrawStencilSight", DrawSight)
