--***********************************************************
--**                    THE INDIE STONE                    **
--***********************************************************

if isClient() then return end

require "Map/SGlobalObject"

STrapGlobalObject = SGlobalObject:derive("STrapGlobalObject")

function STrapGlobalObject:new(luaSystem, globalObject)
    local o = SGlobalObject.new(self, luaSystem, globalObject)
    return o;
end

function STrapGlobalObject:initNew()
    self.trapType = ""
    self.trapBait = ""
    self.trapBaitDay = 0
    self.lastUpdate = 0
    self.baitAmountMulti = 0
    self.animal = {}
    self.animalHour = 0
    self.openSprite = ""
    self.closedSprite = ""
    self.zone = "TownZone"
    self.player = "unknown"
    self.trappingSkill = 0
    self.destroyed = false;
end

function STrapGlobalObject:stateFromIsoObject(isoObject)
    local square = isoObject:getSquare()
    self:initNew()

    -- Compatibility: Data used to be stored in the square not the object
    if STrapSystem.isValidModData(square:getModData()) then
        self:noise('moving square modData to object')
        self:fromModData(square:getModData())
        self:reinitModData(square)
        self:toModData(isoObject:getModData())
    end

    self:fromModData(isoObject:getModData())

    self:setDef()
    if self.animal.type then
        isoObject:setSprite(self.closedSprite)
    else
        isoObject:setSprite(self.openSprite)
    end

    -- MapObjects-related code (see MOTrap.lua) might have changed the
    -- isoObject when it was loaded, we must sync with clients.
    if isServer() then
        isoObject:sendObjectChange('name')
        isoObject:sendObjectChange('sprite')
        isoObject:transmitModData()
    end
end

function STrapGlobalObject:stateToIsoObject(isoObject)
    local square = isoObject:getSquare()
    if STrapSystem.isValidModData(square:getModData()) then
        -- Compatibility: data used to be stored in the square not the object
        self:noise('removing square modData')
        self:reinitModData(square)
    end

    if self.destroyed then
        self:spawnDestroyItems(square, isoObject)
        -- FIXME: may not be safe to do here.
        self:removeIsoObject()
        return;
    end

    self:toModData(isoObject:getModData())

    if self.trapBait == "" then
        self.bait = nil
    else
        self.bait = self.trapBait
    end

    self:setDef()
    self.animal = self.animal or {}
    if self.animal.type then
        isoObject:setSprite(self.closedSprite)
    else
        isoObject:setSprite(self.openSprite)
    end

    if isServer() then
        isoObject:sendObjectChange('name')
        isoObject:sendObjectChange('sprite')
        isoObject:transmitModData()
    end
end

function STrapGlobalObject:calculTrap(square)
    self:setDef();
    if self.bait then
        self:checkForAnimal(square);
    end
    -- already something inside this trap
    if not self.animal.type then
        self:checkDestroy(square);
    end
end

function STrapGlobalObject:sendSound(soundName, square)
    if isServer() then
        playServerSound(soundName, square)
        return
    end
    square:playSound(soundName, true)
end

function STrapGlobalObject:addSound(square)
    -- add sound to attract the surrounding zombies
    if self.animal.type and square then
        if ZombRand(4) == 0 then
            addSound(self:getIsoObject(), square:getX(),square:getY(),square:getZ(), 40, 10);
        end
        if self.animal.type == "bird" then
--            getSoundManager():PlayWorldSoundWav("PZ_BidTrapped_wood_0" .. ZombRand(3)+1, square, 0, 15, 2, true);
            if self:isMetalTrap() then
                self:sendSound("BirdInMetalTrap", square);
            elseif self:isWoodenTrap() then
                self:sendSound("BirdInWoodTrap", square);
            end
        else
            if self:isWoodenTrap() then
--                getSoundManager():PlayWorldSoundWav("PZ_AnimalTrapped_wood_0" .. ZombRand(4)+1, square, 0, 15, 2, true);
                self:sendSound("AnimalInWoodTrap", square);
            elseif self:isMetalTrap() then
