OUTLINE_MODE_BOTH	= 0
OUTLINE_MODE_NOTVISIBLE	= 1
OUTLINE_MODE_VISIBLE	= 2

local istable		= istable
local render		= render
local LocalPlayer	= LocalPlayer
local Material		= Material
local CreateMaterial	= CreateMaterial
local hook		= hook
local cam		= cam
local ScrW		= ScrW
local ScrH		= ScrH
local IsValid		= IsValid
local surface		= surface

module( "outline", package.seeall )

local List, ListSize = {}, 0
local RenderEnt = NULL

local OutlineMatSettings = {
	[ "$ignorez" ] = 1,
	[ "$alphatest" ] = 1
}

local CopyMat		= Material( "pp/copy" )
local OutlineMat	= CreateMaterial( "OutlineMat", "UnlitGeneric", OutlineMatSettings )
local StoreTexture	= render.GetScreenEffectTexture( 0 )
local DrawTexture	= render.GetScreenEffectTexture( 1 )

local ENTS	= 1
local COLOR	= 2
local MODE	= 3

function Add( ents, color, mode )

	if ( ListSize >= 255 ) then return end			--Maximum 255 reference values
	if ( !istable( ents ) ) then ents = { ents } end	--Support for passing Entity as first argument
	if ( ents[ 1 ] == nil ) then return end			--Do not pass empty tables

	local t = {
		[ ENTS ] = ents,
		[ COLOR ] = color,
		[ MODE ] = mode or OUTLINE_MODE_BOTH
	}

	ListSize = ListSize + 1
	List[ ListSize ] = t
end

function RenderedEntity()

	return RenderEnt

end

local function Render()
	local ply = LocalPlayer()
	local IsLineOfSightClear = ply.IsLineOfSightClear

	local scene = render.GetRenderTarget()
	render.CopyRenderTargetToTexture( StoreTexture )

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

	render.Clear( 0, 0, 0, 0, true, true )

	render.SetStencilEnable( true )
		cam.IgnoreZ( true )
		render.SuppressEngineLighting( true )

		render.SetStencilWriteMask( 0xFF )
		render.SetStencilTestMask( 0xFF )

		render.SetStencilCompareFunction( STENCIL_ALWAYS )
		render.SetStencilFailOperation( STENCIL_KEEP )
		render.SetStencilZFailOperation( STENCIL_REPLACE )
		render.SetStencilPassOperation( STENCIL_REPLACE )

		cam.Start3D()

			for i = 1, ListSize do
				local v = List[ i ]
				local mode = v[ MODE ]
				local ents = v[ ENTS ]

				render.SetStencilReferenceValue( i )

				for j = 1, #ents do
					local ent = ents[ j ]

					if ( !IsValid( ent ) ) then continue end

					if ( ( mode == OUTLINE_MODE_NOTVISIBLE && IsLineOfSightClear( ply, ent ) ) || ( mode == OUTLINE_MODE_VISIBLE && !IsLineOfSightClear( ply, ent ) ) ) then
						continue
					end

					RenderEnt = ent

					ent:DrawModel()
				end
			end

			RenderEnt = NULL

		cam.End3D()

		render.SetStencilCompareFunction( STENCIL_EQUAL )

		cam.Start2D()

			for i = 1, ListSize do
				render.SetStencilReferenceValue( i )

				surface.SetDrawColor( List[ i ][ COLOR ] )
				surface.DrawRect( 0, 0, w, h )
			end

		cam.End2D()

		render.SuppressEngineLighting( false )
		cam.IgnoreZ( false )
	render.SetStencilEnable( false )

	render.CopyRenderTargetToTexture( DrawTexture )

	render.SetRenderTarget( scene )
	CopyMat:SetTexture( "$basetexture", StoreTexture )
	render.SetMaterial( CopyMat )
	render.DrawScreenQuad()

	render.SetStencilEnable( true )
		render.SetStencilReferenceValue( 0 )
		render.SetStencilCompareFunction( STENCIL_EQUAL )

		OutlineMat:SetTexture( "$basetexture", DrawTexture )
		render.SetMaterial( OutlineMat )

		render.DrawScreenQuadEx( -1, -1, w ,h )
		render.DrawScreenQuadEx( -1, 0, w, h )
		render.DrawScreenQuadEx( -1, 1, w, h )
		render.DrawScreenQuadEx( 0, -1, w, h )
		render.DrawScreenQuadEx( 0, 1, w, h )
		render.DrawScreenQuadEx( 1, 1, w, h )
		render.DrawScreenQuadEx( 1, 0, w, h )
		render.DrawScreenQuadEx( 1, 1, w, h )

	render.SetStencilEnable( false )

end

hook.Add( "PostDrawEffects", "RenderOutlines", function()

	hook.Run( "PreDrawOutlines" )

	if ( ListSize == 0 ) then return end

	Render()

	List, ListSize = {}, 0

end )