SILE = {}
SILE.version = "0.9.4"
SILE.utilities = require("core/utilities")
SU = SILE.utilities
SILE.inputs = {}
SILE.Commands = {};
SILE.debugFlags = {}
SILE.nodeMakers = {}
SILE.tokenizers = {}

loadstring = loadstring or load -- 5.3 compatibility
if not unpack then unpack = table.unpack end -- 5.3 compatibility
std = require("std")
lfs = require("lfs")
if (os.getenv("SILE_COVERAGE")) then require("luacov") end

SILE.documentState = std.object {};
SILE.scratch = {};
SILE.length = require("core/length")
require("core/parserbits")
require("core/measurements")
require("core/baseclass")
SILE.nodefactory = require("core/nodefactory")
require("core/settings")
require("core/inputs-texlike")
require("core/inputs-xml")
require("core/inputs-common")
require("core/papersizes")
require("core/colorparser")
require("core/pagebuilder")
require("core/typesetter")
require("core/hyphenator-liang")
require("core/languages")
require("core/font")

SILE.frameParser = require("core/frameparser")
SILE.linebreak = require("core/break")

require("core/frame")

SILE.init = function()
  if not SILE.backend then
    if pcall(function () require("justenoughharfbuzz") end) then
      SILE.backend = "libtexpdf"
    else
      SU.error("libtexpdf backend not available!")
    end
  end
  if SILE.backend == "libtexpdf" then
    require("core/harfbuzz-shaper")
    require("core/libtexpdf-output")
  end
  if SILE.dolua then
    _, err = pcall(SILE.dolua)
    if err then error(err) end
  end
end

SILE.require = function(d)
  local f = SILE.resolveFile(d..".lua")
  if f then return require(f:gsub(".lua$","")) end
  return require(d)
end

SILE.parseArguments = function()
local parser = std.optparse ("SILE "..SILE.version..[[

Usage: sile [options] file.sil|file.xml

The SILE typesetter reads a single input file in either SIL or XML format to
generate an output in PDF format. The output will be writted to the same name
as the input file with the extention changed to .pdf.

Options:

  -b, --backend=VALUE      choose an alternative output backend
  -d, --debug=VALUE        debug SILE's operation
  -e, --evaluate=VALUE     evaluate some Lua code before processing file
  -o, --output=[FILE]      explicitly set output file name
  -I, --include=[FILE]     include a class or SILE file before processing input
      --help               display this help, then exit
      --version            display version information, then exit
]])

  parser:on ('--', parser.finished)
  _G.unparsed, _G.opts = parser:parse(_G.arg)
  -- Turn slashes around in the event we get passed a path from a Windows shell
  if _G.unparsed[1] then
    SILE.masterFilename = _G.unparsed[1]:gsub("\\", "/")
  end
  SILE.debugFlags = {}
  if opts.backend then
    SILE.backend = opts.backend
  end
  if opts.debug then
    for k,v in ipairs(std.string.split(opts.debug, ",")) do SILE.debugFlags[v] = 1 end
  end
  if opts.evaluate then
    SILE.dolua,err = loadstring(opts.evaluate)
    if err then SU.error(err) end
  end
  if opts.output then
    SILE.outputFilename = opts.output
  end
  if opts.include then
    SILE.preamble = opts.include
  end
end

function SILE.initRepl ()
  SILE._repl          = require 'repl.console'
  local has_linenoise = pcall(require, 'linenoise')

  if has_linenoise then
    SILE._repl:loadplugin 'linenoise'
  else
    -- XXX check that we're not receiving input from a non-tty
    local has_rlwrap = os.execute('which rlwrap >/dev/null 2>/dev/null') == 0

    if has_rlwrap and not os.getenv 'LUA_REPL_RLWRAP' then
      local command = 'LUA_REPL_RLWRAP=1 rlwrap'
      local index = 0
      while arg[index - 1] do
        index = index - 1
      end
      while arg[index] do
        command = string.format('%s %q', command, arg[index])
        index = index + 1
      end
      os.execute(command)
      return
    end
  end

  SILE._repl:loadplugin 'history'
  SILE._repl:loadplugin 'completion'
  SILE._repl:loadplugin 'autoreturn'
  SILE._repl:loadplugin 'rcfile'
end

function SILE.repl()
  if not SILE._repl then SILE.initRepl() end
  SILE._repl:run()
end

function SILE.readFile(fn)
  SILE.currentlyProcessingFile = fn
  fn = SILE.resolveFile(fn)
  if not fn then
    SU.error("Could not find file")
  end
  if lfs.attributes(fn).mode ~= "file" then
    SU.error(fn.." isn't a file, it's a "..lfs.attributes(fn).mode.."!")
  end
  local file, err = io.open(fn)
  if not file then
    print("Could not open "..fn..": "..err)
    return
  end
  io.write("<"..fn..">\n")
  -- Sniff first few bytes
  local sniff = file:read("*l") or ""
  file:seek("set", 0)
  local inputsOrder = {}
  for n in pairs(SILE.inputs) do
    if SILE.inputs[n].order then table.insert(inputsOrder, n) end
  end
  table.sort(inputsOrder,function(a,b) return SILE.inputs[a].order < SILE.inputs[b].order end)
  for i = 1,#inputsOrder do local input = SILE.inputs[inputsOrder[i]]
    if input.appropriate(fn, sniff) then
      input.process(fn)
      return
    end
  end
  SU.error("No input processor available for "..fn.." (should never happen)",1)
end

local function file_exists(name)
   local f=io.open(name,"r")
   if f~=nil then io.close(f) return true else return false end
end

function SILE.resolveFile(fn)
  if file_exists(fn) then return fn end
  if file_exists(fn..".sil") then return fn..".sil" end
  if not SILE.masterFilename then return nil end

  local dirname = SILE.masterFilename:match("(.-)[^%/]+$")
  for k in SU.gtoke(dirname..";"..tostring(os.getenv("SILE_PATH")), ";") do
    if k.string then
      local f = std.io.catfile(k.string, fn)
      if file_exists(f) then return f end
      if file_exists(f..".sil") then return f..".sil" end
    end
  end
end

function SILE.call(cmd,options, content)
  SILE.currentCommand = content
  if not SILE.Commands[cmd] then SU.error("Unknown command "..cmd) end
  SILE.Commands[cmd](options or {}, content or {})
end

return SILE