--                getSoundManager():PlayWorldSoundWav("PZ_AnimalTrappedMetal_0" .. ZombRand(3)+1, square, 0, 15, 2, true);
                self:sendSound("AnimalInMetalTrap", square);
            end
        end
    end
end

function STrapGlobalObject:isWoodenTrap()
    return self.trapType == "Base.TrapCrate" or self.trapType == "Base.TrapBox" or self.trapType == "Base.TrapStick"
end

function STrapGlobalObject:isMetalTrap()
    return self.trapType =="Base.TrapCage"
end

-- check if an animal stole your bait/destroy the trap (every hour)
function STrapGlobalObject:checkDestroy(square)
    if self.bait and ZombRand(self.def.trapStrength + 10) == 0 then
        self:removeBait();
    end
    -- slight chance of trap being destroying (by bigger animal for example)
    if (not square) and (ZombRand(40) == 0 and ZombRand(self.def.trapStrength) == 0) then
        self.destroyed = true;
    end
end

function STrapGlobalObject:removeBait(character)
    if self.bait then
--        print("remove bait");
--        if character then -- give the player his item
--            local bait = InventoryItemFactory.CreateItem(self.bait)
--            bait:setAge(self.trapBaitDay);
--            bait:multiplyFoodValues(-0.05 / bait:getBaseHunger())
--            if isServer() then
--                character:sendObjectChange('addItem', { item = bait })
--            else
--                character:getInventory():AddItem(bait)
--            end
--        end
        self.bait = nil;
        self.trapBait = ""
        self.trapBaitDay = 0;
        self:toObject(self:getIsoObject(), true);
    end
end

function STrapGlobalObject:removeAnimal(character)
    if character then
--        print("remove animal");
        local item = InventoryItemFactory.CreateItem(self.animal.item)
        -- Randomize the hunger reduction of the animal
        local size = ZombRand(self.animal.minSize, self.animal.maxSize);
        local xp = size / 3;
        if xp < 3 then
            xp = 3;
        end
        -- give xp only if you are the one who placed this trap);
        if self.player == character:getUsername() then
            if isServer() then
                character:sendObjectChange('addXp', { perk = Perks.Trapping:index(), xp = xp })
            else
                character:getXp():AddXP(Perks.Trapping, xp);
            end
        end

        local typicalSize  = item:getBaseHunger() * -100;
        local statsModifier = size / typicalSize;

        -- Set the animal's stats depending on the random size
        item:setBaseHunger(item:getBaseHunger() * statsModifier);
        item:setHungChange(item:getHungChange() * statsModifier);

        local actualWeight = item:getActualWeight() * statsModifier;
        item:setActualWeight(actualWeight);
        item:setWeight(actualWeight);
        item:setCustomWeight(true);

        item:setCarbohydrates(item:getCarbohydrates() * statsModifier);
        item:setLipids(item:getLipids() * statsModifier);
        item:setProteins(item:getProteins() * statsModifier);
        item:setCalories(item:getCalories() * statsModifier);

        if isServer() then
            character:sendObjectChange('addItem', { item = item })
        else
            character:getInventory():AddItem(item)
        end
    end
    self.animalHour = 0;
    self.animal = {};
    -- change the sprite of the item
    local trapObject = self:getIsoObject();
    if trapObject then
        trapObject:setSprite(self.openSprite);
        trapObject:transmitUpdatedSpriteToClients()
        self:toObject(trapObject, true)
    end
end

