local outfit = fishing.GetOutfit("rod")

local SWEP = {Primary = {}, Secondary = {}}

SWEP.IsFishingEntity = true

SWEP.Category = "Fishing Mod"
SWEP.Spawnable = false
SWEP.Instructions = "To reel down, hold left mouse button\nTo reel up, hold right mouse button\nTo reel faster, hold shift\nTo reel slower, hold alt\nTo release bait, press e\nTo release catch, press r\nTo access the menu, press B"

SWEP.Author = "CapsAdmin"

SWEP.ClassName = "weapon_fishing"
SWEP.Base = "weapon_base"

SWEP.ViewModel = "models/weapons/v_pistol.mdl"
SWEP.WorldModel = "models/weapons/w_pistol.mdl"

SWEP.Primary.ClipSize = -1
SWEP.Primary.DefaultClip = -1
SWEP.Primary.Automatic = true
SWEP.Primary.Ammo = "none"

SWEP.Secondary.ClipSize	= -1
SWEP.Secondary.DefaultClip	= -1
SWEP.Secondary.Automatic = true
SWEP.Secondary.Ammo = "none"

if CLIENT then
	SWEP.PrintName = "Fishing Rod"
	SWEP.Slot = 3
	SWEP.SlotPos = 1
	SWEP.DrawAmmo = false
	SWEP.DrawCrosshair = false
else
	SWEP.Weight = 5
	SWEP.AutoSwitchTo = false
	SWEP.AutoSwitchFrom	= false
end

function SWEP:SetupDataTables()
	self:DTVar("Entity", 1, "hook")
	self:DTVar("Float", 1, "length")
	self:DTVar("Bool", 1, "snapped")
end

-- this will silent the gun
function SWEP:PrimaryAttack()
end

function SWEP:SecondaryAttack()
end


