Permanently protected module

Difference between revisions of "Module:DM Fusion display"

From Yugipedia
Jump to: navigation, search
(Fixed wrong table being iterated on. Removing unnecessary checks. Fixed some exceptions being carried over to the next combination)
(Fixed (yet another) sorting issue regarding pairs (yet again fixed by using ipairs). Fixed combinations for a same Fusion not being grouped if all of them had no description)
 
(9 intermediate revisions by the same user not shown)
Line 19: Line 19:
 
NAMESPACE,
 
NAMESPACE,
 
_frame,
 
_frame,
_args
+
_args,
 +
_fusionDisplay,
 +
_pageName,
 +
_combinationsQuery,
 +
_exceptionsQuery
 
;
 
;
  
Line 63: Line 67:
 
end
 
end
  
-- @name format_card_to_display
+
-- @name toTable
 +
-- @description Receives an object, and converts it to a table containing itself, unless it is already a table
 +
local function toTable(obj)
 +
return type(obj) == "table" and obj or { obj }
 +
end
 +
 
 +
-- @name formatCardDisplay
 
-- @description Receives a card page name and returns the expected string to be displayed
 
-- @description Receives a card page name and returns the expected string to be displayed
local function format_card_to_display(card)
+
local function formatCardDisplay(card)
 
attr = CARD_INFO[card]
 
attr = CARD_INFO[card]
 
return string.format("#%s: %s\"[[%s|%s]]\"", attr["number"], attr["level"] and ( attr["level"] .. " [[File:CG Star.svg|15px|alt=|class=noviewer]] " ) or '', card, attr["name"])
 
return string.format("#%s: %s\"[[%s|%s]]\"", attr["number"], attr["level"] and ( attr["level"] .. " [[File:CG Star.svg|15px|alt=|class=noviewer]] " ) or '', card, attr["name"])
 
end
 
end
  
-- @name fill_fusion_result_table
+
-- @name fillTableForFusionResult
-- @description Appends the row with the materials belonging to the current combination to a table
+
-- @description Appends the row with the materials of the current combination to a table belonging to a Fusion Result section
local function fill_fusion_result_table( cur_table, mats1, mats2, columns, mats_exceptions )
+
local function fillTableForFusionResult( currentTable, mats1, mats2, columns, mats_exceptions )
local mats_row = cur_table:tag("tr")
+
local matsRow = currentTable:tag("tr")
local mat1_cell = mats_row:tag("td"):tag("ul")
+
local mat1Cell = matsRow:tag("td"):tag("ul")
local mat2_cell = mats_row:tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")
+
local mat2Cell = matsRow:tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")
 
 
for _, card in pairs(mats1) do
+
-- Prints the first cell
 +
for _, card in ipairs(mats1) do
 
card = unlink(card)
 
card = unlink(card)
mat1_cell:tag("li"):wikitext(format_card_to_display(card))
+
mat1Cell:tag("li"):wikitext(formatCardDisplay(card))
 
end
 
end
 
 
 
-- The second cell also includes the "except with" addition
 
-- The second cell also includes the "except with" addition
for _, card in pairs(mats2) do
+
for _, card in ipairs(mats2) do
 
card = unlink(card)
 
card = unlink(card)
mat2_cell:tag("li"):wikitext(format_card_to_display(card) .. (mats_exceptions[card] and string.format(" (except with \"[[%s|%s]]\")", mats_exceptions[card], CARD_INFO[mats_exceptions[card]]["name"]) or ''))
+
mat2Cell:tag("li"):wikitext(formatCardDisplay(card) .. (mats_exceptions[card] and string.format(" (except with \"[[%s|%s]]\")", mats_exceptions[card], CARD_INFO[mats_exceptions[card]]["name"]) or ''))
 
end
 
end
 
 
return cur_table
+
return currentTable
 
end
 
end
  
-- @name fill_fusion_material_table
+
-- @name fillTableForFusionMaterial
-- @description Appends the row with the materials belonging to the current combination to a table
+
-- @description Appends the row with the materials of the current combination to a table belonging to a Fusion Material section
local function fill_fusion_material_table( cur_table, current_card, mats, columns, other_mat_exceptions )
+
local function fillTableForFusionMaterial( currentTable, mats, columns, excepts )
local mats_row = cur_table:tag("tr"):tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")
+
local matsRow = currentTable:tag("tr"):tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")
  
 
-- Print other materials, but don't include those present in the exceptions table
 
-- Print other materials, but don't include those present in the exceptions table
for _, card in pairs(mats) do
+
for _, card in ipairs(mats) do
if not other_mat_exceptions[card] then
+
if not excepts[card] then
 
card = unlink(card)
 
