require "TimedActions/ISBaseTimedAction"

ISFillFuelPumpFromW900Cistern = ISBaseTimedAction:derive("ISFillFuelPumpFromW900Cistern")

local DEFAULT_JERRYCAN_LITRES = 10

local function getPumpUnitsPerLitre()
    local jerryCanLitres = DEFAULT_JERRYCAN_LITRES
    local vehiclesConfig = rawget(_G, "Vehicles")

    if vehiclesConfig and vehiclesConfig.JerryCanLitres and vehiclesConfig.JerryCanLitres > 0 then
        jerryCanLitres = vehiclesConfig.JerryCanLitres
    end

    return 8 / jerryCanLitres
end

local function isNamedFuelPump(fuelPump)
    if not fuelPump then
        return false
    end

    if fuelPump.getObjectName then
        local ok, objectName = pcall(function()
            return fuelPump:getObjectName()
        end)
        if ok and (objectName == "FuelPump" or objectName == "Fuel Pump") then
            return true
        end
    end

    if fuelPump.getName then
        local ok, name = pcall(function()
            return fuelPump:getName()
        end)
        if ok and (name == "FuelPump" or name == "Fuel Pump") then
            return true
        end
    end

    local sprite = fuelPump.getSprite and fuelPump:getSprite() or nil
    local spriteName = sprite and sprite:getName() or nil
    return spriteName ~= nil and string.find(spriteName, "location_shop_gas2go_01_", 1, true) == 1
end

local function isBlacklistedFuelPumpFillTarget(fuelPump)
    if not fuelPump then
        return false
    end

    local candidateNames = {}
    if fuelPump.getObjectName then
        local ok, objectName = pcall(function()
            return fuelPump:getObjectName()
        end)
        if ok and objectName and objectName ~= "" then
            table.insert(candidateNames, objectName)
        end
    end

    if fuelPump.getName then
        local ok, name = pcall(function()
            return fuelPump:getName()
        end)
        if ok and name and name ~= "" then
            table.insert(candidateNames, name)
        end
    end

    local sprite = fuelPump.getSprite and fuelPump:getSprite() or nil
    local spriteName = sprite and sprite:getName() or nil
    if spriteName and spriteName ~= "" then
        table.insert(candidateNames, spriteName)
    end

    for _, candidateName in ipairs(candidateNames) do
        local loweredName = string.lower(candidateName)
        if loweredName == "raincollector_tarp"
            or loweredName == "raincollectorround_tarp"
            or loweredName == "raincollectorround_tarpclosed"
            or loweredName == "carpentry_02_120"
            or loweredName == "carpentry_02_124"
            or loweredName == "carpentry_02_127"
            or string.find(loweredName, "raincollector", 1, true)
            or string.find(loweredName, "rain collector", 1, true)
            or string.find(loweredName, "barrel", 1, true) then
            return true
        end
    end

    return false
end

local function isPoweredFuelPump(fuelPump)
    if not fuelPump or not fuelPump.getPipedFuelAmount or not fuelPump.getSquare then
        return false
    end

    if fuelPump:getPipedFuelAmount() <= 0 then
        return false
    end

    local square = fuelPump:getSquare()
    return square and ((SandboxVars.AllowExteriorGenerator and square:haveElectricity()) or square:hasGridPower())
end

local function isPumpTargetValid(fuelPump)
    if not fuelPump
        or not fuelPump.getPipedFuelAmount
        or not fuelPump.setPipedFuelAmount
        or not fuelPump.getSquare
        or fuelPump:getSquare() == nil then
        return false
    end

    if isBlacklistedFuelPumpFillTarget(fuelPump) then
        return false
    end

    if isPoweredFuelPump(fuelPump) then
        return true
    end

    if isNamedFuelPump(fuelPump) then
        return true
    end

    if not fuelPump.getFluidContainer then
        return false
    end

    local ok, fluidContainer = pcall(function()
        return fuelPump:getFluidContainer()
    end)

    return ok
        and fluidContainer ~= nil
        and fluidContainer:canAddFluid(Fluid.Petrol)
end

