require "ISBaseObject"

local TABAS_TfcBase = ISBaseObject:derive("TABAS_TfcBase")

local TABAS_Utils = require("TABAS_Utils")
local TABAS_GT = require("TABAS_GameTimes")
local TABAS_Sprites = require("TABAS_Sprites")
local TFC_System = require("TABAS_TFCSystem")

--------------------- Construct  ---------------------

local tfcName = "TubFluidContainer"
local defaultTempe = 22.0
local defalutColors = {r=0.529, g=0.808, b=0.98, a=0.50} -- LightSkyBlue
local filledConst = {Low = 0.05, HalfLow = 0.30, Half = 0.55, Full = 0.80}

--------------------- TFC Object  ---------------------

function TABAS_TfcBase:createTfcObject(bathObject, square, facing, sprites)
    local exsiting = TABAS_Utils.getTfcObjectOnSquare(square, facing)
    if exsiting then
        print("TABAS_TfcBase: Water object already exists in square!")
        return exsiting
    else
        if not sprites then return end
        local tfcObject = IsoObject.new(square, sprites.low, tfcName)
        square:AddSpecialObject(tfcObject)
        -- square:AddTileObject(tfcObject)

        bathObject:setSpecialTooltip(true)
        tfcObject:transmitCompleteItemToClients()
        -- local index = tfcObject:getObjectIndex()
        -- square:transmitAddObjectToSquare(tfcObject, index)

        -- local attachedSprites = bathObject:getAttachedAnimSprite()
        -- if bathObject:getAttachedAnimSprite() ~= nil then
        --     for i = 0, attachedSprites:size()-1 do
		-- 		local sprite = attachedSprites:get(i):getParentSprite()
        --         if sprite and sprite:getName() ~= nil then
        --             bathObject:setOverlaySprite(sprite:getName(), true)
        --         end
        --     end
        --     bathObject:RemoveAttachedAnims()
        -- end
        return tfcObject
    end
end

function TABAS_TfcBase:removeTfcObject()
    if self.linkedTfcObject and self.linkedSquare then
        if isClient() then self.linkedSquare:transmitRemoveItemFromSquare(self.linkedTfcObject) end
        if isServer() then self.linkedSquare:transmitRemoveItemFromSquareOnClients(self.linkedTfcObject) end
        TABAS_Utils.removeTileObject(self.linkedSquare, self.linkedTfcObject)
        self.linkedBathObject:setSpecialTooltip(false)
    end
    if self.tfcObject and self.square then
        if isClient() then self.square:transmitRemoveItemFromSquare(self.tfcObject) end
        if isServer() then self.square:transmitRemoveItemFromSquareOnClients(self.tfcObject) end
        TABAS_Utils.removeTileObject(self.square, self.tfcObject)
        self.bathObject:setSpecialTooltip(false)
    end
    self.tfcObject = nil
    self.linkedTfcObject = nil
end

function TABAS_TfcBase:getTfcName()
    if not self.tfcObject then return nil end
    if self.tfcObject:getSprite() then
        local props = self.tfcObject:getSprite():getProperties()
        if props and props:Val("GroupName") then
            local gName = props:Val("GroupName")
            local cName = props:Val("CustomName")
            return gName .. " " .. cName
        end
    end
    return nil
end

function TABAS_TfcBase:hasTfc()
    local hasObj = self.tfcObject ~= nil and self.tfcObject:isExistInTheWorld()
    if hasObj and self:hasLinked() then
        hasObj = self.linkedTfcObject ~= nil and self.tfcObject:isExistInTheWorld()
        if not hasObj then
            print("hasTfc():: Not find linked tfc object!")
        end
    end
    return hasObj
end

function TABAS_TfcBase:updateTfc(amount)
    local newAmount = amount or round(self:getAmount(), 2)
    self:setWaterData("amount", newAmount)
    self:updateWaterLevel()
    self:updateWaterColor()
    TFC_System.transmitData(self.x, self.y, self.z, "amount", newAmount)
end


--------------------- FluidContainer  ---------------------

