--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]


TOOL.Category = "Poser"
TOOL.Name = "#tool.faceposer.name"

local MAXSTUDIOFLEXCTRL = 96

TOOL.FaceTimer = 0

TOOL.Information = {
	{ name = "left" },
	{ name = "right" }
}

local function IsUselessFaceFlex( strName )

	if ( strName == "gesture_rightleft" ) then return true end
	if ( strName == "gesture_updown" ) then return true end
	if ( strName == "head_forwardback" ) then return true end
	if ( strName == "chest_rightleft" ) then return true end
	if ( strName == "body_rightleft" ) then return true end
	if ( strName == "eyes_rightleft" ) then return true end
	if ( strName == "eyes_updown" ) then return true end
	if ( strName == "head_tilt" ) then return true end
	if ( strName == "head_updown" ) then return true end
	if ( strName == "head_rightleft" ) then return true end

	return false

end

local function GenerateDefaultFlexValue( ent, flexID )
	local min, max = ent:GetFlexBounds( flexID )
	if ( !max || max - min == 0 ) then return 0 end
	return ( 0 - min ) / ( max - min )
end

function TOOL:FacePoserEntity()
	return self:GetWeapon():GetNWEntity( 1 )
end

function TOOL:SetFacePoserEntity( ent )
	if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end
	return self:GetWeapon():SetNWEntity( 1, ent )
end

local LastFPEntity = NULL
local LastFPEntityValid = false
function TOOL:Think()

	-- If we're on the client just make sure the context menu is up to date
	if ( CLIENT ) then

		if ( self:FacePoserEntity() == LastFPEntity && IsValid( LastFPEntity ) == LastFPEntityValid ) then return end

		LastFPEntity = self:FacePoserEntity()
		LastFPEntityValid = IsValid( LastFPEntity )
		self:RebuildControlPanel( self:FacePoserEntity() )

		return
	end

	-- On the server we continually set the flex weights
	if ( self.FaceTimer > CurTime() ) then return end

	local ent = self:FacePoserEntity()
	if ( !IsValid( ent ) ) then return end

	local FlexNum = ent:GetFlexNum()
	if ( FlexNum <= 0 ) then return end

	for i = 0, FlexNum do

		local num = self:GetClientNumber( "flex" .. i )
		ent:SetFlexWeight( i, num )

	end

	local num = self:GetClientNumber( "scale" )
	ent:SetFlexScale( num )

end

--[[---------------------------------------------------------
	Alt fire sucks the facepose from the model's face
-----------------------------------------------------------]]
function TOOL:RightClick( trace )

	local ent = trace.Entity
	if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end

	if ( SERVER ) then
		self:SetFacePoserEntity( ent )
	end

	if ( !IsValid( ent ) ) then return true end

	local FlexNum = ent:GetFlexNum()
	if ( FlexNum == 0 ) then return false end

	if ( SERVER ) then

		-- This stops it applying the current sliders to the newly selected face..
		-- it should probably be linked to the ping somehow.. but 1 second seems pretty safe
		self.FaceTimer = CurTime() + 1

		-- In multiplayer the rest is only done on the client to save bandwidth.
		-- We can't do that in single player because these functions don't get called on the client
		if ( !game.SinglePlayer() ) then return true end

	end

	for i = 0, FlexNum - 1 do

		local Weight = "0.0"

		if ( !ent:HasFlexManipulatior() ) then
			Weight = GenerateDefaultFlexValue( ent, i )
		elseif ( i <= FlexNum ) then
			Weight = ent:GetFlexWeight( i )
		end

		self:GetOwner():ConCommand( "faceposer_flex" .. i .. " " .. Weight )

	end

	self:GetOwner():ConCommand( "faceposer_scale " .. ent:GetFlexScale() )

	return true

end

--[[---------------------------------------------------------
	Just select as the current object
	Current settings will get applied
-----------------------------------------------------------]]
function TOOL:LeftClick( trace )

	local ent = trace.Entity
	if ( IsValid( ent ) && ent:GetClass() == "prop_effect" ) then ent = ent.AttachedEntity end

	if ( !IsValid( ent ) ) then return false end
	if ( ent:GetFlexNum() == 0 ) then return false end

	self.FaceTimer = 0
	self:SetFacePoserEntity( ent )

	return true