card = unlink(card)
mats_row:tag("li"):wikitext(format_card_to_display(card))
+
matsRow:tag("li"):wikitext(formatCardDisplay(card))
 
end
 
end
 
end
 
end
 
 
return cur_table
+
return currentTable
 
end
 
end
  
-- @name process_combination_exception_query
+
-- @name runCombinationExceptionsQuery
 
-- @description Gets the combinations exceptions for a query of combinatigons
 
-- @description Gets the combinations exceptions for a query of combinatigons
local function process_combination_exception_query(combinations_query)
+
local function runCombinationExceptionsQuery()
 
local fusion_ids = {}
 
local fusion_ids = {}
for _, combination in pairs(combinations_query) do
+
for _, combination in ipairs(_combinationsQuery) do
 
table.insert(fusion_ids, unlink(combination[1]))
 
table.insert(fusion_ids, unlink(combination[1]))
 
end
 
end
Line 120: Line 131:
 
end
 
end
  
-- @name process_fusion_combination_query
+
-- @name processCombinationsQuery
 
-- @description Gets the required attributes for a Fusion query by running the provided conditions.  
 
-- @description Gets the required attributes for a Fusion query by running the provided conditions.  
 
-- By running the query only once, the module saves a considerable amount of time that would be spent querying each card's name every time it is required
 
-- By running the query only once, the module saves a considerable amount of time that would be spent querying each card's name every time it is required
local function process_fusion_combination_query(conditions)
+
local function processCombinationsQuery(conditions)
 
-- The attributes that should be returned
 
-- The attributes that should be returned
 
local required_attributes = {  
 
local required_attributes = {  
"Fusion Material 1", "Fusion Material 2", "Fusion result", "Description", "Columns", -- Attributes about the Fusion combination
+
"Fusion Material 1", "Fusion Material 2", "Fusion result", "Description", "Columns",                                                                                                 -- Attributes about the Fusion combination
"Fusion Material 1.English name = English name 1", "Fusion Material 1.Level = Level 1", "Fusion Material 1.Card number = Card number 1", -- Attributes about each card listed as Material 1
+
"Fusion Material 1.English name = English name 1", "Fusion Material 1.Level = Level 1", "Fusion Material 1.Card number = Card number 1", "Fusion Material 1.Card type = Card type 1", -- Attributes about each card listed as Material 1
"Fusion Material 2.English name = English name 2", "Fusion Material 2.Level = Level 2", "Fusion Material 2.Card number = Card number 2", -- Attributes about each card listed as Material 2
+
"Fusion Material 2.English name = English name 2", "Fusion Material 2.Level = Level 2", "Fusion Material 2.Card number = Card number 2", "Fusion Material 2.Card type = Card type 2", -- Attributes about each card listed as Material 2
"Fusion result.English name = English name 3",    "Fusion result.Level = Level 3",    "Fusion result.Card number = Card number 3",    -- Attributes about each card listed as Result
+
"Fusion result.English name = English name 3",    "Fusion result.Level = Level 3",    "Fusion result.Card number = Card number 3",    "Fusion result.Card type = Card type 3",    -- Attributes about each card listed as Result
"Fusion result = Fusion Material 3"
+
"Fusion result = Fusion Material 3"                                               -- Small hack to help iterate below
 
}
 
}
 
 
local query = mw.smw.ask( conditions .. "|?" .. table.concat(required_attributes, "|?"))
+
local query = mw.smw.ask( conditions .. "|?" .. table.concat(required_attributes, "|?")) or {}
 +
local nonMonsterOffset
 
 
if query then
+
-- Iterates over both Fusion Material lists, and adds each card to the table
-- Iterates over both Fusion Material lists, and adds each card to the table
+
for _, combination in ipairs(query) do
for _, combination in pairs(query) do
+
for combi_index = 1,3 do
for _, combi_index in pairs({"1", "2", "3"}) do
+
-- MW doesn't return multiple-valued properties as tables, so we convert them here
-- Pre-processing on the multi-value attributes
+
materials = toTable(combination["Fusion Material " .. combi_index])
materials = combination["Fusion Material " .. combi_index]
+
en_names = toTable(combination["English name " .. combi_index])
materials = type(materials) == "table" and materials or { materials }
+
levels   = toTable(combination["Level " .. combi_index])
+
numbers  = toTable(combination["Card number " .. combi_index])
en_names = combination["English name " .. combi_index]
+
types    = toTable(combination["Card type " .. combi_index])
en_names = type(en_names) == "table" and en_names or { en_names }
+
+
-- Since non-Monsters don't have Levels, the properties get returned with some lines missing for Monster-specific properties (e.g. Level)
levels = combination["Level " .. combi_index]
+
-- This variable is used to offset that
levels = type(levels) == "table" and levels or { levels }
+
nonMonsterOffset = 0
+
numbers = combination["Card number " .. combi_index]
+
    for i, fm in ipairs(materials) do
