-- 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
