local TABAS_WaterReader = {}

local function clamp(_value, _min, _max)
	if _min > _max then _min, _max = _max, _min; end
	return math.min(math.max(_value, _min), _max)
end

local function isUnmovedPipedWaterSource(obj)
    return obj:getSprite():getProperties():has(IsoFlagType.waterPiped) and (not obj:hasModData() or not obj:getModData().canBeWaterPiped) or not obj:getModData().canBeWaterPiped
end

local function isWaterInfinite(obj)
    local sprite = obj:getSprite()
    if not sprite then return false end
    
    local props = sprite:getProperties()
    if props:has("waterAmount") and tonumber(props:get("waterAmount")) >= 9999 then
        return true
    elseif obj:getSquare() and obj:getSquare():getRoom() then
        if not props:has(IsoFlagType.waterPiped) then
            return false
        elseif obj:getSquare():isNoWater() then
            return false
        elseif getGameTime():getWorldAgeHours() / 24 + ((getSandboxOptions():getOptionByName("TimeSinceApo"):getValue() - 1) * 30) >= getSandboxOptions():getWaterShutModifier() then
            return false
        else
            return not obj:hasModData() or not obj:getModData().canBeWaterPiped
        end
    else
        return false
    end
end

local function containsWater(fluidContainer)
    local fluid
    local waterAmount = 0
    local fluids = Fluid.getAllFluids()
    for i=0,fluids:size()-1 do
        fluid = fluids:get(i)
        if fluid:isCategory(FluidCategory.Water) and fluidContainer:contains(fluid) then
            local amount = fluidContainer:getSpecificFluidAmount(fluid)
            if amount then
                waterAmount = waterAmount + amount
            end
        end
    end
    return waterAmount
end

local function findWaterSourceOnSquare(square)
    if not square then return end
    for i=1, square:getObjects():size() do
        local obj = square:getObjects():get(i-1)
        if instanceof(obj, "IsoThumpable") and (not obj:getSprite() or not obj:getSprite().solidfloor) and not obj:getUsesExternalWaterSource() and obj:getFluidCapacity() > 0 then
            return obj
        end
    end
end

-- local function hasFluid(containers, container)
--     if SandboxVars.TakeABathAndShower.AvailableAllFluid then
--         return container:hasFluid()
--     else
--         return container:hasWater()
--     end
-- end

-- Return all containers in the top 3x3 square of coordinates
local function findExternalWaterSource(x, y, z)
    local square = getCell():getGridSquare(x, y, z +1)
    local containers = {}
    local container = findWaterSourceOnSquare(square)
    if container then
        if container:hasWater() then
            table.insert(containers, container)
        end
    end
    for i=-1, 1 do
        for j=-1, 1 do
            if i ~= 0 or j ~= 0 then
                local _square = getCell():getGridSquare(x + i, y + j, z +1)
                local _container = findWaterSourceOnSquare(_square)
                if _container then
                    if _container:hasWater() then
                        table.insert(containers, _container)
                    end
                end
            end
        end
    end
    return containers
end



-- Returns the counts of all the piped containers.
function TABAS_WaterReader.getExternalContainerCount(obj)
    if not obj:getSprite() then return 0 end
    if obj:hasExternalWaterSource() then
        if isWaterInfinite(obj) then -- if is use the water supply or natural water tiles
            return 0
        else
            local square = obj:getSquare()
            local containers = findExternalWaterSource(square:getX(), square:getY(), square:getZ())
            return #containers
        end
    else
        return 0
    end
end

-- Returns the water amount of all piped containers.
function TABAS_WaterReader.getWaterAmount(obj)
    if not obj:getSprite() then return 0 end
    if obj:getUsesExternalWaterSource() then
        if isWaterInfinite(obj) then 
            return 10000
        else
            local square = obj:getSquare()
            local total = 0
            local containers = findExternalWaterSource(square:getX(), square:getY(), square:getZ())
            if #containers > 0 then
                for i=1, #containers do
                    local container = containers[i]
                    if container and container:getFluidContainer() then
                        local amount = container:getFluidAmount()
                        total = total + amount
                    end
                end
            end
            return total
        end
    else
        return obj:getFluidAmount()
    end
end

-- Read the total capacity of all piped containers.
function TABAS_WaterReader.getWaterCapacity(obj)
    if not obj:getSprite() then return 0 end
    if obj:hasExternalWaterSource() then
        if isWaterInfinite(obj) then
            return 10000
        else
            local square = obj:getSquare()
            local total = 0
            local containers = findExternalWaterSource(square:getX(), square:getY(), square:getZ())
            if #containers > 0 then
                for i=1, #containers do
                    local container = containers[i]
                    if container and container:getFluidCapacity() then
                        local capacity = container:getFluidCapacity()
                        total = total + capacity
                    end
                end
            end
            return total
        end
    else
        return obj:getFluidCapacity()
    end
end

-- Use water from all piped containers.
-- This is done one by one from lowest coordinate to highest coordinate until there are no fluid left.
function TABAS_WaterReader.useWater(obj, amount)
    if not obj:getSprite() then return 0 end
    if obj:hasExternalWaterSource() then
        if isWaterInfinite(obj) then
            return amount
        else
            local square = obj:getSquare()
            local total = amount
            local containers = findExternalWaterSource(square:getX(), square:getY(), square:getZ())
            if #containers > 0 then
                for i=1, #containers do
                    if total > 0 then
                        local container = containers[i]
                        if container and container:getFluidAmount() then
                            local fAmount = container:getFluidAmount()
                            local uses = clamp(total, 0, fAmount)
                            if (isServer() or not isClient()) and uses > 0 then
                                local fluidContainer = container:getFluidContainer()
                                if fluidContainer and fluidContainer:getAmount() > 0 then
                                    fluidContainer:removeFluid(uses)
                                    container:sync()
                                    triggerEvent("OnWaterAmountChange", container, fAmount)
                                elseif isUnmovedPipedWaterSource(container) and container:hasReserveWater() then
                                    container:setReserveWaterAmount(container:getReserveWaterAmount() - uses)
                                end
                                total = total - uses
                            end
                        end
                    end
                end
            end
            return total
            -- Returns the unused amount.
        end
    else
        return obj:useFluid(amount)
    end
end

function TABAS_WaterReader.totalUseWater(square, amount)
    local total = amount
    local containers = findExternalWaterSource(square:getX(), square:getY(), square:getZ())
    if #containers == 0 then return 0 end

    for i=1, #containers do
        if total > 0 then
            local container = containers[i]
            if container and container:getFluidAmount() then
                local fAmount = container:getFluidAmount()
                local uses = clamp(total, 0, fAmount)
                if (isServer() or not isClient()) and uses > 0 then
                    local fluidContainer = container:fluidContainer()
                    if fluidContainer and fluidContainer:getAmount() > 0 then
                        fluidContainer:removeFluid(uses)
                        container:sync()
                        triggerEvent("OnWaterAmountChange", container, fAmount)
                    elseif isUnmovedPipedWaterSource(container) and container:hasReserveWater() then
                        container:setReserveWaterAmount(container:getReserveWaterAmount() - uses)
                    end
                    total = total - uses
                end
            end
        end
    end
end

return TABAS_WaterReader