numbers = type(numbers) == "table" and numbers or { numbers }
+
    fm = unlink(fm)
+
    if not CARD_INFO[fm] then
    for i, fm_full in pairs(materials) do
+
    CARD_INFO[fm] = {
    fm = unlink(fm_full)
+
    ["name"]   = en_names[i],
    if not CARD_INFO[fm] then
+
    ["level"] = levels[i-nonMonsterOffset],
    CARD_INFO[fm] = {
+
    ["number"] = numbers[i]
    ["name"] = en_names[i],
+
    }
    ["level"] = levels[i],
+
end
    ["number"] = numbers[i]
+
if unlink(types[i]) ~= 'Monster Card' then
    }
+
    nonMonsterOffset = nonMonsterOffset + 1
    end
+
    CARD_INFO[fm]["level"] = nil
    end
+
    end
 
     end
 
     end
    end
+
end
    end
+
end
  
 
return query
 
return query
 
end
 
end
  
-- @name fusion_result
+
-- @name setListsForFusionMaterialDisplay
-- @description Generates the material lists for a given Fusion result. To be called through #invoke.
+
-- @description Creates the material and exception lists for a Fusion Material display
local function fusion_result( frame )
+
local function setListsForFusionMaterialDisplay(mats1, mats2)
_frame = frame;
+
local materialIndex, mats
_args  = UTIL.getArgs( frame, {
+
local excepts = {}
trim        = true,
+
removeBlanks = true,
+
-- Checks to which of the two lists should be displayed (if a card belongs to Materials 2, display Materials 1)
parentOnly  = true
+
for _, mat in ipairs(mats1) do
} );
+
if unlink(mat) == _pageName then
 +
materialIndex = 2
 +
break
 +
end
 +
end
 +
 +
-- Also checks the second list to see if the material is present there
 +
if materialIndex then
 +
for _, mat in ipairs(mats2) do
 +
if unlink(mat) == _pageName then
 +
materialIndex = materialIndex + 1
 +
break
 +
end
 +
end
 +
else
 +
materialIndex = 1
 +
end
 +
 +
-- Assigns correct material and exception list, based on the index found
 +
-- Index 3 means that it was found in both lists
 +
if materialIndex == 1 or materialIndex == 3 then
 +
mats = mats1
 +
for _, combi_ex in ipairs(_exceptionsQuery) do
 +
if unlink(combi_ex["Fusion Material exception 1"]) == _pageName then
 +
excepts[combi_ex["Fusion Material exception 2"]] = true
 +
end
 +
end
 +
end
  
if not mw.smw then
+
if materialIndex == 2 or materialIndex == 3 then
        return "mw.smw module not found"
+
if not mats then mats = mats2
    end
+
else
   
+
-- Joins both tables
    local fusion_frame = HTML()
+
for _, m2 in ipairs(mats2) do
   
+
if unlink(m2) ~= _pageName then -- Don't include the same article twice
    local page_name = _args[1] or "Amazon of the Seas (DOR)"
+
table.insert(mats, m2)
   
 
    local query = process_fusion_combination_query( string.format("[[Fusion result::%s]]", page_name ))
 
   
 
    if query then
 
    local combination_exceptions_query = process_combination_exception_query(query)
 
   
 
    local prev_description, cur_table
 
   
 
    for i, combination in pairs(query) do
 
    local combi_id, mats1, mats2, description, columns = combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Description"], combination["Columns"] or 1
 
    local combination_exceptions = {}
 
   
 
    -- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
 
    mats1 = type(mats1) ~= 'table' and { mats1 } or mats1
 
    mats2 = type(mats2) ~= 'table' and { mats2 } or mats2
 
 
for _, combi_ex in pairs(combination_exceptions_query) do
 
if combi_ex["For Fusion"] == combi_id then
 
combination_exceptions[unlink(combi_ex["Fusion Material exception 1"])] = unlink(combi_ex["Fusion Material exception 2"])
 
 
end
 
end
 
end
 
end
   
+
end
    -- Creates a new table on the first itteration, and when a description changes
+
if not cur_table or description ~= prev_description then
+
for _, combi_ex in ipairs(_exceptionsQuery) do
cur_table = fusion_frame:tag("table"):attr("class", "wikitable")
+
if unlink(combi_ex["Fusion Material exception 2"]) == _pageName then
+
excepts[combi_ex["Fusion Material exception 1"]] = true
cur_table:tag("ul"):tag("li"):wikitext(description or '')
 
 
local header = cur_table:tag("tr")
 
 
header:tag("th"):attr("scope", "col"):wikitext("Material 1")
 
header:tag("th"):attr("scope", "col"):wikitext("Material 2")
 
 
end
 
