LSFMBaleStorageMenu = {}
LSFMBaleStorageMenu.modDirectory = g_currentModDirectory

LSFMBaleStorageDialog = {}

LSFMBaleStorageDialog.MODE_UNLOAD = 1
LSFMBaleStorageDialog.MODE_BUY = 2

LSFMBaleStorageDialog.WRAP_COLORS = {
    { l10nKey = "ui_lsfmBaleStorageWrapColorWhite", color = { 1, 1, 1 } },
    { l10nKey = "ui_lsfmBaleStorageWrapColorBlack", color = { 0.02, 0.02, 0.02 } },
    { l10nKey = "ui_lsfmBaleStorageWrapColorGreen", color = { 0.04, 0.32, 0.07 } },
    { l10nKey = "ui_lsfmBaleStorageWrapColorBlue", color = { 0.03, 0.12, 0.55 } },
    { l10nKey = "ui_lsfmBaleStorageWrapColorPink", color = { 0.8, 0.08, 0.38 } },
    { l10nKey = "ui_lsfmBaleStorageWrapColorYellow", color = { 0.9, 0.65, 0.02 } }
}

local LSFMBaleStorageDialog_mt = Class(LSFMBaleStorageDialog, YesNoDialog)

local function getBaleValues(abstractObject)
    if abstractObject == nil then
        return nil
    end

    if abstractObject.baleObject ~= nil then
        local bale = abstractObject.baleObject

        return bale.xmlFilename, bale:getFillType(), bale:getFillLevel(), bale.wrappingState or 0
    end

    local attributes = abstractObject.baleAttributes

    if attributes ~= nil then
        return attributes.xmlFilename, attributes.fillType, attributes.fillLevel, attributes.wrappingState or 0
    end

    return nil
end

local function toCentimeters(value)
    return math.floor((value or 0) * 100 + 0.5)
end

local function getFillTypeIcon(fillTypeIndex)
    if fillTypeIndex == nil then
        return nil
    end

    local fillType = g_fillTypeManager:getFillTypeByIndex(fillTypeIndex)

    return fillType ~= nil and fillType.hudOverlayFilename or nil
end

local function getFormatText(xmlFilename, wrappingState)
    local isRound, width, height, length, diameter = g_baleManager:getBaleInfoByXMLFilename(xmlFilename, true)
    local formatText

    if isRound then
        formatText = string.format(g_i18n:getText("ui_lsfmBaleStorageRoundFormat"), toCentimeters(diameter))
    else
        formatText = string.format(g_i18n:getText("ui_lsfmBaleStorageSquareFormat"), toCentimeters(width), toCentimeters(height), toCentimeters(length))
    end

    if wrappingState ~= nil and wrappingState > 0 then
        formatText = string.format("%s | %s", formatText, g_i18n:getText("ui_lsfmBaleStorageWrapped"))
    end

    return formatText
end

function LSFMBaleStorageDialog.new(target, customMt)
    local self = YesNoDialog.new(target, customMt or LSFMBaleStorageDialog_mt)
    self.rows = {}
    self.unloadRows = {}
    self.purchaseRows = {}
    self.selectedRow = 1
    self.selectedAmount = 1
    self.selectedWrapColorIndex = 1
    self.maxUnloadAmount = math.huge
    self.mode = LSFMBaleStorageDialog.MODE_UNLOAD
    self.storageTarget = nil
    self.storagePlaceable = nil

    return self
end

function LSFMBaleStorageDialog:onCreate()
    LSFMBaleStorageDialog:superClass().onCreate(self)

    if self.itemsElement ~= nil then
        self.itemsElement:setDataSource(self)
        self.itemsElement:setDelegate(self)
    end

    if self.wrapColorElement ~= nil then
        local colorTexts = {}

        for index, colorData in ipairs(LSFMBaleStorageDialog.WRAP_COLORS) do
            colorTexts[index] = g_i18n:getText(colorData.l10nKey)
        end

        self.wrapColorElement:setTexts(colorTexts)
        self.wrapColorElement:setState(self.selectedWrapColorIndex)
    end
