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

PlaceableDynamicFuelPricing = {}

local DEBUG = false

local function debugPrint(...)
    if DEBUG then
        print(...)
    end
end

function PlaceableDynamicFuelPricing.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(PlaceableBuyingStation, specializations)
end

function PlaceableDynamicFuelPricing.registerXMLPaths(schema, baseKey)
    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#minPriceScale", "Diesel minimum price scale", 1.20)
    schema:register(XMLValueType.FLOAT, baseKey .. ".dynamicPricing.diesel#maxPriceScale", "Diesel maximum price scale", 2.00)
    schema:register(XMLValueType.INT, baseKey .. ".dynamicPricing.diesel#updateInterval", "Diesel update interval in hours", 1)
    schema:register(XMLValueType.FLOAT, baseKey .. ".dynamicPricing.def#minPriceScale", "DEF minimum price scale", 0.80)
    schema:register(XMLValueType.FLOAT, baseKey .. ".dynamicPricing.def#maxPriceScale", "DEF maximum price scale", 1.50)
    schema:register(XMLValueType.INT, baseKey .. ".dynamicPricing.def#updateInterval", "DEF update interval in hours", 1)
end

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

function PlaceableDynamicFuelPricing.registerFunctions(placeableType)
    SpecializationUtil.registerFunction(placeableType, "updateFuelPrices", PlaceableDynamicFuelPricing.updateFuelPrices)
    SpecializationUtil.registerFunction(placeableType, "onHourChangedCallback", PlaceableDynamicFuelPricing.onHourChangedCallback)
    SpecializationUtil.registerFunction(placeableType, "updatePriceDisplay", PlaceableDynamicFuelPricing.updatePriceDisplay)
    SpecializationUtil.registerFunction(placeableType, "setPrices", PlaceableDynamicFuelPricing.setPrices)
end

function PlaceableDynamicFuelPricing:onLoad(savegame)
    self.spec_dynamicPricing = {}
    local spec = self.spec_dynamicPricing
    local xmlFile = self.xmlFile
    spec.priceConfigs = {}

    spec.priceConfigs.DIESEL = {
        minPriceScale = xmlFile:getValue("placeable.dynamicPricing.diesel#minPriceScale", 1.20),
        maxPriceScale = xmlFile:getValue("placeable.dynamicPricing.diesel#maxPriceScale", 2.00),
        updateInterval = xmlFile:getValue("placeable.dynamicPricing.diesel#updateInterval", 1),
        lastUpdateHour = -1
    }
    
    spec.priceConfigs.DEF = {
        minPriceScale = xmlFile:getValue("placeable.dynamicPricing.def#minPriceScale", 0.80),
        maxPriceScale = xmlFile:getValue("placeable.dynamicPricing.def#maxPriceScale", 1.50),
        updateInterval = xmlFile:getValue("placeable.dynamicPricing.def#updateInterval", 1),
        lastUpdateHour = -1
    }
    
    spec.currentPrices = {}
    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)
        end
        
        i = i + 1
    end
    
    print("[DynamicPricing] Tankstelle geladen")
end

function PlaceableDynamicFuelPricing:onFinalizePlacement()
    local spec = self.spec_dynamicPricing
    local buyingStation = self:getBuyingStation()
    
    if buyingStation ~= nil then
        for fillType, _ in pairs(buyingStation.fillTypePricesScale) do
            spec.currentPrices[fillType] = buyingStation.fillTypePricesScale[fillType]
        end
        
        self:updatePriceDisplay()
        
        if g_server ~= nil then
            g_messageCenter:subscribe(MessageType.HOUR_CHANGED, self.onHourChangedCallback, self)
            debugPrint("[DynamicPricing] HOUR_CHANGED registriert")
            
            local currentHour = g_currentMission.environment.currentHour
            for fillTypeStr, config in pairs(spec.priceConfigs) do
                config.lastUpdateHour = currentHour
            end
            
            debugPrint("[DynamicPricing] Generiere initiale Preise")
            self:updateFuelPrices()
        end
    end
end

function PlaceableDynamicFuelPricing:onHourChangedCallback(currentHour)
    if g_server ~= nil then
        local spec = self.spec_dynamicPricing
        
        for fillTypeStr, config in pairs(spec.priceConfigs) do
            local hoursSinceLastUpdate = currentHour - config.lastUpdateHour
            
            if hoursSinceLastUpdate < 0 then
                hoursSinceLastUpdate = hoursSinceLastUpdate + 24
            end
            
            if hoursSinceLastUpdate >= config.updateInterval then
                config.lastUpdateHour = currentHour
                self:updateFuelPrices(fillTypeStr)
            end
        end
    end
end

