local medibeams = {}

function medigunTick()
	for medigunhandle, botentity in pairs(medibeams) do
		local medigun = Entity(medigunhandle)
		local player = medigun.m_hOwner
		local isValid = true
		
		if player.m_bUsingActionSlot == 1 and medigun:GetAttributeValue("canteen specialist") then
		--if player presses h and has can spec while healing a bot, remove cond on bot
			botentity:RemoveCond(TF_COND_INVULNERABLE_USER_BUFF)
			botentity:RemoveCond(TF_COND_CRITBOOSTED_USER_BUFF)
		end
		
		if not botentity:IsAlive() then --bot was killed
			ResetMedigun(medigun, medigunhandle)
			player:SetAttributeValue("no_attack", 1)
			timer.Simple(.1, function()
				player:SetAttributeValue("no_attack", nil)
			end)
			isValid = false
		elseif medigun.m_bAttacking == 0 or medigun.m_bHolstered == 1 or player:InCond(TF_COND_TAUNTING) then
			ResetMedigun(medigun, medigunhandle)
			isValid = false
			--may want some way to check m_bHealing still
		elseif medigun.m_hHealingTarget ~= botentity then
			--if medigun.m_hHealingTarget then
			--	print(player:GetPlayerName() .. " patient mismatch, stopping " .. TickCount())
			--end
			
			medibeams[medigunhandle] = nil
			if timers[medigunhandle] then
				timer.Stop(timers[medigunhandle])
				timers[medigunhandle] = nil
			end
			isValid = false
		end
		
		if isValid then
			medigun.m_hHealingTarget = botentity --keep beam force attached
		end
	end
end

--removes vaccinator uber from bots
local chargeDeployedCallback = AddEventCallback("player_chargedeployed", function(eventTable)
	local medic = ents.GetPlayerByUserId(eventTable.userid)
	local target = ents.GetPlayerByUserId(eventTable.targetid)
	local medigun = medic:GetPlayerItemBySlot(1)
	
	if medigun:GetItemName() == "The Vaccinator" and target.m_iTeamNum == 3 then
		timer.Simple(.1, function()
			target:RemoveCond(TF_COND_MEDIGUN_UBER_BULLET_RESIST + medigun.m_nChargeResistType)
		end)
	end
end)

