--[[
BY
 _____   _____   __   _  
|  _  \ /  _  \ |  \ | | 
| | | | | | | | |   \| | 
| | | | | | | | | |\   | 
| |_| | | |_| | | | \  | 
|_____/ \_____/ |_|  \_| 

]]

SGCritterEvents = {}
SGCritterStates = {}


--------------------------------------------------------------------------
SGCritterEvents.OnEat = function()
    return EventHandler("oneat", function(inst) inst.sg:GoToState("eat") end)
end
--------------------------------------------------------------------------

SGCritterStates.AddIdle = function(states, timeline)
    table.insert(states, State
    {
        name = "idle",
        tags = { "idle", "canrotate" },

        onenter = function(inst, pushanim)
			if inst.components.locomotor ~= nil then
				inst.components.locomotor:StopMoving()
			end
			
			inst.AnimState:PlayAnimation(inst.animsdon.idle)	
        end,
        
        timeline = timeline,

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

--------------------------------------------------------------------------
SGCritterStates.AddEat = function(states, timeline, fns)
    table.insert(states, State
    {
        name = "eat",
        tags = { "busy" },

        onenter = function(inst, pushanim)
            if inst.components.locomotor ~= nil then
                inst.components.locomotor:StopMoving()
            end
			
			inst.AnimState:PlayAnimation(inst.animsdon.eat)

            if fns ~= nil and fns.onenter ~= nil then
                fns.onenter(inst)
            end
        end,

		timeline = timeline,

        events =
        {
            EventHandler("animqueueover", function(inst)
                if inst.AnimState:AnimDone() then
                    if inst.isfavoritefood == 1 then
						inst.sg:GoToState("playful2")
                    else
						inst.sg:GoToState("idle")
					end	
                end
            end),
        },

        onexit = fns ~= nil and fns.onexit or nil,
    })
end

--------------------------------------------------------------------------
SGCritterStates.AddHungry = function(states, timeline)
    table.insert(states, State
    {
        name = "hungry",
        tags = {"idle"},
        
        onenter = function(inst)
            inst.AnimState:PlayAnimation(inst.animsdon.hungry)
        end,
       
        timeline = timeline,

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

--------------------------------------------------------------------------
SGCritterStates.AddNuzzle = function(states, actionhandlers, timeline, fns)
    table.insert(actionhandlers, ActionHandler(ACTIONS.NUZZLE, "nuzzle"))

    table.insert(states, State
    {
		name = "nuzzle",
		tags = {"busy"},

		onenter = function(inst)
            if inst.components.locomotor then
                inst.components.locomotor:StopMoving()
            end
            inst.AnimState:PlayAnimation(inst.animsdon.emote_nuzzle)
            
            inst.sg.mem.prevnuzzletime = GetTime()

            if fns ~= nil and fns.onenter ~= nil then
                fns.onenter(inst)
            end
		end,

		onexit = function(inst)
			inst:PerformBufferedAction()
			inst:ClearBufferedAction()
		end,

		timeline = timeline,

		events =
		{
			EventHandler("animover", function(inst) 
                if inst.AnimState:AnimDone() then
					inst:PushEvent("critter_onnuzzle")
					inst.sg:GoToState("idle") 
				end
			end)
		},

        onexit = fns ~= nil and fns.onexit or nil,
    })
end

--------------------------------------------------------------------------
SGCritterStates.AddPetEmote = function(states, timeline, onexit)
    table.insert(states, State
    {
		name = "emote_pet",
		tags = {"busy"},

		onenter = function(inst)
            inst.AnimState:PlayAnimation(inst.animsdon.emote_pet)
		end,

		timeline = timeline,

		events =
		{
			EventHandler("animover", function(inst) 
                if inst.AnimState:AnimDone() then
					inst:PushEvent("critter_onpet")
					inst.sg:GoToState("idle") 
				end
			end)
		},
		
		onexit = onexit,
    })
end
--------------------------------------------------------------------------
SGCritterStates.AddPlayWithOtherCritter = function(states, events, timeline, onexit)
	table.insert(events, EventHandler("critterplayful", function(inst, data)
		local playful_delay = inst.components.crittertraits:IsDominantTrait("playful") and TUNING.CRITTER_DOMINANTTRAIT_PLAYFUL_WITHOTHER_DELAY or TUNING.CRITTER_PLAYFUL_DELAY
		
		if inst:IsPlayful() and data.target ~= nil and data.target:IsValid() 
	        and (GetTime() - (inst.sg.mem.prevplayfultime or 0) > playful_delay)
	        and not inst.sg:HasStateTag("playful") then
		
			inst.sg.mem.queuedplayfultarget = data.target
            inst.sg.mem.queueplayfulanim = inst.sg.mem.playfulanim ~= 1 and 1 or 2
            
		    data.target:PushEvent("critterplaywithme", {target=inst, anim=(inst.sg.mem.queueplayfulanim == 1 and 2 or 1)})
		end
	end))

	table.insert(events, EventHandler("critterplaywithme", function(inst, data)
		if inst:IsPlayful() and data.target ~= nil and data.target:IsValid() then
			if inst.sg.mem.queuedplayfultarget == nil or inst.sg.mem.queuedplayfultarget == data.target then
				inst.sg.mem.queuedplayfultarget = data.target
				inst.sg.mem.queueplayfulanim = data.anim
			end
		end
	end))
	

    table.insert(states, State
    {
		name = "playful",
		tags = {"busy", "canrotate", "playful"},

		onenter = function(inst, target)
            if inst.components.locomotor then
                inst.components.locomotor:StopMoving()
            end
            
            inst.sg.mem.playfulanim = inst.sg.mem.queueplayfulanim
            inst.sg.mem.queueplayfulanim = nil
            
			inst:PushEvent("oncritterplaying")
            
            if target ~= nil and target:IsValid() then
				inst:ForceFacePoint(target:GetPosition())
			end

            if inst.sg.mem.playfulanim == nil or inst.sg.mem.playfulanim == 1 then
				inst.AnimState:PlayAnimation(inst.animsdon.emote_playful)
			else
				inst.sg:GoToState("playful2") 
			end

		end,

		timeline = timeline ~= nil and timeline.active or nil,

        onexit = onexit ~= nil and onexit.active or nil,

		events =
		{
			EventHandler("animover", function(inst) 
                if inst.AnimState:AnimDone() then
					inst.sg:GoToState("idle") 
				end
			end)
		},
    })
    
    table.insert(states, State
    {
		name = "playful2",
		tags = {"busy", "canrotate", "playful"},

		onenter = function(inst, target)          
			inst.AnimState:PlayAnimation(inst.animsdon.emote_playful)
		end,

		timeline = timeline ~= nil and timeline.passive or nil,

        onexit = onexit ~= nil and onexit.inactive or nil,

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

end

--------------------------------------------------------------------------
local function walkontimeout(inst)
    inst.sg:GoToState("walk")
end

SGCritterStates.AddWalkStates = function(states, timelines, softstop)
    table.insert(states, State
    {
        name = "walk_start",
        tags = { "moving", "canrotate", "softstop" },

        onenter = function(inst)
            inst.components.locomotor:WalkForward()
            inst.AnimState:PlayAnimation(inst.animsdon.walk_pre)
        end,

        timeline = timelines ~= nil and timelines.starttimeline or nil,

        events =
        {
			EventHandler("animover", function(inst)
				if inst.AnimState:AnimDone() then
					inst.sg:GoToState("walk")
				end
			end),
        },
    })

    table.insert(states, State
    {
        name = "walk",
        tags = { "moving", "canrotate", "softstop" },

        onenter = function(inst)
			
			if inst.checkpetdonsize > 0.6 then
				inst.components.locomotor:SetExternalSpeedMultiplier(inst, "morefast", 0.92)
			else
				inst.components.locomotor:SetExternalSpeedMultiplier(inst, "morefast", 1.8)
			end

            
			inst.components.locomotor:WalkForward()			
            inst.AnimState:PlayAnimation(inst.animsdon.walk_loop, true)
            inst.sg:SetTimeout(inst.AnimState:GetCurrentAnimationLength())
        end,

        timeline = timelines ~= nil and timelines.walktimeline or nil,

        ontimeout = walkontimeout,
    })

    table.insert(states, State
    {
        name = "walk_stop",
        tags = { "canrotate", "softstop" },

        onenter = function(inst)
            if softstop == true or (type(softstop) == "function" and softstop(inst)) then
                inst.AnimState:PushAnimation(inst.animsdon.walk_pst, false)
                if inst.AnimState:IsCurrentAnimation(inst.animsdon.walk_pst) then
                    inst.components.locomotor:StopMoving()
                else
                    local remaining = inst.AnimState:GetCurrentAnimationLength() - inst.AnimState:GetCurrentAnimationTime() - (inst:HasTag("flying") and 0 or 2 * FRAMES)
                    if remaining > 0 then
                        inst.sg.statemem.softstopmult = .9
                        inst.components.locomotor:SetExternalSpeedMultiplier(inst, "softstop", inst.sg.statemem.softstopmult)
                        inst.components.locomotor:WalkForward()
                        inst.sg:SetTimeout(remaining)
                    else
                        inst.components.locomotor:StopMoving()
                    end
                end
            else
                inst.components.locomotor:StopMoving()
                inst.AnimState:PlayAnimation(inst.animsdon.walk_pst)
            end
        end,

        timeline = timelines ~= nil and timelines.endtimeline or nil,

        onupdate = function(inst)
            if inst.sg.statemem.softstopmult ~= nil then
                inst.sg.statemem.softstopmult = inst.sg.statemem.softstopmult * .9
                inst.components.locomotor:SetExternalSpeedMultiplier(inst, "softstop", inst.sg.statemem.softstopmult)
                inst.components.locomotor:WalkForward()
            end
        end,

        ontimeout = function(inst)
			inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "morefast")
            inst.sg.statemem.softstopmult = nil
            inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "softstop")
            inst.components.locomotor:StopMoving()
        end,

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

        onexit = function(inst)
            if inst.sg.statemem.softstopmult ~= nil then
                inst.components.locomotor:RemoveExternalSpeedMultiplier(inst, "softstop")
            end
        end,
    })
end


local function sleepexonanimover(inst)
    if inst.AnimState:AnimDone() then
        inst.sg.statemem.continuesleeping = true
        inst.sg:GoToState(inst.sg.mem.sleeping and "sleeping" or "wake")
    end
end

local function sleepingexonanimover(inst)
    if inst.AnimState:AnimDone() then
        inst.sg.statemem.continuesleeping = true
        inst.sg:GoToState("sleeping")
    end
end

local function wakeexonanimover(inst)
    if inst.AnimState:AnimDone() then
        inst.sg:GoToState(inst.sg.mem.sleeping and "sleep" or "idle")
    end
end

SGCritterStates.AddSleepExStates = function(states, timelines, fns)
    table.insert(states, State
    {
        name = "sleep",
        tags = { "busy", "sleeping", "nowake" },

        onenter = function(inst)
            if inst.components.locomotor ~= nil then
                inst.components.locomotor:StopMoving()
            end
            inst.AnimState:PlayAnimation(inst.animsdon.sleep_pre)
            if fns ~= nil and fns.onsleep ~= nil then
                fns.onsleep(inst)
            end
        end,

        timeline = timelines ~= nil and timelines.starttimeline or nil,

        events =
        {
            EventHandler("animover", sleepexonanimover),
        },

        onexit = function(inst)
            if not inst.sg.statemem.continuesleeping and inst.components.sleeper ~= nil and inst.components.sleeper:IsAsleep() then
                inst.components.sleeper:WakeUp()
            end
            if fns ~= nil and fns.onexitsleep ~= nil then
                fns.onexitsleep(inst)
            end
        end,
    })

    table.insert(states, State
    {
        name = "sleeping",
        tags = { "busy", "sleeping" },

        onenter = function(inst)
            inst.AnimState:PlayAnimation(inst.animsdon.sleep_loop)
            if fns ~= nil and fns.onsleeping ~= nil then
                fns.onsleeping(inst)
            end
        end,

        timeline = timelines ~= nil and timelines.sleeptimeline or nil,

        events =
        {
            EventHandler("animover", sleepingexonanimover),
        },

        onexit = function(inst)
            if not inst.sg.statemem.continuesleeping and inst.components.sleeper ~= nil and inst.components.sleeper:IsAsleep() then
                inst.components.sleeper:WakeUp()
            end
            if fns ~= nil and fns.onexitsleeping ~= nil then
                fns.onexitsleeping(inst)
            end
        end,
    })

    table.insert(states, State
    {
        name = "wake",
        tags = { "busy", "waking", "nosleep" },

        onenter = function(inst)
            if inst.components.locomotor ~= nil then
                inst.components.locomotor:StopMoving()
            end
            inst.AnimState:PlayAnimation(inst.animsdon.sleep_pst)
            if inst.components.sleeper ~= nil and inst.components.sleeper:IsAsleep() then
                inst.components.sleeper:WakeUp()
            end
            if fns ~= nil and fns.onwake ~= nil then
                fns.onwake(inst)
            end
        end,

        timeline = timelines ~= nil and timelines.waketimeline or nil,

        events =
        {
            EventHandler("animover", wakeexonanimover),
        },

        onexit = fns ~= nil and fns.onexitwake or nil,
    })
end