--[[
#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",
	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_body = { "vector", { .7, .7, .7 } },
			color_secondary = { "vector", { .423, .423, .423 } },
			color_lights = { "vector", { 0, .713, .81 } },
			color_core = { "vector", { .709, .53, .454 } },
		},
	},
} )

local isFloppy = GetBoolParam( "floppy", false )

local TOOL = {}

TOOL.printname = isFloppy and "Floppy Gun" or "Physics Gun"

function TOOL:Initialize()
	self.motor_fast = LoadLoop( "MOD/sound/motor_fast.ogg" )
	self.motor_med = LoadLoop( "MOD/sound/motor_med.ogg" )
	self.laser = LoadSprite("gfx/laser.png")
end

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
		setcolor( 141, state.skin.config.color_body )
		setcolor( 145, state.skin.config.color_secondary )
		setcolor( 8, state.skin.config.color_lights )
		setcolor( 136, state.skin.config.color_core )
	end
	local palette = CreatePalette( entries )
	for i = 1, #shapes do
		SetTag(shapes[i], "ground")
		CopyShapePalette( palette, shapes[i] )
	end
	Delete( palette )
end

local function clamp( n, min, max )
	return math.min( math.max( n, min ), max )
end
local function steps( x, bounce )
	return x - math.abs( math.sin( x * math.pi ) / (math.pi - bounce) )
end
local function approach( c, t, m, mn )
	return c + math.max( math.min( t - c, m ), mn or -m )
end

local toolpos = Transform( Vec( 8.5 / 20, -7.5 / 20, -6 / 20 - 0.25 + (state.skin.config.tool and 0 or 2) ),
                           QuatEuler( 0, 0, 0 ) )

local toolpos_thirdperson = Transform(
	Vec( 0.2, -0.5, -0.5 ),
	QuatEuler( 0, 0, 0 )
)
function TOOL:Update()
	SetToolTransform( toolpos )
