--[[
#include "../../umf.lua"
#include "../../shape_utils.lua"
]] --
local state = util.structured_table( "game.tool.physgun", {
	bottom = "boolean",
	grabbed = "body",
	grabbing = "boolean",
	relative = "vector",
	dist = "number",
	action = "string",
	rotating = "boolean",
	effect = { freeze_time = "number", freeze_body = "body", unfreeze_time = "number", unfreeze_body = "body" },
	skin = {
		config = {
			outline = { "boolean", true },
			beam = { "boolean", true },
			freeze = { "boolean", true },
			tool = { "boolean", true },
			sound = { "boolean", true },
			random_beam = { "boolean", true },

			color_primary = { "vector", { .878, .878, .878 } },
			color_secondary = { "vector", { .392, .392, .392 } },
			color_light = { "vector", { .173, .58, .729 } },
		},
	},
} )

local TOOL = {}

TOOL.printname = "Quilez Manipulator"


function TOOL:Initialize()
	self.sound_snap = LoadSound( "MOD/sound/snap0" )
	self.sound_slide = LoadSound( "MOD/sound/slide_thud" )
	self.sound_power = LoadSound( "MOD/sound/power" )
	self.sound_hit = LoadSound( "MOD/sound/hit" )
	self.soundloop_static = LoadLoop( "MOD/sound/static.ogg" )
	self.soundloop_static_high = LoadLoop( "MOD/sound/static_high.ogg" )
	self.laser = LoadSprite("gfx/laser.png")
end

local color_map = {
	color_primary = {
		[120] = 1,
		[119] = 218 / 224,
		[118] = 211 / 224,
		[117] = 205 / 224,
		[116] = 199 / 224,
		[115] = 192 / 224,
	},
	color_secondary = {
		[157] = 1,
		[156] = .94,
		[155] = .88,
		[154] = .81,
		[132] = .82,
		[131] = .73,
		[130] = .65,
		[129] = .56,
	},
	color_light = {
		[8] = 1,
	},
}
function TOOL:SetupModel( body, shapes )
	local entries, reverse = GetShapesPaletteContent( shapes )
	do
		local function setcolor(index, vec)
			if not reverse[index] then print("Unknown index", index, VecStr(vec)) return end
			reverse[index].r = vec[1]
			reverse[index].g = vec[2]
			reverse[index].b = vec[3]
		end
		for configname, slots in pairs( color_map ) do
			local val = state.skin.config[configname]
			for slot, scale in pairs(slots) do
				setcolor( slot, VecScale( val, scale ) )
			end
		end
	end
	local palette = CreatePalette( entries )
	for i = 1, #shapes do
		SetTag(shapes[i], "ground")
		CopyShapePalette( palette, shapes[i] )
	end
	Delete( palette )
end

local toolpos = Transform( Vec( 0.425, -0.375, -1 ), QuatEuler( 0, 0, 0 ) )
local toolpos_thirdperson = Transform(
	Vec( 0.2, -0.7, -0.5 ),
	QuatEuler( 0, 0, 5 )
)
local toolpos_thirdperson_holster = Transform(
	Vec( 0, -0.8, -0.5 ),
	QuatEuler( -5, 20, 0 )
)

local anim_data = {
	deploy = math.huge,
	snaps = 0,
	slide = false,
	power = false,
	throw = -10,
	nub_active = {},
	nub_snaps = {},
	rotating = false,
	static_energy = 0,
	pitch = 0,
	yaw = 0,
}
local function subtime(t, start, len)
	return math.max(0, math.min(1, (t - start)/len))
end

