Module:GladeResourcesController: Difference between revisions

From Against the Storm Official Wiki
first version, still need to refine significantly
 
Remove unneeded suppressTrees from render view functions
 
(22 intermediate revisions by 2 users not shown)
Line 8: Line 8:
local GladeResourcesData = require("Module:GladeResourcesData")
local GladeResourcesData = require("Module:GladeResourcesData")
local GoodsData = require("Module:GoodsData")
local GoodsData = require("Module:GoodsData")
local BiomesData = require("Module:BiomesData")
local BuildingDataProxy = require("Module:BuildingDataProxy")
local DataModelsWithGladeResourcesRecipes = {
local DataModelsWithGladeResourcesRecipes = {
     require("Module:MinesData"),
     require("Module:MinesData"),
     require("Module:CampsData"),
     require("Module:CampsData"),
    require("Module:FarmsData"),
     require("Module:FishingData"),
     require("Module:FishingData"),
     require("Module:GatheringData"),
     require("Module:GatheringData"),
Line 37: Line 38:


local VIEW_ARG_DEPOSIT = "deposit"
local VIEW_ARG_DEPOSIT = "deposit"
local VIEW_ARG_BUILDING = "building"
local VIEW_ARG_PRODUCT = "primary"
local VIEW_ARG_PRODUCT = "primary"
local VIEW_ARG_BONUS_1 = "bonus1"
local VIEW_ARG_BONUS_1 = "bonus1"
Line 43: Line 46:
local VIEW_ARG_BONUS_4 = "bonus4"
local VIEW_ARG_BONUS_4 = "bonus4"


local VIEW_ARG_SKIP_BIOMES = "skip_biomes"
local SKIP_FLAG = "skip"
local VIEW_ARG_BIOME_PREFIX = "biome"
local VIEW_ARG_FORCE_DEFAULT = "default"
--endregion
--endregion


Line 49: Line 58:
--region Localization string constants
--region Localization string constants


local ERROR_MESSAGE_PRODUCT_AND_BONUS_BUT_NOT_MATCH = "When specifying both a product and bonus resource, they must match."
local ERROR_MESSAGE_PRODUCT_AND_BONUS_BUT_NOT_MATCH = "When searching for a good as both a main and bonus resource, the parameters must match"
local ERROR_MESSAGE_NO_PARAMETERS_GIVEN = "You must specify the name of a product, a bonus resource, or a building—and as it's spelled in the game"


--endregion
--endregion
Line 58: Line 68:


local frame
local frame
local biomesIDs


--endregion
--endregion
Line 65: Line 77:
--region Private methods
--region Private methods


---@private
---Sorts given the function provided.
---
---@param arrayToSort table
---@param comparisonFunction function
---@return table the sorted array
local function sortArrayWith(arrayToSort, comparisonFunction)
    table.sort(arrayToSort, function(a, b)
        return comparisonFunction(a) < comparisonFunction(b)
    end)
    return arrayToSort
end
---@private
---Uses the specified building ID to go through applicable data models, assembling a list of possible deposits, filtered by the grade of the building.
---
---@param buildingID string a building's ID
---@return table array of deposit records retrieved from GladeResourcesData
local function assembleDepositsFromBuilding(buildingID, suppressTrees)
    local depositsList = {}
    local isWoodcutter = buildingID == "Woodcutters Camp"
   
    if isWoodcutter then
        local newList = GladeResourcesData.getAllDepositsWhereDepositIsTree()
        for _, deposit in ipairs(newList) do
            table.insert(depositsList, deposit)
        end
    else
    --This isn't as expensive as it looks, because typically only one data model will return something. However, we don't know which it will be in advance.
    for _, dataModel in ipairs(DataModelsWithGladeResourcesRecipes) do
        local recipeDataArray = dataModel:getIDsAndRecipesWhereBuildingID(buildingID)
        if #recipeDataArray > 0 then
            local products = {}
            for _, recipeData in ipairs(recipeDataArray) do
                local id = dataModel:getRecipeProductID(recipeData)
                products[id] = dataModel:getRecipeGrade(recipeData)
            end
            for id, grade in pairs(products) do
                local newList = GladeResourcesData.getAllDepositsWherePrimaryResourceAndGrade(id, grade, suppressTrees)
                for _, deposit in ipairs(newList) do
                    table.insert(depositsList, deposit)
                end
            end
        end
    end
    end
    return depositsList
end
---@private
---Looks up the building ID from the building data models for the one that can exploit the given deposit.
---
---@param deposit table record from GladeResourcesData
---@return string, string building display name, icon filename
local function getBuildingForDeposit(deposit)
    local productID = GladeResourcesData.getDepositPrimaryProduct(deposit)
    local isTree = GladeResourcesData.isDepositTree(deposit)
    --This isn't as expensive as it looks, because typically only one data model will return something. However, we don't know which it will be in advance.
    for _, dataModel in ipairs(DataModelsWithGladeResourcesRecipes) do
        local recipeDataArray = dataModel:getIDsAndRecipesWhereProductID(productID)
        if #recipeDataArray > 0 then
            for i = 1, #recipeDataArray do
            local id = dataModel:getRecipeBuildingID(recipeDataArray[i])
            if (isTree or dataModel:getName(id) ~= "Woodcutters' Camp") then
            local name = dataModel:getName(id)
            local icon = dataModel:getIcon(id)
            return name, icon
            end
            end
        end
    end
    return ""
end
---@private
---Checks whether the provided list of deposits can be harvested by a single building. If so, returns that building's name and icon (for use in a building link). Otherwise, returns nil.
---
---@param depositsList table array of deposit records retrieved from GladeResourcesData
---@return string, string building name and icon, if there is only one, nil if none or more than one
local function checkOnlyOneBuildingForAllDeposits(depositsList)
    local buildings = {}
    for _, deposit in ipairs(depositsList) do
        local name, icon = getBuildingForDeposit(deposit)
        buildings[name] = icon
    end
    local firstBuildingName, firstBuildingIcon = next(buildings)
    if firstBuildingName and not next(buildings, firstBuildingName) then
        return firstBuildingName, firstBuildingIcon
    else
        return nil
    end
end
---@private
---Goes through the provided list of deposits and finds the maximum number of bonuses, so that the view can be standardized.
---Goes through the provided list of deposits and finds the maximum number of bonuses, so that the view can be standardized.
---
---
Line 82: Line 204:




local function renderViewStart(depositsList, maxBonuses, displayOverride)
---@private
---Tallies up the categories and concatenates them. Also makes them plural if there are more than one of a particular category.
---
---@param depositsList table
---@return string the categories among the deposits
local function writeCaptionForDeposits(depositsList)
 
    local categories = {}
    for _, deposit in ipairs(depositsList) do
        local category = GladeResourcesData.getDepositCategory(deposit)
        if not categories[category] then
            categories[category] = 1
        else
            categories[category] = categories[category] + 1
        end
    end
 
    local caption = ""
    for category, categoryCount in pairs(categories) do
        if categoryCount > 1 then
            category = category .. "s"
        end
        if caption == "" then
            caption = category
        else
            caption = caption .. " & " .. category
        end
    end
 
    return caption
end
 
 
 
---@private
---Tallies up the descriptions. If there's exactly one, it returns that description, otherwise it returns blank since they can't be combined (and to save space).
---
---@param depositsList table
---@return string the one or shared description in the list, or "" if they are varied
local function writeDescriptionForDeposits(depositsList)
 
    --Heuristic: If there are more then 2, then there's no way all the descriptions match.
    if #depositsList > 2 then
        return ""
    end
 
    local descriptions = {}
    for _, deposit in ipairs(depositsList) do
        local desc = GladeResourcesData.getDepositDescription(deposit)
        descriptions[desc] = true
    end
 
    --Use exactly one description unless there are more, in which case return empty.
    local firstDescription = next(descriptions)
    if firstDescription and not next(descriptions, firstDescription) then
        return firstDescription
    else
        return ""
    end
end
 
 
local function writeBiomePartialLink(biomeID)
 
    local icon = BiomesData.getIcon(biomeID) .. ".png"
    local pageName = BiomesData.getName(biomeID)
    return "File:" .. icon .. "|" .. pageName
end


---@private
---Sets up the view, based on the display override or lack thereof. Returns whatever else needs to be appended to do so.
---
---@param depositsList table list of deposits from GladeResourcesData
---@param maxBonuses number slots for bonus resources
---@param title string title for the whole panel
---@param buildingID string an ID for a building specified, if any
---@param skipFlagForBiomes string flag for skipping biomes columns
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderViewStart(depositsList, maxBonuses, title, buildingID, skipFlagForBiomes, displayOverride)
    displayOverride = displayOverride --TODO write the other view modes
    local skipBuildingColumn = ""
    if buildingID then
        skipBuildingColumn = "skip"
    else
        local buildingName = checkOnlyOneBuildingForAllDeposits(depositsList)
        if buildingName then
            skipBuildingColumn = "skip"
        end
    end
    local caption = writeCaptionForDeposits(depositsList)
     local numDeposits = #depositsList
     local numDeposits = #depositsList
    --Special case where we swap the title out for the name of the one deposit and show the description
    local description = ""
    if numDeposits == 1 and not displayOverride == VIEW_ARG_FORCE_DEFAULT then
        title = GladeResourcesData.getDepositName(depositsList[1])
        description = writeDescriptionForDeposits(depositsList)
    end
    local viewArgs = {
        [VIEW_ARG_HEAD] = title,
        [VIEW_ARG_DESC] = description,
        [VIEW_ARG_CAPTION] = caption,
        [VIEW_ARG_NUM_DEPOSITS] = numDeposits,
        [VIEW_ARG_MAX_BONUSES] = maxBonuses,
        [VIEW_ARG_BUILDING] = skipBuildingColumn,
        [VIEW_ARG_SKIP_BIOMES] = skipFlagForBiomes,
    }
    if skipFlagForBiomes ~= SKIP_FLAG then
        for i, biomeID in ipairs(biomesIDs) do
            local biomeArg = VIEW_ARG_BIOME_PREFIX .. i
            viewArgs[biomeArg] = writeBiomePartialLink(biomeID)
        end
    end


     local wikiMarkup = frame:expandTemplate{
     local wikiMarkup = frame:expandTemplate{
         title = VIEW_TEMPLATE_START,
         title = VIEW_TEMPLATE_START,
         args = {
         args = viewArgs,
            [VIEW_ARG_HEAD] = numDeposits .. " deposits found.",
            [VIEW_ARG_DESC] = "Description?",
            [VIEW_ARG_CAPTION] = "Caption??",
            [VIEW_ARG_NUM_DEPOSITS] = numDeposits,
            [VIEW_ARG_MAX_BONUSES] = maxBonuses,
        },
     }
     }


Line 102: Line 337:




local function renderProduct(deposit)
---@private
-----Converts the data about the product in the specified deposit for the view.
---
---@param deposit table from GladeResourceData
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderProduct(deposit, displayOverride)
 
    displayOverride = displayOverride --TODO write the other display modes


     local productID, amount = GladeResourcesData.getDepositPrimaryProduct(deposit)
     local productID, amount = GladeResourcesData.getDepositPrimaryProduct(deposit)
Line 110: Line 353:
     local wikiMarkup = ""
     local wikiMarkup = ""
     if amount > 1 then
     if amount > 1 then
         wikiMarkup = wikiMarkup .. "''" .. amount .. "''&nbsp;"
         wikiMarkup = wikiMarkup .. "'''" .. amount .. "'''&nbsp;"
     end
     end


Line 128: Line 371:




local function renderBonus(deposit, index)
---@private
-----Converts the data about the specified bonus in the specified deposit for the view.
---
---@param deposit table from GladeResourceData
---@param index number which bonus from deposit to get
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderBonus(deposit, index, displayOverride)
 
    displayOverride = displayOverride --TODO write the other display modes


     local productID, amount, chance = GladeResourcesData.getDepositBonusResourceAt(deposit, index)
     local productID, amount, chance = GladeResourcesData.getDepositBonusResourceAt(deposit, index)
Line 147: Line 399:
         title = VIEW_RESOURCE_LINK,
         title = VIEW_RESOURCE_LINK,
         args = {
         args = {
            ["name"] = productName,
            ["iconfilename"] = iconFilename,
             ["iconsize"] = "medium",
             ["iconsize"] = "medium",
            ["iconfilename"] = iconFilename,
            ["name"] = productName,
             ["display"] = "notext",
             ["display"] = "notext",
         }
         }
     }
     }


     wikiMarkup = wikiMarkup .. "'''" .. chance*100 .. "%'''"
     wikiMarkup = wikiMarkup .. "<br>'''" .. chance*100 .. "%'''"
 
    return wikiMarkup
end
 
 
---@private
---Looks up the building that can get the specified deposit for the view.
---
---@param deposit table from GladeResourceData
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderBuilding(deposit, displayOverride)
 
    displayOverride = displayOverride --TODO write the other display modes
 
    local buildingName, buildingIcon = getBuildingForDeposit(deposit)
 
    local wikiMarkup = frame:expandTemplate{
        title = VIEW_BUILDING_LINK,
        args = {
            ["name"] = buildingName,
            ["iconfilename"] = buildingIcon,
            ["iconsize"] = "large",
            ["display"] = "notext"
        }
    }


     return wikiMarkup
     return wikiMarkup
Line 160: Line 438:




---@private
---Used when a building is specified or there's just one row.
---
---@param buildingID string
---@return string wiki markup
local function renderBottomBuildingLink(buildingID, buildingName, buildingIcon)
    if buildingID then
        buildingName = BuildingDataProxy.getName(buildingID)
        buildingIcon = BuildingDataProxy.getIcon(buildingID)
    end


local function renderViewOneRow(deposit, maxBonuses, displayOverride)
    local wikiMarkup = frame:expandTemplate{
        title = VIEW_BUILDING_LINK,
        args = {
            ["name"] = buildingName,
            ["iconfilename"] = buildingIcon,
            ["iconsize"] = "huge"
        }
    }
 
    return wikiMarkup
end
 
 
 
---@private
---Makes a row for one deposit. If the buildingID is given, skips showing it, because it means it's factored out of the rows.
---
---@param deposit table from GladeResourceData
---@param numDeposits number total number of deposits
---@param maxBonuses number slots for bonus resources
---@param buildingID string a specified buildingID, if any
---@param isSkippingBiomes string flag for skipping biomes columns
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderViewOneRow(deposit, numDeposits, maxBonuses, buildingID, isSkippingBiomes, displayOverride)
 
    displayOverride = displayOverride --TODO write the other display modes
 
    --Special case where we take out the name and building icon when there's just one deposit or it's all harvestable by one building.
    local depositName = ""
    if numDeposits > 1 then
        depositName = GladeResourcesData.getDepositName(deposit)
    end
    local buildingLink = ""
    if not buildingID then
        buildingLink = renderBuilding(deposit, displayOverride)
    end


     local viewArgs = {
     local viewArgs = {
         [VIEW_ARG_DEPOSIT] = GladeResourcesData.getDepositName(deposit),
         [VIEW_ARG_DEPOSIT] = depositName,
        [VIEW_ARG_BUILDING] = buildingLink,
         [VIEW_ARG_CHARGES] = GladeResourcesData.getDepositCharges(deposit),
         [VIEW_ARG_CHARGES] = GladeResourcesData.getDepositCharges(deposit),
         [VIEW_ARG_PRODUCT] = renderProduct(deposit),
         [VIEW_ARG_PRODUCT] = renderProduct(deposit, displayOverride),
         [VIEW_ARG_MAX_BONUSES] = maxBonuses,
         [VIEW_ARG_MAX_BONUSES] = maxBonuses,
         [VIEW_ARG_BONUS_1] = renderBonus(deposit, 1),
         [VIEW_ARG_BONUS_1] = renderBonus(deposit, 1, displayOverride),
         [VIEW_ARG_BONUS_2] = renderBonus(deposit, 2),
         [VIEW_ARG_BONUS_2] = renderBonus(deposit, 2, displayOverride),
         [VIEW_ARG_BONUS_3] = renderBonus(deposit, 3),
         [VIEW_ARG_BONUS_3] = renderBonus(deposit, 3, displayOverride),
         [VIEW_ARG_BONUS_4] = renderBonus(deposit, 4),
         [VIEW_ARG_BONUS_4] = renderBonus(deposit, 4, displayOverride),
        [VIEW_ARG_SKIP_BIOMES] = isSkippingBiomes,
     }
     }
    local depositID = GladeResourcesData.getDepositID(deposit)
    if isSkippingBiomes ~= SKIP_FLAG then
        for i, biomeID in ipairs(biomesIDs) do
            local biomeArg = VIEW_ARG_BIOME_PREFIX .. i
            local isInBiome = BiomesData.isNodeOfAnyTypeInBiome(depositID, biomeID)
            local biomePartialLink = writeBiomePartialLink(biomeID)
            viewArgs[biomeArg] = isInBiome and biomePartialLink or ""
        end
    end


     return frame:expandTemplate{
     return frame:expandTemplate{
Line 182: Line 519:




local function renderViewAllRows(depositsList, maxBonuses, displayOverride)
---@private
---Goes through the list of deposits and makes rows for each.
---
---@param depositsList table list of deposits from GladeResourcesData
---@param maxBonuses number slots for bonus resources
---@param buildingID string an ID for a building specified, if any
---@param skipFlagForBiomes string flag for skipping biomes columns
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderViewAllRows(depositsList, maxBonuses, buildingID, skipFlagForBiomes, displayOverride)
 
    local numDeposits = #depositsList
 
    -- Factor out a single building from the rows.
    if not buildingID then
        local buildingName = checkOnlyOneBuildingForAllDeposits(depositsList)
        if buildingName then
            buildingID = BuildingDataProxy.getID(buildingName)
        end
    end


     local wikiMarkup = ""
     local wikiMarkup = ""
     for _, deposit in ipairs(depositsList) do
     for _, deposit in ipairs(depositsList) do
         wikiMarkup = wikiMarkup .. "\n"
         wikiMarkup = wikiMarkup .. "\n"
         wikiMarkup = wikiMarkup .. renderViewOneRow(deposit, maxBonuses, displayOverride)
         wikiMarkup = wikiMarkup .. renderViewOneRow(deposit, numDeposits, maxBonuses, buildingID, skipFlagForBiomes, displayOverride)
     end
     end


Line 195: Line 551:




local function renderViewEnd()
---@private
--not using parameters depositsList, displayOverride
---Closes out the view, based on the display override or lack thereof. Returns whatever else needs to be appended to do so.
---
---@param depositsList table list of deposits from GladeResourcesData
---@param buildingID string an ID for a building specified, if any
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderViewEnd(depositsList, buildingID, displayOverride)
 
    displayOverride = displayOverride --TODO write the other display modes
 
    local numDeposits = #depositsList
 
    local buildingLink = ""
 
    if numDeposits > 0 then
        if buildingID then
            buildingLink = renderBottomBuildingLink(buildingID)
        else
            local buildingName, buildingIcon = checkOnlyOneBuildingForAllDeposits(depositsList)
            if buildingName and buildingIcon then
                buildingLink = renderBottomBuildingLink(nil, buildingName, buildingIcon)
            end
        end
    end


     local wikiMarkup = ""
     local wikiMarkup = ""
     wikiMarkup = wikiMarkup .. frame:expandTemplate{
     wikiMarkup = wikiMarkup .. frame:expandTemplate{
         title = VIEW_TEMPLATE_END,
         title = VIEW_TEMPLATE_END,
         args = {},
         args = {
            ["numdeposits"] = numDeposits,
            ["building"] = buildingLink,
        },
     }
     }
     return wikiMarkup
     return wikiMarkup
Line 219: Line 601:


     frame = mwFrame
     frame = mwFrame
     local requiredProduct = frame.args.product
     local productName = frame.args.product --TODO these are currently IDs, they need to be names
     local requiredBonus = frame.args.bonus
     local bonusName = frame.args.bonus
     local requiredBuilding = frame.args.building
     local buildingName = frame.args.building
    local skipFlagForBiomes = frame.args[VIEW_ARG_SKIP_BIOMES]
     local displayOverride = frame.args.display
     local displayOverride = frame.args.display
    local suppressTrees = true


     --Unset blanks back to nil
    biomesIDs = sortArrayWith(BiomesData.getAllBiomeIDs(), BiomesData.getName)
     if requiredProduct == "" then
 
         requiredProduct = nil
     --Unset blanks back to nil or get their names from their data model
    local productID
     if productName and productName ~= "" then
         productID = GoodsData.getGoodID(productName)
     end
     end
     if requiredBonus == "" then
     local bonusID
        requiredBonus = nil
     if bonusName and bonusName ~= "" then
    end
         bonusID = GoodsData.getGoodID(bonusName)
     if requiredBuilding == "" then
         requiredBuilding = nil
     end
     end
    local buildingID = BuildingDataProxy.getID(buildingName)
--Find out whether Trees should be excluded
if (not buildingName) or buildingName == "" or buildingName == "Woodcutters' Camp" then
suppressTrees = false
end


     local depositsList = {}
     local depositsList = {}
     if requiredProduct and requiredBonus then
    local title = ""
         if requiredProduct ~= requiredBonus then
     if productID and bonusID then
             return ERROR_MESSAGE_PRODUCT_AND_BONUS_BUT_NOT_MATCH
         if productID ~= bonusID then
             error(ERROR_MESSAGE_PRODUCT_AND_BONUS_BUT_NOT_MATCH)
         else
         else
             depositsList = GladeResourcesData.getAllDepositsWherePrimaryAndBonusResource(requiredProduct)
             depositsList = sortArrayWith(GladeResourcesData.getAllDepositsWherePrimaryOrBonusResource(productID, suppressTrees), GladeResourcesData.getDepositName)
         end
         end
     elseif requiredProduct then
        title = #depositsList .. " Deposits for " .. productName .. " or as a bonus."
         depositsList = GladeResourcesData.getAllDepositsWherePrimaryResource(requiredProduct)
 
     elseif requiredBonus then
     elseif productID then
         depositsList = GladeResourcesData.getAllDepositsWhereBonusResource(requiredBonus)
         depositsList = sortArrayWith(GladeResourcesData.getAllDepositsWherePrimaryResource(productID, suppressTrees), GladeResourcesData.getDepositName)
        title = #depositsList .. " Deposits for " .. productName .. "."
 
     elseif bonusID then
         depositsList = sortArrayWith(GladeResourcesData.getAllDepositsWhereBonusResource(bonusID, suppressTrees), GladeResourcesData.getDepositName)
        title = #depositsList .. " Deposits where " .. bonusName .. " is a bonus."
 
    elseif buildingID then
        depositsList = sortArrayWith(assembleDepositsFromBuilding(buildingID, suppressTrees), GladeResourcesData.getDepositName)
        title = #depositsList .. " Deposits exploited by the " .. buildingName .. "."
 
    else
        error(ERROR_MESSAGE_NO_PARAMETERS_GIVEN)
     end
     end


Line 251: Line 655:


     local wikiMarkup = ""
     local wikiMarkup = ""
     wikiMarkup = wikiMarkup .. renderViewStart(depositsList, maxBonuses, displayOverride)
     wikiMarkup = wikiMarkup .. renderViewStart(depositsList, maxBonuses, title, buildingID, skipFlagForBiomes, displayOverride)
     wikiMarkup = wikiMarkup .. renderViewAllRows(depositsList, maxBonuses, displayOverride)
     wikiMarkup = wikiMarkup .. renderViewAllRows(depositsList, maxBonuses, buildingID, skipFlagForBiomes, displayOverride)
     wikiMarkup = wikiMarkup .. renderViewEnd(depositsList, displayOverride)
     wikiMarkup = wikiMarkup .. renderViewEnd(depositsList, buildingID, displayOverride)


     return wikiMarkup
     return wikiMarkup

Latest revision as of 02:12, 9 August 2025

Documentation for this module may be created at Module:GladeResourcesController/doc

---@class GladeResourcesController
local GladeResourcesController = {}



--region Dependencies

local GladeResourcesData = require("Module:GladeResourcesData")
local GoodsData = require("Module:GoodsData")
local BiomesData = require("Module:BiomesData")
local BuildingDataProxy = require("Module:BuildingDataProxy")
local DataModelsWithGladeResourcesRecipes = {
    require("Module:MinesData"),
    require("Module:CampsData"),
    require("Module:FishingData"),
    require("Module:GatheringData"),
}

local VIEW_TEMPLATE_START = "Deposit/view"
local VIEW_TEMPLATE_ROW = "Deposit/view/row"
local VIEW_TEMPLATE_END = "Deposit/view/end"

local VIEW_BUILDING_LINK = "Building_link/view"
local VIEW_RESOURCE_LINK = "Resource_link/view"

--endregion



--region Private constants

local VIEW_ARG_HEAD = "heading"
local VIEW_ARG_DESC = "description"
local VIEW_ARG_CAPTION = "caption"
local VIEW_ARG_CHARGES = "charges"
local VIEW_ARG_NUM_DEPOSITS = "numdeposits"
local VIEW_ARG_MAX_BONUSES = "maxbonuses"

local VIEW_ARG_DEPOSIT = "deposit"
local VIEW_ARG_BUILDING = "building"

local VIEW_ARG_PRODUCT = "primary"
local VIEW_ARG_BONUS_1 = "bonus1"
local VIEW_ARG_BONUS_2 = "bonus2"
local VIEW_ARG_BONUS_3 = "bonus3"
local VIEW_ARG_BONUS_4 = "bonus4"

local VIEW_ARG_SKIP_BIOMES = "skip_biomes"
local SKIP_FLAG = "skip"

local VIEW_ARG_BIOME_PREFIX = "biome"

local VIEW_ARG_FORCE_DEFAULT = "default"
--endregion



--region Localization string constants

local ERROR_MESSAGE_PRODUCT_AND_BONUS_BUT_NOT_MATCH = "When searching for a good as both a main and bonus resource, the parameters must match"
local ERROR_MESSAGE_NO_PARAMETERS_GIVEN = "You must specify the name of a product, a bonus resource, or a building—and as it's spelled in the game"

--endregion



--region Private member variables

local frame

local biomesIDs

--endregion



--region Private methods

---@private
---Sorts given the function provided.
---
---@param arrayToSort table
---@param comparisonFunction function
---@return table the sorted array
local function sortArrayWith(arrayToSort, comparisonFunction)
    table.sort(arrayToSort, function(a, b)
        return comparisonFunction(a) < comparisonFunction(b)
    end)
    return arrayToSort
end


---@private
---Uses the specified building ID to go through applicable data models, assembling a list of possible deposits, filtered by the grade of the building.
---
---@param buildingID string a building's ID
---@return table array of deposit records retrieved from GladeResourcesData
local function assembleDepositsFromBuilding(buildingID, suppressTrees)

    local depositsList = {}
    local isWoodcutter = buildingID == "Woodcutters Camp"
    
    if isWoodcutter then
        local newList = GladeResourcesData.getAllDepositsWhereDepositIsTree()
        for _, deposit in ipairs(newList) do
            table.insert(depositsList, deposit)
        end
    else
    	--This isn't as expensive as it looks, because typically only one data model will return something. However, we don't know which it will be in advance.
    	for _, dataModel in ipairs(DataModelsWithGladeResourcesRecipes) do
		
        	local recipeDataArray = dataModel:getIDsAndRecipesWhereBuildingID(buildingID)
        	if #recipeDataArray > 0 then

            	local products = {}
            	for _, recipeData in ipairs(recipeDataArray) do
                	local id = dataModel:getRecipeProductID(recipeData)
                	products[id] = dataModel:getRecipeGrade(recipeData)
            	end
            	for id, grade in pairs(products) do
                	local newList = GladeResourcesData.getAllDepositsWherePrimaryResourceAndGrade(id, grade, suppressTrees)
                	for _, deposit in ipairs(newList) do
                    	table.insert(depositsList, deposit)
                	end
            	end
        	end
    	end
    end

    return depositsList
end



---@private
---Looks up the building ID from the building data models for the one that can exploit the given deposit.
---
---@param deposit table record from GladeResourcesData
---@return string, string building display name, icon filename
local function getBuildingForDeposit(deposit)

    local productID = GladeResourcesData.getDepositPrimaryProduct(deposit)
    local isTree = GladeResourcesData.isDepositTree(deposit)

    --This isn't as expensive as it looks, because typically only one data model will return something. However, we don't know which it will be in advance.
    for _, dataModel in ipairs(DataModelsWithGladeResourcesRecipes) do

        local recipeDataArray = dataModel:getIDsAndRecipesWhereProductID(productID)
        if #recipeDataArray > 0 then
            for i = 1, #recipeDataArray do
            	local id = dataModel:getRecipeBuildingID(recipeDataArray[i])
            	if (isTree or dataModel:getName(id) ~= "Woodcutters' Camp") then
            		local name = dataModel:getName(id)
            		local icon = dataModel:getIcon(id)
            		return name, icon
            	end
            end
        end
    end
    return ""
end



---@private
---Checks whether the provided list of deposits can be harvested by a single building. If so, returns that building's name and icon (for use in a building link). Otherwise, returns nil.
---
---@param depositsList table array of deposit records retrieved from GladeResourcesData
---@return string, string building name and icon, if there is only one, nil if none or more than one
local function checkOnlyOneBuildingForAllDeposits(depositsList)

    local buildings = {}
    for _, deposit in ipairs(depositsList) do
        local name, icon = getBuildingForDeposit(deposit)
        buildings[name] = icon
    end

    local firstBuildingName, firstBuildingIcon = next(buildings)
    if firstBuildingName and not next(buildings, firstBuildingName) then
        return firstBuildingName, firstBuildingIcon
    else
        return nil
    end
end



---@private
---Goes through the provided list of deposits and finds the maximum number of bonuses, so that the view can be standardized.
---
---@param depositsList table array of deposit records retrieved from GladeResourcesData
---@return number maximum bonuses across all in the list
local function countMaxBonusesInList(depositsList)
    local maxFound = 0
    for _, deposit in ipairs(depositsList) do
        local numBonuses = GladeResourcesData.getDepositNumBonusResources(deposit)
        if numBonuses > maxFound then
            maxFound = numBonuses
        end
    end
    return maxFound
end



---@private
---Tallies up the categories and concatenates them. Also makes them plural if there are more than one of a particular category.
---
---@param depositsList table
---@return string the categories among the deposits
local function writeCaptionForDeposits(depositsList)

    local categories = {}
    for _, deposit in ipairs(depositsList) do
        local category = GladeResourcesData.getDepositCategory(deposit)
        if not categories[category] then
            categories[category] = 1
        else
            categories[category] = categories[category] + 1
        end
    end

    local caption = ""
    for category, categoryCount in pairs(categories) do
        if categoryCount > 1 then
            category = category .. "s"
        end
        if caption == "" then
            caption = category
        else
            caption = caption .. " & " .. category
        end
    end

    return caption
end



---@private
---Tallies up the descriptions. If there's exactly one, it returns that description, otherwise it returns blank since they can't be combined (and to save space).
---
---@param depositsList table
---@return string the one or shared description in the list, or "" if they are varied
local function writeDescriptionForDeposits(depositsList)

    --Heuristic: If there are more then 2, then there's no way all the descriptions match.
    if #depositsList > 2 then
        return ""
    end

    local descriptions = {}
    for _, deposit in ipairs(depositsList) do
        local desc = GladeResourcesData.getDepositDescription(deposit)
        descriptions[desc] = true
    end

    --Use exactly one description unless there are more, in which case return empty.
    local firstDescription = next(descriptions)
    if firstDescription and not next(descriptions, firstDescription) then
        return firstDescription
    else
        return ""
    end
end


local function writeBiomePartialLink(biomeID)

    local icon = BiomesData.getIcon(biomeID) .. ".png"
    local pageName = BiomesData.getName(biomeID)
    return "File:" .. icon .. "|" .. pageName
end



---@private
---Sets up the view, based on the display override or lack thereof. Returns whatever else needs to be appended to do so.
---
---@param depositsList table list of deposits from GladeResourcesData
---@param maxBonuses number slots for bonus resources
---@param title string title for the whole panel
---@param buildingID string an ID for a building specified, if any
---@param skipFlagForBiomes string flag for skipping biomes columns
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderViewStart(depositsList, maxBonuses, title, buildingID, skipFlagForBiomes, displayOverride)

    displayOverride = displayOverride --TODO write the other view modes

    local skipBuildingColumn = ""
    if buildingID then
        skipBuildingColumn = "skip"
    else
        local buildingName = checkOnlyOneBuildingForAllDeposits(depositsList)
        if buildingName then
            skipBuildingColumn = "skip"
        end
    end


    local caption = writeCaptionForDeposits(depositsList)
    local numDeposits = #depositsList

    --Special case where we swap the title out for the name of the one deposit and show the description
    local description = ""
    if numDeposits == 1 and not displayOverride == VIEW_ARG_FORCE_DEFAULT then
        title = GladeResourcesData.getDepositName(depositsList[1])
        description = writeDescriptionForDeposits(depositsList)
    end

    local viewArgs = {
        [VIEW_ARG_HEAD] = title,
        [VIEW_ARG_DESC] = description,
        [VIEW_ARG_CAPTION] = caption,
        [VIEW_ARG_NUM_DEPOSITS] = numDeposits,
        [VIEW_ARG_MAX_BONUSES] = maxBonuses,
        [VIEW_ARG_BUILDING] = skipBuildingColumn,
        [VIEW_ARG_SKIP_BIOMES] = skipFlagForBiomes,
    }

    if skipFlagForBiomes ~= SKIP_FLAG then
        for i, biomeID in ipairs(biomesIDs) do
            local biomeArg = VIEW_ARG_BIOME_PREFIX .. i
            viewArgs[biomeArg] = writeBiomePartialLink(biomeID)
        end
    end

    local wikiMarkup = frame:expandTemplate{
        title = VIEW_TEMPLATE_START,
        args = viewArgs,
    }

    return wikiMarkup
end



---@private
-----Converts the data about the product in the specified deposit for the view.
---
---@param deposit table from GladeResourceData
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderProduct(deposit, displayOverride)

    displayOverride = displayOverride --TODO write the other display modes

    local productID, amount = GladeResourcesData.getDepositPrimaryProduct(deposit)
    local iconFilename = GoodsData.getIcon(productID)
    local productName = GoodsData.getName(productID)

    local wikiMarkup = ""
    if amount > 1 then
        wikiMarkup = wikiMarkup .. "'''" .. amount .. "'''&nbsp;"
    end

    wikiMarkup = wikiMarkup .. frame:expandTemplate{
        title = VIEW_RESOURCE_LINK,
        args = {
            ["iconsize"] = "medium",
            ["iconfilename"] = iconFilename,
            ["name"] = productName,
            ["display"] = "notext",
        }
    }

    return wikiMarkup
end



---@private
-----Converts the data about the specified bonus in the specified deposit for the view.
---
---@param deposit table from GladeResourceData
---@param index number which bonus from deposit to get
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderBonus(deposit, index, displayOverride)

    displayOverride = displayOverride --TODO write the other display modes

    local productID, amount, chance = GladeResourcesData.getDepositBonusResourceAt(deposit, index)

    if not productID then
        return ""
    end

    local iconFilename = GoodsData.getIcon(productID)
    local productName = GoodsData.getName(productID)

    local wikiMarkup = ""
    if amount and amount > 1 then
        wikiMarkup = wikiMarkup .. "'''" .. amount .. "'''&nbsp;"
    end

    wikiMarkup = wikiMarkup .. frame:expandTemplate{
        title = VIEW_RESOURCE_LINK,
        args = {
            ["name"] = productName,
            ["iconfilename"] = iconFilename,
            ["iconsize"] = "medium",
            ["display"] = "notext",
        }
    }

    wikiMarkup = wikiMarkup .. "<br>'''" .. chance*100 .. "%'''"

    return wikiMarkup
end


---@private
---Looks up the building that can get the specified deposit for the view.
---
---@param deposit table from GladeResourceData
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderBuilding(deposit, displayOverride)

    displayOverride = displayOverride --TODO write the other display modes

    local buildingName, buildingIcon = getBuildingForDeposit(deposit)

    local wikiMarkup = frame:expandTemplate{
        title = VIEW_BUILDING_LINK,
        args = {
            ["name"] = buildingName,
            ["iconfilename"] = buildingIcon,
            ["iconsize"] = "large",
            ["display"] = "notext"
        }
    }

    return wikiMarkup
end


---@private
---Used when a building is specified or there's just one row.
---
---@param buildingID string
---@return string wiki markup
local function renderBottomBuildingLink(buildingID, buildingName, buildingIcon)

    if buildingID then
        buildingName = BuildingDataProxy.getName(buildingID)
        buildingIcon = BuildingDataProxy.getIcon(buildingID)
    end

    local wikiMarkup = frame:expandTemplate{
        title = VIEW_BUILDING_LINK,
        args = {
            ["name"] = buildingName,
            ["iconfilename"] = buildingIcon,
            ["iconsize"] = "huge"
        }
    }

    return wikiMarkup
end



---@private
---Makes a row for one deposit. If the buildingID is given, skips showing it, because it means it's factored out of the rows.
---
---@param deposit table from GladeResourceData
---@param numDeposits number total number of deposits
---@param maxBonuses number slots for bonus resources
---@param buildingID string a specified buildingID, if any
---@param isSkippingBiomes string flag for skipping biomes columns
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderViewOneRow(deposit, numDeposits, maxBonuses, buildingID, isSkippingBiomes, displayOverride)

    displayOverride = displayOverride --TODO write the other display modes

    --Special case where we take out the name and building icon when there's just one deposit or it's all harvestable by one building.
    local depositName = ""
    if numDeposits > 1 then
        depositName = GladeResourcesData.getDepositName(deposit)
    end
    local buildingLink = ""
    if not buildingID then
        buildingLink = renderBuilding(deposit, displayOverride)
    end

    local viewArgs = {
        [VIEW_ARG_DEPOSIT] = depositName,
        [VIEW_ARG_BUILDING] = buildingLink,
        [VIEW_ARG_CHARGES] = GladeResourcesData.getDepositCharges(deposit),
        [VIEW_ARG_PRODUCT] = renderProduct(deposit, displayOverride),
        [VIEW_ARG_MAX_BONUSES] = maxBonuses,
        [VIEW_ARG_BONUS_1] = renderBonus(deposit, 1, displayOverride),
        [VIEW_ARG_BONUS_2] = renderBonus(deposit, 2, displayOverride),
        [VIEW_ARG_BONUS_3] = renderBonus(deposit, 3, displayOverride),
        [VIEW_ARG_BONUS_4] = renderBonus(deposit, 4, displayOverride),
        [VIEW_ARG_SKIP_BIOMES] = isSkippingBiomes,
    }

    local depositID = GladeResourcesData.getDepositID(deposit)
    if isSkippingBiomes ~= SKIP_FLAG then
        for i, biomeID in ipairs(biomesIDs) do
            local biomeArg = VIEW_ARG_BIOME_PREFIX .. i
            local isInBiome = BiomesData.isNodeOfAnyTypeInBiome(depositID, biomeID)
            local biomePartialLink = writeBiomePartialLink(biomeID)
            viewArgs[biomeArg] = isInBiome and biomePartialLink or ""
        end
    end

    return frame:expandTemplate{
        title = VIEW_TEMPLATE_ROW,
        args = viewArgs,
    }
end



---@private
---Goes through the list of deposits and makes rows for each.
---
---@param depositsList table list of deposits from GladeResourcesData
---@param maxBonuses number slots for bonus resources
---@param buildingID string an ID for a building specified, if any
---@param skipFlagForBiomes string flag for skipping biomes columns
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderViewAllRows(depositsList, maxBonuses, buildingID, skipFlagForBiomes, displayOverride)

    local numDeposits = #depositsList

    -- Factor out a single building from the rows.
    if not buildingID then
        local buildingName = checkOnlyOneBuildingForAllDeposits(depositsList)
        if buildingName then
            buildingID = BuildingDataProxy.getID(buildingName)
        end
    end

    local wikiMarkup = ""
    for _, deposit in ipairs(depositsList) do
        wikiMarkup = wikiMarkup .. "\n"
        wikiMarkup = wikiMarkup .. renderViewOneRow(deposit, numDeposits, maxBonuses, buildingID, skipFlagForBiomes, displayOverride)
    end

    return wikiMarkup
end



---@private
---Closes out the view, based on the display override or lack thereof. Returns whatever else needs to be appended to do so.
---
---@param depositsList table list of deposits from GladeResourcesData
---@param buildingID string an ID for a building specified, if any
---@param displayOverride string override code, if any
---@return string wiki markup
local function renderViewEnd(depositsList, buildingID, displayOverride)

    displayOverride = displayOverride --TODO write the other display modes

    local numDeposits = #depositsList

    local buildingLink = ""

    if numDeposits > 0 then
        if buildingID then
            buildingLink = renderBottomBuildingLink(buildingID)
        else
            local buildingName, buildingIcon = checkOnlyOneBuildingForAllDeposits(depositsList)
            if buildingName and buildingIcon then
                buildingLink = renderBottomBuildingLink(nil, buildingName, buildingIcon)
            end
        end
    end

    local wikiMarkup = ""
    wikiMarkup = wikiMarkup .. frame:expandTemplate{
        title = VIEW_TEMPLATE_END,
        args = {
            ["numdeposits"] = numDeposits,
            ["building"] = buildingLink,
        },
    }
    return wikiMarkup
end

--endregion



--region Public methods

---Called from Template:Deposit. Returns markup text for display by using external view templates.
---
---@param mwFrame table the Mediawiki calling context for the template
---@return string wiki markup
function GladeResourcesController.main(mwFrame)

    frame = mwFrame
    local productName = frame.args.product --TODO these are currently IDs, they need to be names
    local bonusName = frame.args.bonus
    local buildingName = frame.args.building
    local skipFlagForBiomes = frame.args[VIEW_ARG_SKIP_BIOMES]
    local displayOverride = frame.args.display
    local suppressTrees = true

    biomesIDs = sortArrayWith(BiomesData.getAllBiomeIDs(), BiomesData.getName)

    --Unset blanks back to nil or get their names from their data model
    local productID
    if productName and productName ~= "" then
        productID = GoodsData.getGoodID(productName)
    end
    local bonusID
    if bonusName and bonusName ~= "" then
        bonusID = GoodsData.getGoodID(bonusName)
    end
    local buildingID = BuildingDataProxy.getID(buildingName)

	--Find out whether Trees should be excluded
	if (not buildingName) or buildingName == "" or buildingName == "Woodcutters' Camp" then
		suppressTrees = false
	end

    local depositsList = {}
    local title = ""
    if productID and bonusID then
        if productID ~= bonusID then
            error(ERROR_MESSAGE_PRODUCT_AND_BONUS_BUT_NOT_MATCH)
        else
            depositsList = sortArrayWith(GladeResourcesData.getAllDepositsWherePrimaryOrBonusResource(productID, suppressTrees), GladeResourcesData.getDepositName)
        end
        title = #depositsList .. " Deposits for " .. productName .. " or as a bonus."

    elseif productID then
        depositsList = sortArrayWith(GladeResourcesData.getAllDepositsWherePrimaryResource(productID, suppressTrees), GladeResourcesData.getDepositName)
        title = #depositsList .. " Deposits for " .. productName .. "."

    elseif bonusID then
        depositsList = sortArrayWith(GladeResourcesData.getAllDepositsWhereBonusResource(bonusID, suppressTrees), GladeResourcesData.getDepositName)
        title = #depositsList .. " Deposits where " .. bonusName .. " is a bonus."

    elseif buildingID then
        depositsList = sortArrayWith(assembleDepositsFromBuilding(buildingID, suppressTrees), GladeResourcesData.getDepositName)
        title = #depositsList .. " Deposits exploited by the " .. buildingName .. "."

    else
        error(ERROR_MESSAGE_NO_PARAMETERS_GIVEN)
    end

    local maxBonuses = countMaxBonusesInList(depositsList)

    local wikiMarkup = ""
    wikiMarkup = wikiMarkup .. renderViewStart(depositsList, maxBonuses, title, buildingID, skipFlagForBiomes, displayOverride)
    wikiMarkup = wikiMarkup .. renderViewAllRows(depositsList, maxBonuses, buildingID, skipFlagForBiomes, displayOverride)
    wikiMarkup = wikiMarkup .. renderViewEnd(depositsList, buildingID, displayOverride)

    return wikiMarkup
end

--endregion



return GladeResourcesController