-- LibBabble-3.0 is hereby placed in the Public Domain
-- Credits: ckknight
local LIBBABBLE_MAJOR, LIBBABBLE_MINOR = 'LibBabble-3.0', 2

local LibBabble = LibStub:NewLibrary(LIBBABBLE_MAJOR, LIBBABBLE_MINOR)
if not LibBabble then
  return
end

local data = LibBabble.data or {}
for k, v in pairs(LibBabble) do
  LibBabble[k] = nil
end
LibBabble.data = data

local tablesToDB = {}
for namespace, db in pairs(data) do
  for k, v in pairs(db) do
    tablesToDB[v] = db
  end
end

local function warn(message)
  local _, ret = pcall(error, message, 3)
  geterrorhandler()(ret)
end

local lookup_mt = {
  __index = function(self, key)
    local db = tablesToDB[self]
    local current_key = db.current[key]
    if current_key then
      self[key] = current_key
      return current_key
    end
    local base_key = db.base[key]
    local real_MAJOR_VERSION
    for k, v in pairs(data) do
      if v == db then
        real_MAJOR_VERSION = k
        break
      end
    end
    if not real_MAJOR_VERSION then
      real_MAJOR_VERSION = LIBBABBLE_MAJOR
    end
    if base_key then
      warn(
        ('%s: Translation %q not found for locale %q'):format(real_MAJOR_VERSION, key, GetLocale())
      )
      rawset(self, key, base_key)
      return base_key
    end
    warn(('%s: Translation %q not found.'):format(real_MAJOR_VERSION, key))
    rawset(self, key, key)
    return key
  end,
}

local function initLookup(module, lookup)
  local db = tablesToDB[module]
  for k in pairs(lookup) do
    lookup[k] = nil
  end
  setmetatable(lookup, lookup_mt)
  tablesToDB[lookup] = db
  db.lookup = lookup
  return lookup
end

local function initReverse(module, reverse)
  local db = tablesToDB[module]
  for k in pairs(reverse) do
    reverse[k] = nil
  end
  for k, v in pairs(db.current) do
    reverse[v] = k
  end
  tablesToDB[reverse] = db
  db.reverse = reverse
  db.reverseIterators = nil
  return reverse
end

local prototype = {}
local prototype_mt = { __index = prototype }

--[[---------------------------------------------------------------------------
Notes:
	* If you try to access a nonexistent key, it will warn but allow the code to pass through.
Returns:
	A lookup table for english to localized words.
Example:
	local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
	local BL = B:GetLookupTable()
	assert(BL["Some english word"] == "Some localized word")
	DoSomething(BL["Some english word that doesn't exist"]) -- warning!
-----------------------------------------------------------------------------]]
function prototype:GetLookupTable()
  local db = tablesToDB[self]

  local lookup = db.lookup
  if lookup then
    return lookup
  end
  return initLookup(self, {})
end
--[[---------------------------------------------------------------------------
Notes:
	* If you try to access a nonexistent key, it will return nil.
Returns:
	A lookup table for english to localized words.
Example:
	local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
	local B_has = B:GetUnstrictLookupTable()
	assert(B_has["Some english word"] == "Some localized word")
	assert(B_has["Some english word that doesn't exist"] == nil)
-----------------------------------------------------------------------------]]
function prototype:GetUnstrictLookupTable()
  local db = tablesToDB[self]

  return db.current
end
--[[---------------------------------------------------------------------------
Notes:
	* If you try to access a nonexistent key, it will return nil.
	* This is useful for checking if the base (English) table has a key, even if the localized one does not have it registered.
Returns:
	A lookup table for english to localized words.
Example:
	local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
	local B_hasBase = B:GetBaseLookupTable()
	assert(B_hasBase["Some english word"] == "Some english word")
	assert(B_hasBase["Some english word that doesn't exist"] == nil)
-----------------------------------------------------------------------------]]
function prototype:GetBaseLookupTable()
  local db = tablesToDB[self]

  return db.base
