#include "script/common.lua"
--extra utilities

hangingLights = {} --each light has max intensity, sustain time, max time
function createLight(pos,r,g,b,intensity,peaktime,maxtime)
	local light = {}
	light.pos = pos
	light.scale = intensity
	light.firstscale = intensity
	light.color = {r,g,b}
	light.time = 0
	light.peaktime = peaktime
	light.maxtime = maxtime
	table.insert(hangingLights,light)
end
function hangingLightOperation(id,dt)
	local light = hangingLights[id]
	if light.time > light.maxtime+0.5 then
		table.remove(hangingLights,id)
		return
	end
	local c = light.color
	if light.time > light.peaktime then
		light.scale = light.firstscale * (1-(light.time-light.peaktime)/(light.maxtime-light.peaktime))
		light.scale = math.max(0,light.scale)
	end
	light.time = light.time + dt
	PointLight(light.pos,c[1],c[2],c[3],light.scale)
end

function tickHangLight(dt)
	for id=#hangingLights,1,-1 do
		hangingLightOperation(id,dt)
	end
end

function rndFloat(mi, ma)
	return mi + (ma-mi)*(math.random(0, 1000000)/1000000.0)
end

function deepcopy(orig) --thanks Cresta
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[deepcopy(orig_key)] = deepcopy(orig_value)
        end
        setmetatable(copy, deepcopy(getmetatable(orig)))
    else
        copy = orig
    end
    return copy
end

function tagToVec(tag) --or QuatEulers, it's just a table of three values
	tag = commasplit(tag) --tag must be able to turn into a table of three
	for key, value in ipairs(tag) do
		value = tonumber(value)
	end
	return tag
end 

function buildParticle(sprite, pos, color, decay, vel, alpha,scale) --particle constructor
	return {
		sprite = sprite;
		pos = pos or {0,0,0};
		rot = {0,0,0};
		alpha = alpha or 1;
		decay = decay or 0.01;
		vel = vel or {0,0,0};
		accel = accel or {0,0,0};
		color = color or {0,0,0};
		scale = scale or {x=1, y=1};
		dead = false;
	}
end

function addParticle(particle) --add a particle and overwrite dead ones
	local i = 1
	while i < #particles do
		if particles[i].dead then
			particles[i] = particle
			return
		end
		i = i + 1
	end
	table.insert(particles, particle)
end

function addProjectile(shot,shottable)
	for i=1, #shottable do
		if not shottable[i].active then
			shottable[i] = shot
			return
		end
	end
	table.insert(shottable,shot)
end

function commasplit(inputstr) --splits a "x,x,x..." string into a table
	local t = {}
	for str in string.gmatch(inputstr,"([^,]+)") do
		table.insert(t,str)
	end
	return t
end

function rejectSelf() --function to exclude self from raycasting
	--base alicanto
	QueryRejectBody(head)
	QueryRejectBody(searchLight)
	for i=1,#tailTable do
		QueryRejectBody(tailTable[i].body)
	end
	for w=1,#leftWingTable do
		QueryRejectBody(leftWingTable[w].body)
	end
	for w=1, #rightWingTable do
		QueryRejectBody(rightWingTable[w].body)
	end
	
	--basilisco weapons
	QueryRejectBody(railGun)
	QueryRejectBody(railGunShroud)
	QueryRejectBody(railGunBarrel)
	QueryRejectBody(engineL)
	QueryRejectBody(engineR)
	QueryRejectBody(launcher)
end

function rejectAll() --rejects every instance of similar entities
	for h, head in ipairs(heads) do
		QueryRejectBody(head)
	end
	for s, searchlight in ipairs(lights) do
		QueryRejectBody(searchlight)
	end
	for t, tail in ipairs(bodies) do
		QueryRejectBody(tail)
	end
	for w, wingl in ipairs(wingsl) do
		QueryRejectBody(wingl)
	end
	for w, wingr in ipairs(wingsr) do
		QueryRejectBody(wingr)
	end
	
	for r, railgun in ipairs(railguns) do
		QueryRejectBody(railgun)
	end
	for r, railgun in ipairs(railgunshrouds) do
		QueryRejectBody(railgun)
	end
	for r, railgun in ipairs(railgunbarrels) do
		QueryRejectBody(railgun)
	end
	
	for e, engine in ipairs(enginesl) do
		QueryRejectBody(engine)
	end
	for e, engine in ipairs(enginesr) do
		QueryRejectBody(engine)
	end
	for e, launcher in ipairs(launchers) do
		QueryRejectBody(launcher)
	end
