Module:MysteriesData: Difference between revisions

From Against the Storm Official Wiki
m (Updated with a method to get all ids)
(Changing unique identifier to be the display name, since it is unique)
 
(4 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_ID = "id"
 
local KEY_DISPLAY_NAME = "displayName"
-- Fields in both
local KEY_DESCRIPTION = "description"
local INDEX_ID = "id"
local KEY_ICON = "icon"
local INDEX_DISPLAY_NAME = "displayName"
local KEY_DIFFICULTY_COST = "difficultyCost"
local INDEX_DESCRIPTION = "description"
local KEY_COST_LABEL = "costLabel"
local INDEX_ICON = "icon"
local KEY_HOSTILITY_LEVEL = "hostilityLevel"
local INDEX_DIFFICULTY_COST = "difficultyCost"
local KEY_SEASON = "season"
local INDEX_COST_LABEL = "costLabel"
-- Keys in Simple Seasonal Effects
local INDEX_HOSTILITY_LEVEL = "hostilityLevel"
local KEY_STACKS_WITH_HOSTILITY = "stacksWithHostility"
local INDEX_SEASON = "season"
local KEY_STACKS_WITH_YEAR = "stacksWithYear"
-- Fields in Simple Seasonal Effects
local KEY_REMOVE_AFTER_YEAR = "removeAfterYear"
local INDEX_STACKS_WITH_HOSTILITY = "stacksWithHostility"
local KEY_EFFECT_ID = "effectId"
local INDEX_STACKS_WITH_YEAR = "stacksWithYear"
local KEY_IN_CUSTOM_MODE = "isInCustomMode"
local INDEX_REMOVE_AFTER_YEAR = "removeAfterYear"
-- Keys in Conditional Seasonal Effects
local INDEX_EFFECT_ID = "effectId"
local KEY_VILLAGER_PERK_ID = "villagerPerkId"
local INDEX_IN_CUSTOM_MODE = "isInCustomMode"
local KEY_CONDITIONS = "conditions"
-- Fields in Conditional Seasonal Effects
-- Keys in subtable
local INDEX_VILLAGER_PERK_ID = "villagerPerkId"
local KEY_CONDITIONS_CATEGORY_ID = "categoryId"
local INDEX_CONDITIONS = "conditions"
local KEY_CONDITIONS_CATEGORY_NAME = "categoryDisplayName"
-- Fields in subtable
local KEY_CONDITIONS_AMOUNT = "amount"
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_ID] = true,
     [INDEX_ID] = true,
     [KEY_DISPLAY_NAME] = true,
     [INDEX_DISPLAY_NAME] = true,
     [KEY_DESCRIPTION] = true,
     [INDEX_DESCRIPTION] = true,
     [KEY_ICON] = true,
     [INDEX_ICON] = true,
     [KEY_DIFFICULTY_COST] = true,
     [INDEX_DIFFICULTY_COST] = true,
     [KEY_COST_LABEL] = true,
     [INDEX_COST_LABEL] = true,
     [KEY_HOSTILITY_LEVEL] = true,
     [INDEX_HOSTILITY_LEVEL] = true,
     [KEY_SEASON] = true,
     [INDEX_SEASON] = true,
     [KEY_STACKS_WITH_HOSTILITY] = true,
     [INDEX_STACKS_WITH_HOSTILITY] = true,
     [KEY_STACKS_WITH_YEAR] = true,
     [INDEX_STACKS_WITH_YEAR] = true,
     [KEY_REMOVE_AFTER_YEAR] = true,
     [INDEX_REMOVE_AFTER_YEAR] = true,
     [KEY_EFFECT_ID] = true,
     [INDEX_EFFECT_ID] = true,
     [KEY_IN_CUSTOM_MODE] = true,
     [INDEX_IN_CUSTOM_MODE] = true,
     [KEY_VILLAGER_PERK_ID] = true,
     [INDEX_VILLAGER_PERK_ID] = true,
     [KEY_CONDITIONS] = 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_ID = "MysteriesData discovered a record in the data that did not contain the required field named 'id' required to index the data. Double-check that the data has not been modified from how it was originally supplied by the developers."
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 77: Line 79:


local mysteriesData
local mysteriesData
local listOfIDs
local listOfNames


--endregion
--endregion
Line 84: 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 93: Line 125:


         mysteriesData = {}
         mysteriesData = {}
         listOfIDs = {}
         listOfNames = {}


         local data1 = JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_1)
         saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_1), INDEX_DISPLAY_NAME)
         local data2 = JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_2)
         saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_2), INDEX_DISPLAY_NAME)
 
        -- 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 = 1
        for _, record1 in ipairs(data1) do
            local mysteryID = record1[KEY_ID]
            if not mysteryID or "" == mysteryID then
                error(ERROR_MESSAGE_OBJECT_MISSING_KEY_ID)
            end
            -- Promote the id field to be the key.
            mysteriesData[mysteryID] = record1
            listOfIDs[i] = mysteryID
            i = i + 1
        end
 
        for _, record2 in ipairs(data2) do
            local mysteryID = record2[KEY_ID]
            if not mysteryID or "" == mysteryID then
                error(ERROR_MESSAGE_OBJECT_MISSING_KEY_ID)
            end
            -- Validate that there are no duplicate IDs in the final table.
            if nil ~= mysteriesData[mysteryID] then
                error(ERROR_MESSAGE_UNEXPECTED_DUPLICATE_ID_ON_MERGE .. "\n id=" .. mysteryID)
            else
                -- Promote the id field to be the key.
                mysteriesData[mysteryID] = record2
                listOfIDs[i] = mysteryID
                i = i + 1
            end
        end
     end
     end
end
end
Line 133: 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 149: 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 158: 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 170: 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 187: 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 197: 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 212: 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 222: 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
end






function MysteriesData.getAllMysteryIDs()
function MysteriesData.getAllMysteryNames()


     loadData()
     loadData()
     return listOfIDs
     return listOfNames
end
end



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