Module:WorkshopsData: Difference between revisions

From Against the Storm Official Wiki
(Adding getID to building interface; removing outdated comment at top)
(Refactoring, removing unused methods that did not conform to building interface)
Line 28: Line 28:
--- session, like this: table[displayName] = workshopID
--- session, like this: table[displayName] = workshopID
local mapNamesToIDs
local mapNamesToIDs
--- Lookup map. Built once and reused on all subsequent calls within this
--- Lookup map. Built once and reused on all subsequent calls within this
--- session, like this: table[recipeID] = { workshopName1, workshopName2, ... }
--- session, like this: table[recipeID] = { workshopName1, workshopName2, ... }
Line 254: Line 255:


return newWorkshopTable
return newWorkshopTable
end
---
--- Data loader function that uses the utility module and restructures the data
--- to be easier to access for invoking methods and later method calls. This
--- method is automatically called by all public member functions if the main
--- data table has not yet been populated in the current session.
local function loadData()
-- Utility module retrieves the data as basic, flat lua tables.
local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.extractTables(DATA_TEMPLATE_NAME)
-- Now restructure to be more conducive.
workshopsTable = restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup)
end
end


Line 277: Line 262:


if not workshopsTable then
if not workshopsTable then
loadData()
end
end
---
--- Uses the display name, which people are more familiar with, to find the
--- encoded ID of the workshop. Useful for retrieving the workshop data that is
--- indexed by ID.
---
--- Returns nil if the workshop with the specified name is not found.
---
--- @param displayName string plain-language name of the workshop to find
--- @return string ID of the workshop found, or nil if not found
local function findWorkshopIDByName(displayName)


-- At runtime, this should never be nil or empty, so throw an error.
-- Utility module retrieves the data as basic, flat lua tables.
if not displayName or displayName == "" then
local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.extractTables(DATA_TEMPLATE_NAME)
error("Parameter is nil or empty for the workshop's name: " .. displayName .. ".")
end


if not workshopsTable then
-- Now restructure to be more conducive.
loadData()
workshopsTable = restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup)
end
end
return mapNamesToIDs[displayName]
end
end


Line 312: Line 277:
--region Public methods
--region Public methods


---
--- DEPRECATED --- DO NOT USE ---
--- Retrieve the whole table of data for the specified workshop. Instead of
function WorkshopsData.getAllWorkshopRecipes(displayName)
--- this, you should probably be calling the individual getter methods.
---
--- Throws an error if called with nil or empty string. Returns nil if the
--- specified workshop cannot be found.
---
--- @param workshopID string ID of the workshop
--- @return table containing the data for the specified workshop with key-value pairs, or nil if not found
function WorkshopsData.getAllDataForWorkshopByID(workshopID)
 
-- At runtime, this should never be nil or empty.
if not workshopID or workshopID == "" then
error("Parameter is nil or empty for the workshop's ID.")
end


if not workshopsTable then
loadData()
end
return workshopsTable[workshopID]
end
---
--- Retrieve the whole table of data for the specified workshop. Instead of
--- this, you should probably be calling the individual getter methods.
---
--- Throws an error if called with nil or empty string. Returns nil if the
--- specified workshop cannot be found.
---
--- @param displayName string plain language name of the workshop
--- @return table containing the data for the specified workshop with key-value pairs, or nil if not found
function WorkshopsData.getAllDataForWorkshop(displayName)
-- At runtime, this should never be nil or empty.
if not displayName or displayName == "" then
if not displayName or displayName == "" then
error("Parameter is nil or empty for the workshop's name.")
error("Parameter is nil or empty for display name.")
end
 
if not workshopsTable then
loadData()
end
 
local workshopID = findWorkshopIDByName(displayName)
if not workshopID then
return nil
end
 
return workshopsTable[workshopID]
end
 
 
 
---
--- Retrieves the ID for the workshop specified by its plain language
--- display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return string the ID of the specified workshop
function WorkshopsData.getWorkshopID(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_ID]
end
 
 
 
---
--- Retrieves the description for the workshop specified by its plain language
--- display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return string the in-game description of the specified workshop
function WorkshopsData.getWorkshopDescription(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_DESCRIPTION]
end
 
 
 
