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 _useWaterDT = 0.125

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
    if self.character:getX() ~= self.setPosition[1] or self.character:getY() ~= self.setPosition[2] then
        TABAS_Utils.setPlayerPosition(self.character, self.setPosition[1], self.setPosition[2], self.setPosition[3])
    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)
    if self.wornItemCount > 0 then
        TABAS_Utils.cleaningWornItems(self.character, self.wornItems, pct, 1)
    end
    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 jobDelta = self:getJobDelta()
    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 then
        if (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")
        elseif not self.showerObj:hasFluid() then
            self.noWaterDelayTime = 1
            self.stopped = true
            print("Take Shower: No Water Comes Out!")
        end
    end
    if self.noWaterDelayTime > 0 then
        self.animCount = 0
        self.noWaterDelayTime = self.noWaterDelayTime + timeStump
        if self.showerObj:hasFluid() then
            self.stopped = false
            self:setAnimVariable("ShowerTimeStarted", true)
        elseif self.noWaterDelayTime > 1.5 then
            self:setAnimVariable("ShowerTimeStarted", false)
            self:setAnimVariable("ShowerTimeAction", "ShowerNoWater")
            self.noWaterDelayTime = -2
        end
    end
    if not self.stopped and char:getVariableBoolean("ShowerTimeStarted") then
        -- add water object
        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)
            -- set amount of water to use per tick\
            self.duration = math.floor(self.maxTime - self.maxTime * jobDelta)
            self:resetJobDelta()
            -- sound
            if not self.sound then
                self.sound = char:getEmitter():playSound("tabas_shower_water")
            end
            if self.wornItemCount > 0 then
                local wornItem
                for i=0, self.wornItems:size()-1 do
                    wornItem = self.wornItems:get(i):getItem()
                    if wornItem:IsClothing() and wornItem:getDisplayName() ~= wornItem:getFullType() then
                        wornItem:setWetness(100)
                    end
                end
                triggerEvent("OnClothingUpdated", char)
            end
        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:getStats():get(CharacterStat.WETNESS) < 80 then
                    char:getBodyDamage():increaseBodyWetness(0.5)
                end
            else
                self.bathingBenefits:apply(waterTemperature)
                local bwTimer = char:getModData().bathingWetTimer or 0
                if bwTimer < self.wetTimerMax then
                    char:getModData().bathingWetTimer = bwTimer + 5 * timeMultiplier
                end
            end
        end
        -- Wash animation
        if not self.washStarted then
            self.washStarted = true
            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)
            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
        if self.showerObj:hasFluid() then
            self.showerWater.object:setSpriteFromName(waterAnimSprites(self.showerWater.sprites))
            local perUses = round(self.consumeWater / self.duration * timeMultiplier, 2)

            local remain = (self.consumeWater or 0) - (self.usedWater or 0)
            if remain > 0 then
                local add = math.min(perUses, remain)
                if add > 0 then
                    self.usedWater    = (self.usedWater or 0) + add
                    self.perUsesWater = (self.perUsesWater or 0) + add
                end
            end

            if self.sound and not self.character:getEmitter():isPlaying(self.sound) then
                self.character:stopOrTriggerSound(self.sound)
            end
        else
            self.showerWater.object:setSpriteFromName("")
            if self.sound and self.character:getEmitter():isPlaying(self.sound) then
                self.character:stopOrTriggerSound(self.sound)
            end
        end
    end
    if self.started and jobDelta > self.useWaterDeltaTime and jobDelta < 1.00 then
        self:useWater()
    end
    if char:getVariableBoolean("ShowerTimeEnded") then
            self:forceComplete()
    end
end

function TABAS_TakeShower:useWater()
    local pending = self.perUsesWater or 0
    if pending <= 0 then return end
    self.usedWaterByUseFluid = self.usedWaterByUseFluid or 0
    local remain = (self.consumeWater or 0) - self.usedWaterByUseFluid
    if remain <= 0 then
        self.perUsesWater = 0
        return
    end

    local request = math.min(pending, remain)
    if request <= 0 then
        self.perUsesWater = 0
        return
    end
    print("Used Water: " .. request)

    self.showerObj:useFluid(request)
    self.usedWaterByUseFluid = self.usedWaterByUseFluid + request

    self.perUsesWater = pending - request
    self.useWaterDeltaTime = (self.useWaterDeltaTime or 0) + _useWaterDT
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, "Shower", self.wornItemCount)

    self.showerObj:getModData().using = true
    self.showerObj:transmitModData()
    self:setActionAnim("TABAS_TakeShower")
    self:setAnimVariable("ShowerTimeAction", "ShowerStart")
    self.character:setVariable("ShoweWashSpeed", self.washAnimSpeed)
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)

    self:useWater()
    print("Shower Time Used Water = " .. self.usedWater)

    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:perform()
    self:stopSound()
    self:clearVariables()
    -- needed to remove from queue / start next.
    ISBaseTimedAction.perform(self)
end

function TABAS_TakeShower:complete()
    self.character:getModData().afterBathing = 1
    self:preComplete()
    return true
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:new (character, showerObj, soapList, consumeSoap, inTub, useHot)
    local o = ISBaseTimedAction.new(self, character)
    o.character = character
    o.wornItems = nil
    o.wornItemCount = 0 -- These are assigned values when the action actually started.

    o.showerObj = showerObj
    o.square = showerObj:getSquare()
    o.facing = TABAS_Utils.getObjectFacing(showerObj)
    if o.facing == "N" then
        o.setPosition = {o.square:getX()+0.5, o.square:getY()+0.45, o.square:getZ()}
    elseif o.facing == "W" then
        o.setPosition = {o.square:getX()+0.45, o.square:getY()+0.5, o.square:getZ()}
    else
        o.setPosition = {o.square:getX()+0.5, o.square:getY()+0.5, o.square:getZ()}
    end

    if inTub then
        o.bathObj = TABAS_Utils.getBathObjectOnSquare(o.square)
        if o.bathObj then
            o.subObj = TABAS_Utils.getGridExtensionBath(o.bathObj)
        end
    end
    o.wetTimerMax = 600 * SandboxVars.TakeABathAndShower.BathingWetTime
    o.consumeWater = SandboxVars.TakeABathAndShower.ShowerConsumeWater
    o.usedWater = 0
    o.perUsesWater = 0
    o.perUsesMax = SandboxVars.TakeABathAndShower.RemainShowerFaucetWater
    o.useWaterDeltaTime = _useWaterDT
    o.duration = 0
    o.noWaterDelayTime = -1

    o.soapList = soapList
    o.consumeSoap = consumeSoap
    o.usedSoap = #soapList > 0 and consumeSoap > 0
    o.cleansingFactor = 0.75
    if o.usedSoap then
        o.cleansingFactor = 0.95
    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
        local playerNum = character:getPlayerNum()
        o.moodle_bathingWet = MF.getMoodle("Wet_Bathing", playerNum)
        o.moodle_cooling = MF.getMoodle("CoolingBody", playerNum)
        o.moodle_warming = MF.getMoodle("WarmingBody", 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