require "prefabutil"

local assets =
{
    Asset("ANIM", "anim/esentry.zip"),
	
	Asset("ATLAS", "images/inventoryimages/esentry.xml"),
    Asset("IMAGE", "images/inventoryimages/esentry.tex"),

    Asset("ANIM", "anim/esentry_item.zip"),
}

local prefabs =
{
    "esentry_bullet",
    "esentry_rocket",
    "scrap",
    "gunpowder",
}


-- attacking functions ---------------------

local brain = require "brains/eyeturretbrain" --borrow

local function EquipWeapon(inst)
    if inst.components.inventory and not inst.components.inventory:GetEquippedItem(EQUIPSLOTS.HANDS) then
        local weapon = CreateEntity()
        --[[Non-networked entity]]
        weapon.entity:AddTransform()
        weapon:AddComponent("weapon")
	if inst:HasTag("lvl1") then
            weapon.components.weapon:SetDamage(SENTRY_DAMAGE)
	end
	if inst:HasTag("lvl2") then
            weapon.components.weapon:SetDamage(SENTRY_DAMAGE + 4)
	end
	if inst:HasTag("lvl3") then
            weapon.components.weapon:SetDamage(SENTRY_DAMAGE + 6)
	end
        weapon.components.weapon:SetRange(inst.components.combat.attackrange, inst.components.combat.attackrange+4)
	weapon.components.weapon:SetProjectile("esentry_bullet")
        weapon:AddComponent("inventoryitem")
        weapon.persists = false
        weapon.components.inventoryitem:SetOnDroppedFn(weapon.Remove)
        weapon:AddComponent("equippable")
        inst.components.inventory:Equip(weapon)
    end
end

local function retargetfn(inst)
    local newtarget = FindEntity(inst, SENTRY_RANGE, function(guy)
            return  guy.components.combat and 
                    inst.components.combat:CanTarget(guy) and
                    (guy.components.combat.target == GetPlayer() or GetPlayer().components.combat.target == guy or guy:HasTag("hostile"))
    end)

    return newtarget
end

local function shouldKeepTarget(inst, target)
    if target and target:IsValid() and
        (target.components.health and not target.components.health:IsDead()) then
        local distsq = target:GetDistanceSqToInst(inst)
        return distsq < SENTRY_RANGE*SENTRY_RANGE
    else
        return false
    end
end

local function OnAttacked(inst, data)
    EquipWeapon(inst)
    local attacker = data and data.attacker
    if attacker == GetPlayer() then
        return
    end
    inst.components.combat:SetTarget(attacker)
end

local function lighttweencb(inst, light)
    if light ~= nil then
        light:Enable(false)
    end
end

local function dotweenin(inst, l)
    inst.components.lighttweener:StartTween(nil, 0, .65, .7, nil, 0.15, lighttweencb)
end

-- structure functions -------------------

local function SetNamedInfo(inst)
    if inst:HasTag("lvl1") then
	inst.components.named:SetName("Sentry Gun lvl 1".."\n"..inst.components.fueled.currentfuel.." Rounds Remaining".."\n"..inst.components.health.currenthealth.." Health")
    elseif inst:HasTag("lvl2") then
	inst.components.named:SetName("Sentry Gun lvl 2".."\n"..inst.components.fueled.currentfuel.." Rounds Remaining".."\n"..inst.components.health.currenthealth.." Health")
    elseif inst:HasTag("lvl3") then
	inst.components.named:SetName("Sentry Gun lvl 3".."\n"..inst.components.fueled.currentfuel.." Rounds Remaining".."\n"..inst.components.health.currenthealth.." Health")
    end
end

local function SetHitAnim(inst)
    if inst:HasTag("lvl1") then
	inst.AnimState:PlayAnimation("hit")
	inst.AnimState:PushAnimation("idle_loop", true)
    elseif inst:HasTag("lvl2") then
	inst.AnimState:PlayAnimation("hit2")
	inst.AnimState:PushAnimation("idle_loop_2", true)
    elseif inst:HasTag("lvl3") then
	inst.AnimState:PlayAnimation("hit3")
	inst.AnimState:PushAnimation("idle_loop_3", true)
    end
end

