require "ISBaseObject"

local TABAS_BathingBenefits = ISBaseObject:derive("TABAS_BathingBenefits")

local BathSaltDefs = require("TABAS_BathSaltDefs")
local TABAS_GT = require("TABAS_GameTimes")

local sittingEnduranceMultiplier = 5.0 -- from ZomboidGlobals
local imobileEnduranceIncrease = 0.0000930/3
local benefitMax = 100

TABAS_BathingBenefits.BaseBenefits = {
    Bath = {"Anger", "FatigueBase", "Sanity", "Stress", "DrunkennessInc", "Boredom", "Unhappyness", "StiffnessBase"},
    Shower = {"Anger", "Stress", "Boredom", "Unhappyness"}
}

local Factor = {
    Anger = 3E-3,
    Fatigue = 4E-5,
    FatigueBase = 3E-5,
    Sanity = 4E-6,
    Stress = 2E-4,
    StressFormCigarettes = 2E-4,
    Drunkenness = 4E-4,
    DrunkennessInc = 8E-4,

    Pain = 4E-4,
    Health = 2E-3,
    Boredom = 0.0385,
    Unhappyness = 5E-3,
    Cold = 0.02,

    AdditionalPain = 0.02,
    AdditionalPainInc = 4E-2,
    Burn = 2E-4,
    Bite = 4E-6, -- Injulies
    Cut = 8E-5, -- Injulies
    DeepWound = 1E-5, -- Injulies
    Scratch = 2E-4, -- Injulies
    Stitch = 4E-4, -- Injulies
    Fracture = 5E-5,
    Stiffness = 3E-2,
    StiffnessBase = 5E-3,
    WoundInfection = 2E-4,
    Sustain = 3E-5,
}

local BenefitForStats = {
    Anger = function(s, m) s:setAnger(s:getAnger() - Factor.Anger * m) end,
    Fatigue = function(s, m) if s:getFatigue() > 0.45 then
        s:setFatigue(s:getFatigue() - Factor.Fatigue * m) end;end,
    FatigueBase = function(s, m) if s:getFatigue() > 0.65 then
        s:setFatigue(s:getFatigue() - Factor.FatigueBase * m) end;end,
    Sanity = function(s, m) if s:getSanity() < 1 then
        s:setSanity(s:getSanity() + Factor.Sanity * m) end;end,
    Stress = function(s, m) s:setStress((s:getBasicStress() - Factor.Stress * m)) end,
    StressFromCigarettes = function(s, m) s:setStressFromCigarettes(s:getStressFromCigarettes() - Factor.StressFormCigarettes * m) end,
    Pain = function (s, m) s:setPain(s:getPain() - Factor.Pain * m) end,
    Drunkenness = function (s, m) if s:getDrunkenness() > 0 then
        s:setDrunkenness(s:getDrunkenness() - Factor.Drunkenness * m) end;end,
    DrunkennessInc = function (s, m) if s:getDrunkenness() > 10.0 then
        s:setDrunkenness(s:getDrunkenness() + Factor.DrunkennessInc * m) end;end,
}

local BenefitForBody = {
    Boredom = function (b, m) if b:getBoredomLevel() > 0 then
        b:setBoredomLevel(b:getBoredomLevel() - Factor.Boredom * m) end;end,
    Unhappyness = function (b, m) if b:getUnhappynessLevel() > 0 then
        b:setUnhappynessLevel(b:getUnhappynessLevel() - Factor.Unhappyness * m) end;end,
    Cold = function(b, m) if b:getColdStrength() > 0 then
        b:setColdStrength(b:getColdStrength() - b:getColdProgressionRate() * 1.25 * m) end;end,
    Health = function(b, m) if b:getOverallBodyHealth() < 100 then
        b:AddGeneralHealth(Factor.Health * m) end;end,
}

local BenefitsForChar = { -- These give various pill effects. 
    Sleep = function(c, m) if c:getSleepingTabletEffect() < 4000.0 then
        c:setSleepingTabletEffect(c:getSleepingTabletEffect() + 30 * m) end;end,
    Panic = function(c, m) if c:getBetaEffect() < 4000.0 then
        c:setBetaEffect(c:getBetaEffect() + 30 * m) end;end,
    AntiDepress = function(c, m) if c:getDepressEffect() < 4000.0 then
        c:setDepressEffect(c:getDepressEffect() + 30 * m) end;end,
    Pain = function(c, m) if c:getPainEffect() < 4000.0 then
        c:setPainEffect(c:getPainEffect() + 30 * m) end;end,
}


local BenefitForBodyParts = {
    AdditionalPain = function (p, m) if p:getAdditionalPain() > 0 then
        p:setAdditionalPain(p:getAdditionalPain() - Factor.AdditionalPain * m) end;end,
    AdditionalPainInc = function (p, m) if p:HasInjury() then
        p:setAdditionalPain(p:getAdditionalPain() + Factor.AdditionalPainInc * m) end;end,
    PartHealth = function (p, m) if p:getHealth() < 100 then
        p:AddHealth(Factor.Health * m) end;end,
    Burn = function (p, m) if p:getBurnTime() > 0 then
        p:setBurnTime(p:getBurnTime() - Factor.Burn * m)
        if p:isNeedBurnWash() then
            p:setLastTimeBurnWash(0)
            p:setNeedBurnWash(false)
        end;end;end,
    Fracture = function (p, m) if p:getFractureTime() > 0 then
        p:setFractureTime(p:getFractureTime() - Factor.Fracture * m) end;end,
    Bite = function (p, m) if p:getBiteTime() > 0 then p:setBiteTime(p:getBiteTime() - Factor.Bite * m) end;end,
    Cut  = function (p, m) if p:getCutTime() > 0 then
         p:setCutTime(p:getCutTime() - Factor.Cut * m) end;end,
    DeepWound = function (p, m) if p:getDeepWoundTime() > 0 then
        p:setDeepWoundTime(p:getDeepWoundTime() - Factor.DeepWound * m) end;end,
    Scratch = function (p, m) if p:getScratchTime() > 0 then
         p:setScratchTime(p:getScratchTime() - Factor.Scratch * m) end;end,
    Stitch = function (p, m) if p:getStitchTime() > 0 then
         p:setStitchTime(p:getStitchTime() - Factor.Stitch * m) end;end,
    Stiffness = function (p, m) if p:getStiffness() > 0 then
        p:setStiffness(p:getStiffness() - Factor.Stiffness * m) end;end,
    StiffnessBase = function (p, m) if p:getStiffness() > 0 then
        p:setStiffness(p:getStiffness() - Factor.StiffnessBase * m) end;end,
    WoundInfection = function (p, m) if p:getWoundInfectionLevel() > 0 then
        p:setWoundInfectionLevel(p:getWoundInfectionLevel() - Factor.WoundInfection * m) end;end,
    Injuries_Sustain = function (p, m) if p:HasInjury() and p:getPlantainFactor() < 30 then
        p:setPlantainFactor(p:getPlantainFactor() + Factor.Sustain * m) end;end,
    Fracture_Sustain = function (p, m) if p:getFractureTime() > 0 and p:getComfreyFactor() < 30 then
        p:setComfreyFactor(p:getComfreyFactor() + Factor.Sustain * m) end;end,
    WoundInfection_Sustain = function (p, m) if p:getWoundInfectionLevel() > 0 and p:getGarlicFactor() < 30 then
        p:setGarlicFactor(p:getGarlicFactor() + Factor.Sustain * m) end;end,
}

function TABAS_BathingBenefits:apply(temperature, ratio)
    local stats = self.character:getStats()
    local bodyDamage = self.character:getBodyDamage()
    local timeMultiplier = TABAS_GT.GameTime:getMultiplier()
    if self.bathBenefitedLimit < benefitMax then
        self.bathBenefitedLimit = self.bathBenefitedLimit + 0.03 * timeMultiplier
        self.character:getModData().bathBenefitedLimit = self.bathBenefitedLimit

        local dirtMultiplier = self.dirtyLevel < 20 and 1 or 0.2
        local amountMultiplier = ratio or 1
        local tempeMultiplier = 1
        local tempeDiff = temperature - 38
        if tempeDiff < -1 then
            tempeMultiplier = 0.5
        elseif tempeDiff > 0 then
            tempeMultiplier = math.max(3, 1 + tempeDiff * 0.25)
        end
        for i=1, #self.benefits do
            local benefit = self.benefits[i]
            if benefit then
                if BenefitForStats[benefit] then
                    BenefitForStats[benefit](stats, timeMultiplier * tempeMultiplier * dirtMultiplier)
                elseif BenefitForBody[benefit] then
                    BenefitForBody[benefit](bodyDamage, timeMultiplier * amountMultiplier * dirtMultiplier)
                elseif BenefitsForChar[benefit] then
                    BenefitsForChar[benefit](self.character, timeMultiplier * dirtMultiplier)
                elseif BenefitForBodyParts[benefit] then
                    local bodyParts = bodyDamage:getBodyParts()
                    for j=0,BodyPartType.ToIndex(BodyPartType.MAX) - 1 do
                        local part = bodyParts:get(j)
                        if part then
                            BenefitForBodyParts[benefit](part, timeMultiplier * tempeMultiplier * dirtMultiplier)
                        end
                    end
                end
            end
        end
    elseif self.bathBenefitedLimit > benefitMax then
        self.bathBenefitedLimit = benefitMax
    end
    if stats:getEndurance() < 1 then
        local fatigueMod = 1 - stats:getFatigue() * 0.8
        local recoveryMod = self.character:getRecoveryMod()
        local recovery = fatigueMod * recoveryMod * self.enduranceMultiplier * timeMultiplier
        stats:setEndurance(stats:getEndurance() + recovery)
    end
    if not SandboxVars.TakeABathAndShower.DisableNegativeEffectsOfBathing then
        if self.wornItemCount > 0 then
            bodyDamage:setDiscomfortLevel(self.wornItemCount * 5)
            if bodyDamage:getBoredomLevel() < 30 then
                bodyDamage:setBoredomLevel(bodyDamage:getBoredomLevel() + Factor.Boredom * self.wornItemCount * 0.25 * timeMultiplier)
            end
        end
        if self.dirtyLevel > 60 then
            if bodyDamage:getBoredomLevel() < 60 then
                bodyDamage:setBoredomLevel(bodyDamage:getBoredomLevel() + Factor.Boredom * 3 * timeMultiplier)
            end
            if bodyDamage:getUnhappynessLevel() < 50 then
                bodyDamage:setUnhappynessLevel(bodyDamage:getUnhappynessLevel() + Factor.Unhappyness * 3 * timeMultiplier)
            end
        elseif self.dirtyLevel > 30 then
            if bodyDamage:getBoredomLevel() < 30 then
                bodyDamage:setBoredomLevel(bodyDamage:getBoredomLevel() + Factor.Boredom * 2 * timeMultiplier)
            end
            if bodyDamage:getUnhappynessLevel() < 25 then
                bodyDamage:setUnhappynessLevel(bodyDamage:getUnhappynessLevel() + Factor.Unhappyness * 2 * timeMultiplier)
            end
        end
    end
end

function TABAS_BathingBenefits:new(character, wornItemCount, baseBenefitsType, bathSalt, dirtyLevel)
    local o = {}
    setmetatable(o, self)
    self.__index = self
    if not character then return end
    local modData = character:getModData()
    if not modData or not modData.isBathing then return end

    o.character = character
    o.wornItemCount = wornItemCount or 0
    o.dirtyLevel = dirtyLevel or 0
    o.benefits = TABAS_BathingBenefits.BaseBenefits[baseBenefitsType]
    if bathSalt then
        local def = BathSaltDefs.BathSaltTypes[bathSalt]
        local bathSaltBenefits = BathSaltDefs.getBathSaltBenefits(def)
        for i=1, #bathSaltBenefits do
            table.insert(o.benefits, bathSaltBenefits[i])
        end
    end
    o.bathBenefitedLimit = character:getModData().bathBenefitedLimit or 0

    local bathMod = baseBenefitsType == "Bath" and 1.5 or 1
    local enduranceRegenMulutiplier = getSandboxOptions():getEnduranceRegenMultiplier()
    o.enduranceMultiplier = sittingEnduranceMultiplier * imobileEnduranceIncrease * enduranceRegenMulutiplier * bathMod
    return o
end

return TABAS_BathingBenefits