local tempVector1 = Vector3f.new()
local tempVector2 = Vector3f.new()
local AGbladeRound = 0
local AGbladeRoundSmall = 0
local wTransformFieldNum = nil
local AGlastHeli = nil -- Used for last helicopter you rode	
local targetFPS = 90 -- Decides Target FPS for keep velocity same across various FPS
-------------------------
local function getJavaFieldNum(object, fieldName)
    for i = 0, getNumClassFields(object) - 1 do
        local javaField = getClassField(object, i)
        if luautils.stringEnds(tostring(javaField), '.' .. fieldName) then
            return i
        end
    end
end

local function moveVehicle(vehicle, x_delta, y_delta, z_delta)
    if wTransformFieldNum == nil then
        wTransformFieldNum = getJavaFieldNum(vehicle, "tempTransform")
    end

    local tmpTransform = getClassFieldVal(vehicle, getClassField(vehicle, wTransformFieldNum))
    local wTranform = vehicle:getWorldTransform(tmpTransform)
    local origin = getClassFieldVal(wTranform, getClassField(wTranform, 1))
    origin:set(origin:x() + x_delta, origin:y() + y_delta, origin:z() + z_delta)
    vehicle:setWorldTransform(wTranform)
end

local function stopSoundExcept(emi, exception)
    if emi:isPlaying("AGHeliSound1") and "AGHeliSound1" ~= exception then
        emi:stopSoundByName("AGHeliSound1")
        return
    end
    if emi:isPlaying("AGHeliSound2") and "AGHeliSound2" ~= exception  then
        emi:stopSoundByName("AGHeliSound2")
        return
    end
    if emi:isPlaying("AGHeliSound3") and "AGHeliSound3" ~= exception  then
        emi:stopSoundByName("AGHeliSound3")
        return
    end
    if emi:isPlaying("AGHeliSound4") and "AGHeliSound4" ~= exception  then
        emi:stopSoundByName("AGHeliSound4")
        return
    end
end

local function updateHelicopterSound(AGhelicopter, player)
    local dist = IsoUtils.DistanceTo(player:getX(), player:getY(), AGhelicopter:getX(), AGhelicopter:getY())
    local emi = AGhelicopter:getEmitter()
    if not AGhelicopter:isEngineRunning() or dist > 40 then 
        stopSoundExcept(emi, "NONE")
    else
        emi:setPos(0, 0, 0)

        if player:getVehicle() == AGhelicopter then
            stopSoundExcept(emi, "AGHeliSound2")
            if emi:isPlaying("AGHeliSound2") then return end
            emi:playSound("AGHeliSound2", AGhelicopter)
        else
            if dist < 10 then
                stopSoundExcept(emi, "AGHeliSound1")
                if emi:isPlaying("AGHeliSound1") then return end
                emi:playSound("AGHeliSound1", AGhelicopter)
            elseif dist < 20 then
                stopSoundExcept(emi, "AGHeliSound2")
                if emi:isPlaying("AGHeliSound2") then return end
                emi:playSound("AGHeliSound2", AGhelicopter)
            elseif dist < 30 then
                stopSoundExcept(emi, "AGHeliSound3")
                if emi:isPlaying("AGHeliSound3") then return end
                emi:playSound("AGHeliSound3", AGhelicopter)
            elseif dist < 40 then
                stopSoundExcept(emi, "AGHeliSound4")
                if emi:isPlaying("AGHeliSound4") then return end
                emi:playSound("AGHeliSound4", AGhelicopter)
            else
                stopSoundExcept(emi, "NONE")
            end
        end
    end
    AGhelicopter:update()
end

local function rotateBlades(AGhelicopter)
    local part = AGhelicopter:getPartById("AGheliblade")
    if part == nil then return end
    if AGbladeRound == 1 then
        part:setModelVisible("AGblade8", false)
        part:setModelVisible("AGblade1", true)
    elseif AGbladeRound == 2 then
        part:setModelVisible("AGblade1", false)
        part:setModelVisible("AGblade2", true)
    elseif AGbladeRound == 3 then
        part:setModelVisible("AGblade2", false)
        part:setModelVisible("AGblade3", true)
    elseif AGbladeRound == 4 then
        part:setModelVisible("AGblade3", false)
        part:setModelVisible("AGblade4", true)
    elseif AGbladeRound == 5 then
        part:setModelVisible("AGblade4", false)
        part:setModelVisible("AGblade5", true)
    elseif AGbladeRound == 6 then
        part:setModelVisible("AGblade5", false)
        part:setModelVisible("AGblade6", true)
    elseif AGbladeRound == 7 then
        part:setModelVisible("AGblade6", false)
        part:setModelVisible("AGblade7", true)
    elseif AGbladeRound == 8 then
        part:setModelVisible("AGblade7", false)
        part:setModelVisible("AGblade8", true)
    end

    part = AGhelicopter:getPartById("AGhelibladeSmall")
    if part == nil then return end
    if AGbladeRoundSmall == 1 then
        part:setModelVisible("AGblade4Small", false)
        part:setModelVisible("AGblade1Small", true)
    elseif AGbladeRoundSmall == 2 then
        part:setModelVisible("AGblade1Small", false)
        part:setModelVisible("AGblade2Small", true)
    elseif AGbladeRoundSmall == 3 then
        part:setModelVisible("AGblade2Small", false)
        part:setModelVisible("AGblade3Small", true)
    elseif AGbladeRoundSmall == 4 then
        part:setModelVisible("AGblade3Small", false)
        part:setModelVisible("AGblade4Small", true)
    end

    AGhelicopter:update()
    
    AGbladeRoundSmall = AGbladeRoundSmall + 1
    if AGbladeRoundSmall > 4 then
        AGbladeRoundSmall = 1
    end

    AGbladeRound = AGbladeRound + 1
    if AGbladeRound > 8 then
        AGbladeRound = 1
    end
