--- spelling-stage-4.lua
--- Copyright 2012, 2013 Stephan Hennig
--
-- This work may be distributed and/or modified under the conditions of
-- the LaTeX Project Public License, either version 1.3 of this license
-- or (at your option) any later version.  The latest version of this
-- license is in http://www.latex-project.org/lppl.txt
-- and version 1.3 or later is part of all distributions of LaTeX
-- version 2005/12/01 or later.
--
-- See file README for more information.
--


--- At the end of a LuaTeX run, write the text stored in a text document
--- data structure to a file.
-- This module provides means to write the text stored in a text
-- document data structure to a file at the end of a LuaTeX run.
--
-- @author Stephan Hennig
-- @copyright 2012, 2013 Stephan Hennig
-- @release version 0.41
--
-- @trick Prevent LuaDoc from looking past here for module description.
--[[ Trick LuaDoc into entering 'module' mode without using that command.
module(...)
--]]


-- Module table.
local M = {}


-- Import external modules.
local unicode = require('unicode')


-- Function short-cuts.
local tabconcat = table.concat
local tabinsert = table.insert

local Ulen = unicode.utf8.len


-- Declare local variables to store references to resources that are
-- provided by external code.
--
-- Text document data structure.
local __text_document


--- Module options.
-- This table contains all module options.  User functions to set
-- options are provided.
--
-- @class table
-- @name __opts
-- @field output_file_name  Output file name.
-- @field output_line_length  Line length in output.
local __opts = {
  output_file_name,
  output_line_lenght,
}


--- Set output file name.
-- Text output will be written to a file with the given name.
--
-- @param name  New output file name.
local function set_output_file_name(name)
  __opts.output_file_name = name
end
M.set_output_file_name = set_output_file_name


--- Set output line length.
-- Set the number of columns in text output.  Text output will be
-- wrapped at spaces so that lines don't contain more than the specified
-- number of characters per line.  There's one exception: if a word is
-- longer than the specified number of characters, the word is put on
-- its own line and that line will be overfull.
--
-- @param cols  New line length in output.  If the argument is smaller
-- than 1, lines aren't wrapped, i.e., all text of a paragraph is put on
-- a single line.
local function set_output_line_length(cols)
  __opts.output_line_length = cols
end
M.set_output_line_length = set_output_line_length


--- Break a text paragraph into lines.
-- Lines are broken at spaces only.  Lines containing only one word may
-- exceed maximum line length.
--
-- @param par  A text paragraph (an array of words).
-- @param max_line_len Maximum length of lines in wrapped paragraph.  If
-- the argument is less then 1, paragraph isn't wrapped at all.
-- @return Table containing the lines of the paragraph.
local function __wrap_text_paragraph(par, max_line_len)
  local wrapped_par = {}
  -- Index of first word on current line.  Initialize current line with
  -- first word of paragraph.
  local line_start = 1
  -- Track current line length.
  local line_len = Ulen(par[line_start])
  -- Iterate over remaining words in paragraph.
  for i = 2,#par do
    local word_len = Ulen(par[i])
    local new_line_len = line_len + 1 + word_len
    -- Maximum line length exceeded?
    if new_line_len > max_line_len and max_line_len >= 1 then
      -- Insert current line into wrapped paragraph.
      tabinsert(wrapped_par, tabconcat(par, ' ', line_start, i-1))
      -- Initialize new current line.
      line_start = i
      new_line_len = word_len
    end
    -- Append word to current line.
    line_len = new_line_len
  end
  -- Insert last line of paragraph.
  tabinsert(wrapped_par, tabconcat(par, ' ', line_start))
  return wrapped_par
end


--- Write all text stored in the text document to a file.
--
local function __write_text_document()
  -- Open output file.
  local fname = __opts.output_file_name or (tex.jobname .. '.spell.txt')
  local f = assert(io.open(fname, 'w'))
  local max_line_len = __opts.output_line_length
  -- Iterate through document paragraphs.
  for _,par in ipairs(__text_document) do
    -- Write wrapped paragraph to file with a leading empty line.
    f:write('\n', tabconcat(__wrap_text_paragraph(par, max_line_len), '\n'), '\n')
    -- Delete paragraph from memory.
    __text_document[_] = nil
  end
  -- Close output file.
  f:close()
end


--- Callback function that writes all document text into a file.
local function __cb_stopr_pkg_spelling()
  __write_text_document()
end


-- Call-back status.
local __is_active_output


--- Enable text document output.
-- Registers call-back `stop_run` to output the text stored in the text
-- document at the end of the LuaTeX run.
local function enable_text_output()
  if not __is_active_output then
    -- Register call-back: At the end of the LuaTeX run, output all text
    -- stored in the text document.
    luatexbase.add_to_callback('stop_run', __write_text_document, '__cb_stop_run_pkg_spelling')
    __is_active_output = true
  end
end
M.enable_text_output = enable_text_output


--- Disable text document output.
-- De-registers call-back `stop_run`.
local function disable_text_output()
  if __is_active_output then
    -- De-register call-back.
    luatexbase.remove_from_callback('stop_run', '__cb_stop_run_pkg_spelling')
    __is_active_output = false
  end
end
M.disable_text_output = disable_text_output


--- Module initialisation.
--
local function __init()
  -- Get local references to package ressources.
  __text_document = PKG_spelling.res.text_document
  -- Set default output file name.
  set_output_file_name(nil)
  -- Set default output line length.
  set_output_line_length(72)
  -- Remember call-back status.
  __is_active_output = false
end


-- Initialize module.
__init()


-- Return module table.
return M