end
   
+
end
    -- Appends materials to the table
 
    cur_table = fill_fusion_result_table(cur_table, mats1, mats2, columns, combination_exceptions)
 
   
 
    prev_description = description or prev_description
 
    end
 
 
end
 
end
+
 
return tostring(fusion_frame)
+
return mats, excepts
 +
end
 +
 
 +
-- @name createFusionResultTable
 +
-- @description Creates the table to be displayed for Fusion Result monsters
 +
local function createFusionResultTable()
 +
local previousDescription, currentTable
 +
   
 +
    -- Creates tables/rows for each combination found
 +
    for _, combination in ipairs(_combinationsQuery) do
 +
    local combi_id, mats1, mats2, description, columns = combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Description"], combination["Columns"] or 1
 +
    local combination_exceptions = {}
 +
   
 +
    -- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
 +
    mats1 = toTable(mats1)
 +
    mats2 = toTable(mats2)
 +
 +
for _, combi_ex in ipairs(_exceptionsQuery) do
 +
if combi_ex["For Fusion"] == combi_id then
 +
combination_exceptions[unlink(combi_ex["Fusion Material exception 1"])] = unlink(combi_ex["Fusion Material exception 2"])
 +
end
 +
end
 +
   
 +
    -- Creates a new table on the first itteration, and when a description changes
 +
if not currentTable or description ~= previousDescription then
 +
currentTable = _fusionDisplay:tag("table"):attr("class", "wikitable")
 +
currentTable:tag("ul"):tag("li"):wikitext(description or '')
 +
 +
local header = currentTable:tag("tr")
 +
header:tag("th"):attr("scope", "col"):wikitext("Material 1")
 +
header:tag("th"):attr("scope", "col"):wikitext("Material 2")
 +
end
 +
   
 +
    -- Appends materials to the table
 +
    currentTable = fillTableForFusionResult(currentTable, mats1, mats2, columns, combination_exceptions)
 +
   
 +
    previousDescription = description
 +
    end
 +
end
 +
 
 +
-- @name createFusionMaterialTable
 +
-- @description Creates the table to be displayed for Fusion Material monsters
 +
local function createFusionMaterialTable()
 +
local previousDescription, previousResult, currentTable
 +
   
 +
    -- Creates tables/rows for each combination found
 +
    for _, combination in ipairs(_combinationsQuery) do
 +
    local combi_id, mats1, mats2, result, description, columns =
 +
    combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Fusion result"], combination["Description"], combination["Columns"] or 1
 +
    local cur_combi_exceptions = {}
 +
 
 +
    -- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
 +
    mats1 = toTable(mats1)
 +
    mats2 = toTable(mats2)
 +
   
 +
    -- Build the materials and exceptions lists
 +
    local mats, excepts = setListsForFusionMaterialDisplay(mats1, mats2)
 +
   
 +
    -- Dynamically calculate the number of columns
 +
    if #mats > 17 then
 +
    columns = 3
 +
    elseif #mats > 8 then
 +
    columns = 2
 +
    end
 +
   
 +
    -- Creates a new table on the first itteration, and when a description changes
 +
if not currentTable or description ~= previousDescription or result ~= previousResult then
 +
mw.log(result .. ": " .. (description or 'nil') .. " " .. (previousDescription or 'nil'))
 +
currentTable = _fusionDisplay:tag("table"):attr("class", "wikitable")
 +
currentTable:tag("ul"):tag("li"):wikitext(formatCardDisplay(unlink(result)) .. (description and (" (" .. description .. ')') or ''))
 +
currentTable:tag("tr"):tag("th"):attr("scope", "col"):wikitext("Materials")
 +
end
 +
   
 +
    -- Appends materials to the table
 +
    currentTable = fillTableForFusionMaterial(currentTable, mats, columns, excepts)
 +
   
 +
    previousDescription = description
 +
    previousResult = result or previousResult
 +
    end
 +
end
 +
 
 +
-- @name getCombinationsAndExceptionsForFusionResult
 +
-- @description Gets the materials combinations and exceptions that result in this card
 +
local function getCombinationsAndExceptionsForFusionResult()
 +
    _combinationsQuery = processCombinationsQuery( string.format("[[Fusion result::%s]]", _pageName ) ) or {}
 +
    _exceptionsQuery = runCombinationExceptionsQuery()
 +
end
 +
 
 +
-- @name getCombinationsAndExceptionsForFusionMaterial
 +
-- @description Gets the materials combinations and exceptions that this card is a material of
 +
local function getCombinationsAndExceptionsForFusionMaterial()
 +
    _combinationsQuery = processCombinationsQuery( string.format("<q>[[Fusion Material 1::%s]] OR [[Fusion Material 2::%s]]</q>|sort = Fusion result.Card number", _pageName, _pageName ) ) or {}
 +
    _exceptionsQuery = runCombinationExceptionsQuery()
 