end

-- Visual and Sound Update
local function helicopterVisualSoundUpdate()
	local playerObj = getPlayer()
	if playerObj == nil then return end
	local AGhelicopters = {}
	local vehicleList = playerObj:getCell():getVehicles()
	for i = 0, vehicleList:size() - 1 do
		local tempVehicle = vehicleList:get(i)
		if tempVehicle:getScript():getFullName() == "Base.AGHelicopter" then
			table.insert(AGhelicopters, tempVehicle)
		end
	end
	for idx,heli in ipairs(AGhelicopters) do
		if heli ~= nil then
			if heli:getSquare() ~= nil then
				if heli:isEngineRunning() then
					rotateBlades(heli)
				end
				updateHelicopterSound(heli, playerObj)
			else
				heli = nil
			end
		end
	end
	return
end



-- Movement Update function for helicopter
local function helicopterMovementUpdate()
    local playerObj = getPlayer()
    if playerObj == nil then return end
    local AGhelicopter = playerObj:getVehicle()
    local seat = nil

    if AGhelicopter ~= nil then seat = AGhelicopter:getSeat(playerObj) end
    if AGhelicopter == nil or AGhelicopter:getScript():getFullName() ~= "Base.AGHelicopter" then 

        return
    end

    local fpsMultiplier = targetFPS / getAverageFPS()
    local curr_z = AGhelicopter:getWorldPos(0, 0, 0, tempVector2):z()
    playerObj:setZ(curr_z)
    if curr_z > 2.999 then
        if not playerObj:isGhostMode() then
            playerObj:setGhostMode(true)
        end
    else
        if playerObj:isGhostMode() then
            playerObj:setGhostMode(false)
        end
    end

    if seat ~= 0 then
        return
    end

    AGlastHeli = AGhelicopter
	


    
	
    -- If the player does not know how to drive a helicopter, stop the engine and return
    if not playerObj:isRecipeKnown("Heli driving") then 
        if AGhelicopter:isEngineRunning() then
            AGhelicopter:engineDoShuttingDown() -- shut down the engine
            playerObj:Say(getText("Tooltip_I_dont_know")) -- tell the player they don't know how to fly
        end
        return 
    end

    -- If the helicopter engine is not running, and the helicopter is above a certain height, start falling
    if not AGhelicopter:isEngineRunning() then 
        if curr_z > 0.4 then -- check if helicopter is above a certain height
            local tempValue = -0.07 -- the amount by which the helicopter falls
            tempValue = tempValue * fpsMultiplier -- adjust for frame rate
            moveVehicle(AGhelicopter, 0, tempValue, 0) -- move the helicopter downwards
        end
        return
    end
    
    

    -- Use fuel and change zombie behavior when in the air
    local function useFuelAndUpdateZombieBehavior()
        local curr_z = AGhelicopter:getWorldPos(0, 0, 0, tempVector2):z()

        -- Decrease fuel amount based on helicopter's height
        if curr_z > 1 then
            AGhelicopter:getPartById("GasTank"):setContainerContentAmount(AGhelicopter:getPartById("GasTank"):getContainerContentAmount() - 0.03 * fpsMultiplier)
            AGhelicopter:setZombiesDontAttack(true) -- Zombies don't attack when flying
        else
            AGhelicopter:getPartById("GasTank"):setContainerContentAmount(AGhelicopter:getPartById("GasTank"):getContainerContentAmount() - 0.01 * fpsMultiplier)
            AGhelicopter:setZombiesDontAttack(false) -- Zombies attack when on the ground
        end
    end


	
    if curr_z > 1 then
        local vec0 = Vector3f.new(0, 0, curr_z)
        local vec1 = AGhelicopter:getWorldPos(0, 0, 1, Vector3f.new()):add(-AGhelicopter:getX(), -AGhelicopter:getY(), -curr_z)
        local angleZ = vec1:angle(vec0)
        vec1 = AGhelicopter:getWorldPos(1, 0, 0, Vector3f.new()):add(-AGhelicopter:getX(), -AGhelicopter:getY(), -curr_z)
        local angleX = vec1:angle(vec0)

        local dq = CustomQuaternion.fromEulerAngles(math.rad(AGhelicopter:getAngleX()), math.rad(AGhelicopter:getAngleY()), math.rad(AGhelicopter:getAngleZ()))
        local ax = 0
        local ay = 0
        local az = 0
		local angle_90 = math.rad(90)
		
        if isKeyDown(Keyboard.KEY_A) then
            ay = 0.7 * fpsMultiplier
        end
    
        if isKeyDown(Keyboard.KEY_D) then
            ay = -0.7 * fpsMultiplier
        end
    
        if isKeyDown(Keyboard.KEY_UP) then
            if not isKeyDown(Keyboard.KEY_LEFT) and not isKeyDown(Keyboard.KEY_RIGHT) then
                if angleZ < angle_90 + 0.25 then
                    ax = 0.2 * fpsMultiplier 
                else
                    ax = -0.1 * fpsMultiplier
                end
            end
        elseif isKeyDown(Keyboard.KEY_DOWN) then
            if not isKeyDown(Keyboard.KEY_LEFT) and not isKeyDown(Keyboard.KEY_RIGHT) then
                if angleZ > angle_90 - 0.25 then
                    ax = -0.2 * fpsMultiplier
                else
                    ax = 0.2 * fpsMultiplier
                end
            end
        else -- For easier helicopter control, I make helicopter keeping its balance while we press nothing
            if angleZ > angle_90 then
                ax = -2 * (angleZ - angle_90) * fpsMultiplier
            elseif angleZ < angle_90 then
                ax = 2 * (angle_90 - angleZ) * fpsMultiplier
            end
        end
        
        if isKeyDown(Keyboard.KEY_LEFT) then
            if not isKeyDown(Keyboard.KEY_UP) and not isKeyDown(Keyboard.KEY_DOWN) then
                if angleX < angle_90 + 0.25 then
                    az = -0.2 * fpsMultiplier
                else
                    az = 0.2 * fpsMultiplier
                end
            end
        elseif isKeyDown(Keyboard.KEY_RIGHT) and not isKeyDown(Keyboard.KEY_DOWN) then
            if not isKeyDown(Keyboard.KEY_UP) then
                if angleX > angle_90 - 0.25 then
                    az = 0.2 * fpsMultiplier
                else
                    az = -0.2 * fpsMultiplier
                end
            end
        else
            if angleX > angle_90 then
                az = 5 * (angleX - angle_90) * fpsMultiplier
            elseif angleX < angle_90 then
                az = -5 * (angle_90 - angleX) * fpsMultiplier
            end
        end

        local nqx = CustomQuaternion.fromAngleAxis(math.rad(ax), 1, 0, 0)
        local nqy = CustomQuaternion.fromAngleAxis(math.rad(ay), 0, 1, 0)
        local nqz = CustomQuaternion.fromAngleAxis(math.rad(az), 0, 0, 1)
        local rs = dq * nqx * nqy * nqz
        local resx, resy, resz = CustomQuaternion.toEulerAngles(CustomQuaternion.toRotationMatrix(rs))
        AGhelicopter:setAngles(resx, resy, resz)

        local forceVec = AGhelicopter:getWorldPos(0, 0, 1, Vector3f.new()):add(-AGhelicopter:getX(), -AGhelicopter:getY(), -curr_z)
        forceVec = forceVec:normalize()
        if angleZ > math.pi / 2 then
            forceVec:mul(math.abs(math.cos(angleZ))/2)
            forceVec:mul(1)
			forceVec:mul(fpsMultiplier)
            forceVec:set(forceVec:x(), 0, forceVec:y())
            moveVehicle(AGhelicopter, forceVec:x(), forceVec:y(), forceVec:z())
        else
            forceVec:mul(-math.cos(angleZ)/2)
            forceVec:mul(1)
			forceVec:mul(fpsMultiplier)
            forceVec:set(forceVec:x(), 0, forceVec:y())
            moveVehicle(AGhelicopter, forceVec:x(), forceVec:y(), forceVec:z())
        end

        forceVec = AGhelicopter:getWorldPos(1, 0, 0, Vector3f.new()):add(-AGhelicopter:getX(), -AGhelicopter:getY(), -curr_z)
        forceVec = forceVec:normalize()
        if angleX > math.pi / 2 then
            forceVec:mul(math.abs(math.cos(angleX))/2)
            forceVec:mul(1)
			forceVec:mul(fpsMultiplier)
            forceVec:set(forceVec:x(), 0, forceVec:y())
            moveVehicle(AGhelicopter, forceVec:x(), forceVec:y(), forceVec:z())
        else
            forceVec:mul(-math.cos(angleX)/2)
            forceVec:mul(1)
			forceVec:mul(fpsMultiplier)
            forceVec:set(forceVec:x(), 0, forceVec:y())
            moveVehicle(AGhelicopter, forceVec:x(), forceVec:y(), forceVec:z())
        end

        if curr_z < 3.0 then
            if isKeyDown(Keyboard.KEY_W) and AGhelicopter:getRemainingFuelPercentage() > 0 then
				local tempValue = 0.04
				tempValue = tempValue * fpsMultiplier
                moveVehicle(AGhelicopter, 0, tempValue, 0)
            end
        end
		if alreadyGhostMode == 0 then
            playerObj:setGhostMode(false)
        end
    else
        if isKeyDown(Keyboard.KEY_W) and AGhelicopter:getRemainingFuelPercentage() > 0 then 
			local tempValue = 0.04
			tempValue = tempValue * fpsMultiplier
            moveVehicle(AGhelicopter, 0, tempValue, 0)
        end
    end

    if isKeyDown(Keyboard.KEY_S) and curr_z > 0.4 then 
		local tempValue = -0.03
		tempValue = tempValue * fpsMultiplier
        moveVehicle(AGhelicopter, 0, tempValue, 0)
	elseif AGhelicopter:getRemainingFuelPercentage() <= 0 and curr_z > 0.4 then -- We are falling!
		local tempValue = -0.07
		tempValue = tempValue * fpsMultiplier
        moveVehicle(AGhelicopter, 0, tempValue, 0)
    end
