local log = require "telescope.log"

local LinkedList = require "telescope.algos.linked_list"

local EntryManager = {}
EntryManager.__index = EntryManager

function EntryManager:new(max_results, set_entry, info)
  log.trace "Creating entry_manager..."

  info = info or {}
  info.looped = 0
  info.inserted = 0
  info.find_loop = 0

  -- state contains list of
  --    { entry, score }
  --    Stored directly in a table, accessed as [1], [2]
  set_entry = set_entry or function() end

  return setmetatable({
    linked_states = LinkedList:new { track_at = max_results },
    info = info,
    max_results = max_results,
    set_entry = set_entry,
    worst_acceptable_score = math.huge,
  }, self)
end

function EntryManager:num_results()
  return self.linked_states.size
end

function EntryManager:get_container(index)
  local count = 0
  for val in self.linked_states:iter() do
    count = count + 1

    if count == index then
      return val
    end
  end

  return {}
end

function EntryManager:get_entry(index)
  return self:get_container(index)[1]
end

function EntryManager:get_score(index)
  return self:get_container(index)[2]
end

function EntryManager:get_ordinal(index)
  return self:get_entry(index).ordinal
end

function EntryManager:find_entry(entry)
  local info = self.info

  local count = 0
  for container in self.linked_states:iter() do
    count = count + 1

    if container[1] == entry then
      info.find_loop = info.find_loop + count

      return count
    end
  end

  info.find_loop = info.find_loop + count
  return nil
end

function EntryManager:_update_score_from_tracked()
  local linked = self.linked_states

  if linked.tracked then
    self.worst_acceptable_score = math.min(self.worst_acceptable_score, linked.tracked[2])
  end
end

function EntryManager:_insert_container_before(picker, index, linked_node, new_container)
  self.linked_states:place_before(index, linked_node, new_container)
  self.set_entry(picker, index, new_container[1], new_container[2], true)

  self:_update_score_from_tracked()
end

function EntryManager:_insert_container_after(picker, index, linked_node, new_container)
  self.linked_states:place_after(index, linked_node, new_container)
  self.set_entry(picker, index, new_container[1], new_container[2], true)

  self:_update_score_from_tracked()
end

function EntryManager:_append_container(picker, new_container, should_update)
  self.linked_states:append(new_container)
  self.worst_acceptable_score = math.min(self.worst_acceptable_score, new_container[2])

  if should_update then
    self.set_entry(picker, self.linked_states.size, new_container[1], new_container[2])
  end
end

function EntryManager:add_entry(picker, score, entry, prompt)
  score = score or 0

  local max_res = self.max_results
  local worst_score = self.worst_acceptable_score
  local size = self.linked_states.size

  local info = self.info
  info.maxed = info.maxed or 0

  local new_container = { entry, score }

  -- Short circuit for bad scores -- they never need to be displayed.
  --    Just save them and we'll deal with them later.
  if score >= worst_score then
    return self.linked_states:append(new_container)
  end

  -- Short circuit for first entry.
  if size == 0 then
    self.linked_states:prepend(new_container)
    self.set_entry(picker, 1, entry, score)
    return
  end

  for index, container, node in self.linked_states:ipairs() do
    info.looped = info.looped + 1

    if container[2] > score then
      return self:_insert_container_before(picker, index, node, new_container)
    end

    if score < 1 and container[2] == score and picker.tiebreak(entry, container[1], prompt) then
      return self:_insert_container_before(picker, index, node, new_container)
    end

    -- Don't add results that are too bad.
    if index >= max_res then
      info.maxed = info.maxed + 1
      return self:_append_container(picker, new_container, false)
    end
  end

  if self.linked_states.size >= max_res then
    self.worst_acceptable_score = math.min(self.worst_acceptable_score, score)
  end

  return self:_insert_container_after(picker, size + 1, self.linked_states.tail, new_container)
end

function EntryManager:iter()
  local iterator = self.linked_states:iter()
  return function()
    local val = iterator()
    if val then
      return val[1]
    end
  end
end

return EntryManager