end
--[[---------------------------------------------------------------------------
Notes:
	* If you try to access a nonexistent key, it will return nil.
	* This will return only one English word that it maps to, if there are more than one to check, see :GetReverseIterator("word")
Returns:
	A lookup table for localized to english words.
Example:
	local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
	local BR = B:GetReverseLookupTable()
	assert(BR["Some localized word"] == "Some english word")
	assert(BR["Some localized word that doesn't exist"] == nil)
-----------------------------------------------------------------------------]]
function prototype:GetReverseLookupTable()
  local db = tablesToDB[self]

  local reverse = db.reverse
  if reverse then
    return reverse
  end
  return initReverse(self, {})
end
local blank = {}
local weakVal = { __mode = 'v' }
--[[---------------------------------------------------------------------------
Arguments:
	string - the localized word to chek for.
Returns:
	An iterator to traverse all English words that map to the given key
Example:
	local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
	for word in B:GetReverseIterator("Some localized word") do
		DoSomething(word)
	end
-----------------------------------------------------------------------------]]
function prototype:GetReverseIterator(key)
  local db = tablesToDB[self]
  local reverseIterators = db.reverseIterators
  if not reverseIterators then
    reverseIterators = setmetatable({}, weakVal)
    db.reverseIterators = reverseIterators
  elseif reverseIterators[key] then
    return pairs(reverseIterators[key])
  end
  local t
  for k, v in pairs(db.current) do
    if v == key then
      if not t then
        t = {}
      end
      t[k] = true
    end
  end
  reverseIterators[key] = t or blank
  return pairs(reverseIterators[key])
end
--[[---------------------------------------------------------------------------
Returns:
	An iterator to traverse all translations English to localized.
Example:
	local B = LibStub("LibBabble-Module-3.0") -- where Module is what you want.
	for english, localized in B:Iterate() do
		DoSomething(english, localized)
	end
-----------------------------------------------------------------------------]]
function prototype:Iterate()
  local db = tablesToDB[self]

  return pairs(db.current)
end

-- #NODOC
-- modules need to call this to set the base table
function prototype:SetBaseTranslations(base)
  local db = tablesToDB[self]
  local oldBase = db.base
  if oldBase then
    for k in pairs(oldBase) do
      oldBase[k] = nil
    end
    for k, v in pairs(base) do
      oldBase[k] = v
    end
    base = oldBase
  else
    db.base = base
  end
  for k, v in pairs(base) do
    if v == true then
      base[k] = k
    end
  end
end

local function init(module)
  local db = tablesToDB[module]
  if db.lookup then
    initLookup(module, db.lookup)
  end
  if db.reverse then
    initReverse(module, db.reverse)
  end
  db.reverseIterators = nil
end

-- #NODOC
-- modules need to call this to set the current table. if current is true, use the base table.
function prototype:SetCurrentTranslations(current)
  local db = tablesToDB[self]
  if current == true then
    db.current = db.base
  else
    local oldCurrent = db.current
    if oldCurrent then
      for k in pairs(oldCurrent) do
        oldCurrent[k] = nil
      end
      for k, v in pairs(current) do
        oldCurrent[k] = v
      end
      current = oldCurrent
    else
      db.current = current
    end
  end
  init(self)
end

for namespace, db in pairs(data) do
  setmetatable(db.module, prototype_mt)
  init(db.module)
end

-- #NODOC
-- modules need to call this to create a new namespace.
function LibBabble:New(namespace, minor)
  local module, oldminor = LibStub:NewLibrary(namespace, minor)
  if not module then
    return
  end

  if not oldminor then
    local db = {
      module = module,
    }
    data[namespace] = db
    tablesToDB[module] = db
  else
    for k, v in pairs(module) do
      module[k] = nil
    end
  end

  setmetatable(module, prototype_mt)

  return module
end