function TABAS_TfcBase:addFluidContainer()
    if not self.tfcObject or self.tfcObject:hasComponent(ComponentType.FluidContainer) then return end

    local component = ComponentType.FluidContainer:CreateComponent()
    local tubWaterCapacity = SandboxVars.TakeABathAndShower.TubWaterCapacity
    component:setCapacity(tubWaterCapacity)
    component:setContainerName(tfcName)

    GameEntityFactory.AddComponent(self.tfcObject, true, component)
    buildUtil.setHaveConstruction(self.square, true)
    self.fluidContainer = self.tfcObject:getComponent(ComponentType.FluidContainer)
end

function TABAS_TfcBase:hasFluidContainer()
    if not self.tfcObject or not self.tfcObject:isExistInTheWorld() then return end
    self.fluidContainer = self.tfcObject:getFluidContainer()
    return self.fluidContainer ~= nil
end

function TABAS_TfcBase:getTubFluidContainer()
    if not self:hasFluidContainer() then return end
    return self.tfcObject:getComponent(ComponentType.FluidContainer)
end

function TABAS_TfcBase:removeFluidContainer()
    if not self:hasFluidContainer() then return end
    GameEntityFactory.RemoveComponent(self.tfcObject, self.tfcObject:getFluidContainer())
    self.fluidContainer = nil
end

function TABAS_TfcBase:enabledRainCatcher()
    if not SandboxVars.TakeABathAndShower.TubRainCollect then return end
    if not self.square:isOutside() then return end
    self.fluidContainer:setRainCatcher(0.5)
end


function TABAS_TfcBase:hasFluid()
    if not self:hasFluidContainer() then return false end
    local amount = self.fluidContainer:getAmount()
    return amount > 0
end

function TABAS_TfcBase:getAmount()
    if not self:hasFluidContainer() then return 0 end
    return self.fluidContainer:getAmount()
end

function TABAS_TfcBase:getPrimaryFluid()
    if not self:hasFluidContainer() then return end
    return self.fluidContainer:getPrimaryFluid()
end

function TABAS_TfcBase:getPrimaryFluidAmount()
    if not self:hasFluidContainer() then return 0 end
    return self.fluidContainer:getPrimaryFluidAmount()
end

function TABAS_TfcBase:isEmpty()
    if not self:hasFluidContainer() then return false end
    return self.fluidContainer:isEmpty()
end

function TABAS_TfcBase:isFull()
    if not self:hasFluidContainer() then return false end
    return self.fluidContainer:isFull()
end

function TABAS_TfcBase:getRatio()
    if not self:hasFluidContainer() then return 0 end
    return self.fluidContainer:getFilledRatio()
end

function TABAS_TfcBase:getCapacity()
    if not self:hasFluidContainer() then return 0 end
    return self.fluidContainer:getCapacity()
end

function TABAS_TfcBase:getFreeCapacity()
    if not self:hasFluidContainer() then return 0 end
    return self.fluidContainer:getCapacity() - self.fluidContainer:getAmount()
end

function TABAS_TfcBase:adjustAmount(amount)
    if not self:hasFluidContainer() then return end
    self.fluidContainer:adjustAmount(amount)
    self:updateWaterLevel()
    self:setWaterData("amount", round(self.fluidContainer:getAmount(),2))
end

function TABAS_TfcBase:canAddFluid(fluid)
    if not self:hasFluidContainer() then return false end
    return self.fluidContainer:canAddFluid(fluid)
end

function TABAS_TfcBase:addFluid(fluid, amount, temperature)
    if not self:hasFluidContainer() then return end
    if not self:canAddFluid(fluid) then return end
    self.fluidContainer:addFluid(fluid, amount)
    self:updateWaterLevel()
    self:setFluidColor()
    if temperature then
        self:adjustTemperature(amount, temperature)
    end
    self:updateHotSteamOverlay()
    local newAmount = round(self.fluidContainer:getAmount(),2)
    self:setWaterData("amount", newAmount)
    TFC_System.transmitData(self.x, self.y, self.z, "amount", newAmount)
end

