require "prefabutil"
local brain = require "brains/dogchesterbrain"

local WAKE_TO_FOLLOW_DISTANCE = 14
local SLEEP_NEAR_LEADER_DISTANCE = 7

local assets =
{
    Asset("ANIM", "anim/ui_chester_shadow_3x4.zip"),
    Asset("ANIM", "anim/ui_chest_3x3.zip"),

    Asset("ANIM", "anim/chester.zip"),
    Asset("ANIM", "anim/liwuhe_build.zip"),
    Asset("ANIM", "anim/liwuhe_shadow_build.zip"),
    Asset("ANIM", "anim/liwuhe_snow_build.zip"),

    Asset("SOUND", "sound/chester.fsb"),
}

local prefabs =
{
    "personal_liwuhe_eyebone",
    "chesterlight",
    "chester_transform_fx",
}

local sounds =
{
    hurt = "dontstarve/creatures/chester/hurt",
    pant = "dontstarve/creatures/chester/pant",
    death = "dontstarve/creatures/chester/death",
    open = "dontstarve/creatures/chester/open",
    close = "dontstarve/creatures/chester/close",
    pop = "dontstarve/creatures/chester/pop",
    boing = "dontstarve/creatures/chester/boing",
    lick = "dontstarve/creatures/chester/lick",
}

local function ShouldWakeUp(inst)
    return DefaultWakeTest(inst) or not inst.components.follower:IsNearLeader(WAKE_TO_FOLLOW_DISTANCE)
end

local function ShouldSleep(inst)
    --print(inst, "ShouldSleep", DefaultSleepTest(inst), not inst.sg:HasStateTag("open"), inst.components.follower:IsNearLeader(SLEEP_NEAR_LEADER_DISTANCE))
    return DefaultSleepTest(inst) and not inst.sg:HasStateTag("open") and inst.components.follower:IsNearLeader(SLEEP_NEAR_LEADER_DISTANCE) and not TheWorld.state.isfullmoon
end

local function ShouldKeepTarget()
    return false -- liwuhe can't attack, and won't sleep if he has a target
end

local function OnOpen(inst)
    if not inst.components.health:IsDead() then
        inst.sg:GoToState("open")
    end
end

local function OnClose(inst)
    if not inst.components.health:IsDead() and inst.sg.currentstate.name ~= "transition" then
        inst.sg:GoToState("close")
    end
	local container = inst.components.container
	local stacksize=0
        for i = 1, container:GetNumSlots() do
            local item = container:GetItemInSlot(i)
            if item and item.prefab=="meatballs" then 
                inst:AddTag("liwuhechop")
				if item.components.stackable then 
                stacksize = item.components.stackable:StackSize()
                end 
				container:RemoveItemBySlot(i)
                item:Remove()
                inst:DoTaskInTime(120*stacksize, function()
                inst:RemoveTag("liwuhechop")
				end)              
            end
			--[[if item and item.prefab=="monsterlasagna" and not inst:HasTag("liwuhechop") then 
                inst:AddTag("liwuhemine")
				if item.components.stackable then 
                stacksize = item.components.stackable:StackSize()
                end 
				container:RemoveItemBySlot(i)
                item:Remove()
                inst:DoTaskInTime(120*stacksize, function()
                inst:RemoveTag("liwuhemine")
				end)              
            end--]]
        end
	if inst:HasTag("spoiler") then
	 for i = 1, container:GetNumSlots() do
            local item = container:GetItemInSlot(i)
            if item then 
			    local replacement = nil 
                if item.components.cookable  then 
                    replacement = item.components.cookable.product    
                 
                if replacement then 
                    local stacksize = 1 
                    if item.components.stackable then 
                        stacksize = item.components.stackable:StackSize()
                    end 
                    local newprefab = SpawnPrefab(replacement)
                    if newprefab.components.stackable then 
                        newprefab.components.stackable:SetStackSize(stacksize)
                    end
                    container:RemoveItemBySlot(i)
                    item:Remove()
                    container:GiveItem(newprefab, i)
                end 
                end				
                if  item.prefab=="cookedsmallmeat" or item.prefab=="smallmeat_dried" or item.prefab=="cookedmeat" or item.prefab=="meat_dried" or item.prefab=="bird_egg_cooked" or 
                    item.prefab=="drumstick_cooked" or item.prefab=="monstermeat_dried" or item.prefab=="cookedmonstermeat" or item.prefab=="plantmeat_cooked" or 
                    item.prefab=="froglegs_cooked" or item.prefab=="batwing_cooked" or item.prefab=="fish_cooked" or item.prefab=="eel_cooked"   
				then 
                    local stacksize = 1 
                    if item.components.stackable then 
                        stacksize = item.components.stackable:StackSize()
                    end 
                    local newprefab = SpawnPrefab("bird_egg")
                    if newprefab.components.stackable then 
                        newprefab.components.stackable:SetStackSize(stacksize)
                    end
                    container:RemoveItemBySlot(i)
                    item:Remove()
                    container:GiveItem(newprefab, i)
                end				
            end
        end
	end
		
