local _module = {}
 
_module.create = function(frame, title, teams, isLimitedOvers, tableStyle, useWebSlice)
 
    ---------- Functions ----------
    local strMatch = string.match
    local strFind = string.find
    local strFormat = string.format
    local strSub = string.sub
    local strRepeat = string.rep
    local strUpper = string.upper
    local yesno = require("Module:Yesno")
 
    ---------- Background colours for table cells ----------
    local noMatchColour = "#C0C0C0"     -- No match defined
    local homeWinColour = "#CCCCFF"     -- Home team wins
    local awayWinColour = "#FFCCCC"     -- Away team wins
    local noResultColour = "#FFDEAD"    -- Match abandoned
    local drawColour = "#F0E68C"        -- Match drawn
    local tieColour = "#DDFFDD"         -- Match tied
    local notPlayedColour = "inherit"   -- Not played yet
    local errorColour = "#FF7777"       -- Error
 
    -- The table containing all the matches, looked up by home and away teams.
    local matches = {}
 
    -- Matches which have been defined have a 'true' value for their match number key in this table.
    local matchIDs = {}
 
    -- This is used to lookup team objects by name
    local teamLookup = {}
 
    -- The total number of teams
    local teamCount = #teams
 
    -- The output buffer. It contains all the strings to be output. The 'print' function adds a string to the output buffer
    local output = {}
    local outputIndex = 1
    local print = function(s)
        output[outputIndex] = s
        outputIndex = outputIndex + 1
    end
 
    -- The error buffer. It contains all the errors accumulated when the table was generated. addError() adds an error to the buffer
    local errors
    local errorIndex = 1
    local addError = function(msg, matchID)
        errors = errors or {}
        if matchID then
            errors[errorIndex] = strFormat('* <span class="error" style="font-weight: normal">Error (match %s): %s</span>\n', matchID, msg)
        else
            errors[errorIndex] = strFormat('* <span class="error" style="font-weight: normal">Error: %s</span>\n', msg)
        end
        errorIndex = errorIndex + 1
    end
 
    -- Sort the teams and create the lookup table
    local teamSorter = function(t1, t2)
        return t1.fullName < t2.fullName
    end
    table.sort(teams, teamSorter)
    for i = 1, teamCount do
        local teamObj = teams[i]
        teamLookup[teamObj.code] = teamObj
        teamLookup[teamObj.shortName] = teamObj
        teamLookup[teamObj.fullName] = teamObj
    end
 
    -- Parse the rows
    for i, row in ipairs(frame.args) do
        local match = {}
 
        -- Key/value pairs for each row are separated by semicolons, and a colon separates the key from the value.
        local semicolon = 0
        repeat
            local start = semicolon + 1
            semicolon = strFind(row, ';', start, true) or 0
            local colon = strFind(row, ':', start, true)
            if colon and (colon < semicolon or semicolon == 0) then
                match[strMatch(strSub(row, start, colon - 1), '^%s*(.-)%s*$')] = strMatch(strSub(row, colon + 1, semicolon - 1), '^%s*(.-)%s*$')
            end
        until semicolon == 0
 
        local hasError = false
        local id = match.match
        if not id then
            addError(strFormat("Parameter #%d does not have a match number.", i))
            hasError = true
        end
 
        -- Get the home and away teams for the match.
        local home, away, t
        t = match.home
        if t then
            home = teamLookup[t]
            if not home then addError("The team '" .. t .. "' does not exist.", id); hasError = true end
        else addError("Missing parameter: 'home'."); hasError = true end
        t = match.away
        if t then
            away = teamLookup[t]
            if not away then addError("The team '" .. t .. "' does not exist.", id); hasError = true end
        else addError("Missing parameter: 'away'."); hasError = true end
 
        if home == away then   -- Home and away teams cannot be the same.
            addError("Invalid match (home and away teams are equal).", id); hasError = true
        elseif matchIDs[id] then    -- A match with the given match number is already defined
            addError("A match with this match number has already been defined.", id); hasError = true
        end
 
        if not hasError then   -- If there is no error, store the match in the matches table.
            matchIDs[id] = true
            if not matches[home] then matches[home] = {} end
            matches[home][away] = match
        end
    end
 
    -- Construct the header
    print(strFormat([[
%s
<div style="float: left">
{| class="wikitable" style="%s"
! scope="row" | Visitor team &#x2192;
]],
    useWebSlice and frame:expandTemplate({ title = "WebSlice-begin", args = { id = "427", title = name } }) or "",
    tableStyle or "text-align: center; white-space: nowrap; width: 100%"))
 
    for i = 1, teamCount do
        local team = teams[i]
        print(strFormat('! rowspan="2" scope="col" style="padding: inherit 10px" | [[%s|%s]]\n', team.pageName, team.code))
    end
    print('|-\n! scope="col" | Home team &#x2193;\n')
 
    -- The wikitext fot the table cell where a match is not defined.
    local noMatchCell = '| style="background-color: ' .. noMatchColour .. '" |\n'
 
    -- The text appended to the margin string if the match's 'dl' parameter is true
    local dlString = isDL and ' <span style="font-size: 85%">(D/L)</span>' or ""
 
    --[[
        Returns a margin string from the given parameters, or nil if an error occurs.
        id: The match number (used in error messages).
        margin: A number suffixed with 'R' (runs) or 'W' (wickets), or 'F' for a forfeited match.
        isDL: If the result is decided by the D/L method, this should be true.
        isSuperOver: If the result is decided by a super over, this should be true.
    ]]
    local getMarginString = function(id, margin, isDL, isSuperOver)
        if not margin then
            addError("The result 'H' or 'A' requires a margin.", id)
        elseif margin == 'F' then return "Forfeited"
        elseif isSuperOver then return "Super Over"
        else
            local n, t = tonumber(strSub(margin, 1, -2)), strUpper(strSub(margin, -1, -1))
            if not n then
                addError("Margin must be a valid number suffixed with R or W.", id)
            elseif t == 'R' then
                return strFormat("%d runs%s", n, dlString)
            elseif t == 'W' then
                return strFormat("%d wickets%s", n, dlString)
            elseif t == 'I' then
                if isLimitedOvers then
                    addError("A margin suffixed with 'I' cannot be used in limited-overs formats.", id)
                else
                    return strFormat("inns &amp; %d runs%s", n, dlString)
                end
            else
                addError("Margin must be 'F', or a valid number suffixed with 'R', 'W' or 'I'.", id)
            end
        end
 
        return nil   -- Return nil in case of an error
    end
 
    -- Outputs an error cell for a given match number.
    local printErrorCell = function(id)
        print(strFormat('| style="background-color: %s; padding: 3px 5px; line-height: 300%%" |[[#match%s|Match %s]]\n', errorColour, id, id))
    end
 
    -- Output the main body of the table
 
    for i = 1, teamCount do
        -- Each row starts with a home team header
        local home = teams[i]
        local noHomeMatches = not matches[home]
        print(strFormat('|-\n! scope="row" style="text-align: left; padding: 3px 5px; white-space: normal%s" | [[%s|%s]]\n', noHomeMatches and "; line-height: 275%" or "", home.pageName, home.fullName))
 
        if noHomeMatches then   -- No home matches played by the team
            print(strRepeat(noMatchCell, teamCount))
        else    
            for j = 1, teamCount do
                local away = teams[j]
                local match = matches[home][away]
 
                if match then
                    local id, result, dl, superover = match.match, match.result, yesno(match.dl, false), yesno(match.superover, false)
 
                    if not result then       -- Result not provided
                        addError("Missing parameter: 'result'.")
                        printErrorCell(id)
                    elseif result == 'H' then   -- Home team wins
                        local m = getMarginString(id, match.margin, dl, superover)
                        if m then
                            print(strFormat('| style="background-color: %s; padding: 3px 5px" | %s<br />[[#match%s|%s]]\n', homeWinColour, home.shortName, id, m))
                        else printErrorCell(id) end
                    elseif result == 'A' then     -- Away team wins
                        local m = getMarginString(id, match.margin, dl, superover)
                        if m then
                            print(strFormat('| style="background-color: %s; padding: 3px 5px" | %s<br />[[#match%s|%s]]\n', awayWinColour, home.shortName, id, m))
                        else printErrorCell(id) end
                    elseif result == 'X' then     -- Match has not been played yet
                        print(strFormat('| style="background-color: %s; padding: 3px 5px; line-height: 300%%" | [[#match%s|Match %s]]\n', notPlayedColour, id, id))
                    elseif result == 'D' then     -- Drawn match
                        if isLimitedOvers then    -- No draws in limited-overs matches
                            addError("The result 'D' cannot be used in limited-overs formats.", id)
                            printErrorCell(id)
                        else
                            print(strFormat('| style="background-color: %s; padding: 3px 5px; line-height: 300%%" | [[#match%s|Match drawn]]\n', drawColour, id))
                        end
                    elseif result == 'N' then     -- Abandoned match
                        print(strFormat('| style="background-color: %s; padding: 3px 5px" | [[#match%s|Match<br />abandoned]]\n', noResultColour, id))
                    elseif result == 'T' then     -- Tied match
                        print(strFormat('| style="background-color: %s; padding: 3px 5px;" | [[#match%s|Match tied]]\n', tieColour, id))
                    else  -- Invalid result
                        addError("Invalid result: '" .. result .. "'. Expected: H, A, X, N, D or T.", id)
                        printErrorCell(id)
                    end
                else
                    -- The match is not defined.
                    print(noMatchCell)
                end
 
                print('\n')
            end
        end
    end
 
    -- Legend and notes
    if isLimitedOvers then
        print(strFormat([[
|}
{| class="wikitable" style="float:right; text-align: center; font-size: 90%%; width: 30%%"
| style="background-color: %s; width: 50%%" | Home team won
| style="background-color: %s; width: 50%%" | Visitor team won
|-
| style="background-color: %s; width: 50%%" | Match abandoned
| style="background-color: %s; width: 50%%" | Match tied
|}
<ul style="font-size: 90%%">
<li>'''Note''': Results listed are according to the home (horizontal) and visitor (vertical) teams.</li>
<li>'''Note''': Click on a result to see a summary of the match.</li>
</ul>
</div>
<div style="clear: both" />
%s
]],
        homeWinColour, awayWinColour, noResultColour, tieColour,
        useWebSlice and frame:expandTemplate({ title = 'WebSlice-end', args = {} }) or ""))
 
    else
        print(strFormat([[
|}
{| class="wikitable" style="float:right; text-align: center; font-size: 90%%;"
| style="background-color: %s; width: 33.333333%%" | Home team won
| style="background-color: %s; width: 33.333333%%" | Visitor team won
| style="background-color: %s; width: 33.333333%%" rowspan="2" | Match drawn
|-
| style="background-color: %s; width: 33.333333%%" | Match abandoned
| style="background-color: %s; width: 33.333333%%" | Match tied
|}
<ul style="font-size: 90%%">
<li>'''Note''': Results listed are according to the home (horizontal) and visitor (vertical) teams.</li>
<li>'''Note''': Click on a result to see a summary of the match.</li>
</ul>
</div>
<div style="clear: both" />
%s]],
        homeWinColour, awayWinColour, noResultColour, tieColour, drawColour,
        useWebSlice and frame:expandTemplate({ title = 'WebSlice-end', args = {} }) or ""))
    end
 
    if errorIndex ~= 1 then  -- If there are errors, output them and insert a tracking category
        return strFormat('<div class="plainlist">\n%s</div>[[Category:Pages with errors reported by Module:CricketLeagueGroupStageSummary]]\n%s', table.concat(errors), table.concat(output))
    end
 
    return table.concat(output)
 
end
 
return _module