include("shared.lua")

function ENT:Initialize()
	self:SharedInit()
	self:SetupOffsets()
	net.Start("textscreen_update")
		net.WriteEntity(self)
	net.SendToServer()
end
	
function ENT:SetupOffsets()
	local tb = self:GetTable()

	local mdl = string.gsub(self:GetModel(), "[/\\]+", "/")
	local mdldata = list.Get("TextScreenModels")[mdl]
	if mdldata then
		tb.offset_pos = mdldata.pos or Vector()
		tb.offset_ang = mdldata.ang or Angle()
		tb.offset_scale = mdldata.scale or Vector(1, 1, 1)
	else
		tb.offset_pos = Vector()
		tb.offset_ang = Angle()
		tb.offset_scale = Vector(1, 1, 1)
	end
	tb.offset_scale = Vector(tb.offset_scale.x, tb.offset_scale.y, ((tb.offset_scale.x + tb.offset_scale.y) / 2) / 128)
end

local function SetFontSafe(name, dont_fallback)
	local result = pcall(surface.SetFont, name)
	if !result and !dont_fallback then
		surface.SetFont("Trebuchet24")
	end
end

function ENT:UpdateMarkup()
	local tb = self:GetTable()

	SetFontSafe(tb.font)
	tb.lines = {}
	local spacew, spaceh = surface.GetTextSize(" ")
	local x, y = 0, 0
	local w = tb.offset_scale.x * 2 / (tb.offset_scale.z * tb.scale)
	local origin_h = (tb.offset_scale.y * 2 / (tb.offset_scale.z * tb.scale))
	local h = (tb.offset_scale.y * 2 / (tb.offset_scale.z * tb.scale)) + spaceh
	local maxh = spaceh
	local text = tostring(tb.text)
	local lpos = 1
	local newline = false
	local curword = ""
	local isword = true
	for i, c in utf8.codes(text) do
		c = utf8.char(c)
		if c == "\n" then
			if y + maxh + spaceh >= h then break end
			local old_text = string.sub(text, lpos, i - 1) or ""
			local new_text = string.Trim(old_text)
			if new_text != old_text then
				for i=1, (#old_text - #new_text) do
					x = x - spacew
				end
			end
			table.insert(tb.lines, {x = tb.halign * (w - x), y = y, w = x, h = maxh, text = new_text})
			lpos = i + 1
			x = 0
			y = y + maxh
			maxh = spaceh
			curword = ""
			isword = true
		elseif c == " " then
			local newx = x + spacew
			if newx > w then
				if y + maxh + spaceh >= h then break end
				local old_text = string.sub(text, lpos, i - 1) or ""
				local new_text = string.Trim(old_text)
				if new_text != old_text then
					for i=1, (#old_text - #new_text) do
						x = x - spacew
					end
				end
				table.insert(tb.lines, {x = tb.halign * (w - x), y = y, w = x, h = maxh, text = new_text})
				lpos = i + 1
				x = 0
				y = y + maxh
				maxh = spaceh
			else
				x = x + spacew
			end
			curword = ""
			isword = true
		else
			local cw, ch = surface.GetTextSize(c)
			maxh = math.max(maxh, ch)
			local newx = x + cw
			if newx > w then
				local curword_len = isword and #curword or 0
				if curword_len > 0 then
					for i, c in utf8.codes(curword) do
						c = utf8.char(c)
						local cw, ch = surface.GetTextSize(c)
						x = x - cw
					end
				end
				local npos = i - 1 - curword_len
				if npos > lpos then
					if y + maxh + spaceh >= h then break end
					local old_text = string.sub(text, lpos, npos) or ""
					local new_text = string.Trim(old_text)
					if new_text != old_text then
						for i=1, (#old_text - #new_text) do
							x = x - spacew
						end
					end
					table.insert(tb.lines, {x = tb.halign * (w - x), y = y, w = x, h = maxh, text = new_text})
					y = y + maxh
					maxh = spaceh
				end
				lpos = npos + 1
				x = cw
				newline = true
			else
				x = newx
			end
			if newline and #curword > 0 then
				for _, c in utf8.codes(curword) do
					c = utf8.char(c)
					local cw, ch = surface.GetTextSize(c)
					maxh = math.max(maxh, ch)
					local newx = x + cw
					if newx > w then
						if y + maxh + spaceh >= h then break end
						local old_text = string.sub(text, lpos, i - 1) or ""
						local new_text = string.Trim(old_text)
						if new_text != old_text then
							for i=1, (#old_text - #new_text) do
								x = x - spacew
							end
						end
						table.insert(tb.lines, {x = tb.halign * (w - x), y = y, w = x, h = maxh, text = new_text})
						lpos = i
						x = cw
						y = y + maxh
						maxh = spaceh
					else
						x = newx
					end
				end
				newline = false
				curword = ""
				isword = false
			else
				newline = false
			end
			if isword then
				curword = curword..c
			end
		end
	end
	if lpos <= #text and y + maxh + spaceh < h then
		maxh = spaceh
		for i, c in utf8.codes(text) do
			if lpos <= i then
				c = utf8.char(c)
				local cw, ch = surface.GetTextSize(c)
				maxh = math.max(maxh, ch)
			end
		end
		local old_text = string.sub(text, lpos, #text) or ""
		local new_text = string.Trim(old_text)
		if new_text != old_text then
			for i=1, (#old_text - #new_text) do
				x = x - spacew
			end
		end
		table.insert(tb.lines, {x = tb.halign * (w - x), y = y, w = x, h = maxh, text = new_text})
		y = y + maxh
	end
	for _, v in ipairs(tb.lines) do
		v.y = v.y - (y - h + maxh) * tb.valign
	end
end

local veh, product
local function IsInFront(local_ply, pos, shoot_pos, direction)
	veh = local_ply:GetVehicle()
	if veh:IsValid() then
		veh = veh:GetThirdPersonMode()
		if veh then
			return true
		end
	end
	product = (pos.x - shoot_pos.x) * direction.x + (pos.y - shoot_pos.y) * direction.y + (pos.z - shoot_pos.z) * direction.z
	return product < 0
end

local local_ply, pos, distance, offset_ang
local stcid = 1
local angle, vector = Angle(), Vector()

local reg = debug.getregistry()
local IsSolid = reg.Entity.IsSolid
local GetPos = reg.Entity.GetPos
local EyePos = reg.Entity.EyePos
local DistToSqr = reg.Vector.DistToSqr
local LocalToWorldAngles = reg.Entity.LocalToWorldAngles
local GetShootPos = reg.Player.GetShootPos
local Up = reg.Angle.Up
local GetAngles = reg.Entity.GetAngles
local LocalToWorld_ent = reg.Entity.LocalToWorld
local Forward = reg.Angle.Forward
local Right = reg.Angle.Right

local render = render

function ENT:DrawTranslucent()
	if !IsSolid(self) then return end
	local tb = self:GetTable()

	local_ply = local_ply or LocalPlayer()
	pos = GetPos(self)
	distance = DistToSqr(EyePos(local_ply), pos)
	offset_ang = LocalToWorldAngles(self, tb.offset_ang)

	if IsInFront(local_ply, pos, GetShootPos(local_ply), Up(offset_ang)) then
		render.SetColorMaterial()
		render.SetBlend(tb.bgcolor.a)
		local ang = GetAngles(self)
		local normx = LocalToWorld(tb.offset_scale.x * Forward(tb.offset_ang), angle, vector, ang)
		local normy = LocalToWorld(tb.offset_scale.y * Right(tb.offset_ang), angle, vector, ang)
		local origin = LocalToWorld_ent(self, tb.offset_pos)
		render.ClearStencil()
		render.SetStencilEnable(true)
		render.SetStencilFailOperation(STENCIL_KEEP)
		render.SetStencilPassOperation(STENCIL_REPLACE)
		render.SetStencilCompareFunction(STENCIL_ALWAYS)
		render.SetStencilReferenceValue(stcid)
		render.SetStencilWriteMask(stcid)
		render.DrawQuad(origin - normx - normy, origin + normx - normy, origin + normx + normy, origin - normx + normy, tb.bgcolor)
		render.SetStencilTestMask(255)
		render.SetStencilCompareFunction(STENCIL_EQUAL)
		if distance < 4000000 then
			if !tb.lines then
				self:UpdateMarkup()
			end
			origin = origin - normx - normy
			cam.Start3D2D(origin, offset_ang, tb.scale * tb.offset_scale.z)
				surface.SetTextColor(tb.fgcolor)
				SetFontSafe(tb.font)
				for _,v in ipairs(tb.lines) do
					surface.SetTextPos(v.x, v.y)
					surface.DrawText(v.text)
				end
			cam.End3D2D()
		end
		render.SetStencilEnable(false)
	end
end

function ENT:Draw()
	self:DrawModel()
end

net.Receive("textscreen_update", function(len) 
	local ent = net.ReadEntity()
	if !IsValid(ent) then return end
	local tb = ent:GetTable()

	tb.text = net.ReadString()
	tb.font = net.ReadString()
	tb.fgcolor = net.ReadColor()
	tb.bgcolor = net.ReadColor()
	tb.scale = net.ReadFloat()
	tb.halign = net.ReadUInt(8) / 255
	tb.valign = net.ReadUInt(8) / 255
	tb.lines = nil
end)