end
local function onclose1(inst)
    if not inst.components.health:IsDead() and inst.sg.currentstate.name ~= "transition" then
        inst.sg:GoToState("close")
    end
    print("1")
    local container = inst.components.container
        for i = 1, container:GetNumSlots() do
            local item = container:GetItemInSlot(i)
            if item then 
                local replacement = nil 
                if item.components.cookable  then 
                    replacement = item.components.cookable.product    
                end  
                if replacement then 
                    local stacksize = 1 
                    if item.components.stackable then 
                        stacksize = item.components.stackable:StackSize()
                    end 
                    local newprefab = SpawnPrefab(replacement)
                    if newprefab.components.stackable then 
                        newprefab.components.stackable:SetStackSize(stacksize)
                    end
                    container:RemoveItemBySlot(i)
                    item:Remove()
                    container:GiveItem(newprefab, i)
                end                 
            end
        end
end


local function onclose2(inst)
    if not inst.components.health:IsDead() and inst.sg.currentstate.name ~= "transition" then
        inst.sg:GoToState("close")
    end
    print("2")
    local container = inst.components.container
        for i = 1, container:GetNumSlots() do
            local item = container:GetItemInSlot(i)
            if item then 
			    local replacement = nil 
                if item.components.cookable  then 
                    replacement = item.components.cookable.product    
                 
                if replacement then 
                    local stacksize = 1 
                    if item.components.stackable then 
                        stacksize = item.components.stackable:StackSize()
                    end 
                    local newprefab = SpawnPrefab(replacement)
                    if newprefab.components.stackable then 
                        newprefab.components.stackable:SetStackSize(stacksize)
                    end
                    container:RemoveItemBySlot(i)
                    item:Remove()
                    container:GiveItem(newprefab, i)
                end 
                end				
                if  item.prefab=="cookedsmallmeat" or item.prefab=="smallmeat_dried" or item.prefab=="cookedmeat" or item.prefab=="meat_dried" or item.prefab=="bird_egg_cooked" or 
                    item.prefab=="drumstick_cooked" or item.prefab=="monstermeat_dried" or item.prefab=="cookedmonstermeat" or item.prefab=="plantmeat_cooked" or 
                    item.prefab=="froglegs_cooked" or item.prefab=="batwing_cooked" or item.prefab=="fish_cooked" or item.prefab=="eel_cooked"   
				then 
                    local stacksize = 1 
                    if item.components.stackable then 
                        stacksize = item.components.stackable:StackSize()
                    end 
                    local newprefab = SpawnPrefab("bird_egg")
                    if newprefab.components.stackable then 
                        newprefab.components.stackable:SetStackSize(stacksize)
                    end
                    container:RemoveItemBySlot(i)
                    item:Remove()
                    container:GiveItem(newprefab, i)
                end				
            end
        end
    -- print("[mod]trash can: onclose")
end

-- eye bone was killed/destroyed
local function OnStopFollowing(inst)
    --print("liwuhe - OnStopFollowing")
    inst:RemoveTag("companion")
end

local function OnStartFollowing(inst)
    --print("liwuhe - OnStartFollowing")
    inst:AddTag("companion")
end

local function MorphShadowliwuhe(inst)
    inst.AnimState:SetBuild("liwuhe_shadow_build")
    inst:AddTag("spoiler")

    inst.components.container:WidgetSetup("shadowchester")

    local leader = inst.components.follower.leader    
    if leader ~= nil then
        inst.components.follower.leader:MorphShadowEyebone()
    end

    inst.liwuheState = "SHADOW"
    inst._isshadowliwuhe:set(true)