end

local function helicopterVisualSoundUpdateOld()
	local playerObj = getPlayer()
	if playerObj == nil then return end
    local AGhelicopter = playerObj:getVehicle()
	local seat = nil
	if AGhelicopter ~= nil then seat = AGhelicopter:getSeat(playerObj) end
    if AGhelicopter == nil or AGhelicopter:getScript():getFullName() ~= "Base.AGHelicopter" then 
        if AGlastHeli == nil and AGLAST_HELI ~= nil then
            AGlastHeli = AGLAST_HELI
            AGLAST_HELI = nil
        end        
        if AGlastHeli ~= nil then
            if AGlastHeli:getSquare() ~= nil then
                if AGlastHeli:isEngineRunning() then
                    rotateBlades(AGlastHeli)
                end
                updateHelicopterSound(AGlastHeli, playerObj)
            else
                AGlastHeli = nil
            end
        end
        return
	elseif seat ~= 0 then
	    if AGhelicopter:getSquare() ~= nil then
			if AGhelicopter:isEngineRunning() then
				rotateBlades(AGhelicopter)
			end
			updateHelicopterSound(AGhelicopter, playerObj)
		else
			AGhelicopter = nil
		end
		return
    end
end

Events.OnTick.Add(helicopterMovementUpdate)
Events.OnTick.Add(helicopterVisualSoundUpdate)
--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- 

