require "TimedActions/ISBaseTimedAction"
require "MF_ISMoodle"

TABAS_TakeBath = ISBaseTimedAction:derive("TABAS_TakeBath")

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

local function debugPrint(label, value)
    if isDebugEnabled() then
        print("TABAS_TakeBath:: " .. label .. " => " .. tostring(value))
    end
end

local _rand = newrandom()

function TABAS_TakeBath:isValid()
    return self.tfc_Base:hasTfc()
end

function TABAS_TakeBath: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()
    if self.character:isTurning() then
        return true
    elseif self.character:shouldBeTurning() then
        self.delayCount = 0.3
    else
		if self.delayCount == nil then
			self.delayCount = 0.3
		end
        self.delayCount = self.delayCount - TABAS_GT.GameTime:getRealworldSecondsSinceLastUpdate()
        if self.delayCount > 0 then
            return true
        end
    end
    return self.character:shouldBeTurning()
end

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

function TABAS_TakeBath:update()
    if self.currentAlpha < 1 then
        -- smoothly upper to Alpha 1.
        self.currentAlpha = self.currentAlpha + 0.001
    end
    self.bathObj:setAlphaAndTarget(self.currentAlpha)
    if self.subObj then
        self.subObj:setAlphaAndTarget(self.currentAlpha)
    end

    local char = self.character
    local waterTemperature = self.tfc_Base:getWaterData("temperature")
    -- BathTimeStopped is from key press events.
    -- if not self.stopped and (char:getVariableBoolean("BathTimeStopped") or waterTemperature > 45) then
    if not self.stopped and (char:pressedMovement(false) or char:pressedCancelAction()) then
        setGameSpeed(1)

        if char:getVariableBoolean("PressedRunButton") then
            self.fastOut = true
            self.character:setVariable("BathOutSpeed", 2.8)
        elseif waterTemperature > 45 then
            self.fastOut = true
            self.character:setVariable("BathOutSpeed", 2.5)
            self.character:Say(getText("IGUI_TABAS_ThatsTooHot"))
        end
        if self.started then
            if char:getVariableBoolean("BathTimeIdleStarted") then
                self:setAnimVariable("BathTimeAction", "IdleOut")
            else
                self:setAnimVariable("BathTimeIdleFinished", true)
            end
        else
            self:setAnimVariable("BathTimeEnded", true)
        end
        self.stopped = true
        debugPrint("Stop Phase", "Start")
    end
    if self.stopped then
        if not self.finished and char:getVariableBoolean("BathTimeIdleFinished") then
            self:setJobDelta((self.maxTime - 200) / self.maxTime)
            self:setAnimVariable("BathTimeIdleStance", "")
            if not self.fastOut then
                self.character:setVariable("BathOutSpeed", 1.5)
            end
            self.character:getEmitter():stopAll()
            self:setAnimVariable("BathTimeAction", "GetUp")
            debugPrint("Stop Phase", "Get Up")
            self.finished = true
        end
    else
        if not self.started and char:getVariableBoolean("BathTimeStarted") then
            self.started = true
            -- self.sound = char:getEmitter():playSound("tabas_bath_water")
            if self.wornItemCount > 0 then
                for i=0, self.wornItems:size()-1 do
                    local wornItem = self.wornItems:get(i):getItem()
                    if instanceof(wornItem, "Clothing") and wornItem:getDisplayName() ~= wornItem:getFullType() then
                        wornItem:setWetness(100)
                    end
                end
                triggerEvent("OnClothingUpdated", char)
            end
            self:resetJobDelta()
            if self.action then
                self.action:setUseProgressBar(self.useBathTimeProgressBar)
            end
        elseif self.started then
            local jobDelta = self:getJobDelta()
            local finishPhase = (self.maxTime - self.maxTime * jobDelta) < 450
            if not finishPhase then
                if not self.animStarted then
                    self.animStarted = true
                    if #self.firstAnimTable > 0 then
                        local action = table.remove(self.firstAnimTable, 1)
                        self:setAnimVariable("BathTimeAction", action)
                        debugPrint("First Action: ", action)
                    else
                        self:setAnimVariable("BathTimeActionFinished", true)
                        debugPrint("First Action: ", "Skipped")
                    end
                elseif not self.firstAnimFinish and self.actionInterval > 1.5 then
                    self.actionInterval = 0
                    if #self.firstAnimTable > 0 then
                        local action = table.remove(self.firstAnimTable, 1)
                        self:setAnimVariable("BathTimeAction", action)
                        self:setAnimVariable("BathTimeActionFinished", false)
                        debugPrint("First Action 2: ", action)
                    elseif not self.idleStance then
                        local randomChoice = _rand:random(1, #self.stances)
                        self.idleStance = self.stances[randomChoice]
                        self:setAnimVariable("BathTimeAction", self.idleStance)
                        self.firstAnimFinish = true
                        debugPrint("Idle Stance: ", self.idleStance)
                    end
                elseif self.idleStance and self.actionInterval > self.idleTime then
                    self.actionInterval = 0
                    self:setAnimVariable("BathTimeAction", "IdleOut")
                    self.idleStance = nil
                    debugPrint("Idle Stance: ", "Out")
                end
                if char:getVariableBoolean("BathTimeActionFinished") then
                    self.actionInterval = self.actionInterval + TABAS_GT.GameTime:getMultipliedSecondsSinceLastUpdate()
                    if char:getVariableBoolean("BathTimeIdleFinished") then
                        if #self.afterAnimTable > 0 then
                            self.actionInterval = 0
                            self:setAnimVariable("BathTimeActionFinished", false)
                            local action = table.remove(self.afterAnimTable, 1)
                            self:setAnimVariable("BathTimeAction", action)
                            debugPrint("After Action: ", action)
                        else
                            self.actionInterval = 0
                            self:setAnimVariable("BathTimeIdleStance", "")
                            self:setAnimVariable("BathTimeIdleFinished", false)
                            local randomChoice = _rand:random(1, #self.stances)
                            local stance = self.stances[randomChoice]
                            self:setAnimVariable("BathTimeAction", stance)
                            self.idleStance = stance
                            self.idleTime = self.afterIdleTime
                            debugPrint("Idle Stance: ", self.idleStance)
                        end
                    end
                end
                if char:getVariableBoolean("BathTimeWashCleansed") then
                    if self.doneWash < 2 then
                        self.doneWash = self.doneWash + 1
                        self:cleansedBody(self.doneWash)
                        self:setAnimVariable("BathTimeWashCleansed", false)
                        debugPrint("WashCleansing: ", self.doneWash)
                    end
                end

                local discomfy = self.worldTempe <= 10 and waterTemperature < 35
                if self.moodle_cooling ~= nil and self.worldTempe >= 30 and not self.hotWater then
                    discomfy = false
                    self.moodle_cooling:setValue(1)
                elseif self.moodle_warming ~= nil and self.worldTempe <= 20 and self.hotWater then
                    discomfy = false
                    self.moodle_warming:setValue(1)
                end
                -- Whether the character gets wet normally or receives a benefit from bathing.
                if discomfy then
                    if char:getBodyDamage():getWetness() < 80 then
                        char:getBodyDamage():increaseBodyWetness(0.5)
                    end
                else
                    local ratio = self.tfc_Base:getRatio()
                    if ratio < self.tfc_Base.filledConst.Full then
                        ratio = 0.5
                    elseif ratio < self.tfc_Base.filledConst.Half then
                        ratio = 0
                    end
                    self.bathingBenefits:apply(waterTemperature, ratio)
                    local bwTimer = char:getModData().bathingWetTimer or 0
                    if bwTimer < self.wetTimerMax then
                        char:getModData().bathingWetTimer = bwTimer + 5
                    end
                end
            else -- finish phase
                if char:getVariableBoolean("BathTimeActionFinished") then
                    if char:getVariableBoolean("BathTimeIdleStarted") then
                        self:setAnimVariable("BathTimeAction", "IdleOut")
                    else
                        self:setAnimVariable("BathTimeIdleFinished", true)
                    end
                    if char:getVariableBoolean("BathTimeIdleFinished") then
                        self.stopped = true
                        debugPrint("Finish Pahse", "Finished")
                    end
                end
            end
        end
    end
    if char:getVariableBoolean("BathTimeEnded") then
        setGameSpeed(1)
        self:forceComplete()
    end
end


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

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

    self:setOverrideHandModels(nil, nil)
    self.character:getModData().isBathing = true
    self.bathObj:getModData().using = true
    self.bathObj:transmitModData()

    local bathSalt = self.tfc_Base:getWaterData("bathSalt")
    self.bathingBenefits = BathingBenefits:new(self.character, self.wornItemCount, "Bath", bathSalt, self.dirtyLevel)

    self:setActionAnim("TABAS_TakeBath")
    self:setAnimVariable("BathTimeAction", "BathIn")
    self.character:setVariable("BathOutSpeed", 1.0)
    -- self.character:setSitOnGround(true)
    self.character:setIgnoreMovement(true)
end

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

function TABAS_TakeBath:clearVariables()
    self.character:clearVariable("BathTimeStarted")
    self.character:clearVariable("BathTimeStopped")
    self.character:clearVariable("BathTimeEnded")
    self.character:clearVariable("BathTimeAction")
    self.character:clearVariable("BathTimeActionFinished")
    self.character:clearVariable("BathTimeIdleStarted")
    self.character:clearVariable("BathTimeIdleStance")
    self.character:clearVariable("BathTimeIdleFinished")
    self.character:clearVariable("BathTimeWashCleansed")
    self.character:clearVariable("BathOutSpeed")
    self.character:clearVariable("BathOutFast")
    self.character:clearVariable("BathWashSpeed")
end

function TABAS_TakeBath:preComplete()
    -- self.character:setSitOnGround(false)
    self.character:setIgnoreMovement(false)
    self.character:getModData().isBathing = nil
    self.character:resetModelNextFrame()
    sendHumanVisual(self.character)

    self.tfc_Base:waterToDirt(self.cleansed)
    self.tfc_Base:removeHeatSource()

    if self.usedWater > 0 then
        if isDebugEnabled() then
            debugPrint("Tub Water to Dirt: Increased", self.cleansed)
            debugPrint("Bath Time Used Water", self.usedWater .. " (However, used 1 since debugging now)")
            self.usedWater = 1
        end
        self.tfc_Base:removeFluid(self.usedWater)
    end
    self.bathObj:getModData().using = nil
    self.bathObj:transmitModData()
end

function TABAS_TakeBath:stop()
    self:stopSound()
    -- If you exit with the Esc key.
    if self.started and not self.character:getVariableBoolean("BathTimeEnded") then
        self.character:getEmitter():playSound("tabas_bath_getup")
    end
    self:preComplete()
    self:clearVariables()

    ISBaseTimedAction.stop(self)
end

function TABAS_TakeBath:perform()
    self.character:getModData().afterBathing = 1
    self:stopSound()
    self:preComplete()
    self:clearVariables()
    -- needed to remove from queue / start next.
    ISBaseTimedAction.perform(self)
end

function TABAS_TakeBath:complete()
    return true
end

function TABAS_TakeBath:adjustMaxTime(maxTime)
    return maxTime
end

function TABAS_TakeBath:getDuration()
    if self.character:isTimedActionInstant() then
        return 1
    end
    if self.bathingTime > 0 then
        return self.bathingTime * 120
    end
    -- return 
    return 3000
end


function TABAS_TakeBath:setValidAnims()
    local stances = self.anims.Stance
    local adjacent
    local blockedDir = function(dir)
        adjacent = self.square:getAdjacentSquare(dir)
        return self.square:isBlockedTo(adjacent)
    end
    if self.facing == "S" then
        if blockedDir(IsoDirections.E) then table.remove(stances, 3) end -- Remove ElbowL
        if blockedDir(IsoDirections.W) then table.remove(stances, 4) end -- Remove ElbowR
    elseif self.facing == "E" then
        if blockedDir(IsoDirections.N) then table.remove(stances, 3) end
        if blockedDir(IsoDirections.S) then table.remove(stances, 4) end
    elseif self.facing == "W" then
        if blockedDir(IsoDirections.S) then table.remove(stances, 3) end
        if blockedDir(IsoDirections.N) then table.remove(stances, 4) end
    else -- N
        if blockedDir(IsoDirections.W) then table.remove(stances, 3) end
        if blockedDir(IsoDirections.E) then table.remove(stances, 4) end
    end
    self.stances = stances
end

function TABAS_TakeBath:setAnimOrder(first)
    local animOrder = {}
    local randomChoice
    local action
    if first then
        local blood, dirt = TABAS_Utils.getBodyBloodAndDirt(self.character)
        local grime = TABAS_Utils.getBodyGrime(self.character)
        local grimes = blood + dirt + grime
        if grimes > 0 then
            local requiredWash = 0
            if grimes > 0 then
                requiredWash = 2
            else
                requiredWash = 1
            end
            if requiredWash > 0 then
                for i=1, requiredWash do
                    randomChoice = _rand:random(1, #self.anims.Wash)
                    action = table.remove(self.anims.Wash, randomChoice)
                    table.insert(animOrder, action)
                end
            end
        else
            randomChoice = _rand:random(1, #self.anims.Ext)
            action = table.remove(self.anims.Ext, randomChoice)
            table.insert(animOrder, action)
        end
    else
        local remains = {}
        for i=1, #self.anims.Wash do
            local remainAct = self.anims.Wash[i]
            table.insert(remains, remainAct)
        end
        for i=1, #self.anims.Ext do
            local anim = self.anims.Ext[i]
            table.insert(remains, anim)
        end
        local animCount = _rand:random(2, 4)
        for i=1, animCount do
            if #remains > 0 then
                randomChoice = _rand:random(1, #remains)
                action = table.remove(remains, randomChoice)
                table.insert(animOrder, action)
            end
        end
    end
    return animOrder
end

function TABAS_TakeBath:new(character, tfc_Base, square, bathingTime)
    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.tfc_Base = tfc_Base
    o.tfc_Base:addHeatSource()
    o.tfc_Base:heatSourceUpdate()

    o.washOffMakeup = TABAS_Utils.ModOptionsValue("WashOffMakeup")
    o.bathObj = tfc_Base.bathObject
    o.square = tfc_Base.square
    o.subObj = tfc_Base.linkedBathObject
    o.subSquare = tfc_Base.linkedSquare
    o.facing = tfc_Base.facing
    o.setPosition = {square:getX()+0.5, square:getY()+0.5, square:getZ()}

    o.bathingTime = bathingTime or 0

    o.wetTimerMax = 600 * SandboxVars.TakeABathAndShower.BathingWetTime
    o.consumeWater = SandboxVars.TakeABathAndShower.BathConsumeWater * 0.01
    o.usedWater = o.tfc_Base:getCapacity() * o.consumeWater
    o.waterTemperature = o.tfc_Base:getWaterData("temperature")
    o.hotWater = o.waterTemperature >= 38

    o.dirtyLevel = o.tfc_Base:getWaterData("dirtyLevel")

    local climate = getWorld():getClimateManager()
    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
    local isFemale = character:isFemale()
    if isFemale then
        o.anims = {
            Wash = {"WashFace", "WashArms", "WashLegsF"},
            Ext = {"ExtShoulder", "ExtStretch", "ExtLookUp"},
            Stance = {"SitF", "Relax", "ElbowLF", "ElbowRF", "LookUpF"}
        }
    else
        o.anims = {
            Wash = {"WashFace", "WashArms", "WashLegs"},
            Ext = {"ExtShoulder", "ExtStretch", "ExtLookUp"},
            Stance = {"Sit", "Relax", "ElbowL", "ElbowR", "LookUp"},
        }
    end
    o:setValidAnims()
    o.firstAnimTable = o:setAnimOrder(true)
    o.afterAnimTable = o:setAnimOrder(false)
    o.actionInterval = 0
    o.idleStance = false
    o.idleTime = _rand:random(12, 24)
    o.afterIdleTime = _rand:random(18, 36)
    if o.bathingTime > 30 then
        o.idleTime = o.idleTime * o.bathingTime * 0.25
        o.afterIdleTime = o.afterIdleTime * o.bathingTime * 0.25
    end
    o.firstAnimFinish = false
    o.doneWash = 0

    o.animStarted = false
    o.started = false
    o.stopped = false
    o.finished = false
    o.finishPhase = false

    o.cleansingFactor = 0.8
    o.cleansed = 0.3 -- This value is used to tainted tub water after the character leaves the bath. It is added each time the character washes off blood and dirt.
    o.currentAlpha = 0

    -- o.stopOnWalk = false
    -- o.stopOnRun = false
    -- o.stopOnAim = false
    o.useBathTimeProgressBar = true
    o.useProgressBar = false

    o.ignoreHandsWounds = true
    o.caloriesModifier = 0.5
    return o
end