end

if ( SERVER ) then

	local function CC_Face_Randomize( ply, command, arguments )

		for i = 0, MAXSTUDIOFLEXCTRL do
			local num = math.Rand( 0, 1 )
			ply:ConCommand( "faceposer_flex" .. i .. " " .. string.format( "%.3f", num ) )
		end

	end

	concommand.Add( "faceposer_randomize", CC_Face_Randomize )

end

-- The rest of the code is clientside only, it is not used on server
if ( SERVER ) then return end

for i = 0, MAXSTUDIOFLEXCTRL do
	TOOL.ClientConVar[ "flex" .. i ] = "0"
end

TOOL.ClientConVar[ "scale" ] = "1.0"

local ConVarsDefault = TOOL:BuildConVarList()

function TOOL.BuildCPanel( CPanel, faceEntity )

	CPanel:AddControl( "Header", { Description = "#tool.faceposer.desc" } )

	if ( !IsValid( faceEntity ) || faceEntity:GetFlexNum() == 0 ) then return end

	CPanel:AddControl( "ComboBox", { MenuButton = 1, Folder = "face", Options = { [ "#preset.default" ] = ConVarsDefault }, CVars = table.GetKeys( ConVarsDefault ) } )

	local QuickFace = vgui.Create( "MatSelect", CPanel )
	QuickFace:SetItemWidth( 64 )
	QuickFace:SetItemHeight( 32 )

	QuickFace.List:SetSpacing( 1 )
	QuickFace.List:SetPadding( 0 )

	QuickFace:SetAutoHeight( true )

	local Clear = {}
	for i = 0, MAXSTUDIOFLEXCTRL do
		Clear[ "faceposer_flex" .. i ] = GenerateDefaultFlexValue( faceEntity, i )
	end
	QuickFace:AddMaterialEx( "#faceposer.clear", "vgui/face/clear", nil, Clear )

	-- Todo: These really need to be the name of the flex.
	QuickFace:AddMaterialEx( "#faceposer.openeyes", "vgui/face/open_eyes", nil, {
		faceposer_flex0 = "1",
		faceposer_flex1 = "1",
		faceposer_flex2 = "0",
		faceposer_flex3 = "0",
		faceposer_flex4 = "0",
		faceposer_flex5 = "0",
		faceposer_flex6 = "0",
		faceposer_flex7 = "0",
		faceposer_flex8 = "0",
		faceposer_flex9 = "0"
	} )

	QuickFace:AddMaterialEx( "#faceposer.closeeyes", "vgui/face/close_eyes", nil, {
		faceposer_flex0 = "0",
		faceposer_flex1 = "0",
		faceposer_flex2 = "1",
		faceposer_flex3 = "1",
		faceposer_flex4 = "1",
		faceposer_flex5 = "1",
		faceposer_flex6 = "1",
		faceposer_flex7 = "1",
		faceposer_flex8 = "1",
		faceposer_flex9 = "1"
	} )

	QuickFace:AddMaterialEx( "#faceposer.angryeyebrows", "vgui/face/angry_eyebrows", nil, {
		faceposer_flex10 = "0",
		faceposer_flex11 = "0",
		faceposer_flex12 = "1",
		faceposer_flex13 = "1",
		faceposer_flex14 = "0.5",
		faceposer_flex15 = "0.5"
	} )

	QuickFace:AddMaterialEx( "#faceposer.normaleyebrows", "vgui/face/normal_eyebrows", nil, {
		faceposer_flex10 = "0",
		faceposer_flex11 = "0",
		faceposer_flex12 = "0",
		faceposer_flex13 = "0",
		faceposer_flex14 = "0",
		faceposer_flex15 = "0"
	} )

	QuickFace:AddMaterialEx( "#faceposer.sorryeyebrows", "vgui/face/sorry_eyebrows", nil, {
		faceposer_flex10 = "1",
		faceposer_flex11 = "1",
		faceposer_flex12 = "0",
		faceposer_flex13 = "0",
		faceposer_flex14 = "0",
		faceposer_flex15 = "0"
	} )

	QuickFace:AddMaterialEx( "#faceposer.grin", "vgui/face/grin", nil, {
		faceposer_flex20 = "1",
		faceposer_flex21 = "1",
		faceposer_flex22 = "1",
		faceposer_flex23 = "1",
		faceposer_flex24 = "0",
		faceposer_flex25 = "0",
		faceposer_flex26 = "0",
		faceposer_flex27 = "1",
		faceposer_flex28 = "1",
		faceposer_flex29 = "0",
		faceposer_flex30 = "0",
		faceposer_flex31 = "0",
		faceposer_flex32 = "0",
		faceposer_flex33 = "1",
		faceposer_flex34 = "1",
		faceposer_flex35 = "0",
		faceposer_flex36 = "0",
		faceposer_flex37 = "0",
		faceposer_flex38 = "0",
		faceposer_flex39 = "1",
		faceposer_flex40 = "0",
		faceposer_flex41 = "0",
		faceposer_flex42 = "1",
		faceposer_flex43 = "1"
	} )

	QuickFace:AddMaterialEx( "#faceposer.sad", "vgui/face/sad", nil, {
		faceposer_flex20 = "0",
		faceposer_flex21 = "0",
		faceposer_flex22 = "0",
		faceposer_flex23 = "0",
		faceposer_flex24 = "1",
		faceposer_flex25 = "1",
		faceposer_flex26 = "0.0",
		faceposer_flex27 = "0",
		faceposer_flex28 = "0",
		faceposer_flex29 = "0",
		faceposer_flex30 = "0",
		faceposer_flex31 = "0",
		faceposer_flex32 = "0",
		faceposer_flex33 = "0",
		faceposer_flex34 = "0",
		faceposer_flex35 = "0",
		faceposer_flex36 = "0",
		faceposer_flex37 = "0",
		faceposer_flex38 = "0.5",
		faceposer_flex39 = "0",
		faceposer_flex40 = "0",
		faceposer_flex41 = "0",
		faceposer_flex42 = "0",
		faceposer_flex43 = "0"
	} )

	QuickFace:AddMaterialEx( "#faceposer.smile", "vgui/face/smile", nil, {
		faceposer_flex20 = "1",
		faceposer_flex21 = "1",
		faceposer_flex22 = "1",
		faceposer_flex23 = "1",
		faceposer_flex24 = "0",
		faceposer_flex25 = "0",
		faceposer_flex26 = "0",
		faceposer_flex27 = "0.6",
		faceposer_flex28 = "0.4",
		faceposer_flex29 = "0",
		faceposer_flex30 = "0",
		faceposer_flex31 = "0",
		faceposer_flex32 = "0",
		faceposer_flex33 = "1",
		faceposer_flex34 = "1",
		faceposer_flex35 = "0",
		faceposer_flex36 = "0",
		faceposer_flex37 = "0",
		faceposer_flex38 = "0",
		faceposer_flex39 = "0",
		faceposer_flex40 = "1",
		faceposer_flex41 = "1",
		faceposer_flex42 = "0",
		faceposer_flex43 = "0",
		faceposer_flex44 = "0",
	} )

	CPanel:AddItem( QuickFace )

	CPanel:AddControl( "Slider", { Label = "#tool.faceposer.scale", Command = "faceposer_scale", Type = "Float", Min = -5, Max = 5, Help = true, Default = 1 } ):SetHeight( 16 )
	CPanel:AddControl( "Button", { Text = "#tool.faceposer.randomize", Command = "faceposer_randomize" } )

	local filter = CPanel:AddControl( "TextBox", { Label = "#spawnmenu.quick_filter_tool" } )
	filter:SetUpdateOnType( true )

	-- Group flex controllers by their type..
	local flexGroups = {}
	for i = 0, faceEntity:GetFlexNum() - 1 do
		local name = faceEntity:GetFlexName( i )

		if ( !IsUselessFaceFlex( name ) ) then
			local group = faceEntity:GetFlexType( i )

			if ( group == name ) then group = "Other" end

			local min, max = faceEntity:GetFlexBounds( i )

			flexGroups[ group ] = flexGroups[ group ] or {}
			table.insert( flexGroups[ group ], { name = name, id = i, min = min, max = max } )
		end
	end

	local flexControllers = {}
	for group, items in pairs( flexGroups ) do

		local groupForm = vgui.Create( "DForm", CPanel )
		groupForm:SetLabel( string.NiceName( group ) )

		-- Give the DForm a nice outline
		groupForm.GetBackgroundColor = function() return color_white end

		function groupForm:Paint( w, h )

			derma.SkinHook( "Paint", "CategoryList", self, w, h )
			derma.SkinHook( "Paint", "CollapsibleCategory", self, w, h )

		end

		CPanel:AddItem( groupForm )

		for id, item in pairs( items ) do

			local ctrl = groupForm:NumSlider( string.NiceName( item.name ), "faceposer_flex" .. item.id, item.min, item.max, 2 )
			ctrl:SetDefaultValue( GenerateDefaultFlexValue( faceEntity, item.id ) )
			ctrl:SetHeight( 11 ) -- This makes the controls all bunched up like how we want
			ctrl:DockPadding( 0, -6, 0, -4 ) -- Try to make the lower part of the text visible
			ctrl.originalName = item.name
			table.insert( flexControllers, ctrl )

			if ( item.id >= MAXSTUDIOFLEXCTRL ) then
				ctrl:SetEnabled( false )
				ctrl:SetTooltip( "#tool.faceposer.too_many_flexes" )
			end
		end

		-- HACK: Add some padding to the bottom of the list, because Dock won't
		local padding = vgui.Create( "Panel", groupForm )
		padding:SetHeight( 0 )
		groupForm:AddItem( padding )

	end

	-- Actual searching
	filter.OnValueChange = function( pnl, txt )
		for id, flxpnl in ipairs( flexControllers ) do
			if ( !flxpnl:GetText():lower():find( txt:lower(), nil, true ) && !flxpnl.originalName:lower():find( txt:lower(), nil, true ) ) then
				flxpnl:SetVisible( false )
			else
				flxpnl:SetVisible( true )
			end

			flxpnl:InvalidateParent()
		end
		CPanel:InvalidateChildren()
	end