end
 
end
  
-- @name fusion_material
+
-- @name initParams
-- @description Generates the list of other materials and Fusion result for a given material. To be called through #invoke.
+
-- @description Sets values for all parameters accessible from the current context
local function fusion_material( frame )
+
local function initParams( frame )
 
_frame = frame;
 
_frame = frame;
 
_args  = UTIL.getArgs( frame, {
 
_args  = UTIL.getArgs( frame, {
Line 239: Line 340:
 
parentOnly  = true
 
parentOnly  = true
 
} );
 
} );
 +
   
 +
    _fusionDisplay = HTML()
 +
    _pageName = _args[1] or "Mavelus (FMR)"
 +
end
  
if not mw.smw then
+
-- @name fusionResult
        return "mw.smw module not found"
+
-- @description Generates the material lists for a given Fusion result. To be called through #invoke.
     end
+
local function fusionResult( frame )
 +
initParams(frame)
 +
 +
     getCombinationsAndExceptionsForFusionResult()
 
      
 
      
     local fusion_frame = HTML()
+
     createFusionResultTable()
 +
 
 +
return tostring(_fusionDisplay)
 +
end
 +
 
 +
-- @name fusionMaterial
 +
-- @description Generates the list of other materials and Fusion result for a given material. To be called through #invoke.
 +
local function fusionMaterial( frame )
 +
initParams(frame)
 
      
 
      
     local page_name = _args[1] or "Thunder Nyan Nyan (DOR)"
+
     getCombinationsAndExceptionsForFusionMaterial()
 
      
 
      
     local query = process_fusion_combination_query( string.format("<q>[[Fusion Material 1::%s]] OR [[Fusion Material 2::%s]]</q>|sort = Fusion result.Card number", page_name, page_name ))
+
     createFusionMaterialTable()
   
 
    if query then
 
    local combination_exceptions_query = process_combination_exception_query(query)
 
   
 
    local prev_description, cur_table
 
   
 
    for i, combination in pairs(query) do
 
    local combi_id, mats1, mats2, result, description, columns =
 
    combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Fusion result"], combination["Description"], combination["Columns"] or 1
 
    local material_index, mats
 
    local cur_combi_exceptions = {}
 
   
 
    -- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
 
    mats1 = type(mats1) ~= 'table' and { mats1 } or mats1
 
    mats2 = type(mats2) ~= 'table' and { mats2 } or mats2
 
   
 
    -- Checks to which of the two lists should be displayed (if a card belongs to Materials 2, display Materials 1)
 
    for _, mat in pairs(mats1) do
 
    if unlink(mat) == page_name then
 
    material_index = 2
 
    break
 
    end
 
end
 
material_index = material_index or 1
 
 
-- Assigns correct material and exception list, based on the index found
 
if material_index == 1 then
 
mats = mats1
 
for _, combi_ex in pairs(combination_exceptions_query) do
 
if unlink(combi_ex["Fusion Material exception 1"]) == page_name then
 
cur_combi_exceptions[combi_ex["Fusion Material exception 2"]] = true
 
end
 
end
 
else
 
mats = mats2
 
for _, combi_ex in pairs(combination_exceptions_query) do
 
if unlink(combi_ex["Fusion Material exception 2"]) == page_name then
 
cur_combi_exceptions[combi_ex["Fusion Material exception 1"]] = true
 
end
 
end
 
end
 
   
 
    -- Creates a new table on the first itteration, and when a description changes
 
if not cur_table or description ~= prev_description then
 
cur_table = fusion_frame:tag("table"):attr("class", "wikitable")
 
 
cur_table:tag("ul"):tag("li"):wikitext(format_card_to_display(unlink(result)) .. (description and (" (" .. description .. ')') or ''))
 
 
local header = cur_table:tag("tr")
 
 
header:tag("th"):attr("scope", "col"):wikitext("Materials")
 
end
 
   
 
    -- Appends materials to the table
 
    cur_table = fill_fusion_material_table(cur_table, page_name, mats, columns, cur_combi_exceptions)
 
   
 
    prev_description = description or prev_description
 
    end
 
end
 
  
return tostring(fusion_frame)
+
return tostring(_fusionDisplay)
 
end
 
end
  
Line 317: Line 374:
 
-- @exports  
 
-- @exports  
 
return {
 
return {
['fusion_result'] = fusion_result,
+
['fusionResult'] = fusionResult,
['fusion_material'] = fusion_material
+
['fusionMaterial'] = fusionMaterial
 
};
 
};
 
-- </pre>
 
-- </pre>

Latest revision as of 00:05, 22 May 2024

