#!/usr/bin/env texlua
--------------------------------------------------------------------------------
--         FILE:  rst_context.lua
--        USAGE:  called by rst_parser.lua
--  DESCRIPTION:  Complement to the reStructuredText parser
--       AUTHOR:  Philipp Gesang (Phg), <phg42.2a@gmail.com>
--      CHANGED:  2013-03-26 22:46:17+0100
--------------------------------------------------------------------------------
--
--- TODO
---   Find an appropriate way to handle generic tables irrespective of the grid
---   settings. The problem is:
---   http://archive.contextgarden.net/message/20100912.112605.8a1aaf13.en.html
---   Seems we'll have to choose either the grid or split tables as default. Not
---   good.


local helpers        = helpers        or thirddata and thirddata.rst_helpers
local rst_directives = rst_directives or thirddata and thirddata.rst_directives

local utf         = unicode.utf8
local utflen      = utf.len
local utflower    = utf.lower
local utfupper    = utf.upper
local iowrite     = io.write
local tableconcat = table.concat

local stringmatch  = string.match
local stringgmatch = string.gmatch
local stringgsub   = string.gsub

local dbg_write = helpers.dbg_writef

local C,  Cb, Cc, Cg, Cmt, Cp,
      Cs, Ct, P,  R,  S,   V,  lpegmatch
      = lpeg.C,  lpeg.Cb, lpeg.Cc, lpeg.Cg, lpeg.Cmt, lpeg.Cp,
        lpeg.Cs, lpeg.Ct, lpeg.P,  lpeg.R,  lpeg.S,   lpeg.V,  lpeg.match

-- This one should ignore escaped spaces.
do
    local stripper = P{
        [1] = "stripper",
        stripper = V"space"^0 * C((V"space"^0 * (V"escaped" + V"nospace")^1)^0),
        space    = S(" \t\v\n"),
        nospace  = 1 - V"space",
        escaped  = P"\\" * V"space"
    }
    function string.strip(str)
        return lpegmatch(stripper, str) or ""
    end
end
local stringstrip  = string.strip
local stringformat = string.format

local err = function(str)
    if str then
        iowrite("\n*[rstctx] Error: " .. str .. "\n\n")
    end
end

local rst_context = thirddata.rst

rst_context.collected_adornments = {}
rst_context.last_section_level   = 0
rst_context.anonymous_targets    = 0
rst_context.anonymous_links      = {}

rst_context.collected_references = {}
rst_context.context_references   = {}
rst_context.structure_references = {}
rst_context.anonymous_set        = {}

rst_context.substitutions        = {}
rst_context.lastitemnumber       = 0  -- enumerations in RST allow arbitrary skips

rst_context.current_footnote_number   = 0
rst_context.current_symbolnote_number = 0

function rst_context.addsetups(item)
    local state = thirddata.rst.state
    state.addme[item] = state.addme[item] or true
    return 0
end

