--- docbook document class.
-- @use classes.docbook

local plain = require("classes.plain")

local class = pl.class(plain)
class._name = "docbook"

SILE.scratch.docbook = {
   seclevel = 0,
   seccount = {},
}

function class:_init (options)
   plain._init(self, options)
   self:loadPackage("image")
   self:loadPackage("simpletable", {
      tableTag = "tgroup",
      trTag = "row",
      tdTag = "entry",
   })
   self:loadPackage("rules")
   self:loadPackage("verbatim")
   self:loadPackage("footnotes")
   -- SILE sensibly does not define a pixels unit because it has no meaning in its frame of reference. However the
   -- Docbook standard requires them and even defaults to them for bare numbers, even while warning against their use.
   -- Here we define a px arbitrarily to be the equivalent point unit if output was 300 DPI.
   SILE.types.unit.px = {
      definition = "0.24pt",
   }
end

function class.push (t, val)
   if not SILE.scratch.docbook[t] then
      SILE.scratch.docbook[t] = {}
   end
   local q = SILE.scratch.docbook[t]
   q[#q + 1] = val
end

function class.pop (t)
   local q = SILE.scratch.docbook[t]
   q[#q] = nil
end

function class.val (t)
   local q = SILE.scratch.docbook[t]
   return q[#q]
end

function class.wipe (tbl)
   while #tbl > 0 do
      tbl[#tbl] = nil
   end
end

class.registerCommands = function (self)
   plain.registerCommands(self)

   -- Unfinished! commands found in howto.xml example document on docbook.org
   self:registerCommand("acronym", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("alt", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("note", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("colspec", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("phrase", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("literal", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("docbook-section-3-title", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("variablelist", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("varlistentry", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("term", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("procedure", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("step", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("screen", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("command", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("option", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("package", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("tip", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("varname", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("qandaset", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("qandadiv", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("qandaentry", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("question", function (_, content)
      SILE.process(content)
   end)
   self:registerCommand("answer", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("docbook-line", function (_, _)
      SILE.call("medskip")
      SILE.call("hrule", { height = "0.5pt", width = "50mm" })
      SILE.call("medskip")
   end)

   self:registerCommand("docbook-sectionsfont", function (_, content)
      SILE.call("font", { family = "DejaVu Sans", style = "Condensed", weight = 800 }, content)
   end)

   self:registerCommand("docbook-ttfont", function (_, content)
      SILE.call("font", { family = "Hack", size = "2ex" }, content)
   end)

   self:registerCommand("docbook-article-title", function (_, content)
      SILE.call("center", {}, function ()
         SILE.call("docbook-sectionsfont", {}, function ()
            SILE.call("font", { size = "20pt" }, content)
         end)
      end)
      SILE.call("bigskip")
   end)

   self:registerCommand("docbook-section-title", function (_, content)
      SILE.call("noindent")
      SILE.call("bigskip")
      SILE.call("docbook-sectionsfont", {}, { content })
      SILE.call("bigskip")
   end)

   self:registerCommand("docbook-main-author", function (_, content)
      SILE.call("center", {}, function ()
         SILE.call("docbook-sectionsfont", {}, content)
      end)
      SILE.call("bigskip")
   end)

   self:registerCommand("docbook-section-1-title", function (_, content)
      SILE.call("font", { size = "16pt" }, function ()
         SILE.call("docbook-section-title", {}, content)
      end)
   end)

   self:registerCommand("docbook-section-2-title", function (_, content)
      SILE.call("font", { size = "12pt" }, function ()
         SILE.call("docbook-section-title", {}, content)
      end)
   end)

   self:registerCommand("docbook-titling", function (_, content)
      SILE.call("noindent")
      SILE.call("docbook-sectionsfont", {}, content)
   end)

   self:registerCommand("para", function (_, content)
      SILE.process(content)
      SILE.call("par")
   end)

   self:registerCommand("emphasis", function (_, content)
      SILE.call("em", {}, content)
   end)

   self:registerCommand("replaceable", function (_, content)
      SILE.call("em", {}, content)
   end)

   self:registerCommand("abbrev", function (_, content)
      SILE.call("font", { variant = "smallcaps" }, content)
   end)

   self:registerCommand("title", function (_, content)
      SILE.call("em", {}, content)
   end)

   self:registerCommand("personname", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("email", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("uri", function (_, content)
      SILE.call("code", {}, content)
   end)

   self:registerCommand("personblurb", function (_, content)
      SILE.call("font", { size = "2ex" }, content)
   end)

   self:registerCommand("affiliation", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("jobtitle", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("orgname", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("application", function (_, content)
      SILE.call("em", {}, content)
   end)

   self:registerCommand("menuchoice", function (_, content)
      SILE.typesetter:typeset("“")
      SILE.process(content)
      SILE.typesetter:typeset("”")
   end)

   self:registerCommand("programlisting", function (_, content)
      SILE.call("verbatim", {}, content)
   end)

   self:registerCommand("tag", function (_, content)
      SILE.call("docbook-ttfont", {}, function ()
         SILE.typesetter:typeset("<")
         SILE.process(content)
         SILE.typesetter:typeset(">")
      end)
   end)

   self:registerCommand("code", function (_, content)
      SILE.call("docbook-ttfont", {}, content)
   end)

   self:registerCommand("filename", function (_, content)
      SILE.call("docbook-ttfont", {}, content)
   end)

   self:registerCommand("guimenu", function (_, content)
      SILE.call("docbook-ttfont", {}, content)
   end)

   self:registerCommand("guimenuitem", function (_, content)
      SILE.typesetter:typeset(" > ")
      SILE.call("docbook-ttfont", {}, content)
   end)

   self:registerCommand("guilabel", function (_, content)
      SILE.call("docbook-ttfont", {}, content)
   end)

   self:registerCommand("guibutton", function (_, content)
      SILE.call("docbook-ttfont", {}, content)
   end)

   self:registerCommand("computeroutput", function (_, content)
      SILE.call("docbook-ttfont", {}, content)
   end)

   self:registerCommand("xref", function (_, _)
      SILE.typesetter:typeset("XXX")
   end)

   self:registerCommand("citetitle", function (_, content)
      SILE.call("em", {}, content)
   end)

   self:registerCommand("quote", function (_, content)
      SILE.typesetter:typeset("“")
      SILE.process(content)
      SILE.typesetter:typeset("”")
   end)

   self:registerCommand("citation", function (_, content)
      SILE.typesetter:typeset("[")
      SILE.process(content)
      SILE.typesetter:typeset("]")
   end)

   self:registerCommand("thead", function (_, content)
      SILE.call("font", { weight = 800 }, content)
   end)

   self:registerCommand("tbody", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("mediaobject", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("imageobject", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("bibliography", function (_, content)
      SILE.call("font", { size = "16pt" }, function ()
         SILE.call("docbook-section-title", { "Bibliography" })
      end)
      SILE.call("par")
      SILE.call("font", { size = "2ex" }, content)
   end)

   self:registerCommand("bibliomixed", function (_, content)
      SILE.process(content)
      SILE.call("smallskip")
   end)

   self:registerCommand("bibliomisc", function (_, content)
      SILE.process(content)
   end)

   self:registerCommand("article", function (_, content)
      local info = SILE.inputter:findInTree(content, "info") or SILE.inputter:findInTree(content, "articleinfo")
      local title = SILE.inputter:findInTree(content, "title") or (info and SILE.inputter:findInTree(info, "title"))
      local author = SILE.inputter:findInTree(content, "author") or (info and SILE.inputter:findInTree(info, "author"))

      if title then
         SILE.call("docbook-article-title", {}, title)
         self.wipe(title)
      end
      if author then
         SILE.call("docbook-main-author", {}, function ()
            for _, t in ipairs(author) do
               if type(t) == "table" then
                  SILE.call(t.command, {}, t)
                  SILE.typesetter:leaveHmode()
                  SILE.call("bigskip")
               end
            end
         end)
      end
      SILE.process(content)
      SILE.typesetter:chuck()
   end)

   self:registerCommand("info", function () end)

   self:registerCommand("section", function (_, content)
      SILE.scratch.docbook.seclevel = SILE.scratch.docbook.seclevel + 1
      SILE.scratch.docbook.seccount[SILE.scratch.docbook.seclevel] = (
         SILE.scratch.docbook.seccount[SILE.scratch.docbook.seclevel] or 0
      ) + 1
      while #SILE.scratch.docbook.seccount > SILE.scratch.docbook.seclevel do
         SILE.scratch.docbook.seccount[#SILE.scratch.docbook.seccount] = nil
      end
      local title = SILE.inputter:findInTree(content, "title")
      local number = table.concat(SILE.scratch.docbook.seccount, ".")
      if title then
         SILE.call("docbook-section-" .. SILE.scratch.docbook.seclevel .. "-title", {}, function ()
            SILE.typesetter:typeset(number .. " ")
            SILE.process(title)
         end)
         self.wipe(title)
      end
      SILE.process(content)
      SILE.scratch.docbook.seclevel = SILE.scratch.docbook.seclevel - 1
   end)

   local function countedThing (thing, _, content)
      SILE.call("increment-counter", { id = thing })
      SILE.call("bigskip")
      SILE.call("docbook-line")
      SILE.call("docbook-titling", {}, function ()
         SILE.typesetter:typeset(thing .. " " .. self.packages.counters:formatCounter(SILE.scratch.counters[thing]))
         local t = SILE.inputter:findInTree(content, "title")
         if t then
            SILE.typesetter:typeset(": ")
            SILE.process(t)
            self.wipe(t)
         end
      end)
      SILE.call("smallskip")
      SILE.process(content)
      SILE.call("docbook-line")
      SILE.call("bigskip")
   end

   self:registerCommand("example", function (options, content)
      countedThing("Example", options, content)
   end)

   self:registerCommand("table", function (options, content)
      countedThing("Table", options, content)
   end)
   self:registerCommand("figure", function (options, content)
      countedThing("Figure", options, content)
   end)

   local function docmook_measurement (value)
      SU.dump(value)
      value = value:gsub("(%d)$", "%1px") -- bare numbers are pixels, not points
      value = value:gsub("(%%)$", "%1lw") -- percentages are relative to viewport
      return SU.cast("measurement", value)
   end

   self:registerCommand("imagedata", function (options, _)
      local width = options.width and docmook_measurement(options.width)
      SILE.call("img", { src = options.fileref, width = width })
   end)

   self:registerCommand("itemizedlist", function (_, content)
      self.push("list", { type = "itemized" })
      SILE.call("medskip")
      -- Indentation
      SILE.process(content)
      SILE.call("medskip")
      self.pop("list")
   end)

   self:registerCommand("orderedlist", function (_, content)
      self.push("list", { type = "ordered", ctr = 1 })
      SILE.call("medskip")
      -- Indentation
      SILE.process(content)
      SILE.call("medskip")
      self.pop("list")
   end)

   self:registerCommand("listitem", function (_, content)
      local ctx = self.val("list")
      if ctx and ctx.type == "ordered" then
         SILE.typesetter:typeset(ctx.ctr .. ". ")
         ctx.ctr = ctx.ctr + 1
      elseif ctx and ctx.type == "itemized" then
         SILE.typesetter:typeset("• ")
      -- elseif ctx and ctx.type == "" then
      --   -- Other types?
      else
         return SU.warn("Listitem in outer space")
      end
      SILE.call("noindent")
      for _ = 1, #ctx - 1 do
         SILE.call("qquad")
      end -- Setting lskip better?
      SILE.process(content)
      SILE.call("medskip")
   end)

   self:registerCommand("link", function (options, content)
      SILE.process(content)
      if options["xl:href"] then
         SILE.typesetter:typeset(" (")
         SILE.call("code", {}, { options["xl:href"] })
         SILE.typesetter:typeset(")")
      end
   end)
end

return class
