-- ============================================================================
-- === PlaceableDynamicFuelPricingSilo.Lua
-- === Mod by [LSMT] Modding Team 
-- === LS25 /FS25
-- === Script by [LSMT] BaTt3RiE @ 2025
-- === Ver 1.0.0.0
-- ============================================================================

PlaceableDynamicFuelPricingSilo = {}

local DEBUG = false

local function debugPrint(...)
    if DEBUG then
        print(string.format("[DynamicPricingSilo] %s", string.format(...)))
    end
end

function PlaceableDynamicFuelPricingSilo.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(PlaceableSilo, specializations)
end

function PlaceableDynamicFuelPricingSilo.registerXMLPaths(schema, baseKey)
    schema:setXMLSpecializationType("DynamicPricingSilo")
    schema:register(XMLValueType.NODE_INDEX, baseKey .. ".priceDisplays.priceDisplay(?)#euroNode", "Price display euro node")
    schema:register(XMLValueType.NODE_INDEX, baseKey .. ".priceDisplays.priceDisplay(?)#decimal1Node", "Price display decimal 1 node")
    schema:register(XMLValueType.NODE_INDEX, baseKey .. ".priceDisplays.priceDisplay(?)#decimal2Node", "Price display decimal 2 node")
    schema:register(XMLValueType.NODE_INDEX, baseKey .. ".priceDisplays.priceDisplay(?)#decimal3Node", "Price display decimal 3 node")
    schema:register(XMLValueType.STRING, baseKey .. ".priceDisplays.priceDisplay(?)#fillType", "Fill type for this display")
    schema:register(XMLValueType.FLOAT, baseKey .. ".dynamicPricing.diesel#defaultPrice", "Diesel default price", 1.500)
    schema:register(XMLValueType.FLOAT, baseKey .. ".dynamicPricing.def#defaultPrice", "DEF default price", 1.200)
    schema:register(XMLValueType.NODE_INDEX, baseKey .. ".dynamicPricing.dieselPriceTrigger#node", "Trigger für Diesel-Preis-Eingabe")
    schema:register(XMLValueType.NODE_INDEX, baseKey .. ".dynamicPricing.defPriceTrigger#node", "Trigger für AdBlue-Preis-Eingabe")
    
    schema:setXMLSpecializationType()
end

function PlaceableDynamicFuelPricingSilo.registerSavegameXMLPaths(schema, baseKey)
    schema:register(XMLValueType.FLOAT, baseKey .. "#dieselPrice", "Saved diesel price")
    schema:register(XMLValueType.FLOAT, baseKey .. "#defPrice", "Saved DEF price")
end

function PlaceableDynamicFuelPricingSilo.registerEventListeners(placeableType)
    SpecializationUtil.registerEventListener(placeableType, "onLoad", PlaceableDynamicFuelPricingSilo)
    SpecializationUtil.registerEventListener(placeableType, "onFinalizePlacement", PlaceableDynamicFuelPricingSilo)
    SpecializationUtil.registerEventListener(placeableType, "onDelete", PlaceableDynamicFuelPricingSilo)
    SpecializationUtil.registerEventListener(placeableType, "onReadStream", PlaceableDynamicFuelPricingSilo)
    SpecializationUtil.registerEventListener(placeableType, "onWriteStream", PlaceableDynamicFuelPricingSilo)
end

function PlaceableDynamicFuelPricingSilo.registerFunctions(placeableType)
    SpecializationUtil.registerFunction(placeableType, "updatePriceDisplay", PlaceableDynamicFuelPricingSilo.updatePriceDisplay)
    SpecializationUtil.registerFunction(placeableType, "getSiloFuelPrice", PlaceableDynamicFuelPricingSilo.getSiloFuelPrice)
    SpecializationUtil.registerFunction(placeableType, "canWithdrawFuel", PlaceableDynamicFuelPricingSilo.canWithdrawFuel)
    SpecializationUtil.registerFunction(placeableType, "withdrawFuel", PlaceableDynamicFuelPricingSilo.withdrawFuel)
    SpecializationUtil.registerFunction(placeableType, "showDieselPriceDialog", PlaceableDynamicFuelPricingSilo.showDieselPriceDialog)
    SpecializationUtil.registerFunction(placeableType, "showDefPriceDialog", PlaceableDynamicFuelPricingSilo.showDefPriceDialog)
    SpecializationUtil.registerFunction(placeableType, "setManualPrice", PlaceableDynamicFuelPricingSilo.setManualPrice)
    SpecializationUtil.registerFunction(placeableType, "dieselPriceTriggerCallback", PlaceableDynamicFuelPricingSilo.dieselPriceTriggerCallback)
    SpecializationUtil.registerFunction(placeableType, "defPriceTriggerCallback", PlaceableDynamicFuelPricingSilo.defPriceTriggerCallback)
    SpecializationUtil.registerFunction(placeableType, "registerDieselPriceActionEvent", PlaceableDynamicFuelPricingSilo.registerDieselPriceActionEvent)
    SpecializationUtil.registerFunction(placeableType, "registerDefPriceActionEvent", PlaceableDynamicFuelPricingSilo.registerDefPriceActionEvent)
    SpecializationUtil.registerFunction(placeableType, "unregisterDieselPriceActionEvent", PlaceableDynamicFuelPricingSilo.unregisterDieselPriceActionEvent)
    SpecializationUtil.registerFunction(placeableType, "unregisterDefPriceActionEvent", PlaceableDynamicFuelPricingSilo.unregisterDefPriceActionEvent)