end

function LSFMBaleStorageDialog:onOpen()
    LSFMBaleStorageDialog:superClass().onOpen(self)

    local focusElement = self.mode == LSFMBaleStorageDialog.MODE_BUY and self.buyTabButton or self.stockTabButton

    if focusElement ~= nil then
        FocusManager:setFocus(focusElement)
    end
end

function LSFMBaleStorageDialog:setStorageData(callback, target, title, objectInfos, maxUnloadAmount)
    self:setCallback(callback, target)
    self:setTitle(title)
    self:setText(nil)

    self.storageTarget = target
    self.storagePlaceable = target ~= nil and target.objectStorage or nil
    self.unloadRows = {}
    self.purchaseRows = {}
    self.rows = {}
    self.selectedRow = 1
    self.selectedAmount = 1
    self.selectedWrapColorIndex = 1
    self.maxUnloadAmount = maxUnloadAmount or math.huge

    self:rebuildUnloadRows(objectInfos)
    self:rebuildPurchaseRows()
    self:updateStockText()

    local startMode = #self.unloadRows > 0 and LSFMBaleStorageDialog.MODE_UNLOAD or LSFMBaleStorageDialog.MODE_BUY
    self:setMode(startMode, true)
end

function LSFMBaleStorageDialog:rebuildUnloadRows(objectInfos)
    self.unloadRows = {}

    for sourceIndex, objectInfo in ipairs(objectInfos or {}) do
        local object = objectInfo.objects ~= nil and objectInfo.objects[1] or nil
        local xmlFilename, fillTypeIndex, fillLevel, wrappingState = getBaleValues(object)

        if xmlFilename ~= nil then
            self.unloadRows[#self.unloadRows + 1] = {
                mode = LSFMBaleStorageDialog.MODE_UNLOAD,
                sourceIndex = sourceIndex,
                objectInfo = objectInfo,
                iconFilename = getFillTypeIcon(fillTypeIndex),
                fillTypeText = g_fillTypeManager:getFillTypeTitleByIndex(fillTypeIndex) or "",
                formatText = getFormatText(xmlFilename, wrappingState),
                fillLevelText = g_i18n:formatFluid(fillLevel or 0),
                quantityText = string.format("%d x", objectInfo.numObjects or 0)
            }
        end
    end
end

function LSFMBaleStorageDialog:rebuildPurchaseRows()
    self.purchaseRows = {}

    if self.storagePlaceable == nil or self.storagePlaceable.getLSFMBalePurchaseCatalog == nil then
        return
    end

    for _, entry in ipairs(self.storagePlaceable:getLSFMBalePurchaseCatalog()) do
        if (entry.maxQuantity or 0) > 0 then
            self.purchaseRows[#self.purchaseRows + 1] = {
                mode = LSFMBaleStorageDialog.MODE_BUY,
                xmlFilename = entry.xmlFilename,
                fillTypeIndex = entry.fillTypeIndex,
                maxQuantity = entry.maxQuantity,
                unitPrice = entry.unitPrice,
                capacity = entry.capacity,
                wrappingState = entry.wrappingState or 0,
                iconFilename = getFillTypeIcon(entry.fillTypeIndex),
                fillTypeText = g_fillTypeManager:getFillTypeTitleByIndex(entry.fillTypeIndex) or "",
                formatText = getFormatText(entry.xmlFilename, entry.wrappingState),
                fillLevelText = g_i18n:formatFluid(entry.capacity or 0),
                quantityText = g_i18n:formatMoney(entry.unitPrice or 0, 0, true, true)
            }
        end
    end
end

function LSFMBaleStorageDialog:updateStockText()
    local storageSpec = self.storagePlaceable ~= nil and self.storagePlaceable.spec_objectStorage or nil
    local stored = storageSpec ~= nil and storageSpec.numStoredObjects or 0
    local capacity = storageSpec ~= nil and storageSpec.capacity or stored

    if self.stockElement ~= nil then
        self.stockElement:setText(string.format(g_i18n:getText("ui_lsfmBaleStorageStock"), stored, capacity))
    end
end

function LSFMBaleStorageDialog:setMode(mode, force)
    if mode == LSFMBaleStorageDialog.MODE_BUY and #self.purchaseRows == 0 and #self.unloadRows > 0 then
        mode = LSFMBaleStorageDialog.MODE_UNLOAD
    elseif mode == LSFMBaleStorageDialog.MODE_UNLOAD and #self.unloadRows == 0 and #self.purchaseRows > 0 then
        mode = LSFMBaleStorageDialog.MODE_BUY
    end

    if self.mode == mode and not force then
        return
    end

    self.mode = mode
    self.rows = mode == LSFMBaleStorageDialog.MODE_BUY and self.purchaseRows or self.unloadRows
    self.selectedRow = 1
    self.selectedAmount = 1

    self:updateTabState()

    if self.quantityHeaderElement ~= nil then
        local headerText = mode == LSFMBaleStorageDialog.MODE_BUY and g_i18n:getText("ui_lsfmBaleStorageUnitPrice") or g_i18n:getText("ui_lsfmBaleStorageQuantity")
        self.quantityHeaderElement:setText(headerText)
    end

    if self.amountLabelElement ~= nil then
        local amountText = mode == LSFMBaleStorageDialog.MODE_BUY and g_i18n:getText("ui_lsfmBaleStorageBuyAmount") or g_i18n:getText("ui_lsfmBaleStorageAmount")
        self.amountLabelElement:setText(amountText)
    end

    if self.yesButton ~= nil then
        local buttonText = mode == LSFMBaleStorageDialog.MODE_BUY and g_i18n:getText("button_lsfmBaleStorageBuy") or g_i18n:getText("button_lsfmBaleStorageUnload")
        self.yesButton:setText(buttonText)
    end

    if self.itemsElement ~= nil then
        if self.itemsElement.clearItems ~= nil then
            self.itemsElement:clearItems()
        end

        self.itemsElement:reloadData(true)

        if #self.rows > 0 then
            self.itemsElement:setSelectedIndex(1, true)
        end
    end

    self:updateAmountSelection(1)
end

function LSFMBaleStorageDialog:updateWrapColorSelection()
    local row = self.rows[self.selectedRow]
    local isVisible = self.mode == LSFMBaleStorageDialog.MODE_BUY and row ~= nil and (row.wrappingState or 0) > 0

    if self.wrapColorLabelElement ~= nil then
        self.wrapColorLabelElement:setVisible(isVisible)
    end

    if self.wrapColorElement ~= nil then
        self.wrapColorElement:setVisible(isVisible)
    end

    if self.wrapColorSwatchElement ~= nil then
        self.wrapColorSwatchElement:setVisible(isVisible)
    end

    if self.itemsElement ~= nil and self.amountElement ~= nil and self.wrapColorElement ~= nil then
        FocusManager:linkElements(self.itemsElement, FocusManager.BOTTOM, isVisible and self.wrapColorElement or self.amountElement)
        FocusManager:linkElements(self.amountElement, FocusManager.TOP, isVisible and self.wrapColorElement or self.itemsElement)
    end

    if not isVisible then
        return
    end

    self.selectedWrapColorIndex = math.clamp(self.selectedWrapColorIndex or 1, 1, #LSFMBaleStorageDialog.WRAP_COLORS)

    if self.wrapColorElement ~= nil then
        self.wrapColorElement:setState(self.selectedWrapColorIndex)
    end

    local colorData = LSFMBaleStorageDialog.WRAP_COLORS[self.selectedWrapColorIndex]

    if self.wrapColorSwatchElement ~= nil and colorData ~= nil then
        self.wrapColorSwatchElement:setImageColor(nil, colorData.color[1], colorData.color[2], colorData.color[3], 1)
    end
end

function LSFMBaleStorageDialog:getSelectedWrappingColor(row)
    if row == nil or (row.wrappingState or 0) <= 0 then
        return { 1, 1, 1 }
    end

    local colorData = LSFMBaleStorageDialog.WRAP_COLORS[self.selectedWrapColorIndex or 1] or LSFMBaleStorageDialog.WRAP_COLORS[1]

    return { colorData.color[1], colorData.color[2], colorData.color[3] }
end

function LSFMBaleStorageDialog:getNumberOfSections()
    return 1
end

function LSFMBaleStorageDialog:getNumberOfItemsInSection(section)
    return #self.rows
end

function LSFMBaleStorageDialog:getCellTypeForItemInSection(section, index)
    return "baleRow"
end

function LSFMBaleStorageDialog:getEmptyCellType()
    return nil
end

function LSFMBaleStorageDialog:populateCellForItemInSection(list, section, index, cell)
    local row = self.rows[index]

    if row == nil then
        return
    end

    local icon = cell:getAttribute("baleIcon")
    local fillType = cell:getAttribute("fillType")
    local format = cell:getAttribute("baleFormat")
    local fillLevel = cell:getAttribute("fillLevel")
    local quantity = cell:getAttribute("quantity")

    if fillType ~= nil then
        fillType:setText(row.fillTypeText)
    end

    if format ~= nil then
        format:setText(row.formatText)
    end

    if fillLevel ~= nil then
        fillLevel:setText(row.fillLevelText)
    end

    if quantity ~= nil then
        quantity:setText(row.quantityText)
    end

    if icon ~= nil then
        local hasIcon = row.iconFilename ~= nil and row.iconFilename ~= ""
        icon:setVisible(hasIcon)

        if hasIcon then
            icon:setImageFilename(row.iconFilename)
        end
    end
end

function LSFMBaleStorageDialog:updateAmountSelection(index, list)
    if list ~= nil and list.getSelectedIndexInSection ~= nil then
        index = list:getSelectedIndexInSection()
    end

    index = math.clamp(tonumber(index) or 1, 1, math.max(#self.rows, 1))
    self.selectedRow = index

    local row = self.rows[index]
    local amountTexts = {}

    if row ~= nil then
        local amountLimit = 0
        local totalCount = 0

        if self.mode == LSFMBaleStorageDialog.MODE_BUY then
            amountLimit = math.max(row.maxQuantity or 0, 0)
            totalCount = amountLimit
        else
            totalCount = row.objectInfo.numObjects or 0
            amountLimit = math.min(totalCount, self.maxUnloadAmount)
        end

        for amount = 1, amountLimit do
            if self.mode == LSFMBaleStorageDialog.MODE_BUY then
                amountTexts[amount] = tostring(amount)
            else
                amountTexts[amount] = string.format("%d / %d", amount, totalCount)
            end
        end
    end

    if self.amountElement ~= nil then
        self.amountElement:setTexts(amountTexts)
        self.amountElement:setDisabled(#amountTexts == 0)

        self.selectedAmount = math.max(#amountTexts, 1)
        self.amountElement:setState(self.selectedAmount, true)
    end

    self:updatePriceText()
    self:updateWrapColorSelection()

    if self.yesButton ~= nil then
        self.yesButton:setDisabled(row == nil or #amountTexts == 0)
    end
end

function LSFMBaleStorageDialog:updatePriceText()
    if self.priceElement == nil then
        return
    end

    if self.mode ~= LSFMBaleStorageDialog.MODE_BUY then
        self.priceElement:setText("")

        return
    end

    local row = self.rows[self.selectedRow]

    if row == nil then
        self.priceElement:setText("")

        return
    end

    local totalPrice = row ~= nil and (row.unitPrice or 0) * (self.selectedAmount or 1) or 0

    self.priceElement:setText(string.format(g_i18n:getText("ui_lsfmBaleStorageTotalPrice"), g_i18n:formatMoney(totalPrice, 0, true, true)))
end

function LSFMBaleStorageDialog:updateTabState()
    local isBuyMode = self.mode == LSFMBaleStorageDialog.MODE_BUY

    if self.stockTabElement ~= nil then
        self.stockTabElement:setSelected(not isBuyMode, true)
        self.stockTabElement:setDisabled(#self.unloadRows == 0, true)
    end

    if self.buyTabElement ~= nil then
        self.buyTabElement:setSelected(isBuyMode, true)
        self.buyTabElement:setDisabled(#self.purchaseRows == 0, true)
    end

    if self.stockTabButton ~= nil then
        self.stockTabButton:setSelected(not isBuyMode, true)
        self.stockTabButton:setDisabled(#self.unloadRows == 0, true)
    end

    if self.buyTabButton ~= nil then
        self.buyTabButton:setSelected(isBuyMode, true)
        self.buyTabButton:setDisabled(#self.purchaseRows == 0, true)
    end
end

function LSFMBaleStorageDialog:onClickListItems(list, section, index)
    self:updateAmountSelection(index, list)
end

function LSFMBaleStorageDialog:onListSelectionChanged(list, section, index)
    self:updateAmountSelection(index, list)
end

function LSFMBaleStorageDialog:onClickMode(state)
    self:setMode(state)
end

function LSFMBaleStorageDialog:onClickStockTab()
    self:setMode(LSFMBaleStorageDialog.MODE_UNLOAD)
end

function LSFMBaleStorageDialog:onClickBuyTab()
    self:setMode(LSFMBaleStorageDialog.MODE_BUY)
end

function LSFMBaleStorageDialog:onClickAmount(state)
    self.selectedAmount = state
    self:updatePriceText()
end

function LSFMBaleStorageDialog:onClickWrapColor(state)
    self.selectedWrapColorIndex = math.clamp(tonumber(state) or 1, 1, #LSFMBaleStorageDialog.WRAP_COLORS)
    self:updateWrapColorSelection()
end

function LSFMBaleStorageDialog:onClickOk()
    local row = self.rows[self.selectedRow]

    if row == nil then
        return false
    end

    if self.mode == LSFMBaleStorageDialog.MODE_BUY then
        self:confirmPurchase(row)

        return false
    end

    self.callbackArgs = self.selectedAmount
    self:sendCallback(row.sourceIndex)

    return false
end

function LSFMBaleStorageDialog:confirmPurchase(row)
    if g_currentMission:getMoney() < (row.unitPrice or 0) * (self.selectedAmount or 1) then
        InfoDialog.show(g_i18n:getText("warning_lsfmBaleStorageNotEnoughMoney"))

        return
    end

    local baleDescription = row.fillTypeText

    if (row.wrappingState or 0) > 0 then
        local colorData = LSFMBaleStorageDialog.WRAP_COLORS[self.selectedWrapColorIndex or 1]

        if colorData ~= nil then
            baleDescription = string.format("%s (%s)", baleDescription, g_i18n:getText(colorData.l10nKey))
        end
    end

    local text = string.format(g_i18n:getText("ui_lsfmBaleStorageBuyConfirm"), self.selectedAmount or 1, baleDescription, g_i18n:formatMoney((row.unitPrice or 0) * (self.selectedAmount or 1), 0, true, true))
    YesNoDialog.show(self.onPurchaseConfirmed, self, text)
end

function LSFMBaleStorageDialog:onPurchaseConfirmed(yes)
    if not yes then
        return
    end

    local row = self.rows[self.selectedRow]

    if row == nil or self.storagePlaceable == nil then
        return
    end

    local wrappingColor = self:getSelectedWrappingColor(row)

    g_messageCenter:subscribeOneshot(LSFMBaleStorageBuyEvent, self.onPurchaseResponse, self)
    g_client:getServerConnection():sendEvent(LSFMBaleStorageBuyEvent.new(self.storagePlaceable, g_localPlayer.farmId, row.xmlFilename, row.fillTypeIndex, self.selectedAmount or 1, wrappingColor))
end

function LSFMBaleStorageDialog:onPurchaseResponse(errorCode, numBoughtBales)
    if errorCode == LSFMBaleStorageBuyEvent.BUY_SUCCESS then
        InfoDialog.show(string.format(g_i18n:getText("info_lsfmBaleStorageBought"), numBoughtBales or 0))
        self:rebuildPurchaseRows()
        self:updateStockText()
        self:setMode(LSFMBaleStorageDialog.MODE_BUY, true)

        return
    end

    local l10nKey = "warning_lsfmBaleStorageBuyFailed"

    if errorCode == LSFMBaleStorageBuyEvent.BUY_ERROR_NO_PERMISSION then
        l10nKey = "warning_lsfmBaleStorageNoPermission"
    elseif errorCode == LSFMBaleStorageBuyEvent.BUY_ERROR_NOT_ENOUGH_MONEY then
        l10nKey = "warning_lsfmBaleStorageNotEnoughMoney"
    elseif errorCode == LSFMBaleStorageBuyEvent.BUY_ERROR_NOT_ENOUGH_SPACE then
        l10nKey = "warning_lsfmBaleStorageNoSpace"
    elseif errorCode == LSFMBaleStorageBuyEvent.BUY_ERROR_STACK_MISMATCH then
        l10nKey = "warning_lsfmBaleStorageStackTypeMismatch"
    elseif errorCode == LSFMBaleStorageBuyEvent.BUY_ERROR_NOT_SUPPORTED then
        l10nKey = "warning_lsfmBaleStorageBuyNotSupported"
    end

    InfoDialog.show(g_i18n:getText(l10nKey))
end

function LSFMBaleStorageDialog:onClickBack(forceBack, usedMenuButton)
    self:sendCallback(nil, nil)

    return false
end

function LSFMBaleStorageMenu:loadMap()
    local dialog = LSFMBaleStorageDialog.new()
    g_gui:loadGui(self.modDirectory .. "gui/LSFMBaleStorageDialog.xml", "LSFMBaleStorageDialog", dialog)
    LSFMBaleStorageDialog.INSTANCE = dialog
end

function LSFMBaleStorageMenu:deleteMap()
    LSFMBaleStorageDialog.INSTANCE = nil
end

function LSFMBaleStorageMenu.showDialog(callback, superFunc, target, title, objectInfos, maxUnloadAmount)
    local storage = target ~= nil and target.objectStorage or nil
    local specName = LSFMBaleStackVisuals ~= nil and LSFMBaleStackVisuals.SPEC or nil
    local isLSFMBaleStorage = storage ~= nil and specName ~= nil and storage[specName] ~= nil

    if not isLSFMBaleStorage or LSFMBaleStorageDialog.INSTANCE == nil then
        return superFunc(callback, target, title, objectInfos, maxUnloadAmount)
    end

    local dialog = LSFMBaleStorageDialog.INSTANCE
    dialog:setStorageData(callback, target, title, objectInfos, maxUnloadAmount)
    g_gui:showDialog("LSFMBaleStorageDialog")
end

addModEventListener(LSFMBaleStorageMenu)

ObjectStorageDialog.show = Utils.overwrittenFunction(ObjectStorageDialog.show, LSFMBaleStorageMenu.showDialog)

if PlaceableObjectStorageActivatable ~= nil and not PlaceableObjectStorageActivatable.lsfmBaleStorageEmptyMenuInstalled then
    local superRunPlaceableObjectStorageActivatable = PlaceableObjectStorageActivatable.run

    function PlaceableObjectStorageActivatable:run()
        local storage = self.objectStorage
        local specName = LSFMBaleStackVisuals ~= nil and LSFMBaleStackVisuals.SPEC or nil
        local isLSFMBaleStorage = storage ~= nil and specName ~= nil and storage[specName] ~= nil

        if isLSFMBaleStorage and LSFMBaleStorageDialog.INSTANCE ~= nil then
            storage:updateDirtyObjectStorageObjectInfos()
            ObjectStorageDialog.show(self.onObjectInfoSelected, self, storage:getName(), storage:getObjectStorageObjectInfos() or {}, storage.spec_objectStorage.maxUnloadAmount)

            return
        end

        return superRunPlaceableObjectStorageActivatable(self)
    end

    PlaceableObjectStorageActivatable.lsfmBaleStorageEmptyMenuInstalled = true
end