function ISFillFuelPumpFromW900Cistern:isValid()
    return self.sourceVehicle:isInArea(self.sourcePart:getArea(), self.character)
        and W900CisternParts.isValidGasSourcePart(self.sourcePart)
        and self.sourcePart:getContainerContentAmount() > 0
        and isPumpTargetValid(self.fuelPump)
end

function ISFillFuelPumpFromW900Cistern:waitToStart()
    self.character:faceThisObject(self.sourceVehicle)
    return self.character:shouldBeTurning()
end

function ISFillFuelPumpFromW900Cistern:update()
    local pumpUnitsPerLitre = getPumpUnitsPerLitre()
    local sourceAmount = self.sourceStart + (self.sourceTarget - self.sourceStart) * self:getJobDelta()
    sourceAmount = math.floor(sourceAmount)

    if sourceAmount ~= self.amountSent then
        self.sourcePart:setContainerContentAmount(sourceAmount)
        self.sourceVehicle:transmitPartModData(self.sourcePart)
        self.amountSent = sourceAmount
    end

    local transferredLitres = self.sourceStart - sourceAmount
    local pumpUnits = self.pumpStart + transferredLitres * pumpUnitsPerLitre
    self.fuelPump:setPipedFuelAmount(math.floor(pumpUnits))

    self.character:setMetabolicTarget(Metabolics.HeavyDomestic)
end

function ISFillFuelPumpFromW900Cistern:start()
    self:setActionAnim("fill_container_tap")
    self:setOverrideHandModels(nil, nil)
    self.character:reportEvent("EventTakeWater")
    self.sound = self.character:playSound("VehicleAddFuelFromGasPump")
end

function ISFillFuelPumpFromW900Cistern:stop()
    self.character:stopOrTriggerSound(self.sound)
    ISBaseTimedAction.stop(self)
end

function ISFillFuelPumpFromW900Cistern:serverStop()
    local pumpUnitsPerLitre = getPumpUnitsPerLitre()
    local sourceAmount = self.sourceStart + (self.sourceTarget - self.sourceStart) * self.netAction:getProgress()
    sourceAmount = math.floor(sourceAmount)
    self.sourcePart:setContainerContentAmount(sourceAmount)
    self.sourceVehicle:transmitPartModData(self.sourcePart)

    local transferredLitres = self.sourceStart - sourceAmount
    local pumpUnits = self.pumpStart + transferredLitres * pumpUnitsPerLitre
    self.fuelPump:setPipedFuelAmount(math.floor(pumpUnits))
end

function ISFillFuelPumpFromW900Cistern:perform()
    self.character:stopOrTriggerSound(self.sound)
    ISBaseTimedAction.perform(self)
end

function ISFillFuelPumpFromW900Cistern:complete()
    self.sourcePart:setContainerContentAmount(self.sourceTarget)
    self.sourceVehicle:transmitPartModData(self.sourcePart)
    self.fuelPump:setPipedFuelAmount(math.floor(self.pumpTarget))
    return true
end

function ISFillFuelPumpFromW900Cistern:getDuration()
    local pumpUnitsPerLitre = getPumpUnitsPerLitre()
    if not W900CisternParts.isValidGasSourcePart(self.sourcePart) or not isPumpTargetValid(self.fuelPump) then
        self.sourceStart = 0
        self.sourceTarget = 0
        self.pumpStart = 0
        self.pumpTarget = 0
        self.amountSent = 0
        return 0
    end

    self.sourceStart = self.sourcePart:getContainerContentAmount()
    self.sourceTarget = 0
    self.pumpStart = self.fuelPump:getPipedFuelAmount()
    self.pumpTarget = self.pumpStart + self.sourceStart * pumpUnitsPerLitre
    self.amountSent = self.sourceStart

    return self.sourceStart * 50
end

function ISFillFuelPumpFromW900Cistern:new(character, sourcePart, fuelPump)
    local o = ISBaseTimedAction.new(self, character)
    o.sourcePart = sourcePart
    o.sourceVehicle = sourcePart:getVehicle()
    o.fuelPump = fuelPump
    o.stopOnWalk = true
    o.stopOnRun = true
    o.maxTime = o:getDuration()
    return o
end