end

function PlaceableDynamicFuelPricingSilo:onLoad(savegame)
    self.spec_dynamicPricingSilo = {}
    local spec = self.spec_dynamicPricingSilo
    
    local xmlFile = self.xmlFile
    local defaultDieselPrice = xmlFile:getValue("placeable.dynamicPricing.diesel#defaultPrice", 1.500)
    local defaultDefPrice = xmlFile:getValue("placeable.dynamicPricing.def#defaultPrice", 1.200)
    
    spec.currentPrices = {
        DIESEL = defaultDieselPrice,
        DEF = defaultDefPrice
    }

    spec.priceDisplays = {
        DIESEL = {},
        DEF = {}
    }
    
    local i = 0
    while true do
        local key = string.format("placeable.priceDisplays.priceDisplay(%d)", i)
        if not xmlFile:hasProperty(key) then break end
        
        local fillTypeStr = xmlFile:getValue(key .. "#fillType", "DIESEL")
        
        local display = {
            euro = xmlFile:getValue(key .. "#euroNode", nil, self.components, self.i3dMappings),
            decimal1 = xmlFile:getValue(key .. "#decimal1Node", nil, self.components, self.i3dMappings),
            decimal2 = xmlFile:getValue(key .. "#decimal2Node", nil, self.components, self.i3dMappings),
            decimal3 = xmlFile:getValue(key .. "#decimal3Node", nil, self.components, self.i3dMappings)
        }
        
        if display.euro ~= nil then
            table.insert(spec.priceDisplays[fillTypeStr], display)
            debugPrint("Preis-Display geladen: %s (4 Nodes)", fillTypeStr)
        end
        
        i = i + 1
    end
    
    spec.dieselPriceTrigger = xmlFile:getValue("placeable.dynamicPricing.dieselPriceTrigger#node", nil, self.components, self.i3dMappings)
    spec.defPriceTrigger = xmlFile:getValue("placeable.dynamicPricing.defPriceTrigger#node", nil, self.components, self.i3dMappings)
    
    if spec.dieselPriceTrigger == nil and self.i3dMappings["dieselpricechancepoint"] ~= nil then
        spec.dieselPriceTrigger = self.i3dMappings["dieselpricechancepoint"]
    end
    
    if spec.defPriceTrigger == nil and self.i3dMappings["defpricechancepoint"] ~= nil then
        spec.defPriceTrigger = self.i3dMappings["defpricechancepoint"]
    end
    
    spec.dieselPriceActionEventId = nil
    spec.defPriceActionEventId = nil
    spec.isPlayerInDieselTrigger = false
    spec.isPlayerInDefTrigger = false
end

function PlaceableDynamicFuelPricingSilo:loadFromXMLFile(xmlFile, key, reset)
    local spec = self.spec_dynamicPricingSilo
    if spec == nil or spec.currentPrices == nil then
        return
    end

    if not reset then
        local savedDiesel = xmlFile:getValue(key .. "#dieselPrice")
        local savedDef    = xmlFile:getValue(key .. "#defPrice")

        if savedDiesel ~= nil then
            spec.currentPrices.DIESEL = savedDiesel
        end

        if savedDef ~= nil then
            spec.currentPrices.DEF = savedDef
        end
    end

    self:updatePriceDisplay()
end

function PlaceableDynamicFuelPricingSilo:saveToXMLFile(xmlFile, key, usedModNames)
    local spec = self.spec_dynamicPricingSilo
    if spec == nil or spec.currentPrices == nil then
        return
    end

    xmlFile:setValue(key .. "#dieselPrice", spec.currentPrices.DIESEL)
    xmlFile:setValue(key .. "#defPrice",  spec.currentPrices.DEF)
