DiseaseFieldStateEvent = {}
local DiseaseFieldStateEvent_mt = Class(DiseaseFieldStateEvent, Event)
InitEventClass(DiseaseFieldStateEvent, "DiseaseFieldStateEvent")

function DiseaseFieldStateEvent.emptyNew()
    return Event.new(DiseaseFieldStateEvent_mt)
end

function DiseaseFieldStateEvent.new(entries, replaceAll)
    local self = DiseaseFieldStateEvent.emptyNew()
    self.entries = entries or {}
    self.replaceAll = replaceAll == true
    return self
end

local function writeOptionalString(streamId, value)
    local hasValue = value ~= nil and value ~= ""
    streamWriteBool(streamId, hasValue)
    if hasValue then
        streamWriteString(streamId, value)
    end
end

local function readOptionalString(streamId)
    if streamReadBool(streamId) then
        return streamReadString(streamId)
    end

    return nil
end

local function writeEntry(streamId, entry)
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.farmlandId or 0) or 0))
    writeOptionalString(streamId, entry.threatKey)
    writeOptionalString(streamId, entry.category)
    streamWriteUIntN(streamId, math.max(0, math.min(3, tonumber(entry.state or 0) or 0)), 2)
    streamWriteBool(streamId, entry.cropActive == true)
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.sessionId or 0) or 0))
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.activeDays or 0) or 0))
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.exposureDays or 0) or 0))
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.neglectDays or 0) or 0))
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.healStreak or 0) or 0))
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.seasonThreatCount or 0) or 0))
    local hasSeasonThreatLimit = entry.seasonThreatLimit ~= nil
    streamWriteBool(streamId, hasSeasonThreatLimit)
    if hasSeasonThreatLimit then
        streamWriteUInt16(streamId, math.max(0, tonumber(entry.seasonThreatLimit or 0) or 0))
    end
    writeOptionalString(streamId, entry.seasonThreatHistory)
    writeOptionalString(streamId, entry.lastSeasonThreatKey)
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.protectedUntilDay or 0) or 0))
    writeOptionalString(streamId, entry.protectedCategory)
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.lastTreatedDay or 0) or 0))
    streamWriteFloat32(streamId, tonumber(entry.lastRisk or 0) or 0)
    streamWriteFloat32(streamId, tonumber(entry.lastThreatRisk or 0) or 0)

    local hasFruitIndex = entry.lastFruitIndex ~= nil
    streamWriteBool(streamId, hasFruitIndex)
    if hasFruitIndex then
        streamWriteUInt16(streamId, math.max(0, tonumber(entry.lastFruitIndex or 0) or 0))
    end

    streamWriteUInt16(streamId, math.max(0, tonumber(entry.lastGrowthState or 0) or 0))
    writeOptionalString(streamId, entry.lastGrowthWindow)
    writeOptionalString(streamId, entry.fruitName)
    streamWriteUInt16(streamId, math.max(0, tonumber(entry.growthState or 0) or 0))
    writeOptionalString(streamId, entry.growthWindow)
    streamWriteFloat32(streamId, tonumber(entry.spray or 0) or 0)
    streamWriteFloat32(streamId, tonumber(entry.lime or 0) or 0)
    streamWriteFloat32(streamId, tonumber(entry.plow or 0) or 0)
    streamWriteFloat32(streamId, tonumber(entry.rollerNeed or 0) or 0)
    streamWriteFloat32(streamId, tonumber(entry.weed or 0) or 0)
    streamWriteFloat32(streamId, tonumber(entry.stubble or 0) or 0)
    streamWriteFloat32(streamId, tonumber(entry.water or 0) or 0)
end

local function readEntry(streamId)
    local entry = {
        farmlandId = streamReadUInt16(streamId),
        threatKey = readOptionalString(streamId),
        category = readOptionalString(streamId),
        state = streamReadUIntN(streamId, 2),
        cropActive = streamReadBool(streamId),
        sessionId = streamReadUInt16(streamId),
        activeDays = streamReadUInt16(streamId),
        exposureDays = streamReadUInt16(streamId),
        neglectDays = streamReadUInt16(streamId),
        healStreak = streamReadUInt16(streamId),
        seasonThreatCount = streamReadUInt16(streamId)
    }

    if streamReadBool(streamId) then
        entry.seasonThreatLimit = streamReadUInt16(streamId)
    end
    entry.seasonThreatHistory = readOptionalString(streamId)
    entry.lastSeasonThreatKey = readOptionalString(streamId)
    entry.protectedUntilDay = streamReadUInt16(streamId)
    entry.protectedCategory = readOptionalString(streamId)
    entry.lastTreatedDay = streamReadUInt16(streamId)
    entry.lastRisk = streamReadFloat32(streamId)
    entry.lastThreatRisk = streamReadFloat32(streamId)

    if streamReadBool(streamId) then
        entry.lastFruitIndex = streamReadUInt16(streamId)
    end

    entry.lastGrowthState = streamReadUInt16(streamId)
    entry.lastGrowthWindow = readOptionalString(streamId)
    entry.fruitName = readOptionalString(streamId)
    entry.growthState = streamReadUInt16(streamId)
    entry.growthWindow = readOptionalString(streamId)
    entry.spray = streamReadFloat32(streamId)
    entry.lime = streamReadFloat32(streamId)
    entry.plow = streamReadFloat32(streamId)
    entry.rollerNeed = streamReadFloat32(streamId)
    entry.weed = streamReadFloat32(streamId)
    entry.stubble = streamReadFloat32(streamId)
    entry.water = streamReadFloat32(streamId)

    return entry
end

function DiseaseFieldStateEvent:writeStream(streamId, _)
    streamWriteBool(streamId, self.replaceAll == true)
    streamWriteUInt16(streamId, #self.entries)
    for _, entry in ipairs(self.entries) do
        writeEntry(streamId, entry)
    end
end

function DiseaseFieldStateEvent:readStream(streamId, connection)
    self.replaceAll = streamReadBool(streamId)
    self.entries = {}

    local count = streamReadUInt16(streamId)
    for _ = 1, count do
        table.insert(self.entries, readEntry(streamId))
    end

    self:run(connection)
end

function DiseaseFieldStateEvent:run(_)
    if DiseaseCore ~= nil and DiseaseCore.applyNetworkEntrySnapshots ~= nil then
        DiseaseCore:applyNetworkEntrySnapshots(self.entries, self.replaceAll == true)
    end
end

function DiseaseFieldStateEvent.sendToConnection(connection, entries, replaceAll)
    if connection ~= nil and entries ~= nil then
        connection:sendEvent(DiseaseFieldStateEvent.new(entries, replaceAll))
    end
end

function DiseaseFieldStateEvent.broadcast(entries, replaceAll)
    if g_server ~= nil and entries ~= nil and #entries > 0 then
        g_server:broadcastEvent(DiseaseFieldStateEvent.new(entries, replaceAll), false)
    end
end