local function setHeliTransformFieldNum(player)
    player:getModData().Heli_transform_num = getJavaFieldNum(player:getVehicle(), "tempTransform")
end
local function heilcopterEnter(player)
    local AGhelicopter = player:getVehicle()
	local seat = nil
	if AGhelicopter ~= nil then seat = AGhelicopter:getSeat(player) end
    if AGhelicopter == nil or AGhelicopter:getScript():getFullName() ~= "Base.AGHelicopter" then 
        return
	elseif seat ~= 0 then
		return
    end
end
local function helicopterExit(player)
	local AGhelicopter = player:getVehicle()
	local seat = nil
	if AGhelicopter ~= nil then seat = AGhelicopter:getSeat(player) end
    if AGhelicopter == nil or AGhelicopter:getScript():getFullName() ~= "Base.AGHelicopter" then 
        return
	elseif seat ~= 0 then
		return
    end
	
	if playerObj:isGhostMode() and not playerObj:isGodMod() then
		playerObj:setGhostMode(false)
	end
	
	beforeTime = nil
end

--[[ This is not used
local function depricatedCode()
	local curr_z = vehicle:getWorldPos(0, 0, 0, tempVector2):z()
	if curr_z < 1.5 then
		return
	else
		sendClientCommand(self.character, 'vehicle', 'setDoorOpen', {
			vehicle = vehicle:getId(),
			part = vehicle:getPassengerDoor(vehicle:getSeat(self.character)),
			open = false
		});
	end
end
--]]

Events.OnEnterVehicle.Add(setHeliTransformFieldNum)
Events.OnEnterVehicle.Add(heilcopterEnter)
Events.OnExitVehicle.Add(helicopterExit)