-- <pre>
-- @name DM Fusion display
-- @description A module used to display Fusion combinations in their respective card articles
-- @author [[User:JustGian]]
-- @contact [[User talk:JustGian]]

----------------
-- Load modules:
----------------
local DATA = require( 'Module:Data' );
local UTIL = require( 'Module:Util' );

--------------------
-- Module variables:
--------------------
-- Parser state:
local
	PAGENAME,
	NAMESPACE,
	_frame,
	_args,
	_fusionDisplay,
	_pageName,
	_combinationsQuery,
	_exceptionsQuery
;

-- Stores properties for cards to avoid making a new query each time they are needed
local CARD_INFO = {}

---------------------
-- Utility functions:
---------------------
-- mw functions:
local HTML = mw.html.create;

--------------------
-- Module functions:
--------------------
--# Local functions

--[[Doc
@function U unlink
@description (From Module:Card type) If a string is passed, unlinks
and returns the page name (not the label, unless «getLabel»).
If a table is passed, unlinks every instance of it
and returns the table with the page names for each respective entry.
@parameter {*} v Any value to unlink.
@parameter {boolean} getLabel Makes the function return the label instead.
@return {*} The unlinked value.
]]
local function unlink( v, getLabel )
	local function __unlink( v, getLabel )
		return UTIL.trim( v ) and (getLabel and v:match( '%[%[:?.-|(.-)]]' ) or v:match( '%[%[:?(.-)[|%]]' )) or UTIL.trim( v );
	end
	if type( v ) == 'string' then
		return __unlink( v, getLabel );
	elseif type( v ) == 'table' then
		local t = {};
		for key, value in ipairs( v ) do
			table.insert( t, __unlink( value, getLabel ) );
		end
		return t;
	else
		return v; -- @@@ error()
	end
end

-- @name toTable
-- @description Receives an object, and converts it to a table containing itself, unless it is already a table
local function toTable(obj)
	return type(obj) == "table" and obj or { obj }
end

-- @name formatCardDisplay
-- @description Receives a card page name and returns the expected string to be displayed
local function formatCardDisplay(card)
	attr = CARD_INFO[card]
	return string.format("#%s: %s\"[[%s|%s]]\"", attr["number"], attr["level"] and ( attr["level"] .. " [[File:CG Star.svg|15px|alt=|class=noviewer]] " ) or '', card, attr["name"])
end

-- @name fillTableForFusionResult
-- @description Appends the row with the materials of the current combination to a table belonging to a Fusion Result section
local function fillTableForFusionResult( currentTable, mats1, mats2, columns, mats_exceptions )
	local matsRow = currentTable:tag("tr")
	local mat1Cell = matsRow:tag("td"):tag("ul")
	local mat2Cell = matsRow:tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")
	
	-- Prints the first cell
	for _, card in ipairs(mats1) do
		card = unlink(card)
		mat1Cell:tag("li"):wikitext(formatCardDisplay(card))
	end
	
	-- The second cell also includes the "except with" addition
	for _, card in ipairs(mats2) do
		card = unlink(card)
		mat2Cell:tag("li"):wikitext(formatCardDisplay(card) .. (mats_exceptions[card] and string.format(" (except with \"[[%s|%s]]\")", mats_exceptions[card], CARD_INFO[mats_exceptions[card]]["name"]) or ''))
	end
	
	return currentTable
end

-- @name fillTableForFusionMaterial
-- @description Appends the row with the materials of the current combination to a table belonging to a Fusion Material section
local function fillTableForFusionMaterial( currentTable, mats, columns, excepts )
	local matsRow = currentTable:tag("tr"):tag("td"):tag("div"):attr("style", "column-width: 13em; column-count: " .. columns):tag("ul")

	-- Print other materials, but don't include those present in the exceptions table
	for _, card in ipairs(mats) do
		if not excepts[card] then
			card = unlink(card)
			matsRow:tag("li"):wikitext(formatCardDisplay(card))
		end
	end
	
	return currentTable
end

-- @name runCombinationExceptionsQuery
-- @description Gets the combinations exceptions for a query of combinatigons
local function runCombinationExceptionsQuery()
	local fusion_ids = {}
	for _, combination in ipairs(_combinationsQuery) do
		table.insert(fusion_ids, unlink(combination[1]))
	end
	
	local query = mw.smw.ask( string.format("[[For Fusion::%s]] |?Fusion Material exception 1 |?Fusion Material exception 2 |?For Fusion", table.concat(fusion_ids, "||")) ) or {}
	
	return query
end

-- @name processCombinationsQuery
-- @description Gets the required attributes for a Fusion query by running the provided conditions. 
-- By running the query only once, the module saves a considerable amount of time that would be spent querying each card's name every time it is required
local function processCombinationsQuery(conditions)
	-- The attributes that should be returned
	local required_attributes = { 
		"Fusion Material 1", "Fusion Material 2", "Fusion result", "Description", "Columns",                                                                                                  -- Attributes about the Fusion combination
		"Fusion Material 1.English name = English name 1", "Fusion Material 1.Level = Level 1", "Fusion Material 1.Card number = Card number 1", "Fusion Material 1.Card type = Card type 1", -- Attributes about each card listed as Material 1
		"Fusion Material 2.English name = English name 2", "Fusion Material 2.Level = Level 2", "Fusion Material 2.Card number = Card number 2", "Fusion Material 2.Card type = Card type 2", -- Attributes about each card listed as Material 2
		"Fusion result.English name = English name 3",     "Fusion result.Level = Level 3",     "Fusion result.Card number = Card number 3",     "Fusion result.Card type = Card type 3",     -- Attributes about each card listed as Result
		"Fusion result = Fusion Material 3"																										                                              -- Small hack to help iterate below
		}
	
	local query = mw.smw.ask( conditions .. "|?" .. table.concat(required_attributes, "|?")) or {}
	local nonMonsterOffset
	
	-- Iterates over both Fusion Material lists, and adds each card to the table
	for _, combination in ipairs(query) do
		for combi_index = 1,3 do
			-- MW doesn't return multiple-valued properties as tables, so we convert them here
			materials = toTable(combination["Fusion Material " .. combi_index])
			en_names  = toTable(combination["English name " .. combi_index])
			levels    = toTable(combination["Level " .. combi_index])
			numbers   = toTable(combination["Card number " .. combi_index])
			types     = toTable(combination["Card type " .. combi_index])
			
			-- Since non-Monsters don't have Levels, the properties get returned with some lines missing for Monster-specific properties (e.g. Level)
			-- This variable is used to offset that
			nonMonsterOffset = 0
			
	    	for i, fm in ipairs(materials) do
	    		fm = unlink(fm)
	    		if not CARD_INFO[fm] then
	    			CARD_INFO[fm] = {
	    				["name"]   = en_names[i],
	    				["level"]  = levels[i-nonMonsterOffset],
	    				["number"] = numbers[i]
	    			}
				end
				if unlink(types[i]) ~= 'Monster Card' then
    				nonMonsterOffset = nonMonsterOffset + 1
    				CARD_INFO[fm]["level"] = nil
    			end
    		end
		end
	end

	return query
end

-- @name setListsForFusionMaterialDisplay
-- @description Creates the material and exception lists for a Fusion Material display
local function setListsForFusionMaterialDisplay(mats1, mats2)
	local materialIndex, mats
	local excepts = {}
	
	-- Checks to which of the two lists should be displayed (if a card belongs to Materials 2, display Materials 1)
	for _, mat in ipairs(mats1) do
		if unlink(mat) == _pageName then
			materialIndex = 2
			break
		end
	end
	
	-- Also checks the second list to see if the material is present there
	if materialIndex then
		for _, mat in ipairs(mats2) do
			if unlink(mat) == _pageName then
				materialIndex = materialIndex + 1
				break
			end
		end
	else
		materialIndex = 1
	end
	
	-- Assigns correct material and exception list, based on the index found
	-- Index 3 means that it was found in both lists
	if materialIndex == 1 or materialIndex == 3 then
		mats = mats1
		for _, combi_ex in ipairs(_exceptionsQuery) do
			if unlink(combi_ex["Fusion Material exception 1"]) == _pageName then
				excepts[combi_ex["Fusion Material exception 2"]] = true
			end
		end
	end

	if materialIndex == 2 or materialIndex == 3 then
		if not mats then mats = mats2
		else
			-- Joins both tables
			for _, m2 in ipairs(mats2) do
				if unlink(m2) ~= _pageName then -- Don't include the same article twice
					table.insert(mats, m2)
				end
			end
		end
		
		for _, combi_ex in ipairs(_exceptionsQuery) do
			if unlink(combi_ex["Fusion Material exception 2"]) == _pageName then
				excepts[combi_ex["Fusion Material exception 1"]] = true
			end
		end
	end

	return mats, excepts
end

-- @name createFusionResultTable
-- @description Creates the table to be displayed for Fusion Result monsters
local function createFusionResultTable()
	local previousDescription, currentTable
    
    -- Creates tables/rows for each combination found
    for _, combination in ipairs(_combinationsQuery) do
    	local combi_id, mats1, mats2, description, columns = combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Description"], combination["Columns"] or 1
    	local combination_exceptions = {}
    	
    	-- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
    	mats1 = toTable(mats1)
    	mats2 = toTable(mats2)
		
		for _, combi_ex in ipairs(_exceptionsQuery) do
			if combi_ex["For Fusion"] == combi_id then
				combination_exceptions[unlink(combi_ex["Fusion Material exception 1"])] = unlink(combi_ex["Fusion Material exception 2"])
			end
		end
    	
    	-- Creates a new table on the first itteration, and when a description changes
		if not currentTable or description ~= previousDescription then
			currentTable = _fusionDisplay:tag("table"):attr("class", "wikitable")
			currentTable:tag("ul"):tag("li"):wikitext(description or '')
			
			local header = currentTable:tag("tr")
			header:tag("th"):attr("scope", "col"):wikitext("Material 1")
			header:tag("th"):attr("scope", "col"):wikitext("Material 2")
		end
    	
    	-- Appends materials to the table
    	currentTable = fillTableForFusionResult(currentTable, mats1, mats2, columns, combination_exceptions)
    	
    	previousDescription = description
    end
end

-- @name createFusionMaterialTable
-- @description Creates the table to be displayed for Fusion Material monsters
local function createFusionMaterialTable()
	local previousDescription, previousResult, currentTable
    
    -- Creates tables/rows for each combination found
    for _, combination in ipairs(_combinationsQuery) do
    	local combi_id, mats1, mats2, result, description, columns = 
    		combination[1], combination["Fusion Material 1"], combination["Fusion Material 2"], combination["Fusion result"], combination["Description"], combination["Columns"] or 1
    	local cur_combi_exceptions = {}

    	-- MW attributes don't return a table if there is only 1 value. Converting both to tables to facilitate future processing
    	mats1 = toTable(mats1)
    	mats2 = toTable(mats2)
    	
    	-- Build the materials and exceptions lists
    	local mats, excepts = setListsForFusionMaterialDisplay(mats1, mats2)
    	
    	-- Dynamically calculate the number of columns
    	if #mats > 17 then
    		columns = 3
    	elseif #mats > 8 then
    		columns = 2
    	end
    	
    	-- Creates a new table on the first itteration, and when a description changes
		if not currentTable or description ~= previousDescription or result ~= previousResult then
			mw.log(result .. ": " .. (description or 'nil') .. " " .. (previousDescription or 'nil'))
			currentTable = _fusionDisplay:tag("table"):attr("class", "wikitable")
			currentTable:tag("ul"):tag("li"):wikitext(formatCardDisplay(unlink(result)) .. (description and (" (" .. description .. ')') or ''))
			currentTable:tag("tr"):tag("th"):attr("scope", "col"):wikitext("Materials")
		end
    	
    	-- Appends materials to the table
    	currentTable = fillTableForFusionMaterial(currentTable, mats, columns, excepts)
    	
    	previousDescription = description
    	previousResult = result or previousResult
    end	
end

-- @name getCombinationsAndExceptionsForFusionResult
-- @description Gets the materials combinations and exceptions that result in this card
local function getCombinationsAndExceptionsForFusionResult()
    _combinationsQuery = processCombinationsQuery( string.format("[[Fusion result::%s]]", _pageName ) ) or {}
    _exceptionsQuery = runCombinationExceptionsQuery()
end

-- @name getCombinationsAndExceptionsForFusionMaterial
-- @description Gets the materials combinations and exceptions that this card is a material of
local function getCombinationsAndExceptionsForFusionMaterial()
    _combinationsQuery = processCombinationsQuery( string.format("<q>[[Fusion Material 1::%s]] OR [[Fusion Material 2::%s]]</q>|sort = Fusion result.Card number", _pageName, _pageName ) ) or {}
    _exceptionsQuery = runCombinationExceptionsQuery()
end

-- @name initParams
-- @description Sets values for all parameters accessible from the current context
local function initParams( frame )
	_frame = frame;
	_args  = UTIL.getArgs( frame, {
		trim         = true,
		removeBlanks = true,
		parentOnly   = true
	} );
    
    _fusionDisplay = HTML()
    _pageName = _args[1] or "Mavelus (FMR)"
end

-- @name fusionResult
-- @description Generates the material lists for a given Fusion result. To be called through #invoke.
local function fusionResult( frame )
	initParams(frame)
	
    getCombinationsAndExceptionsForFusionResult()
    
    createFusionResultTable()

	return tostring(_fusionDisplay)
end

-- @name fusionMaterial
-- @description Generates the list of other materials and Fusion result for a given material. To be called through #invoke.
local function fusionMaterial( frame )
	initParams(frame)
    
    getCombinationsAndExceptionsForFusionMaterial()
    
    createFusionMaterialTable()

	return tostring(_fusionDisplay)
end

----------
-- Return:
----------
-- @exports 
return {
	['fusionResult'] = fusionResult,
	['fusionMaterial'] = fusionMaterial
};
-- </pre>