end


local function MorphSnowliwuhe(inst)
    inst.AnimState:SetBuild("liwuhe_snow_build")
    inst:AddTag("fridge")

    local leader = inst.components.follower.leader
    if leader ~= nil then
        inst.components.follower.leader:MorphSnowEyebone()
    end

    inst.liwuheState = "SNOW"
    inst._isshadowliwuhe:set(false)
end


local function MorphNormalliwuhe(inst)
    inst.AnimState:SetBuild("liwuhe_build")
    inst:RemoveTag("fridge")
    inst:RemoveTag("spoiler")

    inst.components.container:WidgetSetup("chester")

    local leader = inst.components.follower.leader    
    if leader ~= nil then
        inst.components.follower.leader:MorphNormalEyebone()
    end

    inst.liwuheState = "NORMAL"
    inst._isshadowliwuhe:set(false)
end
--


local function CanMorph(inst)
    if inst.liwuheState ~= "NORMAL" or not TheWorld.state.isfullmoon then
        return false, false
    end

    local container = inst.components.container
    if container:IsOpen() then
        return false, false
    end

    local canShadow = true
    local canSnow = true

    for i = 1, container:GetNumSlots() do
        local item = container:GetItemInSlot(i)
        if item == nil then
            return false, false
        end

        canShadow = canShadow and item.prefab == "nightmarefuel"
        canSnow = canSnow and item.prefab == "bluegem"

        if not (canShadow or canSnow) then
            return false, false
        end
    end

    return canShadow, canSnow
end


local function CheckForMorph(inst)
    local canShadow, canSnow = CanMorph(inst)
    if canShadow or canSnow then
        inst.sg:GoToState("transition")
    end
end

local function DoMorph(inst, fn)
    inst.MorphChester = nil
    inst:StopWatchingWorldState("isfullmoon", CheckForMorph)
    inst:RemoveEventCallback("onclose", CheckForMorph)
    fn(inst) 
end
local function Morphliwuhe(inst)
    local canShadow, canSnow = CanMorph(inst)
    if not (canShadow or canSnow) then
        return
    end

    local container = inst.components.container
    for i = 1, container:GetNumSlots() do
        container:RemoveItem(container:GetItemInSlot(i)):Remove()
    end

    DoMorph(inst, canShadow and MorphShadowliwuhe or MorphSnowliwuhe)
end

local function OnSave(inst, data)
    data.liwuheState = inst.liwuheState
end

local function OnPreLoad(inst, data)
    if data == nil then
        return
    elseif data.liwuheState == "SHADOW" then
        DoMorph(inst, MorphShadowliwuhe)
    elseif data.liwuheState == "SNOW" then
        DoMorph(inst, MorphSnowliwuhe)
    end
end

local function OnIsShadowliwuheDirty(inst)
    if inst._isshadowliwuhe:value() ~= inst._clientshadowmorphed then
        inst._clientshadowmorphed = inst._isshadowliwuhe:value()
        inst.replica.container:WidgetSetup(inst._clientshadowmorphed and "shadowchester" or nil)
    end
end