function TABAS_TfcBase:removeFluid(amount)
    if not self:hasFluidContainer() then return end
    local currentAmount = self:getAmount()
    if not amount then
        amount = currentAmount
    end
    self.fluidContainer:removeFluid(amount)
    self:updateWaterLevel()
    self:updateHotSteamOverlay()
    local newAmount = currentAmount - amount
    self:setWaterData("amount", newAmount)
    if newAmount == 0 and self:getWaterData("bathSalt") then
        self:setWaterData("bathSalt", nil)
    end
    TFC_System.transmitData(self.x, self.y, self.z, "amount", newAmount)
end

function TABAS_TfcBase:addWater(amount)
    local temperature = defaultTempe
    if TABAS_Utils.canHot(self.bathObject) then
        temperature = self:getBathData("idealTemperature") or 40.0
    end
    self:addFluid(Fluid.Water, amount, temperature)
    self:updateLastUpdate()
end

function TABAS_TfcBase:getLastUpdate()
    return self:getWaterData("lastUpdate")
end

function TABAS_TfcBase:updateLastUpdate()
    -- local timeCalendar = getGameTime():getCalender()
    local currentTime = TABAS_GT.Calender:getTimeInMillis()
    self:setWaterData("lastUpdate", currentTime)
    TFC_System.transmitData(self.x, self.y, self.z, "lastUpdate", currentTime)
end


--------------------- Water Temperature ---------------------

function TABAS_TfcBase:getCurrentTemperature()
    return self:getWaterData("temperature")
end

function TABAS_TfcBase:setWaterTemperature(temperature)
    local newTemperature = round(temperature, 4)
    self:setWaterData("temperature", newTemperature)
    self:updateHotSteamOverlay()
    TFC_System.transmitData(self.x, self.y, self.z, "temperature", newTemperature)
end

function TABAS_TfcBase:adjustTemperature(addedAmount, targetTempe)
    if not SandboxVars.TakeABathAndShower.WaterTemperatureConcept then return defaultTempe end
    local currentTempe = self:getWaterData("temperature")
    targetTempe = targetTempe and targetTempe or defaultTempe
    if currentTempe == targetTempe then return end

    if currentTempe >= targetTempe - 0.1 and currentTempe <= targetTempe + 0.1 then
        self:setWaterTemperature(targetTempe)
        print("Set new temperature: ".. targetTempe)
        return
    end
    local currentAmount = round(self:getAmount(), 2)
    local old = currentTempe * currentAmount
    local new = targetTempe * addedAmount
    local total = currentAmount + addedAmount
    local culcedTempe = (old + new) / total
    -- print("Set new temperature: ".. culcedTempe)
    self:setWaterTemperature(culcedTempe)
end


--------------------- Water Sprite ---------------------

function TABAS_TfcBase:setWaterSprite(object, sprites)
    if not object or not sprites then return end
    local ratio = self:getRatio() or 0
    if self:getWaterData("isDirty") and sprites.dirty then
        sprites = sprites.dirty
    end
    local sprite
    if ratio > filledConst.Full then
        sprite = sprites.full
    elseif ratio > filledConst.Half then
        sprite = sprites.half
    elseif ratio > filledConst.HalfLow then
        sprite = sprites.halfLow
    elseif ratio > filledConst.Low then
        sprite = sprites.low
    else
        sprite = ""
    end
    object:setSpriteFromName(sprite)
    if isClient() then object:transmitUpdatedSpriteToServer() end
    if isServer() then object:transmitUpdatedSpriteToClients() end
end


function TABAS_TfcBase:updateWaterLevel()
    self:setWaterSprite(self.tfcObject, self.spritesTable)
    self:setWaterSprite(self.linkedTfcObject, self.linkedSprites)
end

function TABAS_TfcBase:setFluidColor()
    if self:hasFluid() then
        local color = self.fluidContainer:getColor()
        if color then
            local r = color:getRedFloat()
            local g = color:getGreenFloat()
            local b = color:getBlueFloat()
            self.colors = {r=r, g=g, b=b, a=0.5}
        end
    end
    self:updateWaterColor()
end

function TABAS_TfcBase:setWaterColor(r,g,b,a)
    self.colors = {r=r, g=g, b=b, a=a or 0.5}
    self:updateWaterColor()
end

