Module:MysteriesData: Difference between revisions

From Against the Storm Official Wiki
(Created to serve forest mysteries data)
 
(Changing unique identifier to be the display name, since it is unique)
 
(6 intermediate revisions by the same user not shown)
Line 19: Line 19:
local JSON_DATA_PAGE_2 = "Template:Conditional_Seasonal_Effects_json"
local JSON_DATA_PAGE_2 = "Template:Conditional_Seasonal_Effects_json"


-- Keys in both
local ID_PREFIX_TO_SKIP = "_TO DELETE"
local KEY_DISPLAY_NAME = "displayName"
 
local KEY_DESCRIPTION = "description"
-- Fields in both
local KEY_ICON = "icon"
local INDEX_ID = "id"
local KEY_DIFFICULTY_COST = "difficultyCost"
local INDEX_DISPLAY_NAME = "displayName"
local KEY_COST_LABEL = "costLabel"
local INDEX_DESCRIPTION = "description"
local KEY_HOSTILITY_LEVEL = "hostilityLevel"
local INDEX_ICON = "icon"
local KEY_SEASON = "season"
local INDEX_DIFFICULTY_COST = "difficultyCost"
-- Keys in Simple Seasonal Effects
local INDEX_COST_LABEL = "costLabel"
local KEY_STACKS_WITH_HOSTILITY = "stacksWithHostility"
local INDEX_HOSTILITY_LEVEL = "hostilityLevel"
local KEY_STACKS_WITH_YEAR = "stacksWithYear"
local INDEX_SEASON = "season"
local KEY_REMOVE_AFTER_YEAR = "removeAfterYear"
-- Fields in Simple Seasonal Effects
local KEY_EFFECT_ID = "effectId"
local INDEX_STACKS_WITH_HOSTILITY = "stacksWithHostility"
local KEY_IN_CUSTOM_MODE = "isInCustomMode"
local INDEX_STACKS_WITH_YEAR = "stacksWithYear"
-- Keys in Conditional Seasonal Effects
local INDEX_REMOVE_AFTER_YEAR = "removeAfterYear"
local KEY_VILLAGER_PERK_ID = "villagerPerkId"
local INDEX_EFFECT_ID = "effectId"
local KEY_CONDITIONS = "conditions"
local INDEX_IN_CUSTOM_MODE = "isInCustomMode"
-- Keys in subtable
-- Fields in Conditional Seasonal Effects
local KEY_CONDITIONS_CATEGORY_ID = "categoryId"
local INDEX_VILLAGER_PERK_ID = "villagerPerkId"
local KEY_CONDITIONS_CATEGORY_NAME = "categoryDisplayName"
local INDEX_CONDITIONS = "conditions"
local KEY_CONDITIONS_AMOUNT = "amount"
-- Fields in subtable
local INDEX_CONDITIONS_CATEGORY_ID = "categoryId"
local INDEX_CONDITIONS_CATEGORY_NAME = "categoryDisplayName"
local INDEX_CONDITIONS_AMOUNT = "amount"


local IS_VALID_KEY = {
local IS_VALID_KEY = {
     [KEY_DISPLAY_NAME] = true,
     [INDEX_ID] = true,
     [KEY_DESCRIPTION] = true,
     [INDEX_DISPLAY_NAME] = true,
     [KEY_ICON] = true,
     [INDEX_DESCRIPTION] = true,
     [KEY_DIFFICULTY_COST] = true,
     [INDEX_ICON] = true,
     [KEY_COST_LABEL] = true,
     [INDEX_DIFFICULTY_COST] = true,
     [KEY_HOSTILITY_LEVEL] = true,
     [INDEX_COST_LABEL] = true,
     [KEY_SEASON] = true,
     [INDEX_HOSTILITY_LEVEL] = true,
     [KEY_STACKS_WITH_HOSTILITY] = true,
     [INDEX_SEASON] = true,
     [KEY_STACKS_WITH_YEAR] = true,
     [INDEX_STACKS_WITH_HOSTILITY] = true,
     [KEY_REMOVE_AFTER_YEAR] = true,
     [INDEX_STACKS_WITH_YEAR] = true,
     [KEY_EFFECT_ID] = true,
     [INDEX_REMOVE_AFTER_YEAR] = true,
     [KEY_IN_CUSTOM_MODE] = true,
     [INDEX_EFFECT_ID] = true,
     [KEY_VILLAGER_PERK_ID] = true,
     [INDEX_IN_CUSTOM_MODE] = true,
     [KEY_CONDITIONS] = true,
     [INDEX_VILLAGER_PERK_ID] = true,
    [INDEX_CONDITIONS] = true,
}
}


local IS_VALID_CONDITIONS_KEY = {
local IS_VALID_CONDITIONS_KEY = {
     [KEY_CONDITIONS_CATEGORY_ID] = true,
     [INDEX_CONDITIONS_CATEGORY_ID] = true,
     [KEY_CONDITIONS_CATEGORY_NAME] = true,
     [INDEX_CONDITIONS_CATEGORY_NAME] = true,
     [KEY_CONDITIONS_AMOUNT] = true,
     [INDEX_CONDITIONS_AMOUNT] = true,
}
}


local ERROR_MESSAGE_ID_MISSING = "MysteriesData requires the unique identifier for a Forest Mystery to get its data. Double-check what was passed as a parameter, because it was either missing, nil, or blank."
local ERROR_MESSAGE_KEY_MISSING = "MysteriesData requires a unique key (the display name) for a Forest Mystery to get its data. Double-check what was passed as a parameter, because it was either missing, nil, or blank."
local ERROR_MESSAGE_UNEXPECTED_DUPLICATE_ID_ON_MERGE = "MysteriesData attempted to merge the two JSON data tables, however it found two records with the same id."
local ERROR_MESSAGE_UNEXPECTED_DUPLICATE_KEY_ON_MERGE = "MysteriesData attempted to merge the two JSON data tables, however it found two records with the same key."
local ERROR_MESSAGE_OBJECT_MISSING_KEY = "MysteriesData discovered a record in the data that did not contain the required field named 'displayName' that is required to uniquely identify each record. Double-check that the data has not been modified from how it was originally supplied by the developers."


--endregion
--endregion
Line 74: Line 79:


local mysteriesData
local mysteriesData
local listOfNames


--endregion
--endregion
Line 80: Line 86:


--region Private methods
--region Private methods
--- This method uses the member variables mysteriesData and listOfIDs
---
---@param dataTable table the data table to loop through
---@param fieldToPromoteToKey string the name of the field to use as the key
local function saveRecordsByKey(dataTable, fieldToPromoteToKey)
    -- Store the mysteries by their ID, but also create a simple array of all IDs since we're already looping over them all.
    local i = #listOfNames + 1
    for _, record in ipairs(dataTable) do
        local key = record[fieldToPromoteToKey]
        -- Validate the id
        if not key or key == "" then
            error(ERROR_MESSAGE_OBJECT_MISSING_KEY)
        else
            -- Skip the records that the devs have marked for deletion
            if string.sub(key, 1, #ID_PREFIX_TO_SKIP) ~= ID_PREFIX_TO_SKIP then
                -- Validate that there are no duplicate IDs in the final table.
                if mysteriesData[key] ~= nil then
                    error(ERROR_MESSAGE_UNEXPECTED_DUPLICATE_KEY_ON_MERGE .. "\n id=" .. key)
                else
                    -- Promote the id field to be the key.
                    mysteriesData[key] = record
                    listOfNames[i] = key
                    i = i + 1
                end
            end
        end
    end
end


--- Checks whether the Mysteries data has been loaded yet. If not, handles that and the post-processing necessary.
--- Checks whether the Mysteries data has been loaded yet. If not, handles that and the post-processing necessary.
Line 85: Line 121:
local function loadData()
local function loadData()


    -- This function gets called any time the data could need to be loaded. Just return as quickly as possible if it's already loaded.
     if not mysteriesData then
     if not mysteriesData then
        local data1 = JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_1)
        local data2 = JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_2)


         for mysteryID, mysteryRecord in pairs(data2) do
         mysteriesData = {}
            -- Validate that there are no duplicate IDs in the two tables.
         listOfNames = {}
            if nil ~= mysteriesData[mysteryID] then
                error(ERROR_MESSAGE_UNEXPECTED_DUPLICATE_ID_ON_MERGE)
            else
                -- Combine.
                data1[mysteryID] = mysteryRecord
            end
         end


         mysteriesData = data1
         saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_1), INDEX_DISPLAY_NAME)
        saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_2), INDEX_DISPLAY_NAME)
     end
     end
end
end
Line 107: Line 136:
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
---
---
---@param mysteryID string the unique identifier of a Mystery
---@param mysteryKey string the unique key of a Mystery
---@return table the whole table row for that Mystery
---@return table the whole table row for that Mystery
local function getRecordWhereID(mysteryID)
local function getRecordWhereKey(mysteryKey)


     if not mysteryID or "" == mysteryID then
     if not mysteryKey or "" == mysteryKey then
         error(ERROR_MESSAGE_ID_MISSING)
         error(ERROR_MESSAGE_KEY_MISSING)
     else
     else
         loadData()
         loadData()
         return mysteriesData[mysteryID]
         return mysteriesData[mysteryKey]
     end
     end
end
end
Line 123: Line 152:
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
---
---
---@param mysteryID string the unique identifier of a Mystery
---@param mysteryKey string the unique key of a Mystery
---@param fieldName string the desired constant field name
---@param fieldName string the desired constant field name
---@return any the value of the field--type is not known by this method
---@return any the value of the field--type is not known by this method
local function getFieldWhereID(mysteryID, fieldName)
local function getFieldWhereKey(mysteryKey, fieldName)


     if not IS_VALID_KEY[fieldName] then
     if not IS_VALID_KEY[fieldName] then
Line 132: Line 161:
     end
     end


     local record = getRecordWhereID(mysteryID)
     local record = getRecordWhereKey(mysteryKey)
     if not record then
     if not record then
         return nil
         return nil
Line 144: Line 173:
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
---
---
---@param mysteryID string the unique identifier of a Mystery
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@param conditionIndex number the index of the desired condition
---@return table the desired condition
---@return table the desired condition
local function getConditionWhereID(mysteryID, conditionIndex)
local function getConditionWhereID(mysteryKey, conditionIndex)


     local conditions = getFieldWhereID(mysteryID, KEY_CONDITIONS)
     local conditions = getFieldWhereKey(mysteryKey, INDEX_CONDITIONS)
     if not conditions then
     if not conditions then
         return nil
         return nil
Line 161: Line 190:
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
---
---
---@param mysteryID string the unique identifier of a Mystery
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@param conditionIndex number the index of the desired condition
---@param fieldName string the desired constant field name
---@param fieldName string the desired constant field name
---@return table the desired condition
---@return table the desired condition
local function getFieldFromConditionWhereID(mysteryID, conditionIndex, fieldName)
local function getFieldFromConditionWhereKey(mysteryKey, conditionIndex, fieldName)


     if not IS_VALID_CONDITIONS_KEY[fieldName] then
     if not IS_VALID_CONDITIONS_KEY[fieldName] then
Line 171: Line 200:
     end
     end


     local condition = getConditionWhereID(mysteryID, conditionIndex)
     local condition = getConditionWhereID(mysteryKey, conditionIndex)
     if not condition then
     if not condition then
         return nil
         return nil
Line 186: Line 215:


---isIDValid
---isIDValid
---@param mysteryID string a unique identifier
---@param mysteryKey string a unique identifier
---@return boolean true if there is a record with the specified identifier
---@return boolean true if there is a record with the specified name
function MysteriesData.isIDValid(mysteryID)
function MysteriesData.isNameValid(mysteryKey)


     local record = getRecordWhereID(mysteryID)
     local record = getRecordWhereKey(mysteryKey)
     if not record then
     if not record then
         return false
         return false
Line 196: Line 225:
         return true
         return true
     end
     end
end
---getID
---@param mysteryKey string the unique key of a Mystery
---@return string the coded unique identifier of the mystery
function MysteriesData.getID(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_ID)
end
end


---getName
---getName
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return string the name of the mystery
---@return string the name of the mystery
function MysteriesData.getName(mysteryID)
function MysteriesData.getName(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_DISPLAY_NAME)
     return getFieldWhereKey(mysteryKey, INDEX_DISPLAY_NAME)
end
end


---getDescription
---getDescription
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return string the description of the mystery
---@return string the description of the mystery
function MysteriesData.getDescription(mysteryID)
function MysteriesData.getDescription(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_DESCRIPTION)
     return getFieldWhereKey(mysteryKey, INDEX_DESCRIPTION)
end
end


---getIcon
---getIcon
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return string the icon of the mystery
---@return string the icon of the mystery
function MysteriesData.getIcon(mysteryID)
function MysteriesData.getIcon(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_ICON)
     return getFieldWhereKey(mysteryKey, INDEX_ICON)
end
end


---getCostDifficulty
---getCostDifficulty
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return number the difficulty cost of the mystery
---@return number the difficulty cost of the mystery
function MysteriesData.getCostDifficulty(mysteryID)
function MysteriesData.getCostDifficulty(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_DIFFICULTY_COST)
     return getFieldWhereKey(mysteryKey, INDEX_DIFFICULTY_COST)
end
end


---getCostLabel
---getCostLabel
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return string the label of the cost of the mystery
---@return string the label of the cost of the mystery
function MysteriesData.getCostLabel(mysteryID)
function MysteriesData.getCostLabel(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_COST_LABEL)
     return getFieldWhereKey(mysteryKey, INDEX_COST_LABEL)
end
end


---getHostilityLevel
---getHostilityLevel
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return number the hostility level when the mystery kicks in
---@return number the hostility level when the mystery kicks in
function MysteriesData.getHostilityLevel(mysteryID)
function MysteriesData.getHostilityLevel(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_HOSTILITY_LEVEL)
     return getFieldWhereKey(mysteryKey, INDEX_HOSTILITY_LEVEL)
end
end


---getSeason
---getSeason
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return number the season when the mystery is in effect
---@return number the season when the mystery is in effect
function MysteriesData.getSeason(mysteryID)
function MysteriesData.getSeason(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_SEASON)
     return getFieldWhereKey(mysteryKey, INDEX_SEASON)
end
end


---isStacksWithHostility
---isStacksWithHostility
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return boolean true if the mystery gets worse with higher hostility
---@return boolean true if the mystery gets worse with higher hostility
function MysteriesData.isStacksWithHostility(mysteryID)
function MysteriesData.isStacksWithHostility(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_STACKS_WITH_HOSTILITY)
     local field = getFieldWhereKey(mysteryKey, INDEX_STACKS_WITH_HOSTILITY)
    if field ~= nil then
        return true
    else
        return false
    end
end
end


---isStacksWithYear
---isStacksWithYear
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return boolean true if the mystery gets worse with each year
---@return boolean true if the mystery gets worse with each year
function MysteriesData.isStacksWithYear(mysteryID)
function MysteriesData.isStacksWithYear(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_STACKS_WITH_YEAR)
     local field = getFieldWhereKey(mysteryKey, INDEX_STACKS_WITH_YEAR)
    if field ~= nil then
        return true
    else
        return false
    end
end
end


---isRemovedAfterYear
---isRemovedAfterYear
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return boolean true if the mystery is removed after one year
---@return boolean true if the mystery is removed after one year
function MysteriesData.isRemovedAfterYear(mysteryID)
function MysteriesData.isRemovedAfterYear(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_REMOVE_AFTER_YEAR)
     local field = getFieldWhereKey(mysteryKey, INDEX_REMOVE_AFTER_YEAR)
    if field ~= nil then
        return true
    else
        return false
    end
end
end


---getEffectID
---getEffectID
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return string the identifier of the effect applied to the settlement
---@return string the identifier of the effect applied to the settlement
function MysteriesData.getEffectID(mysteryID)
function MysteriesData.getEffectID(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_EFFECT_ID)
     return getFieldWhereKey(mysteryKey, INDEX_EFFECT_ID)
end
end


---isInCustomMode
---isInCustomMode
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return boolean true if the mystery is available in custom mode
---@return boolean true if the mystery is available in custom mode
function MysteriesData.isInCustomMode(mysteryID)
function MysteriesData.isInCustomMode(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_IN_CUSTOM_MODE)
     local field = getFieldWhereKey(mysteryKey, INDEX_IN_CUSTOM_MODE)
    if field ~= nil then
        return true
    else
        return false
    end
end
end


---getVillagerPerkID
---getVillagerPerkID
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return string the identifier of the effect applied to each villager
---@return string the identifier of the effect applied to each villager
function MysteriesData.getVillagerPerkID(mysteryID)
function MysteriesData.getVillagerPerkID(mysteryKey)
     return getFieldWhereID(mysteryID, KEY_VILLAGER_PERK_ID)
     return getFieldWhereKey(mysteryKey, INDEX_VILLAGER_PERK_ID)
end
 
---getNumberOfConditions
---@param mysteryKey string the unique key of a Mystery
---@return number how many conditions for the specified mystery
function MysteriesData.getNumberOfConditions(mysteryKey)
    local conditions = getFieldWhereKey(mysteryKey, INDEX_CONDITIONS)
    if not conditions then
        return 0
    else
        return #conditions
    end
end
end


---getCategoryIDForCondition
---getCategoryIDForCondition
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@param conditionIndex number the index of the desired condition
---@return string the identifier of the category of the condition
---@return string the identifier of the category of the condition
function MysteriesData.getCategoryIDForCondition(mysteryID, conditionIndex)
function MysteriesData.getCategoryIDForCondition(mysteryKey, conditionIndex)
     return getFieldFromConditionWhereID(mysteryID, conditionIndex, KEY_CONDITIONS_CATEGORY_ID)
     return getFieldFromConditionWhereKey(mysteryKey, conditionIndex, INDEX_CONDITIONS_CATEGORY_ID)
end
end


---getCategoryNameForCondition
---getCategoryNameForCondition
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@param conditionIndex number the index of the desired condition
---@return string the name of the category of the condition
---@return string the name of the category of the condition
function MysteriesData.getCategoryNameForCondition(mysteryID, conditionIndex)
function MysteriesData.getCategoryNameForCondition(mysteryKey, conditionIndex)
     return getFieldFromConditionWhereID(mysteryID, conditionIndex, KEY_CONDITIONS_CATEGORY_ID)
     return getFieldFromConditionWhereKey(mysteryKey, conditionIndex, INDEX_CONDITIONS_CATEGORY_ID)
end
end


---getAmountForCondition
---getAmountForCondition
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@param conditionIndex number the index of the desired condition
---@return number the number required to meet the condition
---@return number the number required to meet the condition
function MysteriesData.getAmountForCondition(mysteryID, conditionIndex)
function MysteriesData.getAmountForCondition(mysteryKey, conditionIndex)
     return getFieldFromConditionWhereID(mysteryID, conditionIndex, KEY_CONDITIONS_AMOUNT)
     return getFieldFromConditionWhereKey(mysteryKey, conditionIndex, INDEX_CONDITIONS_AMOUNT)
end
 
 
 
function MysteriesData.getAllMysteryNames()
 
    loadData()
    return listOfNames
end
end


--endregion
--endregion


return AltarEffectsData
return MysteriesData

Latest revision as of 03:03, 7 July 2024

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

---
---
---@module MysteriesData
local MysteriesData = {}



--region Dependencies

local JsonUtils = require("Module:JsonUtils")

--endregion



--region Private constants

local JSON_DATA_PAGE_1 = "Template:Simple_Seasonal_Effects_json"
local JSON_DATA_PAGE_2 = "Template:Conditional_Seasonal_Effects_json"

local ID_PREFIX_TO_SKIP = "_TO DELETE"

-- Fields in both
local INDEX_ID = "id"
local INDEX_DISPLAY_NAME = "displayName"
local INDEX_DESCRIPTION = "description"
local INDEX_ICON = "icon"
local INDEX_DIFFICULTY_COST = "difficultyCost"
local INDEX_COST_LABEL = "costLabel"
local INDEX_HOSTILITY_LEVEL = "hostilityLevel"
local INDEX_SEASON = "season"
-- Fields in Simple Seasonal Effects
local INDEX_STACKS_WITH_HOSTILITY = "stacksWithHostility"
local INDEX_STACKS_WITH_YEAR = "stacksWithYear"
local INDEX_REMOVE_AFTER_YEAR = "removeAfterYear"
local INDEX_EFFECT_ID = "effectId"
local INDEX_IN_CUSTOM_MODE = "isInCustomMode"
-- Fields in Conditional Seasonal Effects
local INDEX_VILLAGER_PERK_ID = "villagerPerkId"
local INDEX_CONDITIONS = "conditions"
-- Fields in subtable
local INDEX_CONDITIONS_CATEGORY_ID = "categoryId"
local INDEX_CONDITIONS_CATEGORY_NAME = "categoryDisplayName"
local INDEX_CONDITIONS_AMOUNT = "amount"

local IS_VALID_KEY = {
    [INDEX_ID] = true,
    [INDEX_DISPLAY_NAME] = true,
    [INDEX_DESCRIPTION] = true,
    [INDEX_ICON] = true,
    [INDEX_DIFFICULTY_COST] = true,
    [INDEX_COST_LABEL] = true,
    [INDEX_HOSTILITY_LEVEL] = true,
    [INDEX_SEASON] = true,
    [INDEX_STACKS_WITH_HOSTILITY] = true,
    [INDEX_STACKS_WITH_YEAR] = true,
    [INDEX_REMOVE_AFTER_YEAR] = true,
    [INDEX_EFFECT_ID] = true,
    [INDEX_IN_CUSTOM_MODE] = true,
    [INDEX_VILLAGER_PERK_ID] = true,
    [INDEX_CONDITIONS] = true,
}

local IS_VALID_CONDITIONS_KEY = {
    [INDEX_CONDITIONS_CATEGORY_ID] = true,
    [INDEX_CONDITIONS_CATEGORY_NAME] = true,
    [INDEX_CONDITIONS_AMOUNT] = true,
}

local ERROR_MESSAGE_KEY_MISSING = "MysteriesData requires a unique key (the display name) for a Forest Mystery to get its data. Double-check what was passed as a parameter, because it was either missing, nil, or blank."
local ERROR_MESSAGE_UNEXPECTED_DUPLICATE_KEY_ON_MERGE = "MysteriesData attempted to merge the two JSON data tables, however it found two records with the same key."
local ERROR_MESSAGE_OBJECT_MISSING_KEY = "MysteriesData discovered a record in the data that did not contain the required field named 'displayName' that is required to uniquely identify each record. Double-check that the data has not been modified from how it was originally supplied by the developers."

--endregion



--region Private member variables

local mysteriesData
local listOfNames

--endregion



--region Private methods

--- This method uses the member variables mysteriesData and listOfIDs
---
---@param dataTable table the data table to loop through
---@param fieldToPromoteToKey string the name of the field to use as the key
local function saveRecordsByKey(dataTable, fieldToPromoteToKey)

    -- Store the mysteries by their ID, but also create a simple array of all IDs since we're already looping over them all.
    local i = #listOfNames + 1
    for _, record in ipairs(dataTable) do
        local key = record[fieldToPromoteToKey]
        -- Validate the id
        if not key or key == "" then
            error(ERROR_MESSAGE_OBJECT_MISSING_KEY)
        else
            -- Skip the records that the devs have marked for deletion
            if string.sub(key, 1, #ID_PREFIX_TO_SKIP) ~= ID_PREFIX_TO_SKIP then
                -- Validate that there are no duplicate IDs in the final table.
                if mysteriesData[key] ~= nil then
                    error(ERROR_MESSAGE_UNEXPECTED_DUPLICATE_KEY_ON_MERGE .. "\n id=" .. key)
                else
                    -- Promote the id field to be the key.
                    mysteriesData[key] = record
                    listOfNames[i] = key
                    i = i + 1
                end
            end
        end
    end
end

--- Checks whether the Mysteries data has been loaded yet. If not, handles that and the post-processing necessary.
---
local function loadData()

    -- This function gets called any time the data could need to be loaded. Just return as quickly as possible if it's already loaded.
    if not mysteriesData then

        mysteriesData = {}
        listOfNames = {}

        saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_1), INDEX_DISPLAY_NAME)
        saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_2), INDEX_DISPLAY_NAME)
    end
end

--- Retrieves exactly one mystery table from the data.
--- Throws an error if the parameter is missing, nil, or empty.
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
---
---@param mysteryKey string the unique key of a Mystery
---@return table the whole table row for that Mystery
local function getRecordWhereKey(mysteryKey)

    if not mysteryKey or "" == mysteryKey then
        error(ERROR_MESSAGE_KEY_MISSING)
    else
        loadData()
        return mysteriesData[mysteryKey]
    end
end

--- Retrieves the specified field from the specified mystery.
--- Standardizes the calls from simple getters.
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
---
---@param mysteryKey string the unique key of a Mystery
---@param fieldName string the desired constant field name
---@return any the value of the field--type is not known by this method
local function getFieldWhereKey(mysteryKey, fieldName)

    if not IS_VALID_KEY[fieldName] then
        return nil
    end

    local record = getRecordWhereKey(mysteryKey)
    if not record then
        return nil
    else
        return record[fieldName] or nil
    end
end

--- Retrieves the specified condition from the specified mystery.
--- Standardizes the calls from simple getters.
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
---
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@return table the desired condition
local function getConditionWhereID(mysteryKey, conditionIndex)

    local conditions = getFieldWhereKey(mysteryKey, INDEX_CONDITIONS)
    if not conditions then
        return nil
    else
        return conditions[conditionIndex] or nil
    end
end

--- Retrieves the specified condition from the specified mystery.
--- Standardizes the calls from simple getters.
--- Retrieving data from outside this class should be done via the public getter methods, not by getting the entire record, so this is local.
---
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@param fieldName string the desired constant field name
---@return table the desired condition
local function getFieldFromConditionWhereKey(mysteryKey, conditionIndex, fieldName)

    if not IS_VALID_CONDITIONS_KEY[fieldName] then
        return nil
    end

    local condition = getConditionWhereID(mysteryKey, conditionIndex)
    if not condition then
        return nil
    else
        return condition[fieldName] or nil
    end
end

--endregion



--region Public methods

---isIDValid
---@param mysteryKey string a unique identifier
---@return boolean true if there is a record with the specified name
function MysteriesData.isNameValid(mysteryKey)

    local record = getRecordWhereKey(mysteryKey)
    if not record then
        return false
    else
        return true
    end
end

---getID
---@param mysteryKey string the unique key of a Mystery
---@return string the coded unique identifier of the mystery
function MysteriesData.getID(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_ID)
end

---getName
---@param mysteryKey string the unique key of a Mystery
---@return string the name of the mystery
function MysteriesData.getName(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_DISPLAY_NAME)
end

---getDescription
---@param mysteryKey string the unique key of a Mystery
---@return string the description of the mystery
function MysteriesData.getDescription(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_DESCRIPTION)
end

---getIcon
---@param mysteryKey string the unique key of a Mystery
---@return string the icon of the mystery
function MysteriesData.getIcon(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_ICON)
end

---getCostDifficulty
---@param mysteryKey string the unique key of a Mystery
---@return number the difficulty cost of the mystery
function MysteriesData.getCostDifficulty(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_DIFFICULTY_COST)
end

---getCostLabel
---@param mysteryKey string the unique key of a Mystery
---@return string the label of the cost of the mystery
function MysteriesData.getCostLabel(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_COST_LABEL)
end

---getHostilityLevel
---@param mysteryKey string the unique key of a Mystery
---@return number the hostility level when the mystery kicks in
function MysteriesData.getHostilityLevel(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_HOSTILITY_LEVEL)
end

---getSeason
---@param mysteryKey string the unique key of a Mystery
---@return number the season when the mystery is in effect
function MysteriesData.getSeason(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_SEASON)
end

---isStacksWithHostility
---@param mysteryKey string the unique key of a Mystery
---@return boolean true if the mystery gets worse with higher hostility
function MysteriesData.isStacksWithHostility(mysteryKey)
    local field = getFieldWhereKey(mysteryKey, INDEX_STACKS_WITH_HOSTILITY)
    if field ~= nil then
        return true
    else
        return false
    end
end

---isStacksWithYear
---@param mysteryKey string the unique key of a Mystery
---@return boolean true if the mystery gets worse with each year
function MysteriesData.isStacksWithYear(mysteryKey)
    local field = getFieldWhereKey(mysteryKey, INDEX_STACKS_WITH_YEAR)
    if field ~= nil then
        return true
    else
        return false
    end
end

---isRemovedAfterYear
---@param mysteryKey string the unique key of a Mystery
---@return boolean true if the mystery is removed after one year
function MysteriesData.isRemovedAfterYear(mysteryKey)
    local field = getFieldWhereKey(mysteryKey, INDEX_REMOVE_AFTER_YEAR)
    if field ~= nil then
        return true
    else
        return false
    end
end

---getEffectID
---@param mysteryKey string the unique key of a Mystery
---@return string the identifier of the effect applied to the settlement
function MysteriesData.getEffectID(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_EFFECT_ID)
end

---isInCustomMode
---@param mysteryKey string the unique key of a Mystery
---@return boolean true if the mystery is available in custom mode
function MysteriesData.isInCustomMode(mysteryKey)
    local field = getFieldWhereKey(mysteryKey, INDEX_IN_CUSTOM_MODE)
    if field ~= nil then
        return true
    else
        return false
    end
end

---getVillagerPerkID
---@param mysteryKey string the unique key of a Mystery
---@return string the identifier of the effect applied to each villager
function MysteriesData.getVillagerPerkID(mysteryKey)
    return getFieldWhereKey(mysteryKey, INDEX_VILLAGER_PERK_ID)
end

---getNumberOfConditions
---@param mysteryKey string the unique key of a Mystery
---@return number how many conditions for the specified mystery
function MysteriesData.getNumberOfConditions(mysteryKey)
    local conditions = getFieldWhereKey(mysteryKey, INDEX_CONDITIONS)
    if not conditions then
        return 0
    else
        return #conditions
    end
end

---getCategoryIDForCondition
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@return string the identifier of the category of the condition
function MysteriesData.getCategoryIDForCondition(mysteryKey, conditionIndex)
    return getFieldFromConditionWhereKey(mysteryKey, conditionIndex, INDEX_CONDITIONS_CATEGORY_ID)
end

---getCategoryNameForCondition
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@return string the name of the category of the condition
function MysteriesData.getCategoryNameForCondition(mysteryKey, conditionIndex)
    return getFieldFromConditionWhereKey(mysteryKey, conditionIndex, INDEX_CONDITIONS_CATEGORY_ID)
end

---getAmountForCondition
---@param mysteryKey string the unique key of a Mystery
---@param conditionIndex number the index of the desired condition
---@return number the number required to meet the condition
function MysteriesData.getAmountForCondition(mysteryKey, conditionIndex)
    return getFieldFromConditionWhereKey(mysteryKey, conditionIndex, INDEX_CONDITIONS_AMOUNT)
end



function MysteriesData.getAllMysteryNames()

    loadData()
    return listOfNames
end

--endregion

return MysteriesData