local function upgrade(inst)
    local item = inst.components.inventory:GetEquippedItem(EQUIPSLOTS.HANDS) or nil
    if inst.upgradelevel < 30 then
	inst:AddTag("lvl1")
	inst.components.fueled.maxfuel = 100
	inst.components.health:SetMaxHealth(SENTRY_HEALTH)
        inst.AnimState:PushAnimation("idle_loop", true)
    end
    if inst.upgradelevel >= 30 and inst.upgradelevel < 70 then
	inst:AddTag("lvl2")
	inst:RemoveTag("lvl1")
	inst.components.fueled.maxfuel = 200
	inst.components.health:SetMaxHealth(SENTRY_HEALTH * 2)
	inst.AnimState:PlayAnimation("upgrade2")
	inst.AnimState:PushAnimation("idle_loop_2", true)
    end
    if inst.upgradelevel >= 70 then
	inst:AddTag("lvl3")
	inst:AddTag("rocketsready")
	inst:RemoveTag("lvl1")
	inst:RemoveTag("lvl2")
	inst.components.fueled.maxfuel = 300
	inst.components.health:SetMaxHealth(SENTRY_HEALTH * 3)
	inst.AnimState:PlayAnimation("upgrade3")
	inst.AnimState:PushAnimation("idle_loop_3", true)
    end
    if item ~= nil then
	inst.components.inventory:DropItem(item)
	EquipWeapon(inst)
    end
    SetNamedInfo(inst)
end

local function onbuilt(inst, builder)
    inst.AnimState:PlayAnimation("place")
    inst.components.fueled.currentfuel = 100
    upgrade(inst)
    inst.SoundEmitter:PlaySound("dontstarve/characters/wx78/levelup")
end

local function onsave(inst, data)
    data.upgradelevel = inst.upgradelevel
    data.ammo = inst.components.fueled.currentfuel
end

local function onpreload(inst, data)
    inst.upgradelevel = data.upgradelevel
    inst.components.fueled.currentfuel = data.ammo
    upgrade(inst)
end

local function IsScrap(item)
    return item.prefab == "scrap"
end

local function IsGunpowder(item)
    return item.prefab == "gunpowder"
end

local function workup(inst, worker)
    local scrapstack = worker.components.inventory:FindItem(IsScrap)
    if scrapstack ~= nil then
	worker.components.inventory:ConsumeByName("scrap", 1)
	inst.upgradelevel = inst.upgradelevel + 1
	if inst.upgradelevel == 30 or inst.upgradelevel == 70 then
	    inst.SoundEmitter:PlaySound("dontstarve/characters/wx78/levelup")
	    upgrade(inst)
	end
    end
end

local function onhammered(inst)
    inst.components.lootdropper:DropLoot()
    local fx = SpawnPrefab("collapse_small")
    fx.Transform:SetPosition(inst.Transform:GetWorldPosition())
    inst:Remove()
end

local function onhit(inst, worker)
    SetHitAnim(inst)

    if worker and worker.components.inventory:GetEquippedItem(EQUIPSLOTS.HANDS).prefab == "tf2wrench" then
        local gpstack = worker.components.inventory:FindItem(IsGunpowder)
        local scrapstack = worker.components.inventory:FindItem(IsScrap)
	inst.components.workable:SetWorkLeft(4)
	
	if inst.components.health.currenthealth ~= inst.components.health.maxhealth then
	    if scrapstack ~= nil then
		worker.components.inventory:ConsumeByName("scrap", 1)
		inst.components.health.currenthealth = inst.components.health.currenthealth + 10
		if inst.components.health.currenthealth >= inst.components.health.maxhealth then
		    inst.components.health.currenthealth = inst.components.health.maxhealth
		end
	    end
	end

	if gpstack ~= nil and inst.components.fueled.currentfuel < inst.components.fueled.maxfuel then
	    if inst:HasTag("lvl1") then
		inst.components.fueled.currentfuel = inst.components.fueled.currentfuel + 10
	    elseif inst:HasTag("lvl2") then
		inst.components.fueled.currentfuel = inst.components.fueled.currentfuel + 20
	    elseif inst:HasTag("lvl3") then
		inst.components.fueled.currentfuel = inst.components.fueled.currentfuel + 30
	    end
	    if inst.components.fueled.currentfuel >= inst.components.fueled.maxfuel then
		inst.components.fueled.currentfuel = inst.components.fueled.maxfuel
	    end
	    worker.components.inventory:ConsumeByName("gunpowder", 1)
	    inst.SoundEmitter:PlaySound("dontstarve/common/birdcage_craft")
	    SetNamedInfo(inst)
	    return
	end

	if scrapstack ~= nil and inst.components.fueled.currentfuel < inst.components.fueled.maxfuel then
	    if inst:HasTag("lvl1") then
		inst.components.fueled.currentfuel = inst.components.fueled.currentfuel + 5
	    elseif inst:HasTag("lvl2") then
		inst.components.fueled.currentfuel = inst.components.fueled.currentfuel + 10
	    elseif inst:HasTag("lvl3") then
		inst.components.fueled.currentfuel = inst.components.fueled.currentfuel + 15
	    end
	    if inst.components.fueled.currentfuel >= inst.components.fueled.maxfuel then
		inst.components.fueled.currentfuel = inst.components.fueled.maxfuel
	    end
	    worker.components.inventory:ConsumeByName("scrap", 1)
	    inst.SoundEmitter:PlaySound("dontstarve/common/birdcage_craft")
	    SetNamedInfo(inst)
	    return
	end

	if not inst:HasTag("lvl3") then
	    inst.tick = 0
            while inst.tick ~= 5 and not inst:HasTag("lvl3") do
               workup(inst, worker)
	       inst.tick = inst.tick + 1
            end
	end

    end