function TABAS_TfcBase:updateWaterColor()
    if not self.tfcObject then return end
    local col = self.colors or defalutColors
    local oldCol = self.tfcObject:getCustomColor()
    if not oldCol or (oldCol.r ~= col.r or oldCol.g ~= col.g or oldCol.b ~= col.b) then
        local colorInfo = ColorInfo.new(col.r, col.g, col.b, col.a)
        self.tfcObject:setCustomColor(colorInfo)
        self.tfcObject:transmitCustomColorToClients()
        if self.linkedTfcObject then
            self.linkedTfcObject:setCustomColor(colorInfo)
            self.linkedTfcObject:transmitCustomColorToClients()
        end
    end
end

function TABAS_TfcBase:getSpritesTable()
    if not self.spritesTable then
        print("TABAS_TfcBase: Not found sprites table!")
        return nil
    end
    return self.spritesTable
end

---@param variable string - "spritesTable" or "linkedSptites".
---@param key string - "faucet" or "tub" etc.
function TABAS_TfcBase:setSpritesTable(variable, name, key)
    if TABAS_Sprites[name] == nil then
        print("TABAS_TfcBase: Not found sprites!")
        return
    end
    local var = variable or "spritesTable"
    local spriteDir = "sprite" .. self.facing
    self[var] = TABAS_Sprites[name][spriteDir][key]
    if self[var] == nil then
        print("TABAS_TfcBase: Not found sprites!")
    end
end

function TABAS_TfcBase:waterToDirt(amount)
    if not SandboxVars.TakeABathAndShower.TubWaterGetDirty then
        self:setWaterData("dirtyLevel", 0)
        self:setWaterData("isDirty", false)
        return
    end
    if not self:hasFluidContainer() then return end
    local dirtLvl = self:getWaterData("dirtyLevel")
    local newDirtLvl = round(dirtLvl + amount, 2)
    self:setWaterData("dirtyLevel", newDirtLvl)
    if newDirtLvl < 20 then return end

    self:setWaterData("isDirty", true)
    if not self:getWaterData("bathSalt") then
        self:replaceWater(Fluid.TaintedWater)
    end
    self:updateWaterLevel()
end

-- If you need a numerical value, use "getWaterData("dirtyLevel") or 0".
function TABAS_TfcBase:getDirtyLevel()
    local dirtyLevel = self:getWaterData("dirtyLevel")
    if dirtyLevel and not self:isEmpty() then
        if not TABAS_Utils.ModOptionsValue("DisplayTubWaterDirtyLevel") then
            if dirtyLevel >= 50 then
                return getText("IGUI_TABAS_BathtubInfo_WaterFilthy")
            elseif dirtyLevel >= 20 then
                return getText("IGUI_TABAS_BathtubInfo_WaterDirty")
            else
                return getText("IGUI_TABAS_BathtubInfo_WaterClean")
            end
        else
            return tostring(dirtyLevel or 0)
        end
    else
        return "-"
    end
end

function TABAS_TfcBase:replaceWater(newFluid)
    local oldFluid
    local fluids = Fluid.getAllFluids()
    for i=0,fluids:size()-1 do
        oldFluid = fluids:get(i)
        if oldFluid:isCategory(FluidCategory.Water) and self.fluidContainer:contains(oldFluid) then
            local amount = self.fluidContainer:getSpecificFluidAmount(oldFluid)
            if amount and amount > 0 then
                self.fluidContainer:adjustSpecificFluidAmount(oldFluid, 0)
                self:addFluid(newFluid, amount)
            end
        end
    end
end

--------------------- Hot Steam Overlay ---------------------

function TABAS_TfcBase:setHotSteamOverlay()
    if not self:hasTfc() then return end
    if not self.tfcObject:hasOverlaySprite() then
        local steamSprite1 = TABAS_Sprites.Steam1[self.facing]
        self.tfcObject:setOverlaySprite(steamSprite1, true)
    end
    if self.linkedTfcObject and not self.linkedTfcObject:hasOverlaySprite() then
        local steamSprite2 =  TABAS_Sprites.Steam2[self.facing]
        self.linkedTfcObject:setOverlaySprite(steamSprite2, true)
    end
end