if SERVER then

	function SWEP:PrimaryAttack()
		if self.dt.length > 1 then
			self.dt.length = math.min(self.dt.length + FrameTime() * 200, fishing.GetStats(self.Owner).string_length or 500)
		end
	end

	function SWEP:SecondaryAttack()
		self.dt.length = math.max(self.dt.length - FrameTime() * 200, 1)
	end

	function SWEP:CreateHook()
		SafeRemoveEntity(self.dt.hook)

		local ent = ents.Create("prop_physics")

		-- vars (move this to fishing.CreateHook() ?)
		ent.IsFishingEntity = true
		ent.IsFishingHook = true
		ent.rod = self
		ent.attached = {Bait = NULL, Fish = NULL, Unknown = NULL}

		ent:SetModel("models/props_junk/meathook001a.mdl")
		ent:SetOwner(self.Owner)
		ent:SetPos(self.Owner:EyePos() + self.Owner:GetForward() * 180)
		ent:Spawn()

		self.hook_time = CurTime() + 1

		ent:SetModelScale(0.3, 0)

		-- physics
		local phys = ent:GetPhysicsObject()
		phys:SetDamping(0,0)
		phys:SetMass(10)
		ent:SetCollisionGroup(COLLISION_GROUP_DEBRIS)

		self.dt.hook = ent
	end

	function SWEP:RemoveHook()
		SafeRemoveEntity(self.dt.hook)
	end

	function SWEP:Think()
		local hook = self.dt.hook or NULL
		local owner = self.Owner

		if owner:IsValid() and not self.deploy_hack then
			self:Deploy()
			self.deploy_hack = true
		end

		if hook:IsValid() and not self.dt.snapped then
			local phys = hook:GetPhysicsObject()
			local length = fishing.GetStats(self.Owner).rod_length or 1
			
			local pos = self.Owner:EyePos() + self.Owner:GetForward() * 140 * length
			local dist = phys:GetPos():Distance(pos)
			local rope_length = math.Clamp(self.dt.length / 2, 1, 80)

			do -- hook behavior
				for key, ent in pairs(ents.FindInSphere(hook:GetPos(), 10)) do
					if hook.attached.Bait:IsValid() then
						if ent.IsFishingFish and not hook.attached.Fish:IsValid() then
							self:AttachToHook(ent)
							ent:SetPlayerOwner(owner)
						end
					else
						if not ent.fishing_last_hooked or ent.fishing_last_hooked < CurTime() then
							if ent.IsFishingBait and not hook.attached.Fish:IsValid() then
								self:AttachToHook(ent)
							end
						end
					end
				end

				if self.released_from_start and not (self.Owner:KeyDown(IN_ATTACK) or self.dt.length <= 1) and phys:GetVelocity():Length() > 30 then
					self.released_from_start = false
				end

				if self.Owner:KeyDown(IN_ATTACK) and self.dt.length <= 1 or self.released_from_start then
					self.dt.length = math.min(hook:GetPos():Distance(pos) * 2, fishing.GetStats(self.Owner).string_length or 500)
					self.released_from_start = true
				end

				local tension = phys:GetPos():Distance(pos) / self.dt.length

				if self.dt.length < 40 then
					phys:AddVelocity((pos - phys:GetPos()))
					phys:AddVelocity(phys:GetVelocity() * -0.05)

					local phys = self.dt.hook:GetPhysicsObject()
					phys:AddVelocity((pos - phys:GetPos()))
					phys:AddVelocity(phys:GetVelocity() * -0.05)

					tension = tension / 100
				else

					if pos:Distance(phys:GetPos()) < 50 then
						phys:AddVelocity(phys:GetVelocity() * -0.01)
					end

					if false and
						hook:GetVelocity():Length() < 10 and
						bit.band(util.PointContents(hook:GetPos() * - 10), CONTENTS_WATER) ~= CONTENTS_WATER
					then
						local pos = hook:GetPos() + self.Owner:GetUp() * rope_length * 2
						phys:AddVelocity((pos - phys:GetPos()))
						phys:AddVelocity(phys:GetVelocity() * -0.5)
						return
					end

					-- constrain it in a sphere
					local dir = pos - phys:GetPos() -- direction to the rod
					local len = dir:Length() / self.dt.length -- normalize the length

					len = len ^ 20 -- make it so length isn't linear

					dir:Normalize() -- normalize the dir so we can scale it properly back up

					phys:ApplyForceOffset(dir * (len * self.dt.length), phys:GetPos() + hook:GetUp() * 50)

					if self.Owner:IsPlayer() and tension > 1 then
						self.Owner:SetVelocity((phys:GetPos() - pos):GetNormalized() * tension * 10)
					end


					-- add some damping

					phys:AddAngleVelocity(phys:GetAngleVelocity() * -0.1)
					phys:AddVelocity(phys:GetVelocity() * -0.01)
				end

				if tension > (fishing.GetStats(owner).string_max_tension or 4) and self.hook_time < CurTime() then
					self.Owner:EmitSound("npc/ichthyosaur/snap.wav", 100, 150)
					self.dt.snapped = true
				end

				if self.Owner:WaterLevel() >= 3 or self.Owner:GetMoveType() == MOVETYPE_NOCLIP then
					for key, wep in RandomPairs(self.Owner:GetWeapons()) do
						if wep ~= self then
							self.Owner:SelectWeapon(wep:GetClass())
							return
						end
					end

					self:Remove() -- no weapons? remove the rod
				end
			end
		end
	end

	util.AddNetworkString("weapon_fishing")

	function SWEP:Deploy()
		self.dt.snapped = false
		self.dt.length = 1

		net.Start("weapon_fishing")
			net.WriteEntity(self)
			net.WriteString("AttachOutfit")
		net.Broadcast()

		self:CreateHook()

		return true
	end

	function SWEP:Holster()
		net.Start("weapon_fishing")
			net.WriteEntity(self)
			net.WriteString("RemoveOutfit")
		net.Broadcast()

		self:DetatchFromHook()
		self:RemoveHook()

		return true
	end

	SWEP.OnDrop = SWEP.Holster
	SWEP.OnRemove = SWEP.Holster

	function SWEP:AttachToHook(ent)
		if ent.IsFishingBait or ent.IsFishingFish then
			ent:Attach(self.dt.hook)
		else
			ent:SetPos(self.dt.hook:GetPos())
			ent:SetParent(self.dt.hook)
			self.dt.hook.attached.Unknown = ent
		end

		fishing.CallEvent(ent.IsFishingFish and "FishBite" or "BaitHooked", self.Owner, ent)
	end

	function SWEP:DetatchFromHook()
		if not self.dt.hook:IsValid() then return end

		for _, ent in pairs(self.dt.hook.attached) do
			if ent:IsValid() then
				if ent.IsFishingBait or ent.IsFishingFish then
					ent:Detach(self.dt.hook)
				else
					ent:SetParent()
					ent:SetPos(self.dt.hook:GetPos())
					self.dt.hook.attached.Unknown = NULL
					ent:GetPhysicsObject():SetVelocity(self.dt.hook:GetVelocity())
				end

				self.dt.hook:EmitSound("weapons/slam/mine_mode.wav", 100, math.random(90,110))

				ent.fishing_last_hooked = CurTime() + 2
			end
		end
	end

	function SWEP:Reload()

	end

	function SWEP:AttachBait(class)
		local bait = fishing.CreateBait(class, self.dt.hook:GetPos())
		self:AttachToHook(bait)
	end
end