---
--- Retrieves the construction toolbar category for the workshop specified by
--- its plain language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return string the construction toolbar category of the workshop
function WorkshopsData.getWorkshopCategory(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_CATEGORY]
end
 
 
 
---
--- Retrieves the 2x2 size for the workshop specified by its plain language
--- display name, in two return values.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return number the X-size of the workshop
---@return number the Y-size of the workshop
function WorkshopsData.getWorkshopSize(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_SIZE_X], workshop[INDEX_SIZE_Y]
end
 
 
 
---
--- Retrieves the goods required for construction for the workshop specified by
--- its plain language display name, in a table that looks like this:
---
--- ["requiredGoods"] = {
--- [1] = { ["stackSize"] = 99, ["goodID"] = "good1_ID" },
--- [2] = { ["stackSize"] = 99, ["goodID"] = "good2_ID" },
--- [3] = { ... } or missing if fewer
--- }
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return table of required goods
function WorkshopsData.getAllWorkshopRequiredGoods(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_REQUIRED_GOODS]
end
 
 
 
---
--- Retrieves the specified required construction good for the workshop
--- specified by its plain language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@param requirementIndex number which construction good to retrieve
---@return string the ID of the good that is required, or nil if none
---@return number the stack size of that good, or nil if none
function WorkshopsData.getWorkshopRequiredGood(displayName, requirementIndex)
 
local requiredGoods = WorkshopsData.getAllWorkshopRequiredGoods(displayName)
 
if not requiredGoods then
return nil
end
end


local requirement = requiredGoods[requirementIndex]
load()
if not requirement then
return nil
end
 
return requirement[INDEX_CONSTRUCTION_GOODS_GOOD_ID], requirement[INDEX_CONSTRUCTION_GOODS_STACK_SIZE]
end
 
 
 
---
--- Retrieves the construction time for the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return number of seconds it takes to construct the workshop
function WorkshopsData.getWorkshopConstructionTime(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)


if not workshop then
local id = mapNamesToIDs[displayName]
if not id then
return nil
return nil
end
end


return workshop[INDEX_CONSTRUCTION_TIME]
local workshop = workshopsTable[id]
end
 
 
 
---
--- Retrieves the city score awarded for the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return number of points for city score from having the workshop
function WorkshopsData.getWorkshopCityScore(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_CITY_SCORE]
end
 
 
 
---
--- Retrieves whether the workshop specified by its plain language display
--- name can be moved, at any cost.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return boolean of whether workshop can be moved
function WorkshopsData.isWorkshopMovable(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_MOVABLE]
end
 
 
 
---
--- Retrieves whether the workshop specified by its plain language display
--- name has an essential starting blueprint, and for a new game profile.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return boolean of whether the workshop's blueprint is essential.
function WorkshopsData.isWorkshopInitiallyEssential(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_ESSENTIAL]
end
 
 
 
---
--- Retrieves the storage capacity of the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return number representing the storage capacity of the workshop
function WorkshopsData.getWorkshopStorage(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_STORAGE_CAP]
end
 
 
 