end

local FacePoser = surface.GetTextureID( "gui/faceposer_indicator" )

-- Draw a box indicating the face we have selected
function TOOL:DrawHUD()

	if ( GetConVarNumber( "gmod_drawtooleffects" ) == 0 ) then return end

	local selected = self:FacePoserEntity()

	if ( !IsValid( selected ) || selected:IsWorld() || selected:GetFlexNum() == 0 ) then return end

	local pos = selected:GetPos()
	local eyeattachment = selected:LookupAttachment( "eyes" )
	if ( eyeattachment != 0 ) then
		local attachment = selected:GetAttachment( eyeattachment )
		pos = attachment.Pos
	else
		-- The model has no "eyes" attachment, try to find a bone with "head" in its name
		for i = 0, selected:GetBoneCount() - 1 do
			if ( selected:GetBoneName( i ) && selected:GetBoneName( i ):lower():find( "head" ) ) then
				pos = selected:GetBonePosition( i )
			end
		end
	end

	local scrpos = pos:ToScreen()
	if ( !scrpos.visible ) then return end

	-- Work out the side distance to give a rough headsize box..
	local player_eyes = LocalPlayer():EyeAngles()
	local side = ( pos + player_eyes:Right() * 20 ):ToScreen()
	local size = math.abs( side.x - scrpos.x )

	surface.SetDrawColor( 255, 255, 255, 255 )
	surface.SetTexture( FacePoser )
	surface.DrawTexturedRect( scrpos.x - size, scrpos.y - size, size * 2, size * 2 )

end