function BoughtMedibeam(damage, activator)
	local connectTrace = {
		start = activator, -- Start position vector. Can also be set to entity, in this case the trace will start from entity eyes position
		endpos = nil, -- End position vector. If nil, the trace will be fired in `angles` direction with `distance` length
		distance = 450, -- Used if endpos is nil
		angles = Vector(0,0,0), -- Used if endpos is nil
		mask = MASK_SOLID, -- Solid type mask, see MASK_* globals
		collisiongroup = TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT, -- Pretend the trace to be fired by an entity belonging to this group. See COLLISION_GROUP_* globals
		mins = Vector(0,0,0), -- Extends the size of the trace in negative direction
		maxs = Vector(0,0,0), -- Extends the size of the trace in positive direction
		filter = filterEnts -- Entity to ignore. Can be a single entity, table of entities, or a function with a single entity parameter
	}

	local handle = activator:GetHandleIndex()
	local medigun = activator:GetPlayerItemBySlot(1)
	local medigunhandle = medigun:GetHandleIndex()
	local isQuickFix = false
	local name = activator:GetPlayerName()
	local DAMAGE_PER_HIT = damage --30

	if medigun:GetItemName() == "The Quick-Fix" then
		isQuickFix = true
	end	
		
	--print(activator:GetPlayerName() .. " bought")
	
	--if player disconnects or switches weapons 
	medigun:AddCallback(ON_REMOVE, function(entity)
		--print("removed")
		RemoveMedibeamCallbacks(activator)
		medibeams[medigunhandle] = nil
		if timers[medigunhandle] then
			timer.Stop(timers[medigunhandle])
			timers[medigunhandle] = nil
		end
		if timers[name] then
			timer.Stop(timers[name])
			timers[name] = nil
		end
	end)
	
	activator:AddCallback(ON_KEY_PRESSED, function(_, key)
		if medigun.m_bHolstered == 0 and key == IN_ATTACK then
			--print(activator:GetPlayerName() .. " " .. TickCount() .. " pressed")
			local targetedEntity = 0
			
			if timers[medigunhandle] then --reset the damage timer if it was already ticking
				timer.Stop(timers[medigunhandle])
				timers[medigunhandle] = nil
			end
			
			if medigun.m_hHealingTarget and medigun.m_hHealingTarget.m_iTeamNum == 2 then
				--if real player, let game do its thing
				medibeams[medigunhandle] = nil
			else
				targetedEntity = EyeTrace(activator, connectTrace) --initial trace on click
			
				if targetedEntity and targetedEntity:IsPlayer() and targetedEntity.m_iTeamNum == 3 then
					ConnectMedigun(activator, isQuickFix, DAMAGE_PER_HIT, medigun, medigunhandle, targetedEntity)	
				else --if trace doesn't hit, start hold timer
				
					if not timers[name] then
						timers[name] = timer.Create(.014, function() --run a trace every .1s until we hit something or key is released
							--print("hold timer")
							if medigun.m_bHolstered == 0 and activator.m_bRageDraining == 0 
								and not activator:InCond(TF_COND_TAUNTING) then 	
								
								if medigun.m_hHealingTarget and medigun.m_hHealingTarget.m_iTeamNum == 2 then --healing a friendly
									--print(activator:GetPlayerName() .. " " .. TickCount() .. " started healing, stopping tick")
									timer.Stop(timers[name])
									timers[name] = nil
									medibeams[medigunhandle] = nil
								else 
									targetedEntity = EyeTrace(activator, connectTrace)
								
									if targetedEntity and targetedEntity:IsPlayer() and targetedEntity.m_iTeamNum == 3 then 
										--hit a blu bot
										--print(activator:GetPlayerName() .. " " .. TickCount() .. " started damaging, stopping tick")
										timer.Stop(timers[name])
										timers[name] = nil
										ConnectMedigun(activator, isQuickFix, DAMAGE_PER_HIT, medigun, medigunhandle, targetedEntity)
									end
								end
							else --if shield is draining or medigun is holstered, stop looking for a target
								--print(activator:GetPlayerName() .. " " .. TickCount() .. " released, stopping")
								timer.Stop(timers[name])
								timers[name] = nil
							end
						end, 0)
					end
					
				end
			end
		end
		
		if key == IN_RELOAD and medigun.m_bHolstered == 0 then
			if medigun:GetItemName() == "The Vaccinator" and medibeams[medigunhandle] then
			--if vacc, remove the passive effect on bot
			--chargeresisttype is 0-2
				medibeams[medigunhandle]:RemoveCond(TF_COND_MEDIGUN_SMALL_BULLET_RESIST + medigun.m_nChargeResistType)
			end
		end
	end)
	
	activator:AddCallback(ON_KEY_RELEASED, function(_, key)
		if key == IN_ATTACK then
			if timers[name] then --if hold timer was still running, stop it
				timer.Stop(timers[name])
				timers[name] = nil
			end
		end
	end)
end

function EyeTrace(player, connectTrace, targetedEntity) --trace from player's eyes to something in range 
	local tracetable = {}
	local filterEnts = ents.FindAllByClass("entity_medigun_shield") 
	--table.insert(filterEnts, player) --need to filter out player if using filter
	
	local function getEyeAngles(player) --from royal, gets accurate eye angles
		local pitch = player["m_angEyeAngles[0]"]
		local yaw = player["m_angEyeAngles[1]"]

		return Vector(pitch, yaw, 0)
	end
	
	if targetedEntity then
		for _, p in pairs(ents.GetAllPlayers()) do --if we have a target already and checking connection to it
			if p ~= targetedEntity then --then filter out every player but the targeted bot
				table.insert(filterEnts, p)
			end
		end
		connectTrace.endpos = targetedEntity:GetAbsOrigin() + Vector(0, 0, 42)
		--42 is approx center for bounding, might have to do a check for giants
	else
		table.insert(filterEnts, player) --need to filter out player if using filter
		connectTrace.angles = getEyeAngles(player)
	end
	
	connectTrace.filter = filterEnts
	
	if not util.IsLagCompensationActive() then
		util.StartLagCompensation(player)
		tracetable = util.Trace(connectTrace)
		util.FinishLagCompensation(player)
	end
	
	return tracetable.Entity
end