---
--- Retrieves the workplaces at the workshop specified by its plain language
--- display name, in a table that looks like this:
---
--- ["workplaces"] = {
--- [1] = "Any",
--- [2] = "Any",
--- [3] = "Any",
--- [4] = ... or missing if fewer
--- }
---
--- The number of workplaces can easily be found with #workplaces.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return table of workplaces
function WorkshopsData.getAllWorkshopWorkplaces(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return workshop[INDEX_WORKPLACES]
end
 
 
 
---
--- Retrieves the number of workplaces at the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return number of workplaces
function WorkshopsData.getWorkshopNumberOfWorkplaces(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
return nil
end
 
return #workshop[INDEX_WORKPLACES]
end
 
 
 
---
--- Retrieves the recipes possible at the workshop specified by its plain
--- language display name, in a table that looks like this:
---
--- ["recipes"] = {
--- [1] = "recipe1_ID",
--- [2] = "recipe2_ID",
--- [3] = "recipe3_ID",
--- [4] = ... or missing if fewer
--- }
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return table of recipe IDs
function WorkshopsData.getAllWorkshopRecipes(displayName)
 
local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
 
if not workshop then
if not workshop then
return nil
return nil
Line 691: Line 301:




---
--- DEPRECATED --- DO NOT USE ---
--- Retrieves the specified recipe ID for the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@param recipeIndex number the index of the recipe ID
---@return string the recipe ID specified
function WorkshopsData.getWorkshopRecipe(displayName, recipeIndex)
 
local recipes = WorkshopsData.getAllWorkshopRecipes(displayName)
 
if not recipes then
return nil
end
 
return recipes[recipeIndex]
end
 
 
 
---
--- Retrieves the icon filename for the workshop specified by its plain
--- language display name.
---
--- Returns nil if the workshop named was not found.
---
---@param displayName string the plain language name of the workshop
---@return string the workshop's icon filename, including the extension
function WorkshopsData.getWorkshopIcon(displayName)
function WorkshopsData.getWorkshopIcon(displayName)


local workshop = WorkshopsData.getAllDataForWorkshop(displayName)
if not displayName or displayName == "" then
 
error("Parameter is nil or empty for display name.")
if not workshop then
return nil
end
 
-- the base string of the icon is the ID. It has to be not nil to
-- concatenate
if workshop[INDEX_ID] then
return workshop[INDEX_ID] .. "_icon.png"
else
return nil
end
end
end


load()


 
local id = mapNamesToIDs[displayName]
---
if not id then
--- Retrieves the plain language display name for the workshop specified by its
--- ID.
---
--- Returns nil if the workshop named was not found.
---
---@param workshopID string the ID of the workshop
---@return string the workshop's name as seen in-game
function WorkshopsData.getWorkshopNameByID(workshopID)
 
-- Rather than getting it directly from workshopsTable here, get it via
-- this other method that has additional error checking.
local workshop = WorkshopsData.getAllDataForWorkshopByID(workshopID)
 
if not workshop then
return nil
end
 
return workshop[INDEX_NAME]
end
 
 
 
---
--- Retrieves the icon filename for the workshop specified by its ID.
---
--- Returns nil if the workshop named was not found.
---
---@param workshopID string the ID of the workshop
---@return string the workshop's icon filename, including the extension
function WorkshopsData.getWorkshopIconByID(workshopID)
 
-- Rather than getting it directly from workshopsTable here, get it via
-- this other method that has additional error checking.
local workshop = WorkshopsData.getAllDataForWorkshopByID(workshopID)
 
if not workshop then
return nil
return nil
end
end
Line 782: Line 317:
-- the base string of the icon is the ID. It has to be not nil to
-- the base string of the icon is the ID. It has to be not nil to
-- concatenate
-- concatenate
if workshop[INDEX_ID] then
return id .. "_icon.png"
return workshop[INDEX_ID] .. "_icon.png"
else
return nil
end
end
end






---
--- DEPRECATED --- DO NOT USE ---
--- Retrieve all display names of workshops that have the specified recipe ID.
---
---@param recipeID string the ID of the recipe
---@return table of workshop names that produce the specified recipe
function WorkshopsData.getWorkshopNamesWithRecipeID(recipeID)
function WorkshopsData.getWorkshopNamesWithRecipeID(recipeID)


Line 803: Line 330:
end
end


if not workshopsTable then
load()
loadData()
 
end
return mapRecipeIDsToWorkshopNames[recipeID]
return mapRecipeIDsToWorkshopNames[recipeID]
end
---
--- Retrieves the name of the workshop at the specified index.
---
---@param index number index
---@return string name of the building at the specified index
function WorkshopsData.getWorkshopNameFromDatabase(index)
if not index then
return nil
end
if not workshopsTable then
loadData()
end
return workshopsNames[index]
end
---
--- Returns the number of buildings that can be retrieved from this
--- module's database.
---
---@return number of records
function WorkshopsData.getNumberOfWorkshopsInDatabase()
if not workshopsTable then
loadData()
end
return #workshopsNames
end
function WorkshopsData.getAllIDs()
if not workshopsTable then
loadData()
end
local idsTable = {}
local i = 1
for id in pairs(workshopsTable) do
idsTable[i] = id
i = i + 1
end
return idsTable
end
end


Line 868: Line 340:


--region Public Building interface
--region Public Building interface
-- getID(displayName)
-- getID(displayName)
-- getCategory
-- getCategory(id)
-- getCityScore
-- getCityScore(id)
-- getConstructionCosts (as [goodName] = stack size)
-- getConstructionCosts(id) -- returns { [goodName] = stack size }
-- getConstructionTime
-- getConstructionTime(id)
-- getDescription
-- getDescription(id)
-- getIcon
-- getIcon(id)
-- isMovable
-- isMovable(id)
-- getName
-- getName(id)
-- getNumberOfWorkplaces
-- getNumberOfWorkplaces(id)
-- getSize (as "X x Y")
-- getSize(id) -- returns string "X x Y"
-- getStorage
-- getStorage(id)


function WorkshopsData.getID(displayName)
function WorkshopsData.getID(displayName)

Revision as of 01:17, 20 October 2024

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

--- @module WorkshopsData
local WorkshopsData = {}



--region Dependencies

local DATA_TEMPLATE_NAME = "Template:Workshops_csv"

local CsvUtils = require("Module:CsvUtils")

-- Some dependencies are loaded lazily
local GoodsData

--endregion



--region Private member variables

--- Main data table, like this: table[ID] = table containing data for that ID
local workshopsTable

--- Supporting table, list of names table[i] = name.
local workshopsNames

--- Lookup map. Built once and reused on all subsequent calls within this
--- session, like this: table[displayName] = workshopID
local mapNamesToIDs

--- Lookup map. Built once and reused on all subsequent calls within this
--- session, like this: table[recipeID] = { workshopName1, workshopName2, ... }
local mapRecipeIDsToWorkshopNames

--endregion



--region Private constants

local INDEX_ID = "id"
local INDEX_NAME = "displayName"
local INDEX_DESCRIPTION = "description"
local INDEX_CATEGORY = "category"
local INDEX_SIZE_X = "sizeX"
local INDEX_SIZE_Y = "sizeY"
local INDEX_CITY_SCORE = "cityScore"
local INDEX_MOVABLE = "movable"
local INDEX_ESSENTIAL = "initiallyEssential"
local INDEX_STORAGE_CAP = "storage"
local INDEX_CONSTRUCTION_TIME = "constructionTime"
local INDEX_REQUIRED_GOODS = "requiredGoods"
local INDEX_WORKPLACES = "workplaces"
local INDEX_RECIPES = "recipes"

local INDEX_CONSTRUCTION_GOODS_STACK_SIZE = "stackSize"
local INDEX_CONSTRUCTION_GOODS_GOOD_ID = "goodID"

local PATTERN_SPLIT_STACK_AND_ID = "(%d+)%s([%[%]%s%a]+)"

--endregion



--region Private methods

---
--- Creates a new subtable containing the construction goods required for the
--- specified workshop.
---
--- @param originalWorkshop table workshop data record from which to make subtable
--- @param workshopsHeaderLookup table header lookup built from the CSV data
--- @return table subtable with the required construction goods
local function makeRequiredGoodsSubtable(originalWorkshop, workshopsHeaderLookup)

	-- A constant we'll need only within this function.
	local REQ_GOOD_HEADER_BASE_STRING = "requiredGood"

	-- Copy the originals directly into a subtable.
	local requiredIndex1 = workshopsHeaderLookup[REQ_GOOD_HEADER_BASE_STRING .. "1"]
	local requiredIndex2 = workshopsHeaderLookup[REQ_GOOD_HEADER_BASE_STRING .. "2"]
	local requiredIndex3 = workshopsHeaderLookup[REQ_GOOD_HEADER_BASE_STRING .. "3"]

	local number1, id1 = originalWorkshop[requiredIndex1]:match(PATTERN_SPLIT_STACK_AND_ID)
	local number2, id2 = originalWorkshop[requiredIndex2]:match(PATTERN_SPLIT_STACK_AND_ID)
	local number3, id3 = originalWorkshop[requiredIndex3]:match(PATTERN_SPLIT_STACK_AND_ID)

	-- don't add subtables that would just contain nils
	local requiredGoods = {
		number1 and id1 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = tonumber(number1), [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id1 } or nil,
		number2 and id2 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = tonumber(number2), [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id2 } or nil,
		number3 and id3 and { [INDEX_CONSTRUCTION_GOODS_STACK_SIZE] = tonumber(number3), [INDEX_CONSTRUCTION_GOODS_GOOD_ID] = id3 } or nil
	}

	return requiredGoods
end



---
--- Creates a new subtable containing the workplaces available in the specified
--- workshop.
---
--- @param originalWorkshop table workshop data record from which to make subtable
--- @param workshopsHeaderLookup table header lookup built from the CSV data
--- @return table subtable with the workplaces
local function makeWorkplacesSubtable(originalWorkshop, workshopsHeaderLookup)

	-- A constant we'll need only within this function.
	local WORKPLACE_HEADER_BASE_STRING = "workplace"

	-- Copy the originals directly into a subtable.
	local workplaceIndex1 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "1"]
	local workplaceIndex2 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "2"]
	local workplaceIndex3 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "3"]
	local workplaceIndex4 = workshopsHeaderLookup[WORKPLACE_HEADER_BASE_STRING .. "4"]

	local workplace1 = originalWorkshop[workplaceIndex1]
	local workplace2 = originalWorkshop[workplaceIndex2]
	local workplace3 = originalWorkshop[workplaceIndex3]
	local workplace4 = originalWorkshop[workplaceIndex4]

	-- if it's not an empty string, then save that to the table, otherwise nil
	local workplaces = {
		(workplace1 ~= "" and workplace1) or nil,
		(workplace2 ~= "" and workplace2) or nil,
		(workplace3 ~= "" and workplace3) or nil,
		(workplace4 ~= "" and workplace4) or nil
	}

	return workplaces