end

function PlaceableDynamicFuelPricingSilo:onFinalizePlacement()
    local spec = self.spec_dynamicPricingSilo
    
    if spec.dieselPriceTrigger ~= nil then
        addTrigger(spec.dieselPriceTrigger, "dieselPriceTriggerCallback", self)
        debugPrint("Diesel-Preis-Trigger registriert")
    end
    
    if spec.defPriceTrigger ~= nil then
        addTrigger(spec.defPriceTrigger, "defPriceTriggerCallback", self)
        debugPrint("AdBlue-Preis-Trigger registriert")
    end
    
    self:updatePriceDisplay()
    debugPrint("Preis-System aktiviert")
end

function PlaceableDynamicFuelPricingSilo:dieselPriceTriggerCallback(triggerId, otherId, onEnter, onLeave)
    if g_localPlayer ~= nil and otherId == g_localPlayer.rootNode then
        local spec = self.spec_dynamicPricingSilo
        
        if onEnter then
            spec.isPlayerInDieselTrigger = true
            self:registerDieselPriceActionEvent()
        elseif onLeave then
            spec.isPlayerInDieselTrigger = false
            self:unregisterDieselPriceActionEvent()
        end
    end
end

function PlaceableDynamicFuelPricingSilo:defPriceTriggerCallback(triggerId, otherId, onEnter, onLeave)
    if g_localPlayer ~= nil and otherId == g_localPlayer.rootNode then
        local spec = self.spec_dynamicPricingSilo
        
        if onEnter then
            spec.isPlayerInDefTrigger = true
            self:registerDefPriceActionEvent()
        elseif onLeave then
            spec.isPlayerInDefTrigger = false
            self:unregisterDefPriceActionEvent()
        end
    end
end

function PlaceableDynamicFuelPricingSilo:registerDieselPriceActionEvent()
    local spec = self.spec_dynamicPricingSilo
    
    if g_localPlayer == nil or g_localPlayer.farmId ~= self:getOwnerFarmId() then
        return
    end
    
    if spec.dieselPriceActionEventId == nil then
        local _, actionEventId = g_inputBinding:registerActionEvent(
            InputAction.ACTIVATE_OBJECT,
            self,
            PlaceableDynamicFuelPricingSilo.actionEventChangeDieselPrice,
            false, true, false, true
        )
        
        g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("action_changeDieselPrice"))
        g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL)
        g_inputBinding:setActionEventTextVisibility(actionEventId, true)
        
        spec.dieselPriceActionEventId = actionEventId
    end
end

function PlaceableDynamicFuelPricingSilo:registerDefPriceActionEvent()
    local spec = self.spec_dynamicPricingSilo
    
    if g_localPlayer == nil or g_localPlayer.farmId ~= self:getOwnerFarmId() then
        return
    end
    
    if spec.defPriceActionEventId == nil then
        local _, actionEventId = g_inputBinding:registerActionEvent(
            InputAction.ACTIVATE_OBJECT,
            self,
            PlaceableDynamicFuelPricingSilo.actionEventChangeDefPrice,
            false, true, false, true
        )

        g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("action_changeDefPrice"))
        g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL)
        g_inputBinding:setActionEventTextVisibility(actionEventId, true)
        
        spec.defPriceActionEventId = actionEventId
    end
end

function PlaceableDynamicFuelPricingSilo:unregisterDieselPriceActionEvent()
    local spec = self.spec_dynamicPricingSilo
    
    if spec.dieselPriceActionEventId ~= nil then
        g_inputBinding:removeActionEvent(spec.dieselPriceActionEventId)
        spec.dieselPriceActionEventId = nil
    end
end

function PlaceableDynamicFuelPricingSilo:unregisterDefPriceActionEvent()
    local spec = self.spec_dynamicPricingSilo
    
    if spec.defPriceActionEventId ~= nil then
        g_inputBinding:removeActionEvent(spec.defPriceActionEventId)
        spec.defPriceActionEventId = nil
    end
end

function PlaceableDynamicFuelPricingSilo.actionEventChangeDieselPrice(self, actionName, inputValue, callbackState, isAnalog)
    if inputValue > 0 then
        self:showDieselPriceDialog()
    end
end

function PlaceableDynamicFuelPricingSilo.actionEventChangeDefPrice(self, actionName, inputValue, callbackState, isAnalog)
    if inputValue > 0 then
        self:showDefPriceDialog()
    end
end

