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

local choose = {}

local Element = require("citeproc-element").Element
local SeqIr = require("citeproc-ir-node").SeqIr
local util = require("citeproc-util")

-- [Choose](https://docs.citationstyles.org/en/stable/specification.html#choose)
local Choose = Element:derive("choose")

function Choose:from_node(node)
  local o = Choose:new()
  o.children = {}
  o:process_children_nodes(node)
  if #o.children == 0 then
    return nil
  end
  return o
end

function Choose:build_ir(engine, state, context)
  local branch
  local branch_ir
  for _, child in ipairs(self.children) do
    if child:evaluate_conditions(engine, state, context) then
      branch = child
      branch_ir = child:build_ir(engine, state, context)
      break
    end
  end

  if not branch_ir then
    branch_ir = SeqIr:new({}, self)
    branch_ir.group_var = "missing"
  end

  local ir = SeqIr:new({branch_ir}, self)
  ir.group_var = branch_ir.group_var
  ir.name_count = branch_ir.name_count
  ir.sort_key = branch_ir.sort_key

  if not context.disambiguate then
    context.disambiguate = true

    for _, child in ipairs(self.children) do
      if child:evaluate_conditions(engine, state, context) then
        if child ~= branch then
          ir.disambiguate_branch_ir = child:build_ir(engine, state, context)
        end
        break
      end
    end

    context.disambiguate = false
  end

  return ir
end


local Condition = {
  condition = nil,
  value = nil,
  match_type = "all",
}

function Condition:new(condition, value, match_type)
  local o = {
    condition = condition,
    value = value,
    match_type = match_type or "all"
  }
  setmetatable(o, self)
  self.__index = self
  return o
end


local If = Element:derive("if", {
  conditions = nil,
  match = "all"
})

function If:new()
  local o = Element.new(self)
  o.conditions = {}
  o.match = "all"
  return o
end

function If:from_node(node)
  local o = If:new()
  -- TODO: disambiguate
  -- o:set_bool_attribute(node, "disambiguate")

  o:add_conditions(node, "disambiguate")
  o:add_conditions(node, "is-numeric")
  o:add_conditions(node, "is-uncertain-date")
  o:add_conditions(node, "locator")
  o:add_conditions(node, "position")
  o:add_conditions(node, "type")
  o:add_conditions(node, "variable")

  o.match = node:get_attribute("match")

  o:process_children_nodes(node)

  return o
end

function If:add_conditions(node, attribute)
  local values = node:get_attribute(attribute)
  if not values then
    return
  end
  for _, value in ipairs(util.split(values)) do
    local condition = Condition:new(attribute, value, self.match)
    table.insert(self.conditions, condition)
  end
end

function If:build_children_ir(engine, state, context)
  if not self.children then
    return nil
  end
  local irs = {}
  local name_count
  local ir_sort_key
  local group_var = "plain"

  for _, child_element in ipairs(self.children) do
    local child_ir = child_element:build_ir(engine, state, context)

    if child_ir then
      local child_group_var = child_ir.group_var
      if child_group_var == "important" then
        group_var = "important"
      elseif child_group_var == "missing" and child_ir._type ~= "YearSuffix" then
        if group_var == "plain" then
          group_var = "missing"
        end
      end

      if child_ir.name_count then
        if not name_count then
          name_count = 0
        end
        name_count = name_count + child_ir.name_count
      end

      if child_ir.sort_key ~= nil then
        ir_sort_key = child_ir.sort_key
      end

      -- The condition can be simplified
      table.insert(irs, child_ir)
    end
  end

  if #irs == 0 then
    group_var = "missing"
  end

  local ir = SeqIr:new(irs, self)
  ir.name_count = name_count
  ir.sort_key = ir_sort_key
  ir.group_var = group_var
  return ir
end

function If:build_ir(engine, state, context)
  if not self:evaluate_conditions(engine, state, context) then
    return nil
  end

  local ir = self:build_children_ir(engine, state, context)
  if not ir then
    return nil
  end

  ir.should_inherit_delim = true
  return ir
end

function If:evaluate_conditions(engine, state, context)
  local res = false
  -- util.debug(self.conditions)
  for _, condition in ipairs(self.conditions) do
    if self:evaluate_condition(condition, state, context) then
      if self.match == "any" then
        return true
      elseif self.match == "none" then
        return false
      end
    else
      if self.match == "all" then
        return false
      end
    end
  end
  if self.match == "any" then
    return false
  else
    return true
  end
end

function If:evaluate_condition(condition, state, context)
  -- util.debug(context.cite)
  -- util.debug(condition)
  if condition.condition == "disambiguate" then
    return context.disambiguate or (context.in_bibliography and context.reference.disambiguate)

  elseif condition.condition == "is-numeric" then
    local variable = context:get_variable(condition.value)
    return util.is_numeric(variable)

  elseif condition.condition == "is-uncertain-date" then
    local variable = context:get_variable(condition.value)
    -- TODO
    return self:is_uncertain_date(variable)

  elseif condition.condition == "locator" then
    local locator_label = context:get_variable("label")
    if locator_label == "sub verbo" then
      locator_label = "sub-verbo"
    end
    return locator_label == condition.value

  elseif condition.condition == "position" then
    return self:check_position(condition.value, context)

  elseif condition.condition == "type" then
    local item_type = context:get_variable("type")
    return item_type == condition.value

  elseif condition.condition == "variable" then
    local var = condition.value
    if var == "locator" or var == "label" then
      if context.in_bibliography then
        return false
      else
        return context.cite[var] ~= nil
      end
    else
      local res = context.reference[var] ~= nil
      return res
    end
  end
end

function If:is_uncertain_date(variable)
  if variable == nil then
    return false
  end
  local circa = variable["circa"]
  return circa and circa ~= ""
end

function If:check_position(position, context)
  -- util.debug(context.cite)
  if context.in_bibliography then
    return false
  end

  local position_level = context.cite.position or context.cite.position_level or 0
  -- context.cite.position is for hacking in debugging
  -- bugreports_DemoPageFullCiteCruftOnSubsequent.txt
  if position == "first" then
    return (position_level == util.position_map["first"])
  elseif position == "near-note" then
    return context.cite["near-note"] or context.cite.near_note
  else
    return (position_level >= util.position_map[position])
  end
end


local ElseIf = If:derive("else-if")


local Else = Element:derive("else")

function Else:from_node(node)
  local o = Else:new()
  o:process_children_nodes(node)
  return o
end

function Else:evaluate_conditions(engine, state, context)
  return true
end

Else.build_children_ir = If.build_children_ir
Else.build_ir = If.build_ir


choose.Choose = Choose
choose.If = If
choose.ElseIf = ElseIf
choose.Else = Else

return choose
