local path = require 'pl.path'

local ok, moonscript, line_tables, util = pcall(function()
  return require 'moonscript', require 'moonscript.line_tables', require 'moonscript.util'
end)

local _cache = {}

-- find the line number of `pos` chars into fname
local lookup_line = function(fname, pos)
  if not _cache[fname] then
    local f = io.open(fname)
    _cache[fname] = f:read('*a')
    f:close()
  end

  return util.pos_to_line(_cache[fname], pos)
end

local rewrite_linenumber = function(fname, lineno)
  local tbl = line_tables['@' .. fname]
  if fname and tbl then
    for i = lineno, 0 ,-1 do
      if tbl[i] then
        return lookup_line(fname, tbl[i])
      end
    end
  end

  return lineno
end

local rewrite_filename = function(filename)
  -- sometimes moonscript gives files like [string "./filename.moon"], so
  -- we'll chop it up to only get the filename.
  return filename:match('string "(.+)"') or filename
end

local rewrite_traceback = function(fname, trace)
  local rewrite_one = function(line, pattern, sub)
    if line == nil then return '' end

    local fname, lineno = line:match(pattern)

    if fname and lineno then
      fname = rewrite_filename(fname)
      local new_lineno = rewrite_linenumber(fname, tonumber(lineno))
      if new_lineno then
        line = line:gsub(sub:format(tonumber(lineno)), sub:format(tonumber(new_lineno)))
      end
    end

    return line
  end

  local lines = {}
  local j = 0

  for line in trace:gmatch('[^\r\n]+') do
    j = j + 1
    local newline = rewrite_one(line, '%s*(.-):(%d+): ', ':%d:')
    newline = rewrite_one(newline, '<(.*):(%d+)>', ':%d>')
    lines[j] = newline
  end

  return '\n' .. table.concat(lines, trace:match('[\r\n]+')) .. '\n'
end

local ret = {}

local getTrace = function(filename, info)
  local index = info.traceback:find('\n%s*%[C]')
  info.traceback = info.traceback:sub(1, index)

  info.short_src = rewrite_filename(info.short_src)
  info.traceback = rewrite_traceback(filename, info.traceback)
  info.linedefined = rewrite_linenumber(filename, info.linedefined)
  info.currentline = rewrite_linenumber(filename, info.currentline)

  return info
end

local rewriteMessage = function(filename, message)
  local fname, line, msg = message:match('^([^\n]-):(%d+): (.*)')
  if not fname then
    return message
  end

  fname = rewrite_filename(fname)
  line = rewrite_linenumber(fname, tonumber(line))

  return fname .. ':' .. tostring(line) .. ': ' .. msg
end

ret.match = function(busted, filename)
  return ok and path.extension(filename) == '.moon'
end

ret.load = function(busted, filename)
  local file, err = moonscript.loadfile(filename)
  if not file then
    busted.publish({ 'error', 'file' }, { descriptor = 'file', name = filename }, nil, err, {})
  end
  return file, getTrace, rewriteMessage
end

return ret