-- check if an animal is caught (every hour)
function STrapGlobalObject:checkForAnimal(square)
    -- you won't find an animal if a player is near the trap, so we check the trap only if it's streamed
    if square then
        -- (note turbo) if square~=nil do a check to see if theres any hoppables near the trap, this is an exploit to make traps invincible to zombie damage.
        -- when placing the trap it does check for hoppables, but a window frame could be placed afterwards.
        -- when this is the case, remove bait and animals if any.
        if self:checkForWallExploit(square) then
            self:removeAnimal();
            self:removeBait()
        end
        return;
    end
    if self.destroyed then
        return;
    end
    -- first, get which animal we'll attract
    local animalsList = {};
    for i,v in ipairs(Animals) do
        -- check if at this hour we can get this animal
        local timesOk = self:checkTime(v);
--        local timesOk = true;
        if v.traps[self.trapType] and
                v.baits[self.bait] and ZombRand(100) < (v.traps[self.trapType] + v.baits[self.bait] + (self.trappingSkill * 1.5)) and
                timesOk and v.zone[self.zone] and ZombRand(100) < (v.zone[self.zone] + (self.trappingSkill * 1.5)) then -- this animal can be caught by this trap and we have the correct bait for it
            -- now check if the bait is still fresh
            if self:checkBaitFreshness() then
--                print("can catch " .. v.type);
                table.insert(animalsList, v);
            end
        end
    end
    -- random an animal
    if #animalsList > 0 then
        local int = ZombRand(#animalsList) + 1;
        local testAnimal = animalsList[int];
        if testAnimal then
--            print("get animal : " .. testAnimal.type .. " in zone " .. self.zone);
            self:noise('trapped '..testAnimal.type..' '..self.x..','..self.y..','..self.z)
            self:setAnimal(testAnimal)
        end
    end
end

function STrapGlobalObject:setAnimal(animal)
    if not animal or not animal.type or not animal.item then
        self.noise('invalid animal for trap '..tostring(animal))
        return
    end
    self.animal = animal
    self.animalHour = 0
    self:removeBait()
    -- change the sprite of the item
    local trapObject = self:getIsoObject()
    if trapObject then
        self:toObject(trapObject, true)
        trapObject:setSprite(self.closedSprite)
        trapObject:transmitUpdatedSpriteToClients()
    end
end

function STrapGlobalObject:addBait(bait, age, baitAmountMulti, player)
    self.bait = bait
    self.trapBait = bait
    self.trapBaitDay = age
    self.lastUpdate = getGameTime():getWorldAgeHours() / 24;
    self.baitAmountMulti = baitAmountMulti;
    self.player = player:getUsername();
    local object = self:getIsoObject()
    self:toObject(object, true)
end

function STrapGlobalObject:setDef()
    if self.def then return; end
    for i,v in ipairs(Traps) do
        if v.type == self.trapType then
            self.def = v;
            break;
        end
    end
end

function STrapGlobalObject:toObject(object, transmitData)
    if object then
        self:toModData(object:getModData())
        if transmitData then
            object:transmitModData();
        end
    end
end

function STrapGlobalObject:fromModData(modData)
    self.trapType = modData["trapType"];
    self.trapBait = modData["trapBait"]
    if self.trapBait == "" then
        self.bait = nil;
    else
        self.bait = self.trapBait
    end
    self.trapBaitDay = modData["trapBaitDay"];
    self.lastUpdate = modData["lastUpdate"];
    self.baitAmountMulti = modData["baitAmountMulti"];
    self.animal = {}
    local animalType = modData["animal"];
    for i,v in ipairs(Animals) do
        if v.type == animalType then
            self.animal = v;
        end
    end
    self.animalHour = modData["animalHour"];
    self.openSprite = modData["openSprite"];
    self.closedSprite = modData["closedSprite"];
    self.zone = modData["zone"];
    self.player = modData["player"];
    self.trappingSkill = modData["trappingSkill"];
    self.destroyed = modData["destroyed"];
end

function STrapGlobalObject:toModData(modData)
    modData["trapType"] = self.trapType;
    modData["trapBait"] = self.trapBait or "";
    modData["trapBaitDay"] = self.trapBaitDay;
    modData["lastUpdate"] = self.lastUpdate;
    modData["baitAmountMulti"] = self.baitAmountMulti;
    modData["animal"] = self.animal and self.animal.type or nil;
    modData["animalHour"] = self.animalHour;
    modData["openSprite"] = self.openSprite;
    modData["closedSprite"] = self.closedSprite;
    modData["zone"] = self.zone;
    modData["player"] = self.player;
    modData["trappingSkill"] = self.trappingSkill;
    modData["destroyed"] = self.destroyed;
end

function STrapGlobalObject:checkBaitFreshness()
    local baitItem = InventoryItemFactory.CreateItem(self.bait);
    baitItem:setAge(self.trapBaitDay);
    return baitItem:isFresh();
end

function STrapGlobalObject:checkTime(animal)
    if animal.minHour == animal.maxHour then return true; end -- if min = max then it's 24/7
    local current = getGameTime():getHour()
    if animal.minHour < animal.maxHour then
        return animal.minHour <= current and animal.maxHour >= current
    else
        return animal.minHour <= current or animal.maxHour >= current
    end
end

function STrapGlobalObject:reinitModData(square)
    square:getModData()["trapType"] = nil;
    square:getModData()["trapBait"] = nil;
    square:getModData()["trapBaitDay"] = nil;
    square:getModData()["lastUpdate"] = nil;
    square:getModData()["baitAmountMulti"] = nil;
    square:getModData()["animal"] = nil;
    square:getModData()["animalHour"] = nil;
    square:getModData()["openSprite"] = nil;
    square:getModData()["closedSprite"] = nil;
    square:getModData()["zone"] = nil;
    square:getModData()["player"] = nil;
    square:getModData()["trappingSkill"] = nil;
    square:getModData()["destroyed"] = nil;
    square:transmitModdata()
end

function STrapGlobalObject:checkForWallExploit(square)
    if square then
        local north = getWorld():getCell():getGridSquare(square:getX(), square:getY()-1, square:getZ());
        local south = getWorld():getCell():getGridSquare(square:getX(), square:getY()+1, square:getZ());
        local east = getWorld():getCell():getGridSquare(square:getX()+1, square:getY(), square:getZ());
        local west = getWorld():getCell():getGridSquare(square:getX()-1, square:getY(), square:getZ());
        if square:isHoppableTo(north) or square:isHoppableTo(south) or square:isHoppableTo(east) or square:isHoppableTo(west) then
            return true;
        end
        if square:hasWindowOrWindowFrame() or (south and south:hasWindowOrWindowFrame()) or (east and east:hasWindowOrWindowFrame()) then
            return true;
        end
        return false;
    end
    return false;
end

function STrapGlobalObject:spawnDestroyItems(square, object)
    STrapGlobalObject.SpawnDestroyItems(self.trapType, square, object)
end

function STrapGlobalObject.SpawnDestroyItems(trapType, square, object)
    if square then
        local item = "Base.UnusableWood";

        --traptype might be nil, in that case check isoobject
        if (not trapType) and object and object:hasModData() then
            trapType = object:getModData()["trapType"];
        end

        if trapType then
            for i,v in ipairs(Traps) do
                if v.type == trapType and v.destroyItem then
                    item = v.destroyItem;
                    break;
                end
            end
        end

        if type(item)=="table" then
            for i,v in ipairs(item) do
                local spawnItem = square:AddWorldInventoryItem(v, ZombRand(0.1, 0.5), ZombRand(0.1, 0.5), 0);
                if v=="Base.Twine" then
                    spawnItem:setUsedDelta(spawnItem:getUseDelta());
                end
            end
        else
            local spawnItem = square:AddWorldInventoryItem(item, ZombRand(0.1, 0.5), ZombRand(0.1, 0.5), 0);
            if item=="Base.Twine" then
                spawnItem:setUsedDelta(spawnItem:getUseDelta());
            end
        end
    else
        print("no square for spawnDestroyItems");
    end
end