function PlaceableDynamicFuelPricingSilo:showDieselPriceDialog()
    local spec = self.spec_dynamicPricingSilo
    local currentPrice = spec.currentPrices.DIESEL
    
    local function onTextEntered(text, clickOk)
        if clickOk and text and text ~= "" then
            local cleanText = text:gsub(",", ".")
            local price = tonumber(cleanText)
            
            if price and price >= 0.100 and price <= 5.000 then
                self:setManualPrice("DIESEL", price)
            else
                g_currentMission:showBlinkingWarning(
                    string.format(g_i18n:getText("dialog_invalidPrice"), 0.100, 5.000), 
                    2000
                )
            end
        end
    end
    
    TextInputDialog.show(
        onTextEntered,
        nil,
        string.format("%.3f", currentPrice),
        g_i18n:getText("dialog_dieselPricePrompt"),
        g_i18n:getText("dialog_dieselPriceTitle"),
        6,
        g_i18n:getText("button_save"),
        nil,
        nil,
        false
    )
end

function PlaceableDynamicFuelPricingSilo:showDefPriceDialog()
    local spec = self.spec_dynamicPricingSilo
    local currentPrice = spec.currentPrices.DEF
    
    local function onTextEntered(text, clickOk)
        if clickOk and text and text ~= "" then
            local cleanText = text:gsub(",", ".")
            local price = tonumber(cleanText)
            
            if price and price >= 0.100 and price <= 5.000 then
                self:setManualPrice("DEF", price)
            else
                g_currentMission:showBlinkingWarning(
                    string.format(g_i18n:getText("dialog_invalidPrice"), 0.100, 5.000), 
                    2000
                )
            end
        end
    end
    
    TextInputDialog.show(
        onTextEntered,
        nil,
        string.format("%.3f", currentPrice),
        g_i18n:getText("dialog_defPricePrompt"),
        g_i18n:getText("dialog_defPriceTitle"),
        6,
        g_i18n:getText("button_save"),
        nil,
        nil,
        false
    )
end

function PlaceableDynamicFuelPricingSilo:setManualPrice(fillTypeStr, price)
    local spec = self.spec_dynamicPricingSilo
    spec.currentPrices[fillTypeStr] = price
    self:updatePriceDisplay()

    local messageKey = fillTypeStr == "DIESEL" and "info_priceSetDiesel" or "info_priceSetDef"
    g_currentMission:showBlinkingWarning(
        string.format(g_i18n:getText(messageKey), price),
        3000
    )
    
    debugPrint("Preis gesetzt: %s = %.3f €/L (Display)", fillTypeStr, price)
    
    if g_client ~= nil then
        g_client:getServerConnection():sendEvent(
            SiloFuelPriceUpdateEvent.new(self, spec.currentPrices)
        )
        debugPrint("Preis-Event an Server gesendet (Client)")
    elseif g_server ~= nil then
        g_server:broadcastEvent(
            SiloFuelPriceUpdateEvent.new(self, spec.currentPrices),
            nil, nil, self
        )
        debugPrint("Preis-Event an alle Clients gesendet (Server)")
    end
end

function PlaceableDynamicFuelPricingSilo:updatePriceDisplay()
    local spec = self.spec_dynamicPricingSilo
    
    for fillTypeStr, displays in pairs(spec.priceDisplays) do
        if #displays > 0 then
            local displayPrice = spec.currentPrices[fillTypeStr] or 1.500
            local priceInMillicents = math.floor(displayPrice * 1000 + 0.5)
            
            local euro = math.floor(priceInMillicents / 1000)
            local decimal1 = math.floor((priceInMillicents % 1000) / 100)
            local decimal2 = math.floor((priceInMillicents % 100) / 10)
            local decimal3 = priceInMillicents % 10
            
            debugPrint("Display %s: %d.%d%d%d (%.3f €/L)", 
                fillTypeStr, euro, decimal1, decimal2, decimal3, displayPrice)
            
            for _, display in ipairs(displays) do
                setShaderParameter(display.euro, "index", euro, 0, 0, 0, false)
                setShaderParameter(display.decimal1, "index", decimal1, 0, 0, 0, false)
                setShaderParameter(display.decimal2, "index", decimal2, 0, 0, 0, false)
                
                if display.decimal3 ~= nil then
                    setShaderParameter(display.decimal3, "index", decimal3, 0, 0, 0, false)
                end
            end
        end
    end
end

function PlaceableDynamicFuelPricingSilo:getSiloFuelPrice(fillType)
    local spec = self.spec_dynamicPricingSilo
    local fillTypeName = g_fillTypeManager:getFillTypeNameByIndex(fillType)
    
    local price = spec.currentPrices[fillTypeName] or 1.500
    
    return price
