local LEAP_MAX_DIST = 10
local LEAP_MIN_DIST = 3
local LEAP_MAX_SPEED = 16
local LEAP_MIN_SPEED = LEAP_MAX_SPEED * LEAP_MIN_DIST / LEAP_MAX_DIST
local LEAP_MAX_DIST_SQ = LEAP_MAX_DIST * LEAP_MAX_DIST
local LEAP_MIN_DIST_SQ = LEAP_MIN_DIST * LEAP_MIN_DIST

local require = GLOBAL.require
local STRINGS = GLOBAL.STRINGS
local Action = GLOBAL.Action
local ACTIONS = GLOBAL.ACTIONS
local FRAMES = GLOBAL.FRAMES
local DEGREES = GLOBAL.DEGREES
local EQUIPSLOTS = GLOBAL.EQUIPSLOTS
local COLLISION = GLOBAL.COLLISION
local GROUND = GLOBAL.GROUND
local TheSim = GLOBAL.TheSim
local State = GLOBAL.State
local EventHandler = GLOBAL.EventHandler
local TimeEvent = GLOBAL.TimeEvent
local ActionHandler = GLOBAL.ActionHandler
local Vector3 = GLOBAL.Vector3
local PlayFootstep = GLOBAL.PlayFootstep
local SpawnPrefab = GLOBAL.SpawnPrefab


local leap_action = Action(10, false, true, 1024)
leap_action.str = "Leap"
leap_action.id = "LEAP"
leap_action.fn = function(act)
    if act.doer ~= nil and act.doer.sg ~= nil and act.doer.sg.currentstate.name == "leap_pre" then
        act.doer.sg:GoToState("leap", { pos = act.pos })
        return true
    end
end
AddAction(leap_action)


--------------------------------------------------------------------------

AddStategraphState("wilson", State{
    name = "leap_pre",
    tags = { "doing", "busy", "canrotate" },

    onenter = function(inst)
        inst.components.locomotor:Stop()
        inst.AnimState:PlayAnimation("leap_pre")
    end,

    events =
    {
        EventHandler("animover", function(inst)
            if inst.AnimState:AnimDone() then
                if inst.bufferedaction ~= nil then
                    inst:PerformBufferedAction()
                else
                    inst.sg:GoToState("idle")
                end
            end
        end),
    },
})

local function ToggleOffPhysics(inst)
    inst.sg.statemem.isphysicstoggle = true
    inst.Physics:ClearCollisionMask()
    inst.Physics:CollidesWith(COLLISION.GROUND)
end

local function ToggleOnPhysics(inst)
    inst.sg.statemem.isphysicstoggle = nil
    inst.Physics:ClearCollisionMask()
    inst.Physics:CollidesWith(COLLISION.WORLD)
    inst.Physics:CollidesWith(COLLISION.OBSTACLES)
    inst.Physics:CollidesWith(COLLISION.CHARACTERS)
end