end

local function resetrockets(inst)
    if inst.rockettask == nil then
	inst.rockettask = inst:DoTaskInTime(5, function(inst)
	    inst:AddTag("rocketsready")
	    inst.rockettask:Cancel()
	    inst.rockettask = nil
	end)
    end
end

local function fn()
    local inst = CreateEntity()
    local trans = inst.entity:AddTransform()
    local anim = inst.entity:AddAnimState()

    inst.entity:AddSoundEmitter()
    inst.entity:AddMiniMapEntity()
    inst.entity:AddLight()

    MakeObstaclePhysics(inst, 1)

    inst.MiniMapEntity:SetIcon("esentry.tex")

    inst.AnimState:SetBank("esentry")
    inst.AnimState:SetBuild("esentry")
    inst.AnimState:PlayAnimation("idle_loop", true)

    inst:AddTag("structure")
    inst:AddTag("eyeturret")
    inst:AddTag("companion")
    inst:AddTag("esentry")

    inst:AddComponent("inventory")

    inst:AddComponent("inspectable")

    inst:AddComponent("lootdropper")

    inst:AddComponent("named")

    inst:AddComponent("engieworkable")
    inst.components.engieworkable:SetWorkAction(ACTIONS.ENGIEWORK)
    inst.components.engieworkable:SetOnWorkCallback(onhit)

    inst:AddComponent("lighttweener")
    inst.components.lighttweener:StartTween(inst.Light, 0, .65, .7, {251/255, 234/255, 234/255}, 0, lighttweencb)

    inst:AddComponent("health")
    inst.components.health:SetMaxHealth(SENTRY_HEALTH)
    inst.components.health.canheal = false
    local modname = KnownModIndex:GetModActualName("The Engineer")
    local sentryhregen = GetModConfigData("sentryh_regen", modname)
    if sentryhregen == "y" then
	inst.components.health:StartRegen(20, 1)
    end

    inst:AddComponent("fueled")
    inst.components.fueled.fueltype = "PIGTORCH"
    inst.components.fueled.accepting = false

    inst:AddComponent("combat")
    inst.components.combat:SetRange(SENTRY_RANGE)
    inst.components.combat:SetDefaultDamage(SENTRY_DAMAGE)
    inst.components.combat:SetAttackPeriod(SENTRY_ROF)
    inst.components.combat:SetRetargetFunction(0, retargetfn)
    inst.components.combat:SetKeepTargetFunction(shouldKeepTarget)

    inst:AddComponent("workable")
    inst.components.workable:SetWorkAction(ACTIONS.HAMMER)
    inst.components.workable:SetMaxWork(4)
    inst.components.workable:SetWorkLeft(4)
    inst.components.workable:SetOnFinishCallback(onhammered)
    inst.components.workable:SetOnWorkCallback(onhit)
    inst.components.workable.destroyed = "NODESTROY" --Hack to prevent workable:Destroy() only

    inst:SetStateGraph("SGesentry")
    inst:SetBrain(brain)

    inst.OnSave = onsave
    inst.OnPreLoad = onpreload
    inst:ListenForEvent( "onbuilt", onbuilt)

    inst:ListenForEvent("attacked", OnAttacked)
    inst:ListenForEvent("rocketsshot", resetrockets)
    inst:ListenForEvent("checkwep", EquipWeapon)
    inst:ListenForEvent("namedinfo", SetNamedInfo)
    inst:ListenForEvent("hitanim", SetHitAnim)

    inst.dotweenin = dotweenin
    inst.upgradelevel = 0

    inst.checkammo = inst:DoPeriodicTask(.1, function(inst)
        SetNamedInfo(inst)
	if inst.components.fueled.currentfuel == 0 then
	    inst.components.named:SetName("Sentry Gun".."\n".."EMPTY!".."\n"..inst.components.health.currenthealth.." Health")
	    if math.random() > .96 then
	        local x,y,z = inst.Transform:GetWorldPosition()
	        SpawnPrefab("sparks").Transform:SetPosition(x + math.random(), 1.75, z + math.random())
	    end
	end
    end)

    return inst
end

return Prefab("common/esentry", fn, assets, prefabs),
	MakePlacer("common/esentry_placer", "esentry_item", "esentry_item", "idle")