-- ------------------------------------------------------------------------------ --
--                                TradeSkillMaster                                --
--                http://www.curse.com/addons/wow/tradeskill-master               --
--                                                                                --
--             A TradeSkillMaster Addon (http://tradeskillmaster.com)             --
--    All Rights Reserved* - Detailed license information included with addon.    --
-- ------------------------------------------------------------------------------ --

-- This file handled multi-account syncing and communication

-- register this file with Ace libraries
local TSM = select(2, ...)
local Sync = TSM:NewModule("Sync", "AceComm-3.0", "AceEvent-3.0")
TSMAPI.Sync = {}
local private = {callbacks={}, addedFriends={}, invalidPlayers={}}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Sync_private")
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table

-- Request friend info from the server
ShowFriends()


function Sync:OnEnable()
	Sync:RegisterComm("TSMSyncData")
	Sync:RegisterEvent("CHAT_MSG_SYSTEM")
	
	local data = {characters={}, accountKey=TSMAPI.Sync:GetAccountKey()}
	for name in pairs(TSM.db.realm.characters) do
		data.characters[name] = TSMAPI.Sync:GetAccountKey()
	end
	TSMAPI:CreateTimeDelay("syncSetupDelay", 3, function() TSMAPI.Sync:BroadcastData("TradeSkillMaster", "SETUP", data) end)
end

--Load the libraries
local libS = LibStub:GetLibrary("AceSerializer-3.0")
local libC = LibStub:GetLibrary("LibCompress")
local libCE = libC:GetAddonEncodeTable()

function Sync:OnCommReceived(_, data, _, source)
	-- remove realm name from source
	source = ("-"):split(source)
	
	-- Decode the compressed data
	data = libCE:Decode(data)
		
	-- Decompress the decoded data
	local data = libC:Decompress(data)
	if not data then return end
		
	-- Deserialize the decompressed data
	local success
	local tmpData = data
	success, data = libS:Deserialize(data)
	if not success or not data then return end
	
	-- data is good, do callback
	local key = data.__key
	local module = data.__module
	local account = data.__account
	if not key or not module or not private.callbacks[module] then return end
	data.__key = nil
	data.__module = nil
	data.__account = nil
	
	-- make sure we are getting this from a known source
	if not TSM.db.realm.syncAccounts[account] and (module ~= "TradeSkillMaster" and not data.isSetup) then return end
	private.callbacks[module](key, data, source)
end

function Sync:CHAT_MSG_SYSTEM(_, msg)
	if #private.addedFriends == 0 then return end
	if msg == ERR_FRIEND_NOT_FOUND then
		tremove(private.addedFriends, 1)
	else
		for i, v in ipairs(private.addedFriends) do
			if format(ERR_FRIEND_ADDED_S, v) == msg then
				tremove(private.addedFriends, i)
				private.invalidPlayers[strlower(v)] = true
			end
		end
	end
end


function TSMAPI.Sync:GetAccountKey()
	return TSM.db.realm.accountKey
end

function TSM:RegisterSyncCallback(module, callback)
	private.callbacks[module] = callback
end

function private:IsPlayerOnline(target, noAdd)
	for i=1, GetNumFriends() do
		local name, _, _, _, connected = GetFriendInfo(i)
		if name and strlower(name) == strlower(target) then
			return connected
		end
	end
	
	if not noAdd and not private.invalidPlayers[strlower(target)] and GetNumFriends() ~= 50 then
		-- add them as a friend
		AddFriend(target)
		tinsert(private.addedFriends, target)
		for i=1, GetNumFriends() do
			local name, _, _, _, connected = GetFriendInfo(i)
			if name and strlower(name) == strlower(target) then
				return connected
			end
		end
	end
end

function TSMAPI.Sync:SendData(module, key, data, target)
	local playerOnline = private:IsPlayerOnline(target)
	if not playerOnline then return end

	data.__module = module
	data.__key = key
	data.__account = TSMAPI.Sync:GetAccountKey()
	
	-- we will encode using Huffman, LZW, and no compression separately
	-- this is to deal with a bug in the compression code
	local orig = data
	local serialized = libS:Serialize(data)
	local encodedData = {}
	encodedData[1] = libCE:Encode(libC:CompressHuffman(serialized))
	encodedData[2] = libCE:Encode(libC:CompressLZW(serialized))
	encodedData[3] = libCE:Encode("\001"..serialized)
	
	-- verify each compresion and pick the shorted valid one
	local minIndex = -1
	local minLen = math.huge
	for i=#encodedData, 1, -1 do
		local test = libC:Decompress(libCE:Decode(encodedData[i]))
		if test and test == serialized and #encodedData[i] < minLen then
			minLen = #encodedData[i]
			minIndex = i
		end
	end
	
	-- sanity check
	if not encodedData[minIndex] then error("Could not encode data.") end
	-- send off the serialized, compressed, and encoded data
	Sync:SendCommMessage("TSMSyncData", encodedData[minIndex], "WHISPER", target)
end


function TSMAPI.Sync:BroadcastData(module, key, data)
	for account, players in pairs(TSM.db.realm.syncAccounts) do
		if account ~= TSMAPI.Sync:GetAccountKey() then
			local sent
			for player in pairs(players) do
				if private:IsPlayerOnline(player, true) then
					TSMAPI.Sync:SendData(module, key, data, player)
					sent = true
					break
				end
			end
			if not sent then
				for player in pairs(players) do
					if private:IsPlayerOnline(player) then
						TSMAPI.Sync:SendData(module, key, data, player)
						sent = true
						break
					end
				end
			end
		end
	end
end


function private:SendSetupData(target, isResponse, isSetup)
	local data = {isResponse=isResponse, isSetup=isSetup, characters={}, accountKey=TSMAPI.Sync:GetAccountKey()}
	for name in pairs(TSM.db.realm.characters) do
		data.characters[name] = true
	end
	TSMAPI.Sync:SendData("TradeSkillMaster", "SETUP", data, target)
end

function TSM:DoSyncSetup(target)
	if target == "" then
		private.syncSetupTarget = nil
		return
	end
	private.syncSetupTarget = target
	private:SendSetupData(target, nil, true)
end

function TSM:SyncCallback(key, data, source)
	if key == "SETUP" then
		if (data.isSetup and strlower(source) ~= strlower(private.syncSetupTarget or "")) or (not data.isSetup and not TSM.db.realm.syncAccounts[data.accountKey]) then
			return
		end
		TSMAPI:Verify(data.accountKey ~= TSMAPI.Sync:GetAccountKey(), "It appears that you've manually copied your saved variables between accounts which will cause TSM's automatic sync'ing to not work. You'll need to undo this, and/or delete the TradeSkillMaster, TSM_Crafting, and TSM_ItemTracker saved variables files on both accounts (with WoW closed) in order to fix this.")
		TSM.db.realm.syncAccounts[data.accountKey] = data.characters
		if data.isSetup then
			TSMAPI:CloseFrame()
			TSM:Printf(L["Setup account sync'ing with the account which '%s' is on."], source)
		end
		if data.isResponse then return end -- already responded
		private:SendSetupData(source, true, data.isSetup)
	end
end