local MAJOR, MINOR = "LibReputationData-1.0", 1

assert(_G.LibStub, MAJOR .. " requires LibStub")
local lib = _G.LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end

lib.callbacks = lib.callbacks or _G.LibStub("CallbackHandler-1.0"):New(lib)

-- local store
local reputationChanges = {}
local allFactions = {}
local watchedFaction = 0

-- blizzard api
local GetFactionInfo                = _G.GetFactionInfo
local GetFriendshipReputation		= _G.GetFriendshipReputation
local IsFactionInactive 			= _G.IsFactionInactive
local SetWatchedFactionIndex        = _G.SetWatchedFactionIndex
local TimerAfter					= _G.C_Timer.After

-- lua api
local select   = _G.select
local strmatch = _G.string.match
local tonumber = _G.tonumber

local private = {} -- private space for the event handlers

lib.frame = lib.frame or _G.CreateFrame("Frame")
local frame = lib.frame
frame:UnregisterAllEvents() -- deactivate old versions
frame:SetScript("OnEvent", function(_, event, ...) private[event](event, ...) end)
frame:RegisterEvent("PLAYER_ENTERING_WORLD")

local function CopyTable(tbl)
	if not tbl then return {} end
	local copy = {};
	for k, v in pairs(tbl) do
		if ( type(v) == "table" ) then
			copy[k] = CopyTable(v);
		else
			copy[k] = v;
		end
	end
	return copy;
end

local function GetFLocalFactionInfo(factionIndex)
	return allFactions[factionIndex]
end

local function GetFactionIndex(factionName)
	for i = 1, #allFactions do
		local name, _, _, _, _, _, _, _, _, _, _, _, _ = GetFLocalFactionInfo(i);
		if name == factionName then return i end
	end
end

local function GetFactionData(factionIndex)

	local name, _, standingID, bottomValue, topValue, earnedValue, _,
		_, isHeader, isCollapsed, hasRep, isWatched, isChild, factionID = GetFactionInfo(factionIndex)
	if not name then return nil end

	if isWatched then watchedFaction = factionIndex end
	local standingText = _G['FACTION_STANDING_LABEL'..standingID]

	local friendID, friendRep, friendMaxRep, _, _, _, friendTextLevel, friendThresh = GetFriendshipReputation(factionID)
	if friendID ~= nil then
		bottomValue = friendThresh
		if nextThresh then
			topValue = friendThresh + min( friendMaxRep - friendThresh, 8400 ) -- Magic number! Yay!
		end
		earnedValue = friendRep
		standingText = friendTextLevel
	end
	
	local faction = {
		factionIndex = factionIndex,
		name = name,
		standingID = standingID,
		standing = standingText,
		min = bottomValue,
		max = topValue,
		value = earnedValue,
		isHeader = isHeader,
		isChild = isChild,
		hasRep = hasRep,
		isActive = not IsFactionInactive(factionIndex),
		factionID = factionID,
		friendID = friendID
	}
	return faction	
end

-- Refresh the list of known factions
local function RefreshAllFactions()
	local i = 1
	local lastName
	local factions = {}

	repeat
		local name, description, standingId, bottomValue, topValue, earnedValue, atWarWith,
			canToggleAtWar, isHeader, isCollapsed, hasRep, isWatched, isChild, factionID = GetFactionInfo(i)
		if not name or name == lastName and name ~= GUILD then break end

		lastName = name
		local faction = GetFactionData(i)

		if faction then 
			tinsert(factions, faction)
		end

		if isCollapsed then ExpandFactionHeader(i) end
		i = i + 1
	until i > 200
	allFactions = factions
	lib.callbacks:Fire("FACTIONS_LOADED")
end

local function UpdateFaction(factionIndex)
	allFactions[factionIndex] = GetFactionData(factionIndex)
end


------------------------------------------------------------------------------
-- Ensure factions and guild info are loaded
------------------------------------------------------------------------------
local function EnsureFactionsLoaded()
	-- Sometimes it takes a while for faction and guild info
	-- to load when the game boots up so we need to periodically
	-- check whether its loaded before we can display it
	if GetFactionInfo(1) == nil or (IsInGuild() and GetGuildInfo("player") == nil) then
			TimerAfter(0.5, EnsureFactionsLoaded)
	else
		-- Refresh all factions and notify subscribers
		RefreshAllFactions() 
	end
end

------------------------------------------------------------------------------
-- Update reputation
------------------------------------------------------------------------------
local function UpdateReputationChanges()

	RefreshAllFactions()

	-- Build sorted change table
	local changes = {}
	for name, amount in pairs(reputationChanges) do

		local factionIndex= GetFactionIndex(name)
		if factionIndex then
			UpdateFaction(factionIndex)
			tinsert(changes, {
				name = name,
				amount = amount,
				factionIndex = factionIndex
			})
		end
	end

	if #changes > 1 then
		table.sort(changes, function(a, b) return a.amount > b.amount end)
	end

	if #changes > 0 then
		-- Notify subscribers
		InformReputationsChanged(changes)
	end
	
	reputationChanges = {}
end


local function InformReputationsChanged(changes)
	for _,_,factionIndex in changes do
		lib.callbacks:Fire("REPUTATION_CHANGED", factionIndex)
	end
end

------------------------------------------------------------------------------
-- Events
------------------------------------------------------------------------------
function private.PLAYER_ENTERING_WORLD(event)
	TimerAfter(3, function()
		frame:RegisterEvent("COMBAT_TEXT_UPDATE")
		frame:RegisterEvent("UPDATE_FACTION")
		EnsureFactionsLoaded()
	end)
end

function private.COMBAT_TEXT_UPDATE(event, type, name, amount)
	if (type == "FACTION") then
		if IsInGuild() then
			-- Check name for guild reputation
			if name == GUILD then
				name = (GetGuildInfo("player"))
				if not name or name == "" then return end
			end
		end
	
		if not reputationChanges[name] then
			reputationChanges[name] = amount
		else
			reputationChanges[name] = reputationChanges[name] + amount
		end

		TimerAfter(0.5,UpdateReputationChanges)

	end
end

function private.UPDATE_FACTION(event)
	RefreshAllFactions()
end

------------------------------------------------------------------------------
-- API
------------------------------------------------------------------------------
function lib:SetWatchedFaction(factionIndex)
	
	SetWatchedFactionIndex(factionIndex)
	watchedFaction = factionIndex
	lib.callbacks:Fire("REPUTATION_CHANGED", factionIndex)
end

function lib:GetWatchedFactionIndex()
	return watchedFaction
end

function lib:GetReputationInfo(_, factionIndex)
	factionIndex = factionIndex or watchedFaction
	if allFactions[factionIndex] then
		return factionIndex, CopyTable(allFactions[factionIndex])
	else
		return nil,nil
	end
end

function lib:GetAllFactionsInfo()
	return CopyTable(allFactions)
end

function lib:GetAllActiveFactionsInfo()
	local activeFactions = {}
	for i=1,#allFactions do
		if allFactions[i].isActive then
			tinsert(activeFactions,allFactions[i])
		end
	end

	if #activeFactions > 0 then
		return CopyTable(activeFactions)
	else
		return nil
	end
end

function lib:GetNumObtainedReputations()
	return #allFactions
end

function lib:ForceUpdate()
	EnsureFactionsLoaded()
end