function ConnectMedigun(player, isQuickFix, damage, medigun, medigunhandle, targetedEntity)
	local healrate = 1 + medigun:GetAttributeValueByClass("healing_mastery", 0) * .25
	local damageInfo = {
		Attacker = player,
		Inflictor = nil,
		Weapon = medigun,
		Damage = damage * healrate,
		DamageType = DMG_ENERGYBEAM,
		DamageCustom = TF_DMG_CUSTOM_MERASMUS_ZAP,
		DamagePosition = targetorigin,
		DamageForce = Vector(0, 0, 0),
		ReportedPosition = targetorigin
	}
	local ATTRIBUTENAME = "mod see enemy health"
	local barrierCounter = 0
	if isQuickFix then --m a g i c n u m b e r s
		healrate = healrate * 1.4
	end

	medigun.m_hLastHealingTarget = nil --no cross team flashing
	medigun.m_hHealingTarget = targetedEntity
	medibeams[medigunhandle] = targetedEntity

	timers[medigunhandle] = timer.Create(.25, function()
		local playerorigin = player:GetAbsOrigin()
		local targetorigin = targetedEntity:GetAbsOrigin()
		local dist = playerorigin:Distance(targetorigin)
		
		if dist < 540 and player.m_bRageDraining == 0 then --only damage if player is in range + not shielding
			local connectTrace = {
				start = player, -- Start position vector. Can also be set to entity, in this case the trace will start from entity eyes position
				endpos = nil, -- End position vector. If nil, the trace will be fired in `angles` direction with `distance` length
				distance = 540, -- Used if endpos is nil
				angles = Vector(0,0,0), -- Used if endpos is nil
				mask = MASK_SHOT, -- Solid type mask, see MASK_* globals
				collisiongroup = TFCOLLISION_GROUP_OBJECT_SOLIDTOPLAYERMOVEMENT, -- Pretend the trace to be fired by an entity belonging to this group. See COLLISION_GROUP_* globals
				mins = Vector(0,0,0), -- Extends the size of the trace in negative direction
				maxs = Vector(0,0,0), -- Extends the size of the trace in positive direction
				filter = filterEnts -- Entity to ignore. Can be a single entity, table of entities, or a function with a single entity parameter
			}
			--local tracedEntity = EyeTrace(player, connectTrace, targetedEntity)
			local tracedEntity = EyeTrace(player, connectTrace)
			--print(tracedEntity)
			
			--if tracedEntity == targetedEntity then --unobstructed view of bot?
			--quick fix to prevent healing through barriers, due to general wonkiness of the general trace
			if tracedEntity and tracedEntity:GetClassname() == "func_physbox_multiplayer" then
				barrierCounter = barrierCounter + 1
			
				if barrierCounter >= 4 then
					ResetMedigun(medigun, medigunhandle)
					player:SetAttributeValue("no_attack", 1)
					timer.Simple(.05, function()
						player:SetAttributeValue("no_attack", nil)
					end)
					barrierCounter = 0 --don't think this matters since timer dies anyway
				end
			else 
				barrierCounter = 0
			end
			
			if barrierCounter < 4 then
				damageInfo.Damage = damage * healrate
				if isQuickFix and medigun.m_bChargeRelease == 1 then
					damageInfo.Damage = damageInfo.Damage * (.75 + .25 * medigun:GetAttributeValue(ATTRIBUTENAME))
					if targetedEntity:GetConditionProvider(TF_COND_MEGAHEAL) == player then --occasionally no kb gets applied to bot
						targetedEntity:RemoveCond(TF_COND_MEGAHEAL)
					end
				elseif medigun:GetItemName() == "The Kritzkrieg" and medigun.m_bChargeRelease == 1 then
					damageInfo.Damage = damageInfo.Damage * 2
				end
				targetedEntity:TakeDamage(damageInfo)
			end
			
			-- else --if trace was blocked
				-- ResetMedigun(medigun, medigunhandle)
				-- player:SetAttributeValue("no_attack", 1)
				-- timer.Simple(.05, function()
					-- player:SetAttributeValue("no_attack", nil)
				-- end)	
			-- end
			
		else
			ResetMedigun(medigun, medigunhandle) --need to force disconnect here due to hold to heal
		end
	end, 0)
end

function ResetMedigun(medigun, medigunhandle) --reset medigun to defaultish state once we lose target
	medibeams[medigunhandle] = nil
	medigun.m_hHealingTarget = nil
	medigun.m_hLastHealingTarget = nil --might not be necessary
	medigun.m_bAttacking = 0
	if timers[medigunhandle] then
		timer.Stop(timers[medigunhandle])
		timers[medigunhandle] = nil
	end
end

--if player refunds upgrade/unequips?
function RefundedMedibeam(_, activator)
	if IsValid(activator) then
		--print("refunded")
		local medigun = activator:GetPlayerItemBySlot(1)
		local medigunhandle = medigun:GetHandleIndex()
		
		ResetMedigun(medigun, medigunhandle)
		RemoveMedibeamCallbacks(activator)
		medigun:RemoveAllCallbacks(ON_REMOVE)
	end
end

function RemoveMedibeamCallbacks(player)
	if IsValid(player) then
		player:RemoveAllCallbacks(ON_KEY_PRESSED)
		player:RemoveAllCallbacks(ON_KEY_RELEASED)
	end
end