end



---
--- Creates a new subtable containing the recipes available in the specified
--- workshop.
---
--- @param workshopName string the display name of the workshop
--- @param originalWorkshop table workshop data record from which to make subtable
--- @param workshopsHeaderLookup table header lookup built from the CSV data
--- @return table subtable with the recipe IDs
local function makeRecipesSubtable(workshopName, originalWorkshop, workshopsHeaderLookup)

	-- A constant we'll need only within this function.
	local LOOKUP_RECIPE_BASE_STRING = "recipe"

	-- Copy the originals directly into a subtable.
	local recipeIndex1 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "1"]
	local recipeIndex2 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "2"]
	local recipeIndex3 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "3"]
	local recipeIndex4 = workshopsHeaderLookup[LOOKUP_RECIPE_BASE_STRING .. "4"]

	local recipe1 = originalWorkshop[recipeIndex1]
	local recipe2 = originalWorkshop[recipeIndex2]
	local recipe3 = originalWorkshop[recipeIndex3]
	local recipe4 = originalWorkshop[recipeIndex4]

	local recipes = {
		(recipe1 ~= "" and recipe1) or nil,
		(recipe2 ~= "" and recipe2) or nil,
		(recipe3 ~= "" and recipe3) or nil,
		(recipe4 ~= "" and recipe4) or nil
	}

	if recipe1 and recipe1 ~= "" then
		if not mapRecipeIDsToWorkshopNames[recipe1] then
			mapRecipeIDsToWorkshopNames[recipe1] = {}
		end
		table.insert(mapRecipeIDsToWorkshopNames[recipe1], workshopName)
	end
	if recipe2 and recipe2 ~= "" then
		if not mapRecipeIDsToWorkshopNames[recipe2] then
			mapRecipeIDsToWorkshopNames[recipe2] = {}
		end
		table.insert(mapRecipeIDsToWorkshopNames[recipe2], workshopName)
	end
	if recipe3 and recipe3 ~= "" then
		if not mapRecipeIDsToWorkshopNames[recipe3] then
			mapRecipeIDsToWorkshopNames[recipe3] = {}
		end
		table.insert(mapRecipeIDsToWorkshopNames[recipe3], workshopName)
	end
	if recipe4 and recipe4 ~= "" then
		if not mapRecipeIDsToWorkshopNames[recipe4] then
			mapRecipeIDsToWorkshopNames[recipe4] = {}
		end
		table.insert(mapRecipeIDsToWorkshopNames[recipe4], workshopName)
	end

	return recipes