function TOOL:Animate( body, shapes )
	local dt = GetTimeStep()
	if GetBool("game.thirdperson") then
		SetToolHandPoseLocalTransform(
			Transform(Vec(0,0.05,0.55), QuatEuler(0,90,-45)),
			Transform(Vec(-0.175,0.1,0.075), QuatEuler(90,90,0))
		)
		local pitch = math.min(math.max(GetQuatEuler(GetPlayerEyeTransform().rot), -70), 0) / 70
		toolpos_thirdperson.pos[2] = -pitch / 8 - 0.7 -- avoid colliding with the playermodel
		toolpos_thirdperson.pos[3] = pitch / 4 - 0.5
		SetToolTransform( toolpos_thirdperson )
	else
		SetToolTransform( toolpos )
	end
	local armature = self.armature
	local nozzle_tr = self:GetBoneGlobalTransform("nozzle")

	--[[
		1 - nub2
		2 - nub1
		3 - nub0
		4 - door
		5 - barrel
		6 - body
	]]
	
	local t = math.max(0, GetTime() - anim_data.deploy)
	local activity = math.sin(GetTime())/4+0.75 + math.random() * 0.3
	if state.grabbing then
		activity = activity + math.sin(GetTime()*2)/4 + 0.35
		anim_data.static_energy = anim_data.static_energy * 0.9 + 0.1
	elseif state.grabbed then
		activity = activity + math.sin(GetTime()*5)/2 + 0.5
		anim_data.static_energy = anim_data.static_energy * 0.9 + 0.2
	else
		anim_data.static_energy = anim_data.static_energy * 0.9 + 0
	end
	local light1_intensity = activity
	local light2_intensity = activity

	if t < 2 then
		if not anim_data.slide then
			anim_data.slide = true
			PlaySound(self.sound_slide, nozzle_tr.pos, 0.5)
		end
		if t > 0.4 and not anim_data.power then
			anim_data.power = true
			PlaySound(self.sound_power, nozzle_tr.pos, 0.4)
		end
		local barrel_t = subtime(t, 0.3, 0.25)^2
		light1_intensity = activity * barrel_t
		armature:SetBoneTransform( "barrel", Transform( Vec( 0, 0, barrel_t * -0.15 ), QuatEuler( 0, 0, 0 ) ) )
		shapes[5]:SetEmissiveScale(light1_intensity * 0.25)

		local nub0_t = subtime(t, 0.8, 0.15)^2
		armature:SetBoneTransform( "nub0", Transform( Vec( 0, nub0_t * 0.025, 0 ), QuatEuler( 0, 0, 0 ) ) )
		shapes[3]:SetEmissiveScale((activity * nub0_t) * 0.25)

		local nub1_t = subtime(t, 1.0, 0.15)^2
		armature:SetBoneTransform( "nub1", Transform( Vec( 0, nub1_t * 0.025, 0 ), QuatEuler( 0, 0, 0 ) ) )
		shapes[2]:SetEmissiveScale((activity * nub1_t) * 0.25)

		local nub2_t = subtime(t, 1.2, 0.15)^2
		armature:SetBoneTransform( "nub2", Transform( Vec( 0, nub2_t * 0.025, 0 ), QuatEuler( 0, 0, 0 ) ) )
		shapes[1]:SetEmissiveScale((activity * nub2_t) * 0.25)

		if (nub0_t >= 0.8 and anim_data.snaps < 1)
			or (nub1_t >= 0.8 and anim_data.snaps < 2)
			or (nub2_t >= 0.8 and anim_data.snaps < 3) then
			anim_data.snaps = anim_data.snaps + 1
			PlaySound(self.sound_snap, nozzle_tr.pos, 0.1)
		end

		local body_t = subtime(t, 0.8, 0.6)
		light2_intensity = activity * body_t
		shapes[6]:SetEmissiveScale(body_t * 0.25)
	else
		shapes[6]:SetEmissiveScale(light1_intensity * 0.25)
		shapes[5]:SetEmissiveScale(light1_intensity * 0.25)
		shapes[3]:SetEmissiveScale(light1_intensity * 0.25)
		shapes[2]:SetEmissiveScale(light1_intensity * 0.25)
		shapes[1]:SetEmissiveScale(light1_intensity * 0.25)
	end

	if state.action == "throw" then
		anim_data.throw = GetTime()
		PlaySound(self.sound_hit, nozzle_tr.pos, 0.4)
		PlaySound(self.sound_power, nozzle_tr.pos, 0.05)
		anim_data.nub_active[1] = GetTime()+0.5
		anim_data.nub_active[2] = GetTime()+0.65
		anim_data.nub_active[3] = GetTime()+0.8
		anim_data.nub_snaps[1] = false
		anim_data.nub_snaps[2] = false
		anim_data.nub_snaps[3] = false
	elseif state.action == "freeze" then
		anim_data.nub_active[3] = GetTime()
		anim_data.nub_snaps[3] = false
		PlaySound(self.sound_snap, nozzle_tr.pos, 0.1)
	elseif state.action == "unfreeze" then
		anim_data.nub_active[2] = GetTime()
		anim_data.nub_snaps[2] = false
		PlaySound(self.sound_snap, nozzle_tr.pos, 0.1)
	end
	if state.rotating then
		if not anim_data.rotating then
			PlaySound(self.sound_snap, nozzle_tr.pos, 0.1)
			anim_data.rotating = true
		end
		anim_data.nub_active[1] = GetTime()
		anim_data.nub_snaps[1] = false
	elseif anim_data.rotating then
		anim_data.rotating = false
	end
	if anim_data.throw and anim_data.throw > GetTime() - 1 then
		local t = GetTime() - anim_data.throw
		local pr = (1-subtime(t, 0, 0.04))^2
		local barrel_t = pr + subtime(t, 0.25, 0.3)^2
		light1_intensity = activity * barrel_t
		armature:SetBoneTransform( "barrel", Transform( Vec( 0, 0, barrel_t * -0.15 ), QuatEuler( 0, 0, 0 ) ) )
		shapes[5]:SetEmissiveScale(light1_intensity * 0.25)
		if t >= 1 then
			anim_data.throw = nil
		end
	end
	for i = 1, 3 do
		if anim_data.nub_active[i] and anim_data.nub_active[i] > GetTime() - 1 then
			local nub_t = subtime(GetTime() - anim_data.nub_active[i], 0, 0.15)^2
			armature:SetBoneTransform( "nub" .. (i-1), Transform( Vec( 0, nub_t * 0.025, 0 ), QuatEuler( 0, 0, 0 ) ) )
			shapes[4 - i]:SetEmissiveScale((activity * nub_t) * 0.25)
			if nub_t >= 0.8 and not anim_data.nub_snaps[i] then
				anim_data.nub_snaps[i] = true
				PlaySound(self.sound_snap, nozzle_tr.pos, 0.1)
			end
			if nub_t >= 1 then
				anim_data.nub_active[i] = nil
			end
		end
	end

	if t >= 4 then
		PlayLoop(self.soundloop_static, nozzle_tr.pos, math.min(1, t-5)*0.4)
		if anim_data.static_energy > 0.01 then
			PlayLoop(self.soundloop_static_high, nozzle_tr.pos, anim_data.static_energy)
		end
	end

	local r, g, b = unpack( state.skin.config.color_light )
	local light1_pos = TransformToParentPoint(nozzle_tr, Vec(0,0,0.175))
	local light2_pos = TransformToParentPoint(nozzle_tr, Vec(0,0,0.6))
	PointLight( light1_pos, r, g, b, light1_intensity * 0.025 )
	PointLight( light2_pos, r, g, b, light2_intensity * 0.03 )


	if GetBool("game.thirdperson") then
		armature:SetBoneTransform("swivel", Transform(Vec(), QuatEuler(0, 0, 0)))
	else
		local b0 = self:GetBoneGlobalTransform("__FIXED_swivel")
		local pitch, yaw = GetQuatEuler(TransformToLocalTransform(GetPlayerCameraTransform(), b0).rot)
		local factor = 0.2 * (dt * 20)
		anim_data.pitch = anim_data.pitch * (1-factor) + pitch * factor
		anim_data.yaw = anim_data.yaw * (1-factor) + yaw * factor
		armature:SetBoneTransform("swivel", Transform(Vec(), QuatEuler(-anim_data.pitch*1.2, -anim_data.yaw*1.2, 0)))
	end