if CLIENT then

	net.Receive("weapon_fishing", function()
		local ent = net.ReadEntity()
		local event = net.ReadString()

		if ent:IsValid() and ent[event] then
			ent[event](ent)
		end
	end)

	local mat = Material("trails/physbeam")
	local white = Color(255,255,255,255)
	local width = 4
	local length = 1
	local res = 32

	local hover_mat = Material("sprites/light_glow02_add")
	local hover_col = Color( 70, 180, 255, 255 )

	function SWEP:DrawBeams()
		if not self.dt then return end -- grr
		if self.dt.snapped then return end

		local ent = self.dt.hook

		local apos = self:GetPACPartPosAng(outfit, "first attach point")
		local bpos = self:GetPACPartPosAng(outfit, "second attach point")
		local cpos = ent:IsValid() and (ent:LocalToWorld(Vector(0,1.2,6))) or bpos

		if not apos then return end --- something went wrong so dont try to draw beams to prevent crashing the whole game

		if ent:IsValid() then
			local pos = self.dt.hook:GetPos()

			if self.dt.length < 30 then

				render.SetMaterial(hover_mat)

				render.DrawSprite(pos, 16, 16, hover_col)
				render.DrawSprite(pos, 32, 32, hover_col)
				render.DrawSprite(pos, 64, 64, hover_col)
			end
		end

		length = fishing.GetStats(self.Owner).rod_length or 1
		local tip = self:FindPACPart(outfit, "reel tip")
		if tip:IsValid() then
			tip:SetPosition(Vector(0,0,110 * length))
		end
		local pole = self:FindPACPart(outfit, "signpole001")
		if pole:IsValid() then
			pole:SetScale(Vector(1,1,1 * length))
		end
		
		width = fishing.GetStats(self.Owner).rod_width or 1
		
		local bend_amount = (self.dt.length / 3000) * 100
		local dir = self.Owner:GetAngles():Up()
		local t = -self.dt.length or 0
		local color = Vector(self.Owner:GetInfo("cl_weaponcolor")) * 255
		color = Color(color.r, color.g, color.b, 255)

		render.SetMaterial(mat)

		render.StartBeam(3 + res)
			render.AddBeam(apos, 0, t+0, color)
			render.AddBeam(LerpVector(0.1, apos, bpos), width, t+0, color)
			render.AddBeam(bpos, width, t+1, color)

			for i = 1, res do
				local frac = i / res

				render.AddBeam(
					i == 1 and bpos or i == res and cpos or LerpVector(frac, bpos, cpos) +
					(dir * (math.sin(frac * math.pi)) * bend_amount)
					,

					width,
					t + frac + bend_amount,
					color
				)
			end


		render.EndBeam()
	end

	do -- sounds
		function SWEP:InitializeSounds()
			self.sound_rope = CreateSound(self, "ambient/machines/combine_shield_touch_loop1.wav")
			self.sound_rope:Play()
			self.sound_rope:ChangePitch(0, 0)

			self.sound_reel = CreateSound(self, "buttons/combine_button_locked.wav")
			self.sound_reel:Play()
			self.sound_reel:ChangePitch(0, 0)
			self.last_length = 0

			self.sound_dist = CreateSound(self.Owner, "ambient/energy/electric_loop.wav")
			self.sound_dist:Play()
			self.sound_dist:ChangePitch(0, 0)
		end

		function SWEP:RemoveSounds()
			if not self.sound_reel then return end

			self.sound_reel:Stop()
			self.sound_rope:Stop()
			self.sound_dist:Stop()
		end

		function SWEP:CalcSounds()
			if not self.dt then return end -- grr
			if not self.sound_reel then return end
			if not self.dt.hook:IsValid() then return end
			if self.dt.snapped then self:RemoveSounds() return end

			local delta = self.dt.length - self.last_length

			local velocity_length = self.dt.hook:GetVelocity():Length() or 0
			local pitch = velocity_length / 10 - 0.1
			local volume = velocity_length / 1000 - 0.1
			local reel_velocity = self.dt.length - self.last_length

			local on = (delta ~= 0) and 1 or 0
			self.sound_reel:ChangePitch(math.Clamp(math.abs(50+delta*10),80,200), 0)
			self.sound_reel:ChangeVolume(on, 0)

			self.sound_rope:ChangePitch(math.Clamp(pitch+50, 50, 255), 0)
			self.sound_rope:ChangeVolume(math.Clamp(volume, 0, 1), 0)

			local frac = (-(self.dt.length / 3000) + 1)
			self.sound_dist:ChangePitch(frac * 255, 0)
			self.sound_dist:ChangeVolume(on*0.6*frac, 0)


			self.last_length = self.dt.length
		end
	end

	function SWEP:Think()
		self:CalcSounds()
		self:NextThink(CurTime())
		return true
	end

	function SWEP:AttachOutfit()
		self:AttachPACPart(outfit, self.Owner)

		self:SetShowPACPartsInEditor(true)

		self:InitializeSounds()

		local part = self:FindPACPart(outfit, "beam draw")

		local screen = self:FindPACPart(outfit, "screen")
		self.screen_part = screen

		screen:SetTextureFilter(TEXFILTER.POINT)

		part.OnDraw = function()
			screen.Materialm = fishing.RTMaterial
			self:DrawBeams()
		end
	end

	function SWEP:RemoveOutfit()
		self:RemoveSounds()
		self:RemovePACPart(outfit)
	end

	SWEP.OnRemove = SWEP.RemoveOutfit

	pac.SetupSWEP(SWEP)
end

weapons.Register(SWEP, SWEP.ClassName, true)