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", "IntoxicationInc", "Boredom", "Unhappiness", "StiffnessBase"},
    Shower = {"Anger", "Stress", "Boredom", "Unhappiness"}
}

local Factor = {
    Anger = 3E-3,
    Fatigue = 4E-5,
    FatigueBase = 3E-5,
    Sanity = 4E-6,
    Stress = 2E-4,
    Intoxication = 8E-4,
    IntoxicationInc = 4E-4,

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

    AdditionalPain = 0.02,
    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 = 5E-3,
    StiffnessBase = 3E-3,
    WoundInfection = 2E-4,
    Sustain_Pill = 30,
    Sustain_Compress = 3E-5,
}

local BenefitForStats = {
    Anger = function(s, m) if s:isAboveMinimum(CharacterStat.ANGER) then
        s:remove(CharacterStat.ANGER, Factor.Anger * m) end;end,
    Boredom = function(s, m)if s:isAboveMinimum(CharacterStat.BOREDOM) then
    s:remove(CharacterStat.BOREDOM, Factor.Boredom) end;end,
    Fatigue = function(s, m) if s:get(CharacterStat.FATIGUE) > 0.45 then
        s:remove(CharacterStat.FATIGUE, Factor.Fatigue * m) end;end,
    FatigueBase = function(s, m) if s:get(CharacterStat.FATIGUE) > 0.65 then
        s:remove(CharacterStat.FATIGUE, Factor.FatigueBase * m) end;end,
    Sanity = function(s, m) if not s:isAtMaximum(CharacterStat.SANITY) then
        s:add(CharacterStat.SANITY, Factor.Sanity * m) end;end,
    Stress = function(s, m) if s:isAboveMinimum(CharacterStat.STRESS) then
        s:remove(CharacterStat.STRESS, Factor.Stress * m) end;end,
    Pain = function (s, m) if s:isAboveMinimum(CharacterStat.PAIN) then
        s:remove(CharacterStat.PAIN, Factor.Pain * m) end;end,
    Intoxication = function (s, m) if s:isAboveMinimum(CharacterStat.INTOXICATION) then
        s:remove(CharacterStat.INTOXICATION, Factor.Intoxication * m) end;end,
    IntoxicationInc = function (s, m) if s:get(CharacterStat.INTOXICATION) > 10.0 then
        s:add(CharacterStat.INTOXICATION, Factor.IntoxicationInc * m) end;end,
    Unhappiness = function (s, m) if s:isAboveMinimum(CharacterStat.UNHAPPINESS) then
        s:remove(CharacterStat.UNHAPPINESS, Factor.Unhappiness * m) end;end,
}

local BenefitForBody = {
    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_Sustain = function(c, m) if c:getSleepingTabletEffect() < 4000.0 then
        c:setSleepingTabletEffect(c:getSleepingTabletEffect() + Factor.Sustain_Pill * m) end;end,
    Panic_Sustain = function(c, m) if c:getBetaEffect() < 4000.0 then
        c:setBetaEffect(c:getBetaEffect() + Factor.Sustain_Pill * m) end;end,
    AntiDepress_Sustain = function(c, m) if c:getDepressEffect() < 4000.0 then
        c:setDepressEffect(c:getDepressEffect() + Factor.Sustain_Pill * m) end;end,
    Pain_Sustain = function(c, m) if c:getPainEffect() < 4000.0 then
        c:setPainEffect(c:getPainEffect() + Factor.Sustain_Pill * m) end;end,
}


local BenefitForBodyParts = {
    AdditionalPain = function (p, m) if p:getAdditionalPain() > 0 then
        p:setAdditionalPain(p:getAdditionalPain() - Factor.AdditionalPain * 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_Compress * m) end;end,
    Fracture_Sustain = function (p, m) if p:getFractureTime() > 0 and p:getComfreyFactor() < 30 then
        p:setComfreyFactor(p:getComfreyFactor() + Factor.Sustain_Compress * m) end;end,
    WoundInfection_Sustain = function (p, m) if p:getWoundInfectionLevel() > 0 and p:getGarlicFactor() < 30 then
        p:setGarlicFactor(p:getGarlicFactor() + Factor.Sustain_Compress * m) end;end,
}

function TABAS_BathingBenefits:apply(temperature, amountRatio)
    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 amountMultiplier = amountRatio 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 * self.dirtMultiplier)
                elseif BenefitForBody[benefit] then
                    BenefitForBody[benefit](bodyDamage, timeMultiplier * amountMultiplier * self.dirtMultiplier)
                elseif BenefitsForChar[benefit] then
                    BenefitsForChar[benefit](self.character, timeMultiplier * self.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 * self.dirtMultiplier)
                        end
                    end
                end
            end
        end
    elseif self.bathBenefitedLimit > benefitMax then
        self.bathBenefitedLimit = benefitMax
    end

    -- Adjusts body temperature according to water temperature until a certain level of Moodles occurs.
    local mul
    local num
    if temperature >= 38.0 then
        if temperature >= 50 then num = 41.0; mul = 0.03
        elseif temperature >= 45 then num = 40.0; mul = 0.02
        elseif temperature >= 44 then num = 39.0; mul = 0.01
        elseif temperature >= 43 then num = 37.5; mul = 0.01
        else -- best temperature 
            num = 37.0; mul = 0.01
        end
        if stats:get(CharacterStat.TEMPERATURE) < num then
            stats:add(CharacterStat.TEMPERATURE, mul * timeMultiplier)
        end
    else -- temperature < 38.0
        if temperature < 25 then num = 25.0; mul = 0.03
        elseif temperature < 35 then num = 30.0; mul = 0.02
        elseif temperature < 38 then num = 36.5; mul = 0.01
        else num = 36.5; mul = 0.01
        end
        if stats:get(CharacterStat.TEMPERATURE) >= num then
            stats:remove(CharacterStat.TEMPERATURE, mul * timeMultiplier)
        end
    end

    if not stats:isAtMaximum(CharacterStat.ENDURANCE) then
        local fatigueMod = 1 - stats:get(CharacterStat.FATIGUE) * 0.8
        local recoveryMod = self.character:getRecoveryMod()
        local recovery = fatigueMod * recoveryMod * self.enduranceMultiplier * timeMultiplier
        stats:add(CharacterStat.ENDURANCE, recovery)
    end

    if not SandboxVars.TakeABathAndShower.DisableNegativeEffectsOfBathing then
        if self.wornItemCount > 0 then
            stats:set(CharacterStat.DISCOMFORT, self.wornItemCount * 5)
            if stats:get(CharacterStat.BOREDOM) < 30 then
                stats:add(CharacterStat.BOREDOM, Factor.Boredom * self.wornItemCount * 0.25 * timeMultiplier)
            end
        end
        if self.dirtyLevel > 60 then
            if stats:get(CharacterStat.BOREDOM) < 60 then
                stats:add(CharacterStat.BOREDOM, Factor.Boredom * 3 * timeMultiplier)
            end
            if stats:get(CharacterStat.UNHAPPINESS) < 50 then
                stats:add(CharacterStat.UNHAPPINESS, Factor.Unhappiness * 3 * timeMultiplier)
            end
        elseif self.dirtyLevel > 30 then
            if stats:get(CharacterStat.BOREDOM) < 30 then
                stats:add(CharacterStat.BOREDOM, Factor.Boredom * 2 * timeMultiplier)
            end
            if stats:get(CharacterStat.UNHAPPINESS) < 25 then
                stats:add(CharacterStat.UNHAPPINESS, Factor.Unhappiness * 2 * timeMultiplier)
            end
        end
    end
end


function TABAS_BathingBenefits:new(character, baseBenefitsType, wornItemCount, 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
    if o.dirtyLevel < 20 then
        o.dirtMultiplier = 1
    else
        o.dirtMultiplier = 0.2
    end
    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