local tempVector1 = Vector3f.new()
local tempVector2 = Vector3f.new()
local M5bladeRound = 0
local M5bladeRoundSmall = 0
local wTransformFieldNum = nil
local M5lastHeli = 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("M5HeliSound1") and "M5HeliSound1" ~= exception then
        emi:stopSoundByName("M5HeliSound1")
        return
    end
    if emi:isPlaying("M5HeliSound2") and "M5HeliSound2" ~= exception  then
        emi:stopSoundByName("M5HeliSound2")
        return
    end
    if emi:isPlaying("M5HeliSound3") and "M5HeliSound3" ~= exception  then
        emi:stopSoundByName("M5HeliSound3")
        return
    end
    if emi:isPlaying("M5HeliSound4") and "M5HeliSound4" ~= exception  then
        emi:stopSoundByName("M5HeliSound4")
        return
    end
end

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

        if player:getVehicle() == M5helicopter then
            stopSoundExcept(emi, "M5HeliSound2")
            if emi:isPlaying("M5HeliSound2") then return end
            emi:playSound("M5HeliSound2", M5helicopter)
        else
            if dist < 10 then
                stopSoundExcept(emi, "M5HeliSound1")
                if emi:isPlaying("M5HeliSound1") then return end
                emi:playSound("M5HeliSound1", M5helicopter)
            elseif dist < 20 then
                stopSoundExcept(emi, "M5HeliSound2")
                if emi:isPlaying("M5HeliSound2") then return end
                emi:playSound("M5HeliSound2", M5helicopter)
            elseif dist < 30 then
                stopSoundExcept(emi, "M5HeliSound3")
                if emi:isPlaying("M5HeliSound3") then return end
                emi:playSound("M5HeliSound3", M5helicopter)
            elseif dist < 40 then
                stopSoundExcept(emi, "M5HeliSound4")
                if emi:isPlaying("M5HeliSound4") then return end
                emi:playSound("M5HeliSound4", M5helicopter)
            else
                stopSoundExcept(emi, "NONE")
            end
        end
    end
    M5helicopter:update()
end

local function rotateBlades(M5helicopter)
    local part = M5helicopter:getPartById("M5heliblade")
    if part == nil then return end
    if M5bladeRound == 1 then
        part:setModelVisible("M5blade8", false)
        part:setModelVisible("M5blade1", true)
    elseif M5bladeRound == 2 then
        part:setModelVisible("M5blade1", false)
        part:setModelVisible("M5blade2", true)
    elseif M5bladeRound == 3 then
        part:setModelVisible("M5blade2", false)
        part:setModelVisible("M5blade3", true)
    elseif M5bladeRound == 4 then
        part:setModelVisible("M5blade3", false)
        part:setModelVisible("M5blade4", true)
    elseif M5bladeRound == 5 then
        part:setModelVisible("M5blade4", false)
        part:setModelVisible("M5blade5", true)
    elseif M5bladeRound == 6 then
        part:setModelVisible("M5blade5", false)
        part:setModelVisible("M5blade6", true)
    elseif M5bladeRound == 7 then
        part:setModelVisible("M5blade6", false)
        part:setModelVisible("M5blade7", true)
    elseif M5bladeRound == 8 then
        part:setModelVisible("M5blade7", false)
        part:setModelVisible("M5blade8", true)
    end

    part = M5helicopter:getPartById("M5helibladeSmall")
    if part == nil then return end
    if M5bladeRoundSmall == 1 then
        part:setModelVisible("M5blade4Small", false)
        part:setModelVisible("M5blade1Small", true)
    elseif M5bladeRoundSmall == 2 then
        part:setModelVisible("M5blade1Small", false)
        part:setModelVisible("M5blade2Small", true)
    elseif M5bladeRoundSmall == 3 then
        part:setModelVisible("M5blade2Small", false)
        part:setModelVisible("M5blade3Small", true)
    elseif M5bladeRoundSmall == 4 then
        part:setModelVisible("M5blade3Small", false)
        part:setModelVisible("M5blade4Small", true)
    end

    M5helicopter:update()
    
    M5bladeRoundSmall = M5bladeRoundSmall + 1
    if M5bladeRoundSmall > 4 then
        M5bladeRoundSmall = 1
    end

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