function TABAS_TfcBase:clearHotSteamOverlay()
    if not self:hasTfc() then return end
    if self.tfcObject:hasOverlaySprite() then
        self.tfcObject:setOverlaySprite("", true)
    end
    if self.linkedTfcObject and self.linkedTfcObject:hasOverlaySprite() then
        self.linkedTfcObject:setOverlaySprite("", true)
    end
end

function TABAS_TfcBase:updateHotSteamOverlay()
    if self:getRatio() > filledConst.HalfLow and self:getWaterData("temperature") >= 38 then
        self:setHotSteamOverlay()
    else
        self:clearHotSteamOverlay()
    end
end


--------------------- Bath Salt  ---------------------

function TABAS_TfcBase:canAddBathSalt()
    if not self:hasFluid() then return false end
    if self:getWaterData("bathSalt") then return false end
    local fluid = self:getPrimaryFluid()
    if fluid and fluid:isCategory(FluidCategory.Water) then
        return true
    end
    return false
end

function TABAS_TfcBase:addBathSalt(def)
    if not self:hasFluid() then return end
    local fluitType = def.fluidType
    if fluitType then
        local newFluid = Fluid.Get(def.fluidType)
        self:replaceWater(newFluid)
        self:setWaterData("bathSalt", def.type)
    else
        print("This bathSalt def has not fluid type!")
    end
end

-- function TABAS_TfcBase:getBathSalt()
--     return self:getWaterData("bathSalt")
-- end


--------------------- Heat Source  ---------------------

function TABAS_TfcBase:addHeatSource()
    if self.heatSource then return end
    local temperature = self:getWaterData("temperature")
    self.heatSource = IsoHeatSource.new(self.square:getX(), self.square:getY(), self.square:getZ(), 1, temperature)
    self.square:getCell():addHeatSource(self.heatSource)
end

function TABAS_TfcBase:removeHeatSource()
    if not self.heatSource then return end
    self.square:getCell():removeHeatSource(self.heatSource)
    self.heatSource = nil
end

function TABAS_TfcBase:heatSourceUpdate()
    if not self.heatSource then return end
    local climate = getWorld():getClimateManager()
    local climateF = climate:getClimateFloat(4) -- 4 is TEMPERATURE
    climateF:setEnableOverride(true)
    local temperature = self:getWaterData("temperature")
    self.heatSource:setTemperature(temperature)
    climateF:setEnableOverride(false)
    self.square:getCell():updateHeatSources()
end

--------------------- Bath Object  ---------------------

function TABAS_TfcBase:hasLinked()
    return self.linkedSquare ~= nil and self.linkedSprites ~= nil
end

function TABAS_TfcBase:getBathData(key)
    if not self.bathObject:isExistInTheWorld() then return end
    local modData = self.bathObject:getModData()
    if modData[key] then
        return modData[key]
    end
    return nil
end

function TABAS_TfcBase:setBathData(key, value)
    if not self.bathObject:isExistInTheWorld() then return end
    local modData = self.bathObject:getModData()
    modData[key] = value
    self.bathObject:setModData(modData)
    self.bathObject:transmitModData()
end

function TABAS_TfcBase:isUsing()
    return self:getBathData("using") or false
end

--------------------- Misc  ---------------------

function TABAS_TfcBase:getWaterData(key)
    if not self:hasTfc() then return end
    local modData = self.tfcObject:getModData()
    if modData[key] then
        return modData[key]
    end
    return nil
end

function TABAS_TfcBase:setWaterData(key, value)
    if not self.tfcObject then return end
    local modData = self.tfcObject:getModData()
    modData[key] = value
    self.tfcObject:setModData(modData)
    self.tfcObject:transmitModData()
end

function TABAS_TfcBase:setLinkedWaterData(key, value)
    if not self.linkedTfcObject then return end
    local modData = self.linkedTfcObject:getModData()
    modData[key] = value
    self.linkedTfcObject:setModData(modData)
end

--------------------- TFC Event Handler ---------------------

-- @prams string state - 'fill' or 'empty' or 'reheat'
function TABAS_TfcBase:isActivated(state)
    local currentState = TFC_System.currentActivatedState(self.x, self.y, self.z)
    if not state or state == currentState then
        return TFC_System.isActivated(self.x, self.y, self.z)
    else
        return false
    end