function rst_context.footnote_reference (label)
    local tf = thirddata.rst.state.footnotes
    if stringmatch(label, "^%d+$") then -- all digits
        local c = tonumber(label)
        return [[\\footnote{\\getbuffer[__footnote_number_]].. c .."]}"
    elseif label == "#" then --autonumber
        local rc = rst_context.current_footnote_number
        rc = rc + 1
        rst_context.current_footnote_number = rc
        return [[\\footnote{\\getbuffer[__footnote_number_]].. rc .."]}"
    elseif stringmatch(label, "^#.+$") then
        local thelabel = stringmatch(label, "^#(.+)$")
        return [[\\footnote{\\getbuffer[__footnote_label_]].. thelabel .."]}"
    elseif label == "*" then
        local rc = rst_context.current_symbolnote_number
        rc = rc + 1
        rst_context.current_symbolnote_number = rc
        return [[\\symbolnote{\\getbuffer[__footnote_symbol_]].. rc .."]}"
    else -- “citation reference” for now treating them like footnotes
        rst_context.addsetups("citations")
        return [[\\cite{]] .. label .. [[}]]
    end
end

do
    local w = S" \v\t\n" / "_"
    local wp = Cs((w + 1)^1)
    function rst_context.whitespace_to_underscore(str)
        return  str and lpegmatch(wp, str) or ""
    end
end

--- So we can use crefs[n][2] to refer to the place where the reference was
--- created.
local function get_context_reference (str)
    local crefs = rst_context.context_references
    local srefs = rst_context.structure_references
    srefs[str] = true
    refstring = "__target_" .. rst_context.whitespace_to_underscore(str)
    crefs[#crefs + 1] = { refstring, str }
    return refstring
end

function rst_context.emphasis (str)
    return [[{\\em ]] .. str .. [[}]]
end

function rst_context.strong_emphasis (str)
    return [[{\\sc ]] .. str .. [[}]]
end

function rst_context.literal (str)
    return [[\\type{]] .. str .. [[}]]
end

--- ROLES for interpreted text

rst_context.roles = {}
rst_context.roles.emphasis = rst_context.emphasis
rst_context.roles.strong_emphasis = rst_context.strong_emphasis
rst_context.roles.literal = rst_context.literal
rst_context.roles.bold = function(str)
    return [[{\\bold ]] .. str .. [[}]]
end
rst_context.roles.bf = rst_context.roles.bold

rst_context.roles.italic = function(str)
    return [[{\\italic ]] .. str .. [[}]]
end
rst_context.roles.it = rst_context.roles.italic

rst_context.roles.sans = function(str)
    return [[{\\ss ]] .. str .. [[}]]
end
rst_context.roles.sans_serif = rst_context.roles.sans
rst_context.roles.ss         = rst_context.roles.sans

rst_context.roles.uppercase = function(str)
    return utfupper(str)
end

rst_context.roles.lowercase = function(str)
    return utflower(str)
end

rst_context.roles.color = function(color, str)
    local p = helpers.patterns
    local definition = stringmatch(color, "^color_(.+)$")
    if stringmatch(definition, "^rgb_") then -- assume rgb
        local rgb = lpegmatch(p.rgbvalues, definition)
        definition = stringformat([[r=%s,g=%s,b=%s]], rgb[1], rgb[2], rgb[3])
    end
    return stringformat([[\\colored[%s]{%s}]], definition, str)
end

--------------------------------------------------------------------------------
--- Inofficial text roles for my private bib
--------------------------------------------------------------------------------

-- Afterthought:
-- Different citation commands are essentially typographical instructions:
-- they are concerned with the final representation of the data with respect to
-- a concrete implementation. Not the thing at all that would make reST
-- portable. But then its support for Python-style string escaping &c. ain’t at
-- all portable either. The problem is the same with XML written to be
-- processed with ConTeXt -- when processing the text directly in MkIV you’ll
-- always find yourself adding setups that allow fine-grained control of the
-- typeset output. At the same time those instructions directly contradict the
-- main reason for XML: to provide an application-independent data markup.
-- Typesetting XML (and now reST) with TeX, you will always end up writing TeX
-- code disguised in XML brackets. (Btw. the docutils reST specification has
-- the same kind of inclination to HTML -- some of its components don’t even
-- have a meaning save in HTML peculiarities.) If you strive to avoid this
-- *and* would like to have decent typesetting, you should use the
-- automatically generated TeX code as a starting point for the actual
-- typesetting job. Wish it was possible to have both -- the data in a
-- universal form and the output in the Optimal Typesetting System -- but
-- that’s a dream for now. If you really read these musings, then prove me
-- wrong if you can! Or go tell those digital publishers and their willing
-- subordinates, the authors, who think they can save a few pennys,
-- substituting the typesetter and editor by some fancy software. Keep in mind
-- that zapf.tex is not just random dummy text. </rant>

function rst_context.roles.ctsh(str) -- shorthand
    rst_context.addsetups("citator")
    return [[\\ctsh{]] .. str .. [[}]]
end

function rst_context.roles.ctas(str) -- short cite
    rst_context.addsetups("citator")
    return [[\\ctas{]] .. str .. [[}]]
end

function rst_context.roles.ctau(str) -- author only
    rst_context.addsetups("citator")
    return [[\\ctau{]] .. str .. [[}]]
end

function rst_context.roles.cttt(str) -- title only
    rst_context.addsetups("citator")
    return [[\\cttt{]] .. str .. [[}]]
end

function rst_context.roles.ctay(str) -- author year
    rst_context.addsetups("citator")
    return [[\\ctay{]] .. str .. [[}]]
end

function rst_context.roles.ctfu(str) -- full cite
    rst_context.addsetups("citator")
    return [[\\ctfu{]] .. str .. [[}]]
end

function rst_context.roles.nocite(str) -- nocite
    rst_context.addsetups("citator")
    return [[\\nocite[]] .. str .. [=[]]=]
end

--------------------------------------------------------------------------------
--- End citator roles
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
--- Experimental roles.
--------------------------------------------------------------------------------

--- Feature request by Philipp A.
function rst_context.roles.math(str)
    return [[\\mathematics{]] .. str .. [[}]]
end

--------------------------------------------------------------------------------
--- End roles
--------------------------------------------------------------------------------

function rst_context.interpreted_text (...)
    local tab = { ... }
    local role, str
    role = stringmatch(tab[1], "^:(.*):$") or stringmatch(tab[3], "^:(.*):$")
    str  = tab[2]

    if not role then -- implicit role
        role = "emphasis"
    end

    if stringmatch(role, "^color_") then
        return rst_context.roles.color(role, str)
    end

    return rst_context.roles[role](str)
end

function rst_context.link_standalone (str)
    return "\n"
        .. [[\\goto{\\hyphenatedurl{]] .. str .. [[}}[url(]] .. str .. [=[)]]=]
end

function rst_context.reference (str)
    rst_context.addsetups("references")
    str = stringmatch(str, "^`?([^`]+)`?_$")
    return [[\\RSTchoosegoto{__target_]] .. rst_context.whitespace_to_underscore(str) .. "}{"
            .. str .. "}"
end

function rst_context.anon_reference (str)
    rst_context.addsetups("references")
    str = stringmatch(str, "^`?([^`]+)`?__$")
    rst_context.anonymous_links[#rst_context.anonymous_links+1] = str
    link = "__target_anon_" .. #rst_context.anonymous_links
    return stringformat([[\\RSTchoosegoto{%s}{%s}]], link, str)
end

local whitespace = S" \n\t\v"
local nowhitespace = 1 - whitespace
local removewhitespace = Cs((nowhitespace^1 + Cs(whitespace / ""))^0)

function rst_context.target (tab)
    rst_context.addsetups("references")
    --local tab = { ... }
    local  refs = rst_context.collected_references
    local arefs = rst_context.anonymous_set
    local target = tab[#tab] -- Ct + C could be clearer but who cares
    tab[#tab] = nil

    local function create_anonymous ()
        rst_context.anonymous_targets = rst_context.anonymous_targets + 1
        return { "anon_" .. rst_context.anonymous_targets, rst_context.anonymous_targets }
    end

    local insert = ""

    if target == "" then -- links here
        for _, id in next, tab do
            insert = insert .. "\n\\reference[__target_" .. id .. "]{}"
        end
    else
        for i=1,#tab do
            local id = tab[i]
            if id == "" then -- anonymous
                local anon = create_anonymous()
                id, arefs[anon[1]] = anon[1], anon[2]
            else
                local tmp = tab[i]
                tmp = stringgsub(tmp, "\\:",":")
                tmp = stringmatch(tmp, "`?([^`]+)`?")
                id = tmp
                --id = tab[i]:gsub("\\:",":"):match("`?([^`]+)`?") -- deescaping
            end
            if id then
                refs[id] = refs[id] or target
            end
        end
    end

    return insert
end

function rst_context.inline_internal_target (str)
    return "\\\\reference[__target_" .. rst_context.whitespace_to_underscore(str) .."]{}"
end

function rst_context.substitution_reference (str, underscores)
    local sub = ""
    rst_context.addsetups("substitutions")
    if underscores == "_" then -- normal reference
        sub = sub .. [[\\reference[__target_]] .. rst_context.whitespace_to_underscore(stringstrip(str)) .. "]{}"
    elseif underscores == "__" then -- normal reference
        rst_context.anonymous_targets = rst_context.anonymous_targets + 1
        sub = sub .. [[\\reference[__target_anon_]] .. rst_context.anonymous_targets .. "]{}"
    end
    return sub .. [[{\\RSTsubstitution]] .. str:gsub("%s", "") .. "}"
end

do
    -- see catc-sym.tex
    local escape_me = {
        ["&"]   = [[\letterampersand ]],
        ["$"]   = [[\letterdollar ]],
        ["#"]   = [[\letterhash ]],
        ["^"]   = [[\letterhat ]],
        ["_"]   = [[\letterunderscore ]],
    }

    local chars
    for chr, repl in next, escape_me do
        chars = chars and chars + (P(chr) / repl) or P(chr) / repl
    end

    local p_escape = P{
        [1]      = Cs((V"skip"
                 --+ V"literal" -- achieved via gsub later
                 + chars
                 + 1)^1),
        skip1    = P"\\starttyping" * (1 - P"\\stoptyping")^1,
        balanced = P"{" * (V"balanced" + (1 - P"}"))^0 * P"}",
        skip2    = P"\\type" * V"balanced",
        skip3    = P"\\mathematics" * V"balanced",
        skip     = V"skip1" + V"skip2" + V"skip3",
        --literal  = Cs(P"\\" / "") * 1
    }

    function rst_context.escape (str)
        str = stringgsub(str, "\\(.)", "%1")
        return lpegmatch(p_escape, str)
    end
end

function rst_context.joinindented (tab)
    return tableconcat (tab, "")
end

local corresponding = {
    ['"'] = '"',
    ["'"] = "'",
    ["{"] = "}",
    ["("] = ")",
    ["["] = "]",
    ["<"] = ">",
}

local inline_parser = P{
    [1] = "block",

    block = Cs(V"inline_as_first"^-1 * (V"except" + V"inline_element" + V"normal_char")^0),

    inline_element = V"precede_inline"
                   * Cs(V"inline_do_elements")
                   * #V"succede_inline"
                   + V"footnote_reference"
                   ,

    -- Ugly but needed in case the first element of a paragraph is inline
    -- formatted.
    inline_as_first = V"inline_do_elements" * #V"succede_inline",

    except = P"\\starttyping" * (1 - P"\\stoptyping")^1 * P"\\stoptyping"
           + V"enclosed"
           ,

    inline_do_elements = V"strong_emphasis"
                       + V"substitution_reference"
                       + V"anon_reference"
                       + V"inline_literal"
                       + V"reference"
                       + V"emphasis"
                       + V"interpreted_text"
                       + V"inline_internal_target"
                       + V"link_standalone"
                       ,

    precede_inline = V"spacing"
                   + V"eol"
                   + -P(1)
                   + S[['"([{<-/:]]
                   + P"‘" + P"“" + P"’" + P"«" + P"¡" + P"¿"
                   + V"inline_delimiter"
                   + P"„", -- not in standard Murkin reST

    succede_inline = V"spacing"
                   + V"eol"
                   + S[['")]}>-/:.,;!?\]]
                   + P"’" + P"”" + P"»"
                   + V"inline_delimiter"
                   + -P(1)
                   + P"“" -- non-standard again but who cares
                   ,

    enclosed = V"precede_inline"^-1
             * Cg(V"quote_single" + V"quote_double" + V"leftpar", "lastgroup")
             * V"inline_delimiter"
             * Cmt(C(V"quote_single" + V"quote_double" + V"rightpar") * Cb("lastgroup"), function(s, i, char, oldchar)
                    return corresponding[oldchar] == char
                end)
             * V"succede_inline"^-1
             * -V"underscore"
             ,

    space = P" ",
    whitespace = (P" " + Cs(P"\t") / "        " + Cs(S"\v") / " "),
    spacing = V"whitespace"^1,

    eol = P"\n",
    --inline_delimiters = P"‐" + P"‑" + P"‒" + P"–" + V"emdash" + V"space",        -- inline markup
    inline_delimiter = P"‐" + P"‑" + P"‒" + P"–" + V"emdash" + V"space"
                     + V"bareia"
                     + V"asterisk"
                     + V"bar"
                     + V"lsquare" + V"rsquare"
                     ,        -- inline markup
    asterisk = P"*",
    quote_single = P"'",
    quote_double = P'"',
    double_asterisk = V"asterisk" * V"asterisk",
    bareia = P"`",
    backslash = P"\\",
    bar = P"|",
    double_bareia = V"bareia" * V"bareia",
    escaped_bareia = (Cs(V"backslash") / "" * V"bareia") + 1,
    colon = P":",
    escaped_colon = (Cs(V"backslash") / "" * V"colon") + 1,
    semicolon = P";",
    underscore = P"_",
    double_underscore = V"underscore" * V"underscore",
    dot = P".",
    interpunct = P"·",
    comma = P",",
    dash = P"-",
    emdash = P"—",
    ellipsis   = P"…" + P"...",
    exclamationmark = P"!",
    questionmark = P"?",
    interrobang = P"‽",
    double_dash = V"dash" * V"dash",
    triple_dash = V"double_dash" * V"dash",
    hyphen = P"‐",
    dashes = V"dash" + P"‒" + P"–" + V"emdash" + P"―",

    lparenthesis = P"(",
    rparenthesis = P")",
    lsquare = P"[" / [[{\\letterleftbracket}]],
    rsquare = P"]" / [[{\\letterrightbracket}]],
    lbrace  = P"{" / [[{\\letterleftbrace}]],
    rbrace  = P"}" / [[{\\letterrightbrace}]],
    less    = P"<",
    greater = P">",
    leftpar  = V"lparenthesis" + V"lsquare" + V"lbrace" + V"less",
    rightpar = V"rparenthesis" + V"rsquare" + V"rbrace" + V"greater",

    normal_char = V"lbrace" + V"rbrace" + V"lsquare" + V"rsquare" -- escape those if in input
                + 1
                ,

    --groupchars = S"()[]{}",
    groupchars = V"leftpar" + V"rightpar",
    apostrophe = P"’" + P"'",

    guillemets = P"«" + P"»",
    quotationmarks= P"‘" + P"’" + P"“" + P"”",
    solidus= P"⁄",
    slash = P"/",

    gartenzaun = P"#",
    digit  = R"09",
    letter = R"az" + R"AZ",

    punctuation = V"apostrophe"
                + V"colon"
                + V"comma"
                + V"dashes"
                + V"dot"
                + V"ellipsis"
                + V"exclamationmark"
                + V"guillemets"
                + V"hyphen"
                + V"interpunct"
                + V"interrobang"
                + V"questionmark"
                + V"quotationmarks"
                + V"semicolon"
                + V"slash"
                + V"solidus"
                + V"underscore"
                ,

    emphasis        = (V"asterisk" - V"double_asterisk")
                    * Cs((V"normal_char" - V"spacing" - V"eol" - V"asterisk")
                       * ((V"normal_char" - (V"normal_char" * V"asterisk"))^0
                        * (V"normal_char" - V"spacing" - V"eol" - V"asterisk"))^-1)
                    * V"asterisk"
                    / rst_context.emphasis,

    strong_emphasis = V"double_asterisk"
                    * Cs((V"normal_char" - V"spacing" - V"eol" - V"asterisk")
                       * ((V"normal_char" - (V"normal_char" * V"double_asterisk"))^0
                        * (V"normal_char" - V"spacing" - V"eol" - V"asterisk"))^-1)
                    * V"double_asterisk"
                    / rst_context.strong_emphasis,

    inline_literal  = V"double_bareia"
                    * C ((V"escaped_bareia" - V"spacing" - V"eol" - V"bareia")
                       * ((V"escaped_bareia" - (V"normal_char" * V"double_bareia"))^0
                        * (V"escaped_bareia" - V"spacing" - V"eol" - V"bareia"))^-1)
                    * V"double_bareia"
                    / rst_context.literal,

    interpreted_single_char = (V"normal_char" - V"spacing" - V"eol" - V"bareia") * #V"bareia",
    interpreted_multi_char  = (V"normal_char" - V"spacing" - V"eol" - V"bareia") * (V"normal_char" - (1 * V"bareia"))^0 * (1 - V"spacing" - V"eol" - V"bareia"),

    interpreted_text = C(V"role_marker"^-1)
                     * (V"bareia" - V"double_bareia")
                     * C(V"interpreted_single_char" + V"interpreted_multi_char")
                     * V"bareia"
                     * C(V"role_marker"^-1)
                     / rst_context.interpreted_text,

    role_marker = V"colon" * (V"backslash" * V"colon" + V"letter" + V"digit" + V"dash" + V"underscore" + V"dot")^1 * V"colon",

    link_standalone = C(V"uri")
                    / rst_context.link_standalone,

    anon_reference = Cs(V"anon_phrase_reference" + V"anon_normal_reference")
              / rst_context.anon_reference,

    anon_normal_reference = C((1 - V"underscore" - V"spacing" - V"eol" - V"punctuation" - V"groupchars")^1) * V"double_underscore",

    anon_phrase_reference = (V"bareia" - V"double_bareia")
                          * C((1 - V"bareia")^1)
                          * V"bareia" * V"double_underscore"
                          ,

    reference = Cs(V"normal_reference" + V"phrase_reference")
              / rst_context.reference,

    normal_reference = (1 - V"underscore" - V"spacing" - V"eol" - V"punctuation" - V"groupchars")^1 * V"underscore",

    phrase_reference = (V"bareia" - V"double_bareia")
                     * C((1 - V"bareia")^1)
                     * V"bareia" * V"underscore"
                     ,

    footnote_reference = V"lsquare"
                       * Cs(V"footnote_label" + V"citation_reference_label")
                       * V"rsquare"
                       * V"underscore"
                       / rst_context.footnote_reference
                       ,

    footnote_label = V"digit"^1
                   + V"gartenzaun" * V"letter"^1
                   + V"gartenzaun"
                   + V"asterisk"
                   ,

    citation_reference_label = V"letter" * (1 - V"rsquare")^1,

    inline_internal_target = V"underscore"
                           * V"bareia"
                           * Cs((1 - V"bareia")^1)
                           * V"bareia"
                           / rst_context.inline_internal_target
                           ,

    substitution_reference = V"bar"
                           * C((1 - V"bar")^1)
                           * V"bar"
                           * C((V"double_underscore" + V"underscore")^-1)
                           / rst_context.substitution_reference
                           ,

--------------------------------------------------------------------------------
-- Urls
--------------------------------------------------------------------------------
    uri = V"url_protocol" * V"url_domain" * V"url_path_char"^0,

    url_protocol = (P"http" + P"ftp" + P"shttp" + P"sftp") * P"://",
    url_domain_char = 1 - V"dot" - V"spacing" - V"eol" - V"punctuation",
    url_domain = V"url_domain_char"^1 * (V"dot" * V"url_domain_char"^1)^0,
    url_path_char = R("az", "AZ", "09") + S[[-_.!~*'()/]],
}

rst_context.inline_parser = inline_parser

function rst_context.paragraph (data)
    local str
    if not data then
        return ""
    elseif type(data) == "table" then
--        str = #data > 1 and  helpers.string.wrapat(lpegmatch(inline_parser, tableconcat(data, " ")), 65) 
--                        or   inline_parser:match(data[1])
        if #data > 1 then
            str = helpers.string.wrapat(
                lpegmatch(inline_parser, tableconcat(data, " "))
                , 65)
        else
            str = lpegmatch(inline_parser, data[1])
        end
    else
        str = data
    end
    return stringformat([[

\\startparagraph
%s
\\stopparagraph
]], str)
end

local sectionlevels = {
    [1] = "chapter",
    [2] = "section",
    [3] = "subsection",
    [4] = "subsubsection",
    [5] = "subsubsubsection",
}

local function get_line_pattern (chr)
    return P(chr)^1 * (-P(1))
end

function rst_context.section (...)  -- TODO general cleanup; move validity
    local tab = { ... }             -- checking to parser.
    local section, str = true, ""
    local adornchar 
    local ulen = utflen
    if #tab == 3 then -- TODO use unicode length with ConTeXt
        adornchar = tab[1]:sub(1,1)
        section = ulen(tab[1]) >= ulen(tab[2])
        str = stringstrip(tab[2])
    else -- no overline
        adornchar = tab[2]:sub(1,1)
        section = ulen(tab[1]) <= ulen(tab[2])
        str = tab[1]
    end

    if section then -- determine level
        local level = rst_context.last_section_level
        local rca = rst_context.collected_adornments
        if rca[adornchar] then
            level = rca[adornchar]
        else
            level = level + 1
            rca[adornchar] = level
            rst_context.last_section_level = level
        end

        ref = get_context_reference (str)

        str = stringformat("\n\\\\%s[%s]{%s}\n", sectionlevels[level], ref, str)
    else
        return [[{\\bf fix your sectioning!}\\endgraf}]]
    end

    return section and str or ""
end

-- Prime time for the fancybreak module.
function rst_context.transition (str)
    rst_context.addsetups("breaks")
    --return "\\fancybreak\n"
    return "\\fancybreak{$* * *$}\n"
end

function rst_context.bullet_marker(str)
    return "marker"
end

-- This one should ignore escaped spaces.
do
    local stripper = P{
        [1] = "stripper",
        stripper = V"space"^0 * C((V"space"^0 * V"nospace"^1)^0),
        space    = S(" \t\v\n"),
        escaped  = P"\\" * V"space",
        nospace  = V"escaped" + (1 - V"space"),
    }
    function stringstrip(str)
        return lpegmatch(stripper, str) or ""
    end 
end

local enumeration_types = {
    ["*"] = "*", -- unordered bulleted
    ["+"] = "*",
    ["-"] = "*",
    ["•"] = "*",
    ["‣"] = "*",
    ["⁃"] = "*",

    ["#"] = "n", -- numbered lists and conversion
    ["A"] = "A",
    ["a"] = "a",
    ["I"] = "R",
    ["i"] = "r",
}

-- \setupitemize[left=(, right=), margin=4em, stopper=]

local stripme   = S"()."
local dontstrip = 1 - stripme
local itemstripper = stripme^0 * C(dontstrip^1) * stripme^0

local function parse_itemstring(str)
    local offset = nil
    local setup = ",fit][itemalign=flushright,"
    if stringmatch(str, "^%(") then
        setup = setup .. [[left=(,]]
    end
    if stringmatch(str, "%)$") then
        setup = setup .. [[right=)]]
    end
    if stringmatch(str, "%.$") then
        setup = setup .. [[stopper={.\\space}]]
    end
    local num = stringmatch(str, "^%d")
    if num then
        -- http://thread.gmane.org/gmane.comp.tex.context/61728/focus=61729
        setup = setup .. ",start=" .. num
        str = "n"
    end

    str = lpegmatch(itemstripper, str)
    str = enumeration_types[str] or str
    return { setup = setup, str = str }
end

function rst_context.startitemize(str)
    local setup = ""
    local result = ""
    str = stringstrip(str)

    local listtype = enumeration_types[str] or parse_itemstring(str)

    if type(listtype) == "table" then
        setup = listtype.setup
        listtype = listtype.str
    end

    result = [[
\\startitemize[]] .. listtype .. setup .. [[]
]] 
    return result
end

local last_item = {} -- stack
local current_itemdepth = 0
function rst_context.stopitemize(str)
    last_item[current_itemdepth] = nil
    current_itemdepth = current_itemdepth - 1
    return str .. [[
\\stopitemize
]]
end

function rst_context.bullet_item (tab)
    local li = last_item
    -- The capture of the first item has the \startitemize as 
    -- *second* element in the array.
    local content  = #tab == 2 and tab[2] or tab[3]
    local startstr = #tab == 3 and tab[2] or nil
    local itemtype = tab[1]
    local result = startstr or ""
    if startstr then
        current_itemdepth = current_itemdepth + 1
        li[current_itemdepth] = itemtype
    elseif li[current_itemdepth] then
        if helpers.list.successor(itemtype, li[current_itemdepth]) then
            -- just leave it alone
        elseif helpers.list.greater(itemtype, li[current_itemdepth]) then
            local itemnum = tonumber(stringstrip(itemtype)) or helpers.list.get_decimal(itemtype)
            result = result .. stringformat([[
\\setnumber[itemgroup:itemize]{%s}
]], itemnum)
        end
        li[current_itemdepth] = itemtype
    end

    return result .. [[

\\item ]] .. lpegmatch(inline_parser, content) .. [[

]]
end

--------------------------------------------------------------------------------
-- Definition lists 
--------------------------------------------------------------------------------
-- TODO define proper setups (probably bnf-like and some narrower for def-paragraphs)

function rst_context.deflist (list)
    rst_context.addsetups("deflist")

    local deflist = [[
\\startRSTdefinitionlist
]] 
    for nd=1, #list do
        local item = list[nd]
        local term = item[1]
        local nc = 2
        local tmp = [[

  \\RSTdeflistterm{]] .. stringstrip(term) .. "}"
        if #item > 2 then
            while nc < #item do
                tmp = tmp .. [[

  \\RSTdeflistclassifier{]] .. stringstrip(item[nc]) .. "}"
                nc = nc + 1
            end
        end
        tmp = tmp .. [[

  \\RSTdeflistdefinition{%
]]
        local final = item[#item]
        for np=1, #final do
            local par = final[np]
            tmp = tmp .. [[
    \\RSTdeflistparagraph{%
]] .. lpegmatch(inline_parser, par) .. "}\n"
        end
        tmp = tmp .. "  }"
        deflist = deflist .. tmp
    end
    return deflist .. [[

\\stopRSTdefinitionlist
]]
end

--------------------------------------------------------------------------------
-- Field lists
--------------------------------------------------------------------------------

-- TODO Do something useful with field lists. For now I'm not sure what as the
-- bibliography directives from the reST specification seem to make sense only
-- when using docinfo and, after all, we have .bib files that are portable.

function rst_context.field_list (str)
    rst_context.addsetups("fieldlist")
    return [[

\\startRSTfieldlist]] .. str .. [[\\eTABLEbody\\stopRSTfieldlist
]]
end

function rst_context.field_name (str)
    return [[\\fieldname{]] .. str .. [[}]]
end

function rst_context.field_body (str)
    return [[\\fieldbody{]] .. lpegmatch(inline_parser, str) .. [[}]]
end

function rst_context.field (tab)
    local name, body = tab[1], tab[2]
    return stringformat([[

    \\RSTfieldname{%s}
    \\RSTfieldbody{%s}
]], name, lpegmatch(inline_parser, body))
end

function rst_context.line_comment (str)
    return "% " .. str
end

function rst_context.block_comment (str)
    return stringformat([[

\iffalse %% start block comment
%s\fi %% stop block comment
]], str)
end

function rst_context.option_list (str)
    return [[
\\setupTABLE[c][first] [background=color, backgroundcolor=grey, style=\tt]
\\setupTABLE[c][each]  [frame=off]
\\setupTABLE[r][each]  [frame=off]
\\bTABLE[split=yes,option=stretch]
\\bTABLEhead
\\bTR
  \\bTH  Option \\eTH
  \\bTH  Description \\eTH
\\eTR
\\eTABLEhead
\\bTABLEbody
]] .. lpegmatch(inline_parser, str) .. [[

\\eTABLEbody
\\eTABLE
]]
end

function rst_context.option_item (tab)
    return stringformat([[\\bTR\\bTC %s \\eTC\\bTC %s \\eTC\\eTR
]], tab[1], tab[2])
end

function rst_context.test(str)
    return ":"
end

function rst_context.literal_block (str, included)
    local indent = P" "^1
    local stripme = #str
    for line in stringgmatch(str, "[^\n]+") do
        -- setting to the lowest indend of all lines
        local idt = lpegmatch(indent, line)
        if line and idt then
            stripme = idt < stripme and idt or stripme
        end
    end

    local strip = P{
        [1] = "strip",
        strip = Cs(V"line"^1),
        eol = P"\n",
        restofline = (1 - V"eol")^0,
        stop = Cs(V"eol" * P" "^0) * -P(1) / "", -- remove trailing blank lines
        line = Cs(V"restofline" * (V"stop" + V"eol")) / function (line)
            return #line > stripme and line:sub(stripme) or line
        end,
    }

    str = lpegmatch(strip, str)
    str = [[

\starttyping[lines=hyphenated]
]] .. str .. [[

\stoptyping
]]
    if included then -- escaping can ruin your day
        str = str:gsub("\\", "\\\\")
    end
    return str
end

function rst_context.included_literal_block (str)
    return rst_context.literal_block(str, true)
end

function rst_context.line_block (str)
    rst_context.addsetups("lines")
    return [[

\\startlines
]] .. lpegmatch(inline_parser, str) .. [[\\stoplines
]]
end

function rst_context.line_block_line(str)
    str = str:gsub("\n", " ")
    return str .. "\n"
end

function rst_context.line_block_empty()
    return "\n"
end

function rst_context.block_quote (tab)
    rst_context.addsetups("blockquote")
    local str = [[
\\startlinecorrection
\\blank[small]
\\startblockquote
]] .. lpegmatch(inline_parser, tab[1]) .. [[

\\stopblockquote
]]

    return tab[2] and str .. [[
\\blank[small]
\\startattribution
]] .. lpegmatch(inline_parser, tab[2]) .. [[
\\stopattribution
\\blank[small]
\\stoplinecorrection
]]  or str .. [[
\\blank[small]
\\stoplinecorrection
]] 
end

--function rst_context.table (str)
    --return [[
--\\startlinecorrection
--]] .. str .. [[

--\\stoplinecorrection
--]]
--end

function rst_context.grid_table (tab)
    local body = ""
    local nr = 1
    local head
    if tab.has_head then
        head = [[
\\setupTABLE[c][each]  [frame=off]
\\setupTABLE[r][each]  [frame=off]
%\\startlinecorrection
\\bTABLE[split=repeat,option=stretch]
\\bTABLEhead
]]
        while nr <= tab.head_end do
            local r = tab.rows[nr]
            local isempty = true
            for n=1, #r do
                local cell = r[n]
                if cell.variant == "normal" then
                    isempty = false
                    break
                end
            end

            if not isempty then
                local row = [[\\bTR]]
                for n=1, #r do
                    local c = r[n]
                    if not (c.parent or
                            c.variant == "separator") then
                        local celltext = lpegmatch(inline_parser, c.stripped)
                        if c.span.x or c.span.y then
                            local span_exp = "["
                            if c.span.x then
                                span_exp = span_exp .. "nc=" .. c.span.x .. ","
                            end
                            if c.span.y then
                                span_exp = span_exp .. "nr=" .. c.span.y
                            end
                            celltext  = span_exp .. "] " .. celltext

                        end

                        row = row .. "\n  " .. [[\\bTH ]] .. celltext .. [[\\eTH]]
                    end
                end
                head = head .. row .. "\n" .. [[\\eTR]] .. "\n"
            end
            nr = nr + 1
        end
        head = head .. [[
\\eTABLEhead
\\bTABLEbody
]] 
    else
        head = [[
\\setupTABLE[c][each]  [frame=off]
\\setupTABLE[r][each]  [frame=off]
%\\startlinecorrection
\\bTABLE[split=repeat,option=stretch]
\\bTABLEbody
]] 
    end
    while nr <= #tab.rows do
        local r = tab.rows[nr]
        local isempty = true
        for n=1, #r do
            local cell = r[n]
            if cell.variant == "normal" then
                isempty = false
                break
            end
        end

        if not isempty then
            local row = [[\\bTR]]
            for n=1, #r do
                local c = r[n]
                if not (c.parent or
                        c.variant == "separator") then
                    local celltext = lpegmatch(inline_parser, c.stripped)
                    if c.span.x or c.span.y then
                        local span_exp = "["
                        if c.span.x then
                            span_exp = span_exp .. "nc=" .. c.span.x .. ","
                        end
                        if c.span.y then
                            span_exp = span_exp .. "nr=" .. c.span.y
                        end
                        celltext  = span_exp .. "] " .. celltext

                    end

                    row = row .. "\n  " .. [[\\bTC ]] .. celltext .. [[\\eTC]]
                end
            end
            body = body .. row .. "\n" .. [[\\eTR]] .. "\n"
        end
        nr = nr + 1
    end
    local tail = [[
\\eTABLEbody
\\eTABLE
%\\stoplinecorrection
]]
    return head .. body .. tail
end


function rst_context.simple_table(tab)
    local head
    local nr = 1
    if tab.head_end then
        head = [[
\\setupTABLE[c][each]  [frame=off]
\\setupTABLE[r][each]  [frame=off]
%\\startlinecorrection
\\bTABLE[split=yes,option=stretch]
\\bTABLEhead
]]
        while nr <= tab.head_end do
            local row = tab[nr]
            if not row.ignore then
                dbg_write(">hr>" .. #row)
                head = head .. [[\\bTR]]
                for nc=1, #row do
                    local cell = row[nc]
                    dbg_write("%7s | ", cell.content)
                    local celltext = lpegmatch(inline_parser, cell.content)
                    if cell.span then
                        head = head .. stringformat([=[\\bTH[nc=%s]%s\\eTH]=], cell.span.x, celltext or "")
                    else
                        head = head .. [[\\bTH ]] .. celltext .. [[\\eTH]]
                    end
                end
                dbg_write("\n")
                head = head .. "\\\\eTR\n"
            end
            nr = nr + 1
        end

        head = head .. [[
\\eTABLEhead
\\bTABLEbody
]] 
    else
        head = [[
\\setupTABLE[c][each]  [frame=off]
\\setupTABLE[r][each]  [frame=off]
%\\startlinecorrection
\\bTABLE[split=yes,option=stretch]
\\bTABLEbody
]] 
    end
    local tail = [[
\\eTABLEbody
\\eTABLE
%\\stoplinecorrection
]]
    local body = ""
    while nr <= #tab do
        local row = tab[nr]
        if not row.ignore then
            dbg_write(">tr>" .. #row)
            body = body .. [[\\bTR]]
            for nc=1, #row do
                local cell = row[nc]
                dbg_write("%7s | ", cell.content)
                local celltext = lpegmatch(inline_parser, cell.content)
                if cell.span then
                    body = body .. stringformat([=[\\bTC[nc=%s]%s\\eTC]=], cell.span.x, celltext or "")
                else
                    body = body .. [[\\bTC ]] .. celltext .. [[\\eTC]]
                end
            end
            dbg_write("\n")
            body = body .. "\\\\eTR\n"
        end
        nr = nr + 1
    end
    return head .. body .. tail
end

function rst_context.footnote (label, content)
    local tf = thirddata.rst.state.footnotes
    rst_context.addsetups("footnotes")
    if stringmatch(label, "^%d+$") then -- all digits
        tf.numbered[tonumber(label)] =
            rst_context.escape(lpegmatch(inline_parser, content))
    elseif label == "#" then --autonumber
        repeat -- until next unrequested number
            tf.autonumber = tf.autonumber + 1
        until tf.numbered[tf.autonumber] == nil
        tf.numbered[tf.autonumber] =
            rst_context.escape(lpegmatch(inline_parser, content))
    elseif stringmatch(label, "^#.+$") then
        local thelabel = stringmatch(label, "^#(.+)$")
        tf.autolabel[thelabel] =
            rst_context.escape(lpegmatch(inline_parser, content))
    elseif label == "*" then
        rst_context.addsetups("footnote_symbol")
        tf.symbol[#tf.symbol+1] =
            rst_context.escape(lpegmatch(inline_parser, content))
    else -- “citation reference” treated like ordinary footnote
        repeat -- until next unrequested number
            tf.autonumber = tf.autonumber + 1
        until tf.numbered[tf.autonumber] == nil
        tf.numbered[tf.autonumber] =
            rst_context.escape(lpegmatch(inline_parser, content))
    end
    return ""
end

function rst_context.substitution_definition (subtext, directive, data)
    local tmp
    if data.first ~= "" then
        tmp = { data.first }
    else
        tmp = { }
    end
    data.first = nil
    for i=1, #data do -- paragraphs
        local current = tableconcat(data[i], "\n")
        --current = lpegmatch(inline_parser, current)
        --current = rst_context.escape(current)
        tmp[#tmp+1] = current
    end
    data = tableconcat(tmp, "\n\n")
    rst_context.substitutions[subtext] = { directive = directive,
                                           data      = data }
    return ""
end

-- not to be confused with the directive definition table rst_directives
function rst_context.directive(directive, data)
    local fun  = rst_directives[directive]
    if fun then
        rst_context.addsetups("directive")
        local result = ""
        result = fun(data)
        return result
    end
    return ""
end

-- vim:ft=lua:sw=4:ts=4:expandtab