end

function TOOL:Deploy()
	anim_data.deploy = GetTime()
	anim_data.snaps = 0
	anim_data.slide = false
	anim_data.power = false
	anim_data.throw = nil
	anim_data.nub_active = {}
	anim_data.rotating = false
	anim_data.static_energy = 0
end


function TOOL:DrawBeam( source, target, object, r, g, b, energy )
	local options = { additive = true, turn = true, r = r * 2, g = g * 2, b = b * 2, width = 0.1 }
	if target == object then
		return visual.drawline( self.laser, source, target, options )
	end
	local prev = source
	for i = 1, 15 do
		local t = i / 16
		local t2 = t ^ 2
		local rng = 0.05 * energy * t
		local newpoint = VecAdd(
			source:Lerp( target, t2 ):Lerp( source:Lerp( object, t2 ), t2 ),
			Vec( (math.random()*2-1) * rng, (math.random()*2-1) * rng, (math.random()*2-1) * rng )
		)
		--DrawLine( prev, newpoint, r, g, b )
		visual.drawline( self.laser, prev, newpoint, options )
		prev = newpoint
	end
	visual.drawline( self.laser, prev, object, options )
	--DrawLine( prev, object, r, g, b )
end

local function effect_t( time, duration )
	if not time then
		return
	end
	local t = (GetTime() - time) / duration
	if t >= 0 and t <= 1 then
		return t
	end
end

function TOOL:Render()
	local freeze = effect_t( state.effect.freeze_time, 1 )
	if freeze then
		local body = state.effect.freeze_body
		body:DrawHighlight( .5 - freeze / 2 )
		body:DrawOutline( freeze, .5 + freeze / 2, 1, 1 - freeze )
	end
	local unfreeze = effect_t( state.effect.unfreeze_time, 1 )
	if unfreeze then
		local body = state.effect.unfreeze_body
		body:DrawHighlight( .5 - unfreeze / 2 )
		body:DrawOutline( 1, .5 + unfreeze / 2, unfreeze, 1 - unfreeze )
	end

	local r, g, b = unpack( state.skin.config.color_light )

	local camtr = PLAYER:GetCamera()
	local nozzletr = self:GetBoneGlobalTransform( "nozzle" )
	local nozzle = nozzletr.pos


	if not state.grabbed or not state.grabbed:IsValid() then
		if state.grabbing then
			local ray = camtr:Raycast( 750, -1 )
			if state.skin.config.beam then
				--self:DrawBeam( nozzle, nozzletr:ToGlobal( Vector( 0, 0, -1 ) ), self:GetBoneGlobalTransform("arm0_e").pos, r, g, b, 1 )
				--self:DrawBeam( nozzle, nozzletr:ToGlobal( Vector( 0, 0, -1 ) ), self:GetBoneGlobalTransform("arm1_e").pos, r, g, b, 1 )
				--self:DrawBeam( nozzle, nozzletr:ToGlobal( Vector( 0, 0, -1 ) ), self:GetBoneGlobalTransform("arm2_e").pos, r, g, b, 1 )

				self:DrawBeam( nozzle, nozzletr:ToGlobal( Vector( 0, 0, -ray.dist / 2 ) ), ray.hitpos, r, g, b, 0 )
			end
			if state.skin.config.tool then
				PointLight( nozzle, r, g, b, .1 )
			end
		end
		return
	end

	local bodytr = state.grabbed:GetTransform()
	local onbody = bodytr:ToGlobal( state.relative )
	local aimpos = camtr:ToGlobal( Vector( 0, 0, -state.dist ) )

	if state.skin.config.outline then
		state.grabbed:DrawOutline( r, g, b, 1 )
	end
	if state.skin.config.beam then
		self:DrawBeam( nozzle, nozzletr:ToGlobal( Vector( 0, 0, -state.dist / 2 ) ), onbody, r, g, b, 0 )
	end
	if state.skin.config.tool then
		PointLight( nozzle, r, g, b, .2 )
	end
end


TOOL.model = [[
<prefab version="1.5.3">
	<group name="instance=MOD/tool_skins/quilez/model.xml" pos="0.0 0.0 0.0" rot="0.0 0.0 0.0">
		<group name="swivel">
			<vox pos="-0.075 -0.175 0.2" rot="0.0 -90.0 0.0" file="MOD/vox/quilez.vox" object="mainbody" scale="0.5"/>
			<group name="barrel" pos="0.0 0.0 0.0" rot="0.0 0.0 0.0">
				<vox pos="-0.025 -0.075 -0.1" rot="0.0 -90.0 0.0" file="MOD/vox/quilez.vox" object="barrel" scale="0.5"/>
				<vox pos="-0.025 -0.075 0.2" rot="0.0 -90.0 0.0" file="MOD/vox/quilez.vox" object="door" scale="0.5"/>
				<location name="nozzle" pos="0.0 0.0 -0.275"/>
			</group>
			<group name="nub0" pos="0.0 0.1 0.175" rot="0.0 0.0 0.0">
				<vox pos="-0.025 -0.025 0.025" rot="0.0 0.0 0.0" file="MOD/vox/quilez.vox" object="nub0" scale="0.5"/>
			</group>
			<group name="nub1" pos="0.0 0.1 0.275" rot="0.0 0.0 0.0">
				<vox pos="-0.025 -0.025 0.025" rot="0.0 0.0 0.0" file="MOD/vox/quilez.vox" object="nub1" scale="0.5"/>
			</group>
			<group name="nub2" pos="0.0 0.1 0.375" rot="0.0 0.0 0.0">
				<vox pos="-0.025 -0.025 0.025" rot="0.0 0.0 0.0" file="MOD/vox/quilez.vox" object="nub2" scale="0.5"/>
			</group>
		</group>
	</group>
</prefab>
]]

RegisterToolUMF( "physgun", TOOL, state.bottom )