AddStategraphState("wilson", State{
    name = "leap",
    tags = { "doing", "busy", "canrotate", "nopredict", "nomorph" },

    onenter = function(inst, data)
        inst.components.locomotor:Stop()
        inst.AnimState:PlayAnimation("leap")

        local dist
        if data ~= nil and data.pos ~= nil then
            inst:ForceFacePoint(data.pos:Get())
            local distsq = inst:GetDistanceSqToPoint(data.pos)
            if distsq <= LEAP_MIN_DIST_SQ then
                dist = LEAP_MIN_DIST
                inst.sg.statemem.speed = LEAP_MIN_SPEED
            elseif distsq >= LEAP_MAX_DIST_SQ then
                dist = LEAP_MAX_DIST
                inst.sg.statemem.speed = LEAP_MAX_SPEED
            else
                dist = math.sqrt(distsq)
                inst.sg.statemem.speed = LEAP_MAX_SPEED * dist / LEAP_MAX_DIST
            end
        else
            inst.sg.statemem.speed = LEAP_MAX_SPEED
            dist = LEAP_MAX_DIST
        end

        if inst.components.hunger ~= nil then
            inst.components.hunger:DoDelta(-dist / LEAP_MAX_DIST, true)
        end

        local x, y, z = inst.Transform:GetWorldPosition()
        local angle = inst.Transform:GetRotation() * DEGREES
		if GLOBAL.GetWorld().Map:GetTileAtPoint(x + dist * math.cos(angle), 0, z - dist * math.sin(angle)) ~= GROUND.IMPASSABLE then
            ToggleOffPhysics(inst)
        end

        inst.Physics:SetMotorVel(inst.sg.statemem.speed * .5, 0, 0)

        local fx = SpawnPrefab("small_puff")
        if fx ~= nil then
            fx.Transform:SetScale(.3, .3, .3)
            fx.Transform:SetPosition(x, 0, z)
        end
        PlayFootstep(inst)

        if inst.components.playercontroller ~= nil then
            inst.components.playercontroller:Enable(false)
        end
    end,

    onupdate = function(inst)
        if inst.sg.statemem.isphysicstoggle then
            local x, y, z = inst.Transform:GetWorldPosition()
            local angle = inst.Transform:GetRotation() * DEGREES
            local radius = .5
            x = x + .75 * radius * math.cos(angle)
            z = z - .75 * radius * math.sin(angle)
            local ents = TheSim:FindEntities(x, 0, z, radius, { "wall" })
            for i, v in ipairs(ents) do
                if v.components.health ~= nil and v.components.health:GetPercent() > .5 then
                    ToggleOnPhysics(inst)
                    return
                end
            end
        end
    end,

    timeline =
    {
        TimeEvent(.5 * FRAMES, function(inst)
            inst.Physics:SetMotorVel(inst.sg.statemem.speed * .75, 0, 0)
        end),
        TimeEvent(1 * FRAMES, function(inst)
            inst.Physics:SetMotorVel(inst.sg.statemem.speed, 0, 0)
        end),
        TimeEvent(19 * FRAMES, function(inst)
            inst.Physics:SetMotorVel(inst.sg.statemem.speed * .25, 0, 0)
            inst.SoundEmitter:PlaySound("dontstarve/movement/bodyfall_dirt")

            local fx = SpawnPrefab("small_puff")
            if fx ~= nil then
                fx.Transform:SetScale(.3, .3, .3)
                local x, y, z = inst.Transform:GetWorldPosition()
                local angle = inst.Transform:GetRotation() * DEGREES
                fx.Transform:SetPosition(x + .25 * math.cos(angle), 0, z - .25 * math.sin(angle))
            end
        end),
        TimeEvent(20 * FRAMES, function(inst)
            inst.Physics:Stop()
            inst.sg:GoToState("idle", true)
        end),
    },

    onexit = function(inst)
        if inst.sg.statemem.isphysicstoggle then
            ToggleOnPhysics(inst)
        end

        if inst.components.playercontroller ~= nil then
            inst.components.playercontroller:Enable(true)
        end
    end,
})

AddStategraphActionHandler("wilson", ActionHandler(ACTIONS.LEAP, function(inst)
    return not inst.sg:HasStateTag("busy")
        and (inst.sg:HasStateTag("moving") or inst.sg:HasStateTag("idle"))
        and "leap_pre"
        or nil
end))

--------------------------------------------------------------------------


AddSimPostInit(function(wilson)
    if not (wilson.prefab == "guts") then
        return
    end
	local self = wilson.components.playeractionpicker
    local _GetRightClickActions = self.GetRightClickActions
    self.GetRightClickActions = function(self, target_ent, position)
        local actions = _GetRightClickActions(self, target_ent, position)
        local body = self.inst.components.inventory:GetEquippedItem(EQUIPSLOTS.BODY)
        local isArmorMosquito = body and body.prefab == "armormosquito"
        if isArmorMosquito and position ~= nil then
            return self:SortActionList({ ACTIONS.LEAP }, position)
        end
        return actions
    end
end)
