--
-- Copyright (c) 2021-2022 Zeping Lee
-- Released under the MIT license.
-- Repository: https://github.com/zepinglee/citeproc-lua
--

local text = {}

local element = require("citeproc-element")
local richtext = require("citeproc-richtext")
local util = require("citeproc-util")


local Text = element.Element:new()

function Text:render (item, context)
  self:debug_info(context)
  context = self:process_context(context)

  local res = nil

  local variable = nil
  local variable_name = self:get_attribute("variable")
  if variable_name then
    local form = self:get_attribute("form")
    if form == "short" then
      variable = self:get_variable(item, variable_name  .. "-" .. form, context)
    end
    if not variable then
      variable = self:get_variable(item, variable_name, context)
    end
    if variable then
      res = variable
      if type(res) == "number" then
        res = tostring(res)
      end
      if variable_name == "page" or variable_name == "locator" then
        res = util.lstrip(res)
        res = self:_format_page(res, context)
      end
    end

    table.insert(context.variable_attempt, res ~= nil)
  end

  local macro_name = self:get_attribute("macro")
  if macro_name then
    local macro = self:get_macro(macro_name)
    res = macro:render(item, context)
  end

  local term_name = self:get_attribute("term")
  if term_name then
    local form = self:get_attribute("form")

    local term = self:get_term(term_name, form)
    if term then
      res = term:render(context)
    end
  end

  local value = self:get_attribute("value")
  if value then
    res = value
    res = self:escape(res)
  end

  if type(res) == "string" and res ~= "" then
    res = richtext.new(res)
  end

  if res and variable_name == "URL" then
    res:add_format("URL", "true")
  end

  res = self:strip_periods(res, context)
  res = self:case(res, context)
  res = self:format(res, context)
  res = self:quote(res, context)
  res = self:wrap(res, context)
  res = self:display(res, context)

  if variable_name == "citation-number" then
    res = self:_process_citation_number(variable, res, context)
  end

  return res
end


function Text:_process_citation_number(citation_number, res, context)
  if context.mode == "citation" and not context.sorting and context.options["collapse"] == "citation-number" then
    context.build.item_citation_numbers[context.item.id] = citation_number
    if type(res) == "string" then
      res = richtext.new(res)
    end
    table.insert(context.build.item_citation_number_text, res)
  end
  return res
end


function Text:_format_page (page, context)
  local res = nil

  local page_range_delimiter = self:get_term("page-range-delimiter"):render(context) or util.unicode["en dash"]
  local page_range_format = context.options["page-range-format"]
  if page_range_format == "chicago" then
    if self:get_style():get_version() >= "1.1" then
      page_range_format = "chicago-16"
    else
      page_range_format = "chicago-15"
    end
  end

  local last_position = 1
  local page_parts = {}
  local punct_list = {}
  for part, punct, pos in string.gmatch(page, "(.-)%s*([,&])%s*()") do
    table.insert(page_parts, part)
    table.insert(punct_list, punct)
    last_position = pos
  end
  table.insert(page_parts, string.sub(page, last_position))

  res = ""
  for i, part in ipairs(page_parts) do
    res = res .. self:_format_range(part, page_range_format, page_range_delimiter)
    local punct = punct_list[i]
    if punct then
      if punct == "&" then
        res = res .. " " .. punct .. " "
      else
        res = res .. punct .. " "
      end
    end
  end
  res = self:escape(res)
  return res
end

function Text:_format_range (str, format, range_delimiter)
  local start, delimiter, stop = string.match(str, "(%w+)%s*(%-+)%s*(%S*)")
  if not stop or stop == "" then
    return string.gsub(str, "\\%-", "-")
  end


  local start_prefix, start_num  = string.match(start, "(.-)(%d*)$")
  local stop_prefix, stop_num = string.match(stop, "(.-)(%d*)$")

  if start_prefix ~= stop_prefix then
    -- Not valid range: "n11564-1568" -> "n11564-1568"
    -- 110-N6
    -- N110-P5
    return start .. delimiter .. stop
  end

  if format == "chicago-16" then
    stop = self:_format_range_chicago_16(start_num, stop_num)
  elseif format == "chicago-15" then
    stop = self:_format_range_chicago_15(start_num, stop_num)
  elseif format == "expanded" then
    stop = stop_prefix .. self:_format_range_expanded(start_num, stop_num)
  elseif format == "minimal" then
    stop = self:_format_range_minimal(start_num, stop_num)
  elseif format == "minimal-two" then
    stop = self:_format_range_minimal(start_num, stop_num, 2)
  end

  return start .. range_delimiter .. stop
end

function Text:_format_range_chicago_16(start, stop)
  if #start < 3 or string.sub(start, -2) == "00" then
    return self:_format_range_expanded(start, stop)
  elseif string.sub(start, -2, -2) == "0" then
    return self:_format_range_minimal(start, stop)
  else
    return self:_format_range_minimal(start, stop, 2)
  end
  return stop
end

function Text:_format_range_chicago_15(start, stop)
  if #start < 3 or string.sub(start, -2) == "00" then
    return self:_format_range_expanded(start, stop)
  else
    local changed_digits = self:_format_range_minimal(start, stop)
    if string.sub(start, -2, -2) == "0" then
      return changed_digits
    elseif #start == 4 and #changed_digits == 3 then
      return self:_format_range_expanded(start, stop)
    else
      return self:_format_range_minimal(start, stop, 2)
    end
  end
  return stop
end

function Text:_format_range_expanded(start, stop)
  -- Expand  "1234–56" -> "1234–1256"
  if #start <= #stop then
    return stop
  end
  return string.sub(start, 1, #start - #stop) .. stop
end

function Text:_format_range_minimal(start, stop, threshold)
  threshold = threshold or 1
  if #start < #stop then
    return stop
  end
  local offset = #start - #stop
  for i = 1, #stop - threshold do
    local j = i + offset
    if string.sub(stop, i, i) ~= string.sub(start, j, j) then
      return string.sub(stop, i)
    end
  end
  return string.sub(stop, -threshold)
end


text.Text = Text

return text