-- Visual and Sound Update
local function helicopterVisualSoundUpdate()
	local playerObj = getPlayer()
	if playerObj == nil then return end
	local M5helicopters = {}
	local vehicleList = playerObj:getCell():getVehicles()
	for i = 0, vehicleList:size() - 1 do
		local tempVehicle = vehicleList:get(i)
		if tempVehicle:getScript():getFullName() == "Base.M5Helicopter" then
			table.insert(M5helicopters, tempVehicle)
		end
	end
	for idx,heli in ipairs(M5helicopters) 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 M5helicopter = playerObj:getVehicle()
    local seat = nil

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

        return
    end

    local fpsMultiplier = targetFPS / getAverageFPS()
    local curr_z = M5helicopter:getWorldPos(0, 0, 0, tempVector2):z()
    playerObj:setZ(curr_z)
    if curr_z > 1 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

    M5lastHeli = M5helicopter
	


    
	
    -- If the player does not know how to drive a helicopter, stop the engine and return
    if not playerObj:isRecipeKnown("Heli driving") then 
        if M5helicopter:isEngineRunning() then
            M5helicopter: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 M5helicopter: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(M5helicopter, 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 = M5helicopter:getWorldPos(0, 0, 0, tempVector2):z()

        -- Decrease fuel amount based on helicopter's height
        if curr_z > 1 then
            M5helicopter:getPartById("GasTank"):setContainerContentAmount(M5helicopter:getPartById("GasTank"):getContainerContentAmount() - 0.0006 * fpsMultiplier)
            M5helicopter:setZombiesDontAttack(true) -- Zombies don't attack when flying
        else
            M5helicopter:getPartById("GasTank"):setContainerContentAmount(M5helicopter:getPartById("GasTank"):getContainerContentAmount() - 0.0002 * fpsMultiplier)
            M5helicopter: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 = M5helicopter:getWorldPos(0, 0, 1, Vector3f.new()):add(-M5helicopter:getX(), -M5helicopter:getY(), -curr_z)
        local angleZ = vec1:angle(vec0)
        vec1 = M5helicopter:getWorldPos(1, 0, 0, Vector3f.new()):add(-M5helicopter:getX(), -M5helicopter:getY(), -curr_z)
        local angleX = vec1:angle(vec0)

        local dq = CustomQuaternion.fromEulerAngles(math.rad(M5helicopter:getAngleX()), math.rad(M5helicopter:getAngleY()), math.rad(M5helicopter: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))
        M5helicopter:setAngles(resx, resy, resz)

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

        forceVec = M5helicopter:getWorldPos(1, 0, 0, Vector3f.new()):add(-M5helicopter:getX(), -M5helicopter: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(M5helicopter, 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(M5helicopter, forceVec:x(), forceVec:y(), forceVec:z())
        end

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

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

local function helicopterVisualSoundUpdateOld()
	local playerObj = getPlayer()
	if playerObj == nil then return end
    local M5helicopter = playerObj:getVehicle()
	local seat = nil
	if M5helicopter ~= nil then seat = M5helicopter:getSeat(playerObj) end
    if M5helicopter == nil or M5helicopter:getScript():getFullName() ~= "Base.M5Helicopter" then 
        if M5lastHeli == nil and M5LAST_HELI ~= nil then
            M5lastHeli = M5LAST_HELI
            M5LAST_HELI = nil
        end        
        if M5lastHeli ~= nil then
            if M5lastHeli:getSquare() ~= nil then
                if M5lastHeli:isEngineRunning() then
                    rotateBlades(M5lastHeli)
                end
                updateHelicopterSound(M5lastHeli, playerObj)
            else
                M5lastHeli = nil
            end
        end
        return
	elseif seat ~= 0 then
	    if M5helicopter:getSquare() ~= nil then
			if M5helicopter:isEngineRunning() then
				rotateBlades(M5helicopter)
			end
			updateHelicopterSound(M5helicopter, playerObj)
		else
			M5helicopter = 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 M5helicopter = player:getVehicle()
	local seat = nil
	if M5helicopter ~= nil then seat = M5helicopter:getSeat(player) end
    if M5helicopter == nil or M5helicopter:getScript():getFullName() ~= "Base.M5Helicopter" then 
        return
	elseif seat ~= 0 then
		return
    end
end
local function helicopterExit(player)
	local M5helicopter = player:getVehicle()
	local seat = nil
	if M5helicopter ~= nil then seat = M5helicopter:getSeat(player) end
    if M5helicopter == nil or M5helicopter:getScript():getFullName() ~= "Base.M5Helicopter" 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)