local function create_liwuhe()
    --print("liwuhe - create_liwuhe")

    local inst = CreateEntity()
    
    inst.entity:AddTransform()
    inst.entity:AddAnimState()
    inst.entity:AddSoundEmitter()
    inst.entity:AddDynamicShadow()
    inst.entity:AddMiniMapEntity()
    inst.entity:AddNetwork()

    MakeCharacterPhysics(inst, 75, .5)
    inst.Physics:SetCollisionGroup(COLLISION.CHARACTERS)
    inst.Physics:ClearCollisionMask()
    inst.Physics:CollidesWith(COLLISION.WORLD)
    inst.Physics:CollidesWith(COLLISION.OBSTACLES)
    inst.Physics:CollidesWith(COLLISION.CHARACTERS)

    inst:AddTag("companion")
    inst:AddTag("character")
    inst:AddTag("scarytoprey")
    inst:AddTag("personal_liwuhe")
    inst:AddTag("notraptrigger")
	
    inst:AddTag("_named")

    inst.MiniMapEntity:SetIcon("personal_liwuhe.tex")
    inst.MiniMapEntity:SetCanUseCache(false)

    inst.AnimState:SetBank("liwuhe")
    inst.AnimState:SetBuild("liwuhe_build")

    inst.DynamicShadow:SetSize(2, 1.5)

    inst.Transform:SetFourFaced()

    inst._isshadowliwuhe = net_bool(inst.GUID, "_isshadowliwuhe", "onisshadowliwuhedirty")
	

	

    inst.entity:SetPristine()
	
    if not TheWorld.ismastersim then
		inst:DoTaskInTime(0.1, function(inst)
			inst.replica.container:WidgetSetup(inst._isshadowliwuhe:value() and "shadowchester" or "chester")
		end)
        inst._clientshadowmorphed = false
        inst:ListenForEvent("onisshadowliwuhedirty", OnIsShadowliwuheDirty)
        return inst
    end
	
	-- liwuhe will not be saved normally. He is saved with the player.
	inst.persists = false

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

    --print("   combat")
    inst:AddComponent("combat")
    inst.components.combat.hiteffectsymbol = "liwuhe_body"
    inst.components.combat:SetKeepTargetFunction(ShouldKeepTarget)
	local self = inst.components.combat
	local old = self.GetAttacked
	function self:GetAttacked(attacker, damage, weapon, stimuli)
		if attacker then
			return true
		end
		return old(self, attacker, damage, weapon, stimuli)
	end
    --inst:ListenForEvent("attacked", OnAttacked)

    --print("   health")
    inst:AddComponent("health")
    inst.components.health:SetMaxHealth(TUNING.CHESTER_HEALTH)
    inst.components.health:StartRegen(TUNING.CHESTER_HEALTH_REGEN_AMOUNT, TUNING.CHESTER_HEALTH_REGEN_PERIOD)
    inst:AddTag("noauradamage")

    --print("   inspectable")
    inst:AddComponent("inspectable")
    inst.components.inspectable:RecordViews()
    --inst.components.inspectable.getstatus = GetStatus
    inst.components.inspectable.nameoverride = "liwuhe"

    --print("   locomotor")
    inst:AddComponent("locomotor")
    inst.components.locomotor.walkspeed = 3
    inst.components.locomotor.runspeed = 7

    --print("   follower")
    inst:AddComponent("follower")
    inst:ListenForEvent("stopfollowing", OnStopFollowing)
    inst:ListenForEvent("startfollowing", OnStartFollowing)

    --print("   knownlocations")
    inst:AddComponent("knownlocations")

    --print("   burnable")
    MakeSmallBurnableCharacter(inst, "liwuhe_body")

    --("   container")
    inst:AddComponent("container")
    inst.components.container:WidgetSetup("chester")
    inst.components.container.onopenfn = OnOpen
	inst.components.container.onclosefn = OnClose

	

    --print("   sleeper")
    inst:AddComponent("sleeper")
    inst.components.sleeper:SetResistance(3)
    inst.components.sleeper.testperiod = GetRandomWithVariance(6, 2)
    inst.components.sleeper:SetSleepTest(ShouldSleep)
    inst.components.sleeper:SetWakeTest(ShouldWakeUp)
	
    inst:AddComponent("named")

    MakeHauntableDropFirstItem(inst)
    AddHauntableCustomReaction(inst, function(inst, haunter)
        if math.random() <= TUNING.HAUNT_CHANCE_ALWAYS then
            inst.components.hauntable.panic = true
            inst.components.hauntable.panictimer = TUNING.HAUNT_PANIC_TIME_SMALL
            inst.components.hauntable.hauntvalue = TUNING.HAUNT_SMALL
            return true
        end
        return false
    end, false, false, true)

    --print("   sg")
    inst:SetStateGraph("SGchester")
    inst.sg:GoToState("idle")

    --print("   brain")
    inst:SetBrain(brain)

    inst.liwuheState = "NORMAL"
    inst.MorphChester = Morphliwuhe
    inst:WatchWorldState("isfullmoon", CheckForMorph)
    inst:ListenForEvent("onclose", CheckForMorph)
	inst:DoPeriodicTask(480, function()
    SpawnPrefab("lightbulb").Transform:SetPosition(inst.Transform:GetWorldPosition())
    end)
	
    inst.sounds = sounds

    inst.OnSave = OnSave
    inst.OnPreLoad = OnPreLoad

    --print("liwuhe - create_liwuhe END")
	
		
    return inst
end

return Prefab("common/personal_liwuhe", create_liwuhe, assets, prefabs)