end

function rejectChoppers() --for use alongside base game choppers
	for c, body in ipairs(choppers) do
		QueryRejectBody(body)
	end
	for c, body in ipairs(tailrotors) do
		QueryRejectBody(body)
	end
	for c, body in ipairs(mainrotors) do
		QueryRejectBody(body)
	end
	for c, body in ipairs(chopperlights) do
		QueryRejectBody(body)
	end
end

function rejectAllFlyers()
	local list = FindBodies("flyerfp",true)
	for l,body in ipairs(list) do
		QueryRejectBody(body)
	end
end

averageSurroundingHeight = 0 --for detecting height of the surroundings
function computeSurroundingHeight(radius,controlpoint,extraheight) --gets height of surroundings (optional radius)
	rejectAllFlyers()
	rejectChoppers()
	QueryRequire ("large")
	local radius = radius or 34
	extraheight = extraheight or 0
	controlpoint = controlpoint or headTargetPos
	local probe = VecCopy(controlpoint)
	probe[1] = probe[1] + math.random(-radius, radius)
	probe[2] = 100
	probe[3] = probe[3] + math.random(-radius, radius)
	local hit, dist = QueryRaycast(probe, Vec(0,-1,0), 100, 0.0)
	local hitHeight = 0
	if hit then
		hitHeight = 100 - dist
	end
	local hitVec = Vec(probe[1],hitHeight,probe[3])
	local wet, d = IsPointInWater(hitVec)
	if wet then
		hitHeight = hitHeight + d
	end
	averageSurroundingHeight = math.max(hitHeight+extraheight, averageSurroundingHeight - GetTimeStep()*2)
end

--hearing module 

function getSoundVolume(pos)
	local d = VecLength(VecSub(pos, headTransform.pos))
	local dir = VecNormalize(VecSub(pos, headTransform.pos))
	local distanceLower = 6
	local distanceUpper = maxHearDist
	local distanceFactor = 1.0 - clamp(d-distanceLower, 0, distanceUpper-distanceLower)/(distanceUpper-distanceLower)
		
	local origin = headTransform.pos
	local dist = d - 2
	local blockedFactor = 1.0
	rejectSelf()
	
	if QueryRaycast(origin, dir, dist) then
		blockedFactor = 0.5
	end

	return blockedFactor * distanceFactor
end

--gets distances to player
function getDistanceToPlayer()
	local playerPos = GetPlayerPos()
	return VecLength(VecSub(playerPos, headTransform.pos))
end
function getVecToPlayer() --getDistanceToPlayer but flattened
	local playerPos = GetPlayerPos()
	local dist = VecSub(playerPos, headTransform.pos)
	dist[2] = 0 --flatten
	return VecLength(dist)
end

function getDistanceToLineSegment(point, p0, p1) --idk how this works but lasers without raycast
    local line_vec = VecSub(p1, p0)
    local pnt_vec = VecSub(point, p0) 
    local line_len = VecLength(line_vec) 
    local line_unitvec = VecNormalize(line_vec)
    local pnt_vec_scaled = VecScale(pnt_vec, 1.0/line_len) 
    local t = VecDot(line_unitvec, pnt_vec_scaled)    
    if t < 0.0 then
        t = 0.0
    elseif t > 1.0 then
        t = 1.0
	end
    local nearest = VecScale(line_vec, t) 
    local dist = VecLength(VecSub(nearest, pnt_vec)) 
    local nearest = VecAdd(nearest, p0)
    return dist, nearest
end

function VecDist(vecA,vecB)
	return VecLength(VecSub(vecB,vecA))
end

--move targetpos down if too high
function targetPosShiftDown()
	rejectSelf()
	local hit, dist = QueryRaycast(targetPos,Vec(0,-1,0),6)
	if not hit then --target is too high off ground
		rejectSelf()
		local hit, dist = QueryRaycast(targetPos,Vec(0,-1,0),40)
		if hit then
			targetPos[2] = math.min(targetPos[2],targetPos[2]-dist+6) 
		else
			targetPos[2] = GetPlayerPos()[2]
		end
	end
end

--is player dead
function playerDead()
	if GetPlayerHealth() <= 0 then
		return true
	end
	return false
end