end

function PlaceableDynamicFuelPricingSilo:canWithdrawFuel(fillType, amount)
    local siloSpec = self.spec_silo
    
    if siloSpec == nil or siloSpec.storages == nil then
        return false
    end
    
    for _, storage in ipairs(siloSpec.storages) do
        if storage.fillTypes[fillType] then
            local fillLevel = storage:getFillLevel(fillType)
            return fillLevel >= amount
        end
    end
    
    return false
end

function PlaceableDynamicFuelPricingSilo:withdrawFuel(fillType, amount, farmId)
    local siloSpec = self.spec_silo
    
    if siloSpec == nil or siloSpec.storages == nil then
        return 0
    end
    
    for _, storage in ipairs(siloSpec.storages) do
        if storage.fillTypes[fillType] then
            local fillLevel = storage:getFillLevel(fillType)
            local withdrawn = math.min(amount, fillLevel)
            
            storage:setFillLevel(fillLevel - withdrawn, fillType)
            
            debugPrint("%.2fL %s aus Silo entnommen (verbleibend: %.2fL)", 
                withdrawn, g_fillTypeManager:getFillTypeNameByIndex(fillType), fillLevel - withdrawn)
            
            return withdrawn
        end
    end
    
    return 0
end

function PlaceableDynamicFuelPricingSilo:onWriteStream(streamId, connection)
    local spec = self.spec_dynamicPricingSilo
    
    if not connection:getIsServer() then
        streamWriteFloat32(streamId, spec.currentPrices.DIESEL)
        streamWriteFloat32(streamId, spec.currentPrices.DEF)
    end
end

function PlaceableDynamicFuelPricingSilo:onReadStream(streamId, connection)
    local spec = self.spec_dynamicPricingSilo
    
    if connection:getIsServer() then
        spec.currentPrices.DIESEL = streamReadFloat32(streamId)
        spec.currentPrices.DEF = streamReadFloat32(streamId)
        
        self:updatePriceDisplay()
    end
end

function PlaceableDynamicFuelPricingSilo:onDelete()
    local spec = self.spec_dynamicPricingSilo
    
    if spec.dieselPriceTrigger ~= nil then
        removeTrigger(spec.dieselPriceTrigger)
    end
    
    if spec.defPriceTrigger ~= nil then
        removeTrigger(spec.defPriceTrigger)
    end
    
    self:unregisterDieselPriceActionEvent()
    self:unregisterDefPriceActionEvent()
end

SiloFuelPriceUpdateEvent = {}
local SiloFuelPriceUpdateEvent_mt = Class(SiloFuelPriceUpdateEvent, Event)
InitEventClass(SiloFuelPriceUpdateEvent, "SiloFuelPriceUpdateEvent")

function SiloFuelPriceUpdateEvent.emptyNew()
    return Event.new(SiloFuelPriceUpdateEvent_mt)
end

function SiloFuelPriceUpdateEvent.new(placeable, prices)
    local self = SiloFuelPriceUpdateEvent.emptyNew()
    self.placeable = placeable
    self.prices = prices
    return self
end

function SiloFuelPriceUpdateEvent:readStream(streamId, connection)
    self.placeable = NetworkUtil.readNodeObject(streamId)
    self.prices = {
        DIESEL = streamReadFloat32(streamId),
        DEF = streamReadFloat32(streamId)
    }
    self:run(connection)
end

function SiloFuelPriceUpdateEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.placeable)
    streamWriteFloat32(streamId, self.prices.DIESEL)
    streamWriteFloat32(streamId, self.prices.DEF)
end

function SiloFuelPriceUpdateEvent:run(connection)
    if self.placeable ~= nil and self.placeable:getIsSynchronized() then
        local spec = self.placeable.spec_dynamicPricingSilo
        
        if spec ~= nil then
            spec.currentPrices.DIESEL = self.prices.DIESEL
            spec.currentPrices.DEF = self.prices.DEF
            self.placeable:updatePriceDisplay()
            
            debugPrint("[DynamicPricingSilo] Preis empfangen: Diesel %.3f€/L | AdBlue %.3f€/L", 
                self.prices.DIESEL, self.prices.DEF)
            
            if g_server ~= nil and connection ~= nil then
                g_server:broadcastEvent(self, false, connection, self.placeable)
                debugPrint("[DynamicPricingSilo] Preis-Event weitergeleitet an andere Clients")
            end
        end
    end
end

print("[PlaceableDynamicFuelPricingSilo] Script loaded (MANUAL PRICING ONLY)")