luametalatex = luametalatex or {}
local lmlt = luametalatex
local initex = status.ini_version

if initex then
  lua.prepared_code = {false}
end

bit32 = require'luametalatex-bit32' -- Why? And why so early?
status.init_kpse = 1 -- Why?
status.safer_option = 0 -- compat
status.shell_escape = 0 -- compat -- This is currently a lie.
-- require'module' -- ???
pdf = {
  variable = {},
}
require'luametalatex-font-resolve' -- Replace font.define. Must be loaded before callbacks
require'luametalatex-basecallbacks'
require'luametalatex-oldnames'
local callbacks = require'luametalatex-callbacks'

local function swap_table(t)
  local s = {}
  for k, v in next, t do
    s[v] = k
  end
  return s
end

local primitives = {}
do
  local token_primitives = token.getprimitives()
  local token_new = token.new
  for i=1,#token_primitives do
    local prim = token_primitives[i]
    primitives[prim[3]] = token_new(prim[2], prim[1])
  end
end
lmlt.primitive_tokens = primitives

do
  local command_id = swap_table(token.getcommandvalues())
  function token.command_id(name) return command_id[name] end
end
lmlt.value = swap_table(token.getfunctionvalues())
lmlt.flag = swap_table(tex.getflagvalues())

local functions = lua.getfunctionstable()
-- I am not sure why this is necessary, but otherwise LuaMetaTeX resets
-- the functions table every time the getter is called
function lua.get_functions_table() return functions end
local set_lua = token.set_lua
-- There are two approaches to manage luafunctions ids without triggering
-- issues with ltluatex assigned numbers: Originally we assigned numbers
-- starting with 1, then switched to luatexbase ASAP and synchronised both
-- numbering schemes. But there is a useful quirk in ltluatex's luafunction
-- allocator: It only uses numbers upto 65535, so we can just use bigger
-- numbers. (This might have negative repercussins on performance because it
-- probably doesn't store the function in the array part of the Lua table.
-- Let's reconsider if this ever becomes a problem.
-- local new_luafunction = luatexbase.new_luafunction
local predefined_luafunctions = initex and 65536 -- 1<<16  -- We start with 1<<16 + 1 (1<<16=65536 is reserved for luametalatex-local)
local new_luafunction
function new_luafunction(name)
  if predefined_luafunctions then
    predefined_luafunctions = predefined_luafunctions + 1
    return predefined_luafunctions
  else
    error[[Here only preexisting luafunctions should be set]]
  end
end
local undefined_cmd = token.command_id'undefined_cs'
local lua_call_cmd = token.command_id'lua_call'
local lua_value_cmd = token.command_id'lua_value'
local lua_protected_call_cmd = token.command_id'lua_protected_call'
local if_test_cmd = token.command_id'if_test'
function lmlt.luacmd(name, func, ...)
  local idx
  local tok = token.create(name)
  local cmd = tok.command
  if cmd == lua_value_cmd then
    idx = tok.index
  elseif cmd == lua_call_cmd then
    idx = tok.index
  elseif cmd == lua_protected_call_cmd then
    idx = tok.index
  elseif cmd == if_test_cmd and tok.index > 53 then
    idx = tok.index - 53
  elseif ... == 'force' then
    idx = new_luafunction(name)
    set_lua(name, idx, select(2, ...))
  elseif cmd == undefined_cmd then
    idx = new_luafunction(name)
    set_lua(name, idx, ...)
  else
    error(tok.cmdname)
  end
  if functions[idx] then
    error[[Already defined]]
  end
  functions[idx] = func
  return idx
end

if initex then
  local build_bytecode = nil -- To be filled
  function callbacks.pre_dump()
    local user_callback = callbacks.pre_dump
    if user_callback then user_callback() end

    local prepared = lua.prepared_code
    prepared[1] = string.format("fixupluafunctions(%i)", predefined_luafunctions)
    --[[
    for i=0,0 do -- maybeFIXME: In practise only one language is preloaded in LuaTeX anyway
    -- for i=0,tex.count[19] do -- Sometimes catches reserved language ids which are not used yet
    -- for i=0,lang.new():id()-1 do -- lang.new():id() is always 0 in luametatex?!?
      local l = language.new(i)
      local str = string.format("do \z
        local l = language.new(%i)\z
        l:hyphenationmin(%i)\z
        l:prehyphenchar(%i)\z
        l:posthyphenchar(%i)\z
        l:preexhyphenchar(%i)\z
        l:postexhyphenchar(%i)",
        i,
        l:hyphenationmin(),
        l:prehyphenchar(),
        l:posthyphenchar(),
        l:preexhyphenchar(),
        l:postexhyphenchar())
      local patterns = l:patterns()
      local exceptions = l:hyphenation()
      if patterns and exceptions then
        str = string.format("%sl:patterns(%q)l:hyphenation(%q)end", str, patterns, exceptions)
      elseif patterns then
        str = string.format("%sl:patterns(%q)end", str, patterns)
      elseif exceptions then
        str = string.format("%sl:hyphenation(%q)end", str, exceptions)
      else
        str = str .. 'end'
      end
      prepared[#prepared+1] = str
    end
    ]]
    for i=2,#prepared do
      if type(prepared[i]) ~= 'string' then
        prepared[i] = assert(prepared[i]())
      end
    end
    lua.bytecode[tex.count[262]+1] = build_bytecode(table.concat(prepared, '\n'))
  end
  callbacks.__freeze('pre_dump', true)
  return function(f)
    build_bytecode = f
    return require'luametalatex-firstcode'
  end
else
  function fixupluafunctions(i)
    predefined_luafunctions = i
    fixupluafunctions = nil
  end
  return function(f)
    f()
    return require'luametalatex-firstcode'
  end
end