end



---
--- Transforms the originalWorkshopTable returned from CSV processing to be
--- more conducive to member functions looking up data. Converts text strings
--- into subtables. Converts header row into field keys on every record and
--- stores records by id rather than arbitrary integer.
---
--- @param originalWorkshopsTable table of CSV-based data, with header row, data rows
--- @param workshopsHeaderLookup table lookup table of headers to get indexes
--- @return table better structured with IDs as keys
local function restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup)

	-- A few constants we'll need only within this function.
	local DATA_ROWS = 2
	local INDEX_ORIGINAL_ID = 1
	local INDEX_ORIGINAL_NAME = 2
	local INDEX_ORIGINAL_DESCRIPTION = 3
	local INDEX_ORIGINAL_CATEGORY = 4
	local INDEX_ORIGINAL_SIZE_X = 5
	local INDEX_ORIGINAL_SIZE_Y = 6
	-- Required goods are indexes 7, 8, 9
	local INDEX_ORIGINAL_CONSTRUCTION_TIME = 10
	local INDEX_ORIGINAL_CITY_SCORE = 11
	local INDEX_ORIGINAL_MOVABLE = 12
	local INDEX_ORIGINAL_ESSENTIAL = 13
	local INDEX_ORIGINAL_STORAGE_CAP = 14

	mapNamesToIDs = {}
	workshopsNames = {}
	mapRecipeIDsToWorkshopNames = {}

	local newWorkshopTable = {}
	for _, originalWorkshop in ipairs (originalWorkshopsTable[DATA_ROWS]) do

		-- Copy over the content, mapping unhelpful indexes into headers keys.
		local newWorkshop = {}
		newWorkshop[INDEX_ID] = originalWorkshop[INDEX_ORIGINAL_ID]
		newWorkshop[INDEX_NAME] = originalWorkshop[INDEX_ORIGINAL_NAME]
		newWorkshop[INDEX_DESCRIPTION] = originalWorkshop[INDEX_ORIGINAL_DESCRIPTION]
		newWorkshop[INDEX_CATEGORY] = originalWorkshop[INDEX_ORIGINAL_CATEGORY]
		newWorkshop[INDEX_SIZE_X] = tonumber(originalWorkshop[INDEX_ORIGINAL_SIZE_X])
		newWorkshop[INDEX_SIZE_Y] = tonumber(originalWorkshop[INDEX_ORIGINAL_SIZE_Y])
		newWorkshop[INDEX_CITY_SCORE] = tonumber(originalWorkshop[INDEX_ORIGINAL_CITY_SCORE])
		newWorkshop[INDEX_MOVABLE] = originalWorkshop[INDEX_ORIGINAL_MOVABLE] == "TRUE"
		newWorkshop[INDEX_ESSENTIAL] = originalWorkshop[INDEX_ORIGINAL_ESSENTIAL] == "TRUE"
		newWorkshop[INDEX_STORAGE_CAP] = tonumber(originalWorkshop[INDEX_ORIGINAL_STORAGE_CAP])
		newWorkshop[INDEX_CONSTRUCTION_TIME] = tonumber(originalWorkshop[INDEX_ORIGINAL_CONSTRUCTION_TIME])

		newWorkshop[INDEX_REQUIRED_GOODS] = makeRequiredGoodsSubtable(originalWorkshop, workshopsHeaderLookup)
		newWorkshop[INDEX_WORKPLACES] = makeWorkplacesSubtable(originalWorkshop, workshopsHeaderLookup)
		newWorkshop[INDEX_RECIPES] = makeRecipesSubtable(newWorkshop[INDEX_NAME], originalWorkshop, workshopsHeaderLookup)

		newWorkshopTable[ newWorkshop[INDEX_ID] ] = newWorkshop

		table.insert(workshopsNames, newWorkshop[INDEX_NAME])

		-- Also populate the map for looking up IDs with display names
		mapNamesToIDs[newWorkshop[INDEX_NAME]] = newWorkshop[INDEX_ID]
	end

	return newWorkshopTable
end



local function load()

	if not workshopsTable then

		-- Utility module retrieves the data as basic, flat lua tables.
		local originalWorkshopsTable, workshopsHeaderLookup = CsvUtils.extractTables(DATA_TEMPLATE_NAME)

		-- Now restructure to be more conducive.
		workshopsTable = restructureWorkshopsTable(originalWorkshopsTable, workshopsHeaderLookup)
	end
end

--endregion



--region Public methods

--- DEPRECATED --- DO NOT USE ---
function WorkshopsData.getAllWorkshopRecipes(displayName)

	if not displayName or displayName == "" then
		error("Parameter is nil or empty for display name.")
	end

	load()

	local id = mapNamesToIDs[displayName]
	if not id then
		return nil
	end

	local workshop = workshopsTable[id]
	if not workshop then
		return nil
	end

	return workshop[INDEX_RECIPES]
end



--- DEPRECATED --- DO NOT USE ---
function WorkshopsData.getWorkshopIcon(displayName)

	if not displayName or displayName == "" then
		error("Parameter is nil or empty for display name.")
	end

	load()

	local id = mapNamesToIDs[displayName]
	if not id then
		return nil
	end

	-- the base string of the icon is the ID. It has to be not nil to
	-- concatenate
	return id .. "_icon.png"
end



--- DEPRECATED --- DO NOT USE ---
function WorkshopsData.getWorkshopNamesWithRecipeID(recipeID)

	-- At runtime, this should never be nil or empty.
	if not recipeID or recipeID == "" then
		error("Parameter is nil or empty for product ID.")
	end

	load()

	return mapRecipeIDsToWorkshopNames[recipeID]
end

--endregion



--region Public Building interface

-- getID(displayName)
-- getCategory(id)
-- getCityScore(id)
-- getConstructionCosts(id) -- returns { [goodName] = stack size }
-- getConstructionTime(id)
-- getDescription(id)
-- getIcon(id)
-- isMovable(id)
-- getName(id)
-- getNumberOfWorkplaces(id)
-- getSize(id) -- returns string "X x Y"
-- getStorage(id)

function WorkshopsData.getID(displayName)
	load()
	return mapNamesToIDs[displayName]
end

function WorkshopsData.getCategory(id)
	load()
	return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_CATEGORY]
end

function WorkshopsData.getCityScore(id)
	load()
	return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_CITY_SCORE]
end

function WorkshopsData.getConstructionCosts(id)

	load()
	GoodsData = require("Module:GoodsData")

	workshop = workshopsTable[id]
	if not workshop then
		return nil
	end

	local constructionCosts = {}
	for _, stacks in ipairs(workshop[INDEX_REQUIRED_GOODS]) do
		local goodName = GoodsData.getGoodNameByID(stacks[INDEX_CONSTRUCTION_GOODS_GOOD_ID])
		constructionCosts[goodName] = stacks[INDEX_CONSTRUCTION_GOODS_STACK_SIZE]
	end

	return constructionCosts
end

function WorkshopsData.getConstructionTime(id)
	load()
	return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_CONSTRUCTION_TIME]
end

function WorkshopsData.getDescription(id)
	load()
	return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_DESCRIPTION]
end

function WorkshopsData.getIcon(id)
	load()
	return workshopsTable[id] ~= nil and id .. "_icon.png"
end

function WorkshopsData.isMovable(id)
	load()
	return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_MOVABLE]
end

function WorkshopsData.getName(id)
	load()
	return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_NAME]
end

function WorkshopsData.getNumberOfWorkplaces(id)
	load()
	return workshopsTable[id] ~= nil and #workshopsTable[id][INDEX_WORKPLACES]
end

function WorkshopsData.getSize(id)
	load()

	workshop = workshopsTable[id]
	if not workshop then
		return nil
	end

	return workshop[INDEX_SIZE_X] .. " × " .. workshop[INDEX_SIZE_Y]
end

function WorkshopsData.getStorage(id)
	load()
	return workshopsTable[id] ~= nil and workshopsTable[id][INDEX_STORAGE_CAP]
end

--endregion

return WorkshopsData