local m = {}

local function get_filename(chunk)
  local filename = chunk:match("([^\n^%(]+)") 
  if not filename then 
    return false, "No filename detected"
  end
  local first = filename:match("^[%./\\]+")
  if first then return filename end
  return false
end

local function get_chunks(text)
  -- parse log for particular included files
  local chunks = {}
  -- each file is enclosed in matching () brackets
  local newtext = text:gsub("(%b())", function(a)
    local chunk = string.sub(a,2,-2)
    -- if no filename had been found in the chunk, it is probably not file chunk
    -- so just return the original text
    local filename = get_filename(chunk)
    if not filename then return a end
    local children, text = get_chunks(chunk)
    table.insert(chunks, {filename = filename, text = text, children = children})
    return ""
  end)
  return chunks, newtext
end


function print_chunks(chunks, level)
  local level = level or 0
  local indent = string.rep("  ", level)
  for k,v in ipairs(chunks) do
    print(indent .. (v.filename or "?"), string.len(v.text))
    print_chunks(v.children, level + 1)
  end
end

local function parse_default_error(lines, i)
  local line = lines[i]
  -- get the error message "! msg text"
  local err = line:match("^!(.+)")
  -- the next line should contain line number where error happened
  local next_line = lines[i+1] or ""
  local msg = {}
  -- get the line number and first line of the error context
  local line_no, msg_start = next_line:match("^l%.(%d+)(.+)") 
  line_no = line_no or false 
  msg_start = msg_start or ""
  msg[#msg+1] = msg_start .. " <-"
  -- try to find rest of the error context. 
  for x = i+2, i+5 do
    local next_line = lines[x] or ""
    -- break on blank lines
    if next_line:match("^%s*$") then break end
    msg[#msg+1] = next_line:gsub("^%s*", ""):gsub("%s$", "")
  end
  return err, line_no, table.concat(msg, " ")
end

local  function parse_linenumber_error(lines, i)
  -- parse errors from log created with the -file-line-number option
  local line = lines[i]
  local filename, line_no, err = line:match("^([^%:]+)%:(%d+)%:%s*(.*)")
  local msg = {}
  -- get error context
  for x = i+1, i+2 do
    local next_line = lines[x] or ""
    -- break on blank lines
    if next_line:match("^%s*$") then break end
    msg[#msg+1] = next_line:gsub("^%s*", ""):gsub("%s$", "")
  end
  -- insert mark to the error
  if #msg > 1 then
    table.insert(msg, 2, "<-")
  end
  return err, line_no, table.concat(msg, " ")
end

--- get error messages, linenumbers and contexts from a log file chunk
---@param text string chunk from the long file where we should find errors
---@return table errors error messages
---@return table error_lines error line number 
---@return table error_messages error line contents
local function parse_errors(text)
  local lines = {}
  local errors = {}
  local find_line_no = false
  local error_lines = {}
  local error_messages = {}
  for line in text:gmatch("([^\n]+)") do
    lines[#lines+1] = line
  end
  for i = 1, #lines do
    local line = lines[i]
    local err, line_no, msg
    if line:match("^!(.+)") then
      err, line_no, msg = parse_default_error(lines, i)
    elseif line:match("^[^%:]+%:%d+%:.+") then
      err, line_no, msg = parse_linenumber_error(lines, i)
    end
    if err then
      errors[#errors+1] = err
      error_lines[#errors] = line_no
      error_messages[#errors] = msg
    end
  end
  return errors, error_lines, error_messages
end


local function get_errors(chunks, errors)
  local errors =  errors or {}
  for _, v in ipairs(chunks) do
    local current_errors, error_lines, error_contexts = parse_errors(v.text)
    for i, err in ipairs(current_errors) do
      table.insert(errors, {filename = v.filename, error = err, line = error_lines[i], context = error_contexts[i] })
    end
    errors = get_errors(v.children, errors)
  end
  return errors
end

function m.get_missing_4ht_files(log)
  local used_files = {}
  local used_4ht_files = {}
  local missing_4ht_files = {}
  local pkg_names = {sty=true, cls=true}
  for filename, ext in log:gmatch("[^%s]-([^%/^%\\^%.%s]+)%.([%w][%w]+)") do
    -- break ak
    if ext == "aux" then break end
    if pkg_names[ext] then
      used_files[filename .. "." .. ext] = true
    elseif ext == "4ht" then
      used_4ht_files[filename] = true
    end
  end
  for filename, _ in pairs(used_files) do
    if not used_4ht_files[mkutils.remove_extension(filename)] then
      table.insert(missing_4ht_files, filename)
    end
  end
  return missing_4ht_files
end


function m.parse(log)
  local chunks, newtext = get_chunks(log)
  -- save the unparsed text that contains system messages
  table.insert(chunks, {text = newtext, children = {}})
  -- print_chunks(chunks)
  local errors = get_errors(chunks)
  -- for _,v in ipairs(errors) do 
    -- print("error", v.filename, v.line, v.error)
  -- end
  return errors, chunks
end


m.print_chunks = print_chunks

return m