function PlaceableDynamicFuelPricing:updateFuelPrices(specificFillType)
    if g_server == nil then
        return
    end
    
    local spec = self.spec_dynamicPricing
    local buyingStation = self:getBuyingStation()
    
    if buyingStation == nil or spec.currentPrices == nil or next(spec.currentPrices) == nil then
        return
    end
    
    local newPrices = {}
    
    for fillType, currentPrice in pairs(spec.currentPrices) do
        local fillTypeName = g_fillTypeManager:getFillTypeNameByIndex(fillType)
        
        if specificFillType == nil or fillTypeName == specificFillType then
            local config = spec.priceConfigs[fillTypeName]
            
            if config ~= nil then
                local min = config.minPriceScale * 1000
                local max = config.maxPriceScale * 1000
                local randomMillicents = math.random(min, max)
                local newPrice = randomMillicents / 1000
                
                newPrices[fillType] = newPrice
                
                debugPrint(string.format("[DynamicPricing] Neuer Preis: %s = %.3f €/L", 
                    fillTypeName, newPrice))
            end
        end
    end
    
    if next(newPrices) ~= nil then
        self:setPrices(newPrices, false)
    end
end

function PlaceableDynamicFuelPricing:setPrices(prices, noEventSend)
    local spec = self.spec_dynamicPricing
    local buyingStation = self:getBuyingStation()
    
    if buyingStation == nil then return end
    
    for fillType, newPrice in pairs(prices) do
        spec.currentPrices[fillType] = newPrice
        local fillTypeObject = g_fillTypeManager:getFillTypeByIndex(fillType)
        local basePricePerLiter = fillTypeObject and fillTypeObject.pricePerLiter or 1.0
        local priceScale = newPrice / basePricePerLiter
        
        buyingStation.fillTypePricesScale[fillType] = priceScale
        
        debugPrint(string.format("[DynamicPricing] Set price: %s = %.3f €/L (scale: %.3f, base: %.3f)", 
            g_fillTypeManager:getFillTypeNameByIndex(fillType), 
            newPrice, priceScale, basePricePerLiter))
    end
    
    self:updatePriceDisplay()
    
    if not noEventSend and g_server ~= nil then
        g_server:broadcastEvent(FuelPriceUpdateEvent.new(self, prices), nil, nil, self)
    end
end

function PlaceableDynamicFuelPricing:updatePriceDisplay()
    local spec = self.spec_dynamicPricing
    
    for fillTypeStr, displays in pairs(spec.priceDisplays) do
        if #displays > 0 then
            local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeStr)
            local displayPrice = spec.currentPrices[fillTypeIndex] or 0
            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(string.format("[DynamicPricing] 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 PlaceableDynamicFuelPricing:onWriteStream(streamId, connection)
    local spec = self.spec_dynamicPricing
    
    if not connection:getIsServer() then
        local numPrices = 0
        for _ in pairs(spec.currentPrices) do
            numPrices = numPrices + 1
        end
        
        streamWriteUInt8(streamId, numPrices)
        
        for fillType, price in pairs(spec.currentPrices) do
            streamWriteUInt8(streamId, fillType)
            streamWriteFloat32(streamId, price)
        end
    end
end

function PlaceableDynamicFuelPricing:onReadStream(streamId, connection)
    local spec = self.spec_dynamicPricing
    
    if connection:getIsServer() then
        local numPrices = streamReadUInt8(streamId)
        
        for i = 1, numPrices do
            local fillType = streamReadUInt8(streamId)
            local price = streamReadFloat32(streamId)
            spec.currentPrices[fillType] = price
            
            local buyingStation = self:getBuyingStation()
            if buyingStation ~= nil then
                buyingStation.fillTypePricesScale[fillType] = price
            end
        end
        
        self:updatePriceDisplay()
    end
end

function PlaceableDynamicFuelPricing:onDelete()
    g_messageCenter:unsubscribeAll(self)
end

FuelPriceUpdateEvent = {}
local FuelPriceUpdateEvent_mt = Class(FuelPriceUpdateEvent, Event)

InitEventClass(FuelPriceUpdateEvent, "FuelPriceUpdateEvent")

function FuelPriceUpdateEvent.emptyNew()
    local self = Event.new(FuelPriceUpdateEvent_mt)
    return self
end

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

function FuelPriceUpdateEvent:readStream(streamId, connection)
    self.placeable = NetworkUtil.readNodeObject(streamId)
    
    local numPrices = streamReadUInt8(streamId)
    self.prices = {}
    
    for i = 1, numPrices do
        local fillType = streamReadUInt8(streamId)
        local price = streamReadFloat32(streamId)
        self.prices[fillType] = price
    end
    
    self:run(connection)
end

function FuelPriceUpdateEvent:writeStream(streamId, connection)
    NetworkUtil.writeNodeObject(streamId, self.placeable)
    
    local numPrices = 0
    for _ in pairs(self.prices) do
        numPrices = numPrices + 1
    end
    
    streamWriteUInt8(streamId, numPrices)
    
    for fillType, price in pairs(self.prices) do
        streamWriteUInt8(streamId, fillType)
        streamWriteFloat32(streamId, price)
    end
end

function FuelPriceUpdateEvent:run(connection)
    if self.placeable ~= nil and self.placeable:getIsSynchronized() then
        self.placeable:setPrices(self.prices, true)
    end
end