end

function TABAS_TfcBase:triggerActivate(state, toggle)
    if toggle and TFC_System.isActivated(self.x, self.y, self.z) then
        print("This TFC currently activated!")
    else
        TFC_System.Activate(self.x, self.y, self.z, self.facing, state, toggle)
    end
end

--------------------- Sync Data from TFC System  ---------------------

function TABAS_TfcBase:syncData()
    TFC_System.syncData(self.x, self.y, self.z, self.facing)
    self:updateHotSteamOverlay()
end

--------------------- init  ---------------------

function TABAS_TfcBase:remove()
    self:removeFluidContainer()
    self:removeHeatSource()
    self:removeTfcObject()
    if self.bathObject then
        self:setBathData("hasTubFluidContainer", nil)
    end
    TFC_System.unregister(self.x, self.y, self.z)
end

function TABAS_TfcBase:create()
    if not self.bathObject or not self.spritesTable or not self.square then
        print("TFC create invalid!")
        return
    end
    -- Water Object
    self.tfcObject = self:createTfcObject(self.bathObject, self.square, self.facing, self.spritesTable)
    if self:hasLinked() then
        self.linkedTfcObject = self:createTfcObject(self.linkedBathObject, self.linkedSquare, self.facing, self.linkedSprites)
        self:setLinkedWaterData("facing", self.facing)
        self:setLinkedWaterData("linked", string.format("%s,%s,%s", self.square:getX(), self.square:getY(), self.square:getZ()))
        self:setWaterData("linked", string.format("%s,%s,%s", self.linkedSquare:getX(), self.linkedSquare:getY(), self.linkedSquare:getZ()))
    end
    self:updateWaterLevel()
    self:updateWaterColor()

    -- fluidContainer
    self:addFluidContainer()
    self:enabledRainCatcher()

    -- modData for tfcObject
    local capacity = self.fluidContainer:getCapacity()
    local rainCatcher = self.fluidContainer:isQualifiesForMetaStorage()
    local currentTime = TABAS_GT.Calender:getTimeInMillis() or 0
    local idealTemperature = self:getBathData("idealTemperature") or 40.0
    self:setWaterData("lastUpdate", currentTime)
    self:setWaterData("capacity", capacity)
    self:setWaterData("amount", 0)
    self:setWaterData("temperature", 0)
    self:setWaterData("rainCatcher", rainCatcher)
    self:setWaterData("isDirty", false)
    self:setWaterData("dirtyLevel", 0)
    self:setWaterData("bathSalt", false)
    self:setWaterData("facing", self.facing)

    self:setBathData("hasTubFluidContainer", true)
    self:setBathData("idealTemperature", idealTemperature)

    -- heatSource
    -- self:addHeatSource() -- This is only used when temperature control is required.
    -- self:heatSourceUpdate()

    TFC_System.register(self)
end

function TABAS_TfcBase:initNew()
    -- self:setSpritesTable("spritesTable", "BathWater", "faucet")
    -- self:setSpritesTable("linkedSprites", "BathWater", "tub")
end

function TABAS_TfcBase:init()
    self:initNew()
    self.tfcObject = TABAS_Utils.getTfcObjectOnSquare(self.square, self.facing)
    if self.tfcObject then
       self.fluidContainer = self.tfcObject:getComponent(ComponentType.FluidContainer)
    end
    if self:hasLinked() then
        self.linkedTfcObject = TABAS_Utils.getTfcObjectOnSquare(self.linkedSquare, self.facing)
    end
    self:syncData()
end

function TABAS_TfcBase:new(x, y, z, facing)
    local o = {}
    setmetatable(o, self)
    self.__index = self
    local square = getCell():getGridSquare(x, y, z)
    if not square then return end

    local isoObject = TABAS_Utils.getBathObjectOnSquare(square, facing)
    if not isoObject or not TABAS_Utils.isBathFaucet(isoObject) then return end

    o.bathObject = isoObject
    o.square = square
    o.x = x
    o.y = y
    o.z = z
    o.facing = facing
    o.filledConst = filledConst

    o:init()
    return o
end

return TABAS_TfcBase