end
local deploy_time = 0
local anim_data = { core = { { 0, 0 }, { 0, 0 }, { 0, 0 } }, arms = 0, rot = { 0, 0, 0 } }
local speedmod = { 1, 1.1, 1.05 }
function TOOL:Animate( body, shapes )
	
	if GetBool("game.thirdperson") then
		SetToolHandPoseLocalTransform(
			Transform(Vec(0,0,0.25), QuatEuler(0,90,0)),
			Transform(Vec(-0.25,0,0.05), QuatEuler(85,86,0 + (state.grabbing and 20 or 0)))
		)
		SetToolTransform( toolpos_thirdperson )
	else
		SetToolTransform( toolpos )
	end

	local armature = self.armature
	local dt = GetTimeStep()

	local arm_base_offset = 0
	local arm_tip_offset = 0
	if deploy_time < 3 then
		local stepf = steps( deploy_time * 2.7, 1.5 )

		local a, b = clamp( stepf, 0, 1 ), clamp( stepf, 1, 2 ) - 1
		local deploy = steps( clamp( 1 - deploy_time * 3, 0, 1 ), 0.8 )

		armature:SetBoneTransform( "root", Transform( Vec( 0, deploy * -0.2, 0 ), QuatEuler( deploy * -20, 0, 0 ) ) )

		armature:SetBoneTransform( "spine1", Transform( Vec( 0, 0, 0.1 - a / 10 ), QuatEuler( 0, 0, 0 ) ) )
		armature:SetBoneTransform( "spine2", Transform( Vec( 0, 0, 0.1 - b / 10 ), QuatEuler( 0, 0, 0 ) ) )

		arm_base_offset = 50 * a - 50
		arm_tip_offset = 90 * b - 90
	end
	local ang = state.grabbing and -20 or state.grabbed and -90 or -40
	local rand = state.grabbing and 6 or state.grabbed and 4 or 0
	anim_data.arms = approach( anim_data.arms, ang, 720 * dt )
	local a1, a2, a3 = anim_data.arms + (math.random() - 0.5) * rand, anim_data.arms + (math.random() - 0.5) * rand,
	                   anim_data.arms + (math.random() - 0.5) * rand

	if state.grabbing then
		anim_data.rot[2] = anim_data.rot[2] + dt * 200
		anim_data.rot[3] = GetTime() + 1
	elseif state.grabbed then
		anim_data.rot[2] = anim_data.rot[2] - dt * 100
		anim_data.rot[3] = GetTime() + 1
	elseif anim_data.rot[3] < GetTime() then
		local rt = (1 - math.random() ^ 3)
		anim_data.rot[3] = GetTime() + rt * 2
		anim_data.rot[2] = anim_data.rot[2] + (1 - rt) * (math.random() * 720 - 360)
	end
	if math.abs( anim_data.rot[1] - anim_data.rot[2] ) > 0.001 and state.skin.config.sound then
		PlayLoop( state.grabbed and self.motor_med or self.motor_fast, body:GetTransform().pos,
		          state.grabbed and 0.3 or state.grabbing and 0.2 or 1 )
	end
	anim_data.rot[1] = approach( anim_data.rot[1], anim_data.rot[2], 720 * dt )
	armature:SetBoneTransform( "arms_rot", Transform( Vec(), QuatEuler( 0, 0, anim_data.rot[1] ) ) )

	armature:SetBoneTransform( "arm0_base", Transform( Vec(), QuatEuler( a1 + arm_base_offset, 0, 0 ) ) )
	armature:SetBoneTransform( "arm0_tip", Transform( Vec(), QuatEuler( a2 + arm_tip_offset, 0, 0 ) ) )

	armature:SetBoneTransform( "arm1_base", Transform( Vec(), QuatEuler( a2 + arm_base_offset, 0, 0 ) ) )
	armature:SetBoneTransform( "arm1_tip", Transform( Vec(), QuatEuler( a3 + arm_tip_offset, 0, 0 ) ) )

	armature:SetBoneTransform( "arm2_base", Transform( Vec(), QuatEuler( a3 + arm_base_offset, 0, 0 ) ) )
	armature:SetBoneTransform( "arm2_tip", Transform( Vec(), QuatEuler( a1 + arm_tip_offset, 0, 0 ) ) )

	local warmth = math.min( deploy_time, 10 ) / 10
	local speed = state.grabbing and 360 or state.grabbed and 520 or 120
	for i = 1, 3 do
		if deploy_time > i / 2 then
			anim_data.core[i][1] = approach( anim_data.core[i][1], speed * speedmod[i], warmth * 500 * dt, -200 * dt )
			anim_data.core[i][2] = anim_data.core[i][2] + anim_data.core[i][1] * dt * ((i % 2) * 2 - 1)
		end
	end
	armature:SetBoneTransform( "core0", Transform( Vec( 0, 0, 0 ), QuatEuler( 0, 0, anim_data.core[1][2] ) ) )
	armature:SetBoneTransform( "core1", Transform( Vec( 0, 0, 0 ), QuatEuler( 0, 0, anim_data.core[2][2] ) ) )
	armature:SetBoneTransform( "core2", Transform( Vec( 0, 0, 0 ), QuatEuler( 0, 0, anim_data.core[3][2] ) ) )

	local lit = (anim_data.core[1][1] / 520) ^ 2
	for i = 1, #shapes do
		shapes[i]:SetEmissiveScale( lit )
	end

	deploy_time = deploy_time + 1.5 * dt
end

function TOOL:Deploy()
	deploy_time = 0
	anim_data = { core = { { 0, 0 }, { 0, 0 }, { 0, 0 } }, arms = 0, rot = { 0, 0, GetTime() + 1.5 } }
	if isFloppy then
		local wobble = 2
		local wobble_constraint = { gravity = 0.3 }
		self.armature:SetBoneJiggle( "spine0", wobble, wobble_constraint )
		self.armature:SetBoneJiggle( "spine1", wobble, wobble_constraint )
		self.armature:SetBoneJiggle( "spine2", wobble, wobble_constraint )
		self.armature:SetBoneJiggle( "spine3", wobble, wobble_constraint )
		self.armature:SetBoneJiggle( "arm0_base", wobble * 3, wobble_constraint )
		self.armature:SetBoneJiggle( "arm0_tip", wobble * 5, wobble_constraint )
		self.armature:SetBoneJiggle( "arm1_base", wobble * 3, wobble_constraint )
		self.armature:SetBoneJiggle( "arm1_tip", wobble * 5, wobble_constraint )
		self.armature:SetBoneJiggle( "arm2_base", wobble * 3, wobble_constraint )
		self.armature:SetBoneJiggle( "arm2_tip", wobble * 5, wobble_constraint )
	end
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.03 }
	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:Tick()
	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
	local lit = (anim_data.core[1][1] / 700) ^ 2
	if state.skin.config.random_beam then
		r, g, b = math.random(), math.random(), math.random()
	else
		r, g, b = unpack( state.skin.config.color_lights )
		r, g, b = r + lit, g + lit, b + lit
	end
	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, lit )
				self:DrawBeam( nozzle, nozzletr:ToGlobal( Vector( 0, 0, -1 ) ), self:GetBoneGlobalTransform("arm1_e").pos, r, g, b, lit )
				self:DrawBeam( nozzle, nozzletr:ToGlobal( Vector( 0, 0, -1 ) ), self:GetBoneGlobalTransform("arm2_e").pos, r, g, b, lit )

				self:DrawBeam( nozzle, nozzletr:ToGlobal( Vector( 0, 0, -ray.dist / 2 ) ), ray.hitpos, r, g, b, lit*math.min(ray.dist,10) )
			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, lit )
	end
	if state.skin.config.tool then
		PointLight( nozzle, r, g, b, .2 )
	end
end

TOOL.model = [[
<prefab version="0.9.2">
	<group name="instance=MOD/physgun.xml" pos="-3.5 0.9 9.1" rot="0.0 0.0 0.0">
		<vox pos="-0.125 -0.125 0.125" file="MOD/vox/physgun.vox" object="body" scale="0.5"/>
		<vox tags="invisible collider" pos="-0.125 -0.125 -0.175" file="MOD/vox/physgun.vox" object="collider" scale="0.5"/>
		<group name="spine0" pos="0.0 0.0 -0.025">
			<group name="core0" pos="0.0 0.0 0.0" rot="0.0 0.0 0.0">
				<vox pos="-0.025 -0.125 -0.05" rot="0.0 0.0 0.0" file="MOD/vox/physgun.vox" object="core_0" scale="0.5"/>
			</group>
			<group name="spine1" pos="0.0 0.0 -0.1">
				<group name="core1" pos="0.0 0.0 0.0" rot="0.0 0.0 0.0">
					<vox pos="-0.025 -0.125 -0.05" rot="0.0 0.0 0.0" file="MOD/vox/physgun.vox" object="core_1" scale="0.5"/>
				</group>
				<group name="spine2" pos="0.0 0.0 -0.1">
					<group name="core2" pos="0.0 0.0 0.0" rot="0.0 0.0 0.0">
						<vox pos="-0.025 -0.125 -0.05" rot="0.0 0.0 0.0" file="MOD/vox/physgun.vox" object="core_2" scale="0.5"/>
					</group>
					<group name="spine3" pos="0.0 0.0 -0.1" rot="0.0 0.0 0.0">
						<vox pos="-0.025 -0.125 -0.05" rot="0.0 0.0 0.0" file="MOD/vox/physgun.vox" object="cannon" scale="0.5"/>
						<location name="nozzle" pos="0.0 0.0 -0.15" rot="0.0 0.0 0.0"/>
						<group name="arms_rot" pos="0.0 0.0 -0.05" rot="0.0 0.0 0.0">
							<group name="arm0_base" pos="0.0 0.1 0.0" rot="90.0 0.0 0.0">
								<vox pos="-0.025 0.025 0.05" rot="-90.0 0.0 0.0" file="MOD/vox/physgun.vox" object="arm_00" scale="0.5"/>
								<group name="arm0_tip" pos="0.0 0.0 -0.2" rot="0.0 0.0 0.0">
									<vox pos="-0.025 0.025 0.0" rot="-90.0 0.0 0.0" file="MOD/vox/physgun.vox" object="arm_01" scale="0.5"/>
									<location name="arm0_e" pos="0 0 -0.075"/>
								</group>
							</group>
							<group name="arm1_base" pos="-0.087 -0.05 0.0" rot="-90.0 180.0 60.0">
								<vox pos="-0.025 0.025 0.05" rot="-90.0 0.0 0.0" file="MOD/vox/physgun.vox" object="arm_10" scale="0.5"/>
								<group name="arm1_tip" pos="0.0 0.0 -0.2" rot="0.0 0.0 0.0">
									<vox pos="-0.025 0.025 0.0" rot="-90.0 0.0 0.0" file="MOD/vox/physgun.vox" object="arm_11" scale="0.5"/>
									<location name="arm1_e" pos="0 0 -0.075"/>
								</group>
							</group>
							<group name="arm2_base" pos="0.087 -0.05 0.0" rot="-90.0 -180.0 -60.0">
								<vox pos="-0.025 0.025 0.05" rot="-90.0 0.0 0.0" file="MOD/vox/physgun.vox" object="arm_20" scale="0.5"/>
								<group name="arm2_tip" pos="0.0 0.0 -0.2" rot="0.0 0.0 0.0">
									<vox pos="-0.025 0.025 0.0" rot="-90.0 0.0 0.0" file="MOD/vox/physgun.vox" object="arm_21" scale="0.5"/>
									<location name="arm2_e" pos="0 0 -0.075"/>
								</group>
							</group>
						</group>
					</group>
				</group>
			</group>
		</group>
	</group>
</prefab>
]]

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