local sort = {}

local unicode = require("unicode")

local element = require("citeproc-element")
local names = require("citeproc-node-names")
local date = require("citeproc-node-date")
local util = require("citeproc-util")


local Sort = element.Element:new()

function Sort:sort (items, context)
  -- key_map = {
  --   id1 = {key1, key2, ...},
  --   id2 = {key1, key2, ...},
  --   ...
  -- }
  context.variable_attempt = {}

  local key_map = {}
  local sort_directions = {}
  -- true: ascending
  -- false: descending

  if not Sort.collator_obj then
    local lang = context.style.lang
    local language = string.sub(lang, 1, 2)
    -- It's 6 seconds slower to run the whole test-suite if these package
    -- loading statements are put in the header.
    local ducet = require("lua-uca.lua-uca-ducet")
    local collator = require("lua-uca.lua-uca-collator")
    local languages = require("lua-uca.lua-uca-languages")
    local collator_obj = collator.new(ducet)
    if languages[language] then
      Sort.collator_obj = languages[language](collator_obj)
    else
      util.warning(string.format('Lcoale "%s" is not supported.', lang))
    end
  end

  for _, item in ipairs(items) do
    if not key_map[item.id] then
      key_map[item.id] = {}

      context.item = item
      for i, key in ipairs(self:query_selector("key")) do
        if sort_directions[i] == nil then
          local direction = (key:get_attribute("sort") ~= "descending")
          sort_directions[i] = direction
        end
        local value = key:render(item, context)
        table.insert(key_map[item.id], value)
      end
    end
  end

  -- util.debug(key_map)

  local function compare_entry(item1, item2)
    return self.compare_entry(key_map, sort_directions, item1, item2)
  end
  table.sort(items, compare_entry)

  return items
end

function Sort.compare(value1, value2)
  if type(value1) == "string" then
    return Sort.compare_strings(value1, value2)
  else
    return value1 < value2
  end
end

function Sort.compare_strings(str1, str2)
  if Sort.collator_obj then
    return Sort.collator_obj:compare_strings(str1, str2)
  else
    return str1 < str2
  end
end

function Sort.compare_entry(key_map, sort_directions, item1, item2)
  for i, value1 in ipairs(key_map[item1.id]) do
    local ascending = sort_directions[i]
    local value2 = key_map[item2.id][i]
    if value1 and value2 then
      local res
      if ascending then
        res = Sort.compare(value1, value2)
      else
        res = Sort.compare(value2, value1)
      end
      if res or value1 ~= value2 then
        return res
      end
    elseif value1 then
      return true
    elseif value2 then
      return false
    end
  end
end

local Key = element.Element:new()

function Key:render (item, context)
  context = self:process_context(context)
  context.options["name-as-sort-order"] = "all"
  context.sorting = true
  local variable = self:get_attribute("variable")
  local res = nil
  if variable then
    context.variable = variable
    local variable_type = util.variable_types[variable]
    if variable_type == "name" then
      res = self:_render_name(item, context)
    elseif variable_type == "date" then
      res = self:_render_date(item, context)
    elseif variable_type == "number" then
      res = item[variable]
    else
      res = item[variable]
    end
  else
    local macro = self:get_attribute("macro")
    if macro then
      res = self:get_macro(macro):render(item, context)
    end
  end
  if res == nil then
    res = false
  elseif type(res) == "table" and res._type == "RichText" then
    res = res:render(nil, context)
  end
  if type(res) == "string" then
    res = self._normalize_string(res)
  end
  return res
end

function Key:_render_name (item, context)
  if not self.names then
    self.names = self:create_element("names", {}, self)
    names.Names:set_base_class(self.names)
    self.names:set_attribute("variable", context.options["variable"])
    self.names:set_attribute("form", "long")
  end
  local res = self.names:render(item, context)
  return res
end

function Key:_render_date (item, context)
  if not self.date then
    self.date = self:create_element("date", {}, self)
    date.Date:set_base_class(self.date)
    self.date:set_attribute("variable", context.options["variable"])
    self.date:set_attribute("form", "numeric")
  end
  local res = self.date:render(item, context)
  return res
end
function Key._normalize_string(str)
  str = unicode.utf8.lower(str)
  str = string.gsub(str, "[%[%]]", "")
  local words = {}
  for _, word in ipairs(util.split(str, " ")) do
    -- TODO: strip leading prepositions
    -- remove leading apostrophe on name particle
    word = string.gsub(word, "^" .. util.unicode["apostrophe"], "")
    table.insert(words, word)
  end
  str = table.concat(words, " ")
  str = string.gsub(str, util.unicode["apostrophe"], "'")
  return str
end


sort.Sort = Sort
sort.Key = Key

return sort
