-- mod-version:3

local core = require "core"
local common = require "core.common"
local command = require "core.command"
local keymap = require "core.keymap"
local Doc = require "core.doc"
local DocView = require "core.docview"

local navigate = {
  list = {},
  current = nil,
  index = 0
}

--
-- Private functions
--
local function get_active_view()
  if getmetatable(core.active_view) == DocView then
    return core.active_view
  end
  return nil
end

-- Solution to safely remove elements from array table:
-- found at https://stackoverflow.com/a/53038524
local function array_remove(t, fnKeep)
  local j, n = 1, #t;

  for i=1, n do
    if (fnKeep(t, i, j)) then
      if (i ~= j) then
        t[j] = t[i];
        t[i] = nil;
      end
      j = j + 1;
    else
      t[i] = nil;
    end
  end

  return t;
end

local function add(doc)
  -- Make new navigation point last in list
  if navigate.index > 0 and navigate.index < #navigate.list then
    local remove_start = navigate.index + 1
    local remove_end = #navigate.list
    array_remove(navigate.list, function(_, i)
      if i >= remove_start and i <= remove_end then
        return false
      end
      return true
    end)
  end

  local line, col = doc:get_selection()
  table.insert(navigate.list, {
    filename = doc.filename,
    line = line,
    col = col
  })

  navigate.current = navigate.list[#navigate.list]
  navigate.index = #navigate.list
end

local function open_doc(doc)
  core.root_view:open_doc(
    core.open_doc(
      common.home_expand(
        doc.filename
      )
    )
  )

  local av_doc = get_active_view().doc
  local line, col = av_doc:get_selection()
  if doc.line ~= line or doc.col ~= col then
    av_doc:set_selection(doc.line, doc.col, doc.line, doc.col)
  end
end

--
-- Public functions
--
function navigate.next()
  if navigate.index < #navigate.list then
    navigate.index = navigate.index + 1
    navigate.current = navigate.list[navigate.index]
    open_doc(navigate.current)
  end
end

function navigate.prev()
  if navigate.index > 1 then
    navigate.index = navigate.index - 1
    navigate.current = navigate.list[navigate.index]
    open_doc(navigate.current)
  end
end

--
-- Thread
--
core.add_thread(function()
  while true do
    local av = get_active_view()
    if av and av.doc and av.doc.filename then
      local doc = av.doc
      local line, col = doc:get_selection()
      local current = navigate.current
      if
        not current
        or
        current.filename ~= doc.filename
        or
        current.line ~= line
      then
        add(doc)
      else
        current.col = col
      end
    end
    coroutine.yield(0.5)
  end
end)

--
-- Patching
--
local doc_on_close = Doc.on_close

function Doc:on_close()
  local filename = self.filename
  -- remove all positions referencing closed file
  array_remove(navigate.list, function(t, i)
    if t[i].filename == filename then
      return false
    end
    return true
  end)

  doc_on_close(self)
end

--
-- Commands
--
command.add("core.docview", {
  ["navigate:previous"] = function()
    navigate.prev()
  end,

  ["navigate:next"] = function()
    navigate.next()
  end,
})

--
-- Default Keybindings
--
keymap.add {
  ["alt+left"]    = "navigate:previous",
  ["alt+right"]   = "navigate:next",
}

return navigate
