require "TimedActions/ISBaseTimedAction"
require "MF_ISMoodle"

TABAS_TakeShower = ISBaseTimedAction:derive("TABAS_TakeShower")

local TABAS_Utils = require("TABAS_Utils")
local TABAS_GT = require("TABAS_GameTimes")
local TABAS_Sprites = require("TABAS_Sprites")
local BathingBenefits = require("TABAS_BathingBenefits")


local _waterAnimCount = 0
local _waterSpriteNum = 0

local _rand = newrandom()

local function waterAnimSprites(sprite)
    if _waterAnimCount < 4 then -- One animation frame is updated every 4 tick).
        _waterAnimCount = _waterAnimCount + 1
    else
        _waterAnimCount = 0
        _waterSpriteNum = _waterSpriteNum + 1
    end
    if _waterSpriteNum > 4 then
        _waterSpriteNum = 0
    end
    return sprite .. tostring(_waterSpriteNum)
end

function TABAS_TakeShower:isValid()
    -- return self.showerObj:hasFluid()
    return true
end

function TABAS_TakeShower:waitToStart()
    if self.character:isAiming() then
        self.character:nullifyAiming()
    end
    if self.character:isSneaking() then
        self.character:setSneaking(false)
    end
    if self.character:isCurrentState(ClimbOverFenceState.instance()) then
        return true
    end
    self:forceDirection()
    return self.character:shouldBeTurning()
end

function TABAS_TakeShower:cleansedBody(washCount)
    if washCount == 0 then return end
    local pct = washCount / 2
    TABAS_Utils.cleaningBody(self.character, pct, 1)
    TABAS_Utils.cleaningGrime(self.character, pct, self.cleansingFactor)
    TABAS_Utils.cleaningWornItems(self.character, self.wornItems, pct, 1)
    if washCount == 1 and self.washOffMakeup then
        TABAS_Utils.removeAllMakeup(self.character)
    end
end

