Module:MysteriesData: Difference between revisions

From Against the Storm Official Wiki
(Refactored the data loading, now also skips mysteries marked for deletion)
(Changing unique identifier to be the display name, since it is unique)
 
(One intermediate revision by the same user not shown)
Line 21: Line 21:
local ID_PREFIX_TO_SKIP = "_TO DELETE"
local ID_PREFIX_TO_SKIP = "_TO DELETE"


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


local mysteriesData
local mysteriesData
local listOfIDs
local listOfNames


--endregion
--endregion
Line 88: Line 88:


--- This method uses the member variables mysteriesData and listOfIDs
--- This method uses the member variables mysteriesData and listOfIDs
---  
---
---@param dataTable table the data table to loop through
---@param dataTable table the data table to loop through
local function saveRecordsByKey(dataTable)
---@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.
     -- 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 = #listOfIDs + 1
     local i = #listOfNames + 1
     for _, record in ipairs(dataTable) do
     for _, record in ipairs(dataTable) do
         local mysteryID = record[KEY_ID]
         local key = record[fieldToPromoteToKey]
         -- Validate the id
         -- Validate the id
         if not mysteryID or mysteryID == "" then
         if not key or key == "" then
             error(ERROR_MESSAGE_OBJECT_MISSING_KEY_ID)
             error(ERROR_MESSAGE_OBJECT_MISSING_KEY)
         else
         else
             -- Skip the records that the devs have marked for deletion
             -- Skip the records that the devs have marked for deletion
             if string.sub(mysteryID, 1, #ID_PREFIX_TO_SKIP) ~= ID_PREFIX_TO_SKIP then
             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.
                 -- Validate that there are no duplicate IDs in the final table.
                 if mysteriesData[mysteryID] ~= nil then
                 if mysteriesData[key] ~= nil then
                     error(ERROR_MESSAGE_UNEXPECTED_DUPLICATE_ID_ON_MERGE .. "\n id=" .. mysteryID)
                     error(ERROR_MESSAGE_UNEXPECTED_DUPLICATE_KEY_ON_MERGE .. "\n id=" .. key)
                 else
                 else
                     -- Promote the id field to be the key.
                     -- Promote the id field to be the key.
                     mysteriesData[mysteryID] = record
                     mysteriesData[key] = record
                     listOfIDs[i] = mysteryID
                     listOfNames[i] = key
                     i = i + 1
                     i = i + 1
                 end
                 end
Line 124: Line 125:


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


         saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_1))
         saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_1), INDEX_DISPLAY_NAME)
         saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_2))
         saveRecordsByKey(JsonUtils.convertJSONToLuaTable(JSON_DATA_PAGE_2), INDEX_DISPLAY_NAME)
     end
     end
end
end
Line 135: 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 151: 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 160: 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 172: 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 189: 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 199: 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 214: 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 224: 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) or false
     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) or false
     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) or false
     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) or false
     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
end


---getNumberOfConditions
---getNumberOfConditions
---@param mysteryID string the unique identifier of a mystery
---@param mysteryKey string the unique key of a Mystery
---@return number how many conditions for the specified mystery
---@return number how many conditions for the specified mystery
function MysteriesData.getNumberOfConditions(mysteryID)
function MysteriesData.getNumberOfConditions(mysteryKey)
     local conditions = getFieldWhereID(mysteryID, KEY_CONDITIONS)
     local conditions = getFieldWhereKey(mysteryKey, INDEX_CONDITIONS)
     if not conditions then
     if not conditions then
         return 0
         return 0
Line 330: Line 358:


---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