function TABAS_TakeShower:update()
    if self.currentAlpha < 1 then
        -- smoothly upper to Alpha 1.
        self.currentAlpha = self.currentAlpha + 0.001
    end
    self.showerObj:setAlphaAndTarget(self.currentAlpha)
    if self.bathObj then
        self.bathObj:setAlphaAndTarget(self.currentAlpha)
        if self.subObj then
            self.subObj:setAlphaAndTarget(self.currentAlpha)
        end
    end
    local char = self.character
    local timeStump = TABAS_GT.GameTime:getMultipliedSecondsSinceLastUpdate()
    local timeMultiplier = TABAS_GT.GameTime:getMultiplier()
    self.animCount = self.animCount + timeStump
    if not self.stopped and (char:pressedMovement(true) or char:pressedCancelAction()) then
        -- When a character tries to move, instead of force stopping, 
        -- it will perform an end action and then force complete.
        setGameSpeed(1)
        if self.started or char:getVariableString("ShowerTimeAction") == "ShowerStart" then
            self:setAnimVariable("ShowerTimeStarted", false)
            self:setAnimVariable("ShowerTimeAction", "ShowerStop")
            -- This case, "ShowerTimeEnded" variable is triggered by an action anim event.
        else
            self:setAnimVariable("ShowerTimeEnded", true)
        end
        
        self.stopped = true
        print("Take Shower: Stop Phase Start")
    end
    if not self.stopped and char:getVariableBoolean("ShowerTimeStarted") then
        -- add water animation and sound
        if not self.showerWater.object then
            self.started = true
            self.showerWater.object = IsoObject.new(self.square, self.showerWater.sprite, self.showerWater.sprite)
            self.square:AddTileObject(self.showerWater.object)
            if not self.sound then
                self.sound = char:getEmitter():playSound("tabas_shower_water")
            end
            local wornItem
            for i=0, self.wornItems:size()-1 do
                wornItem = self.wornItems:get(i):getItem()
                if instanceof(wornItem, "Clothing") and wornItem:getDisplayName() ~= wornItem:getFullType() then
                    wornItem:setWetness(100)
                end
            end
            triggerEvent("OnClothingUpdated", char)
        else
            -- add steam animation when use hot water
            if self.hotWater then
                if not self.steam1.object then
                    if self.animCount > 6 then
                        self.steam1.object = IsoObject.new(self.square, self.steam1.sprites, self.steam1.sprites)
                        self.steam1.object:setRenderYOffset(_rand:random(-20, -5))
                        self.steam1.object:setAlphaAndTarget(self.steam1.alpha)
                        self.square:AddTileObject(self.steam1.object)
                    end
                else
                    if self.steam1.alpha < 1 then
                        self.steam1.alpha = self.steam1.alpha + 0.1 * timeMultiplier
                    end
                    self.steam1.object:setAlphaAndTarget(self.steam1.alpha)
                end
                if not self.steam2.object then
                    if self.animCount > 9 then
                        self.steam2.object = IsoObject.new(self.square, self.steam2.sprites, self.steam2.sprites)
                        self.steam2.object:setRenderYOffset(_rand:random(15, 30))
                        self.steam2.object:setAlphaAndTarget(self.steam2.alpha)
                        self.square:AddTileObject(self.steam2.object)
                    end
                else
                    if self.steam2.alpha < 1 then
                        self.steam2.alpha = self.steam2.alpha + 0.1 * timeMultiplier
                    end
                    self.steam2.object:setAlphaAndTarget(self.steam2.alpha)
                end
                -- Heat Source
                if not self.heatSource then
                    self.climateF:setEnableOverride(true)
                    self.heatSource = IsoHeatSource.new(self.square:getX(), self.square:getY(), self.square:getZ(), 1, self.waterTemperature)
                    self.square:getCell():addHeatSource(self.heatSource)
                end
            end
            -- Effects of showering on the body
            local discomfy = self.worldTempe <= 10 and (not self.hotWater)
            local waterTemperature = self.waterTemperature
            if self.worldTempe >= 30 and not self.hotWater then
                discomfy = false
                waterTemperature = 40 -- This relates to the bath benefits multiplier.
                if self.moodle_cooling ~= nil and self.animCount > 5 then
                    self.moodle_cooling:setValue(1)
                end
            elseif self.worldTempe <= 20 and self.hotWater then
                discomfy = false
                if self.moodle_warming ~= nil and self.animCount > 5 then
                    self.moodle_warming:setValue(1)
                end
            end
            if discomfy then
                if char:getBodyDamage():getWetness() < 80 then
                    char:getBodyDamage():increaseBodyWetness(0.5)
                end
            else
                self.bathingBenefits:apply(waterTemperature)
                local bwTimer = char:getModData().bathingWetTimer or 0
                if bwTimer < self.timerMax then
                    char:getModData().bathingWetTimer = bwTimer + 5 * timeMultiplier
                end
            end
        end

        if not self.washStarted then
            self.washStarted = true
            self:resetJobDelta()
            self:setAnimVariable("ShowerWashPart", "Hand")
            self:setAnimVariable("ShowerTimeAction", "Wash")
        else
            -- Randomly change wash action.
            if not self.finished and char:getVariableBoolean("ShowerWashPartFinished") then
                char:setVariable("ShowerWashPartFinished", false)
                if #self.washParts > 0 then
                    local partNum = _rand:random(1, #self.washParts)
                    local part = self.washParts[partNum]
                    self.currentWashPart = table.remove(self.washParts, partNum)
                    self:setAnimVariable("ShowerWashPart", part)
                    print("Shower WashPartChanged to ", self.currentWashPart)
                    -- if self.previousPart then
                    --     table.insert(self.washParts, self.previousPart)
                    -- end
                    -- self.previousPart = self.currentWashPart
                else
                    self:setAnimVariable("ShowerWashPart", "Face")
                end
            end
            -- Cleansed Body (triggered anim events)
            local jobDelta = self:getJobDelta()
            if char:getVariableBoolean("ShowerWashCleansed") then
                if self.doneWash < 2 then
                    self.doneWash = self.doneWash + 1
                    self:cleansedBody(self.doneWash)
                    self:setAnimVariable("ShowerWashCleansed", false)
                    print("Shower Wash Cleansing: " ..  self.doneWash)
                end
            end
            -- Shampoo form
            if self.usedSoap and not self.shampooForm and jobDelta > 0.20 then
                TABAS_Utils.addFakeWornItem(char, "TABAS.ShampooForm")
                self.shampooForm = true
                self:useSoap() -- At this point the soap is consumed.
            end
            if jobDelta > 0.80 and self.shampooForm and not self.removedForm then
                TABAS_Utils.removeFakeWornItem(char, "TABAS.ShampooForm")
                self.removedForm = true
            end

            -- End Action
            local endTime = self.maxTime - self.maxTime * jobDelta
            if endTime < 60 and not self.finished then
                setGameSpeed(1)
                self.finished = true
                char:setVariable("ShowerTimeStarted", false)
                self:setAnimVariable("ShowerTimeAction", "ShowerStop")
            end
        end
    end
    if self.showerWater.object then
        self.showerWater.object:setSpriteFromName(waterAnimSprites(self.showerWater.sprites))
        -- During the shower animation, gradually increase usedWater amount to its set consumed amount.
        -- usedWater amount is subtracted from water source at the complete or stop of Action.
        if self.usedWater < self.consumeWater then
            self.usedWater = self.usedWater + (self.consumeWater * 0.05) * timeMultiplier
        end
    end
    if char:getVariableBoolean("ShowerTimeEnded") then
        self:forceComplete()
    end
end

function TABAS_TakeShower:forceDirection()
    if self.facing == "S" then -- S
        self.character:faceDirection(IsoDirections.N)
    elseif self.facing == "E" then -- E
        self.character:faceDirection(IsoDirections.W)
    elseif self.facing == "W" then -- W
        self.character:faceDirection(IsoDirections.E)
    else -- N
        self.character:faceDirection(IsoDirections.S)
    end
end

function TABAS_TakeShower:start()
    setGameSpeed(1)
    self.currentAlpha = self.showerObj:getAlpha()
    self.wornItems = self.character:getWornItems()
    self.wornItemCount = TABAS_Utils.getWornClothesCountExcluded(self.wornItems, true)

    self.character:setIgnoreMovement(true)
    self.character:getModData().isBathing = true
    self.bathingBenefits = BathingBenefits:new(self.character, self.wornItemCount, "Shower")

    if self.facing == "N" then
        TABAS_Utils.setPlayerPosition(self.character, self.square:getX() + 0.5, self.square:getY() + 0.4)
    elseif self.facing == "W" then
        TABAS_Utils.setPlayerPosition(self.character, self.square:getX() + 0.4, self.square:getY() + 0.5)
    else
        TABAS_Utils.setPlayerPositionC(self.character, self.square)
    end
    self.showerObj:getModData().using = true
    self.showerObj:transmitModData()
    self:setActionAnim("TABAS_TakeShower")
    self:setAnimVariable("ShowerTimeAction", "ShowerStart")
end

function TABAS_TakeShower:stopSound()
    if self.sound and self.character:getEmitter():isPlaying(self.sound) then
        self.character:stopOrTriggerSound(self.sound)
    end
end

function TABAS_TakeShower:useSoap()
    local totalUses = self.consumeSoap
    for i=1, #self.soapList do
        local soap = self.soapList[i]
        for j=1, self.consumeSoap do
            if totalUses > 0 and soap:getCurrentUses() > 0 then
                soap:UseAndSync()
                totalUses = totalUses - 1
                print("Sower Use Soap: [" .. soap:getName() .. "] Remains " .. soap:getCurrentUses())
            else
                break
            end
        end
    end
end

function TABAS_TakeShower:removeExtraObject()
    if self.showerWater.object then
        TABAS_Utils.removeTileObject(self.square, self.showerWater.object)
        self.showerWater.object = nil
    end
    if self.steam1.object then
        TABAS_Utils.removeTileObject(self.square, self.steam1.object)
        self.steam1.object = nil
    end
    if self.steam2.object then
        TABAS_Utils.removeTileObject(self.square, self.steam2.object)
        self.steam2.object = nil
    end
    if self.heatSource then
        self.square:getCell():removeHeatSource(self.heatSource)
        self.heatSource = nil
        self.climateF:setEnableOverride(false)
    end
end

function TABAS_TakeShower:clearVariables()
    self.character:clearVariable("ShowerTimeStarted")
    self.character:clearVariable("ShowerTimeEnded")
    self.character:clearVariable("ShowerTimeAction")
    self.character:clearVariable("ShowerWashPart")
    self.character:clearVariable("ShowerWashPartFinished")
    self.character:clearVariable("ShowerWashCleansed")
end

function TABAS_TakeShower:preComplete()
    self.character:setIgnoreMovement(false)
    self:removeExtraObject()
    TABAS_Utils.removeFakeWornItem(self.character, "TABAS.ShampooForm")
    self.character:getModData().isBathing = nil
    self.character:resetModelNextFrame()
    sendHumanVisual(self.character)
    -- if isDebugEnabled() then
    --     print("Shower Time Used Water = " .. self.usedWater .. " (However, used 1 since debugging now)")
    --     self.usedWater = 1
    -- end
    local used = math.min(self.usedWater, self.consumeWater)
    local WaterReader = require("TABAS_WaterReader")
    WaterReader.useWater(self.showerObj, used)
    self.showerObj:getModData().using = nil
    self.showerObj:transmitModData()
end

function TABAS_TakeShower:stop()
    self:stopSound()
    self:preComplete()
    self:clearVariables()
    ISBaseTimedAction.stop(self)
end

function TABAS_TakeShower:complete()
    self.character:getModData().afterBathing = 1
    return true
end

function TABAS_TakeShower:perform()
    self:stopSound()
    self:preComplete()
    self:clearVariables()
    -- needed to remove from queue / start next.
    ISBaseTimedAction.perform(self)
end

function TABAS_TakeShower:adjustMaxTime(maxTime)
    return maxTime
end

function TABAS_TakeShower:getDuration()
    if self.character:isTimedActionInstant() then
        return 1
    end

    local maxTime = 1500
    if self.usedSoap then
        maxTime = 1750
    end
    if self.wornItemCount > 0 then
        maxTime = maxTime + self.wornItemCount * 40
    end
    return maxTime
end

local function facingToNumber(facing)
    local num = 0 -- S
    if facing == "E" then num = 1
    elseif facing == "W" then num = 2
    elseif facing == "N" then num = 3
    end
    return num
end

function TABAS_TakeShower:initVariables()

end

function TABAS_TakeShower:new (character, showerObj, soapList, consumeSoap, inTub, useHot)
    local o = ISBaseTimedAction.new(self, character)
    o.playerNum = character:getPlayerNum()
    o.wornItems = nil
    o.wornItemCount = 0

    o.showerObj = showerObj
    o.square = showerObj:getSquare()
    o.facing = TABAS_Utils.getObjectFacing(showerObj)
    if inTub then
        o.bathObj = TABAS_Utils.getBathObjectOnSquare(o.square)
        if o.bathObj then
            o.subObj = TABAS_Utils.getGridExtensionBath(o.bathObj)
        end
    end
    o.timerMax = 600 * SandboxVars.TakeABathAndShower.BathingWetTime
    o.consumeWater = SandboxVars.TakeABathAndShower.ShowerConsumeWater
    o.usedWater = 0
    o.soapList = soapList
    o.consumeSoap = consumeSoap
    o.usedSoap = #soapList > 0 and consumeSoap > 0
    o.cleansingFactor = 0.75
    if o.usedSoap then
        o.cleansingFactor = 0.9
    end
    o.washOffMakeup = TABAS_Utils.ModOptionsValue("WashOffMakeup")

    local idealTempe = showerObj:getModData().idealTemperature or 40
    o.waterTemperature = useHot and idealTempe or 10
    o.hotWater = o.waterTemperature >= 38

    if character:isFemale() then
        o.washParts = {"HeadF", "Face", "Arms", "Torso"}
    else
        o.washParts = {"Head", "Face", "Arms", "Torso"}
    end
    local steamSprite1 = TABAS_Sprites.Steam1[o.facing]
    local steamSprite2 = TABAS_Sprites.Steam2[o.facing]
    o.steam1 = {object=false, sprites=steamSprite1, alpha=0}
    o.steam2 = {object=false, sprites=steamSprite2, alpha=0}
    o.showerWater = {
        object = false,
        sprite = "tabas_textures_01_" .. facingToNumber(o.facing),
        sprites = "tabas_shower_water_" .. string.lower(string.sub(o.facing,1,1)) .. "_",
    }
    o.currentAlpha = 0
    o.heatSource = false
    local climate = getWorld():getClimateManager()
    o.climateF = climate:getClimateFloat(4) -- 4 is TEMPERATURE
    o.worldTempe = climate:getTemperature()

    o.maxTime = o:getDuration()
    if MF then -- Moodle Framework
        o.moodle_bathingWet = MF.getMoodle("Wet_Bathing", o.playerNum)
        o.moodle_cooling = MF.getMoodle("CoolingBody", o.playerNum)
        o.moodle_warming = MF.getMoodle("WarmingBody", o.playerNum)
    end
    -- Animation Variables
    o.animCount = 0
    o.stopCount = 0
    o.started = false
    o.washStarted = false
    o.currentWashPart = false
    o.doneWash = 0
    o.shampooForm = false
    o.removedForm = false
    o.finished = false
    o.stopped = false
    o.previousPart = false

    o.stopOnWalk = false
    o.stopOnRun = false
    o.stopOnAim = false
    o.ignoreHandsWounds = true
    o.caloriesModifier = 0.2
    return o
end