-- ------------------------------------------------------------------------------ --
--                                TradeSkillMaster                                --
--                          https://tradeskillmaster.com                          --
--    All Rights Reserved - Detailed license information included with addon.     --
-- ------------------------------------------------------------------------------ --

local _, TSM = ...
local Comm = TSM.Init("Service.SyncClasses.Comm")
local Delay = TSM.Include("Util.Delay")
local Table = TSM.Include("Util.Table")
local TempTable = TSM.Include("Util.TempTable")
local Log = TSM.Include("Util.Log")
local Settings = TSM.Include("Service.Settings")
local Constants = TSM.Include("Service.SyncClasses.Constants")
local private = {
	handler = {},
	queuedPacket = {},
	queuedSourceCharacter = {},
}
-- load libraries
LibStub("AceComm-3.0"):Embed(Comm)
local LibSerialize = LibStub("LibSerialize")
local LibDeflate = LibStub("LibDeflate")



-- ============================================================================
-- Module Loading
-- ============================================================================

Comm:OnModuleLoad(function()
	Comm:RegisterComm("TSMSyncData", private.OnCommReceived)
end)


-- ============================================================================
-- Module Functions
-- ============================================================================

function Comm.RegisterHandler(dataType, handler)
	assert(Table.KeyByValue(Constants.DATA_TYPES, dataType) ~= nil)
	assert(not private.handler[dataType])
	private.handler[dataType] = handler
end

function Comm.SendData(dataType, targetCharacter, data)
	assert(type(dataType) == "string" and #dataType == 1)
	local packet = TempTable.Acquire()
	packet.dt = dataType
	packet.sa = Settings.GetCurrentSyncAccountKey()
	packet.v = Constants.VERSION
	packet.d = data
	local serialized = LibSerialize:Serialize(packet)
	TempTable.Release(packet)
	local compressed = LibDeflate:EncodeForWoWAddonChannel(LibDeflate:CompressDeflate(serialized))
	assert(LibDeflate:DecompressDeflate(LibDeflate:DecodeForWoWAddonChannel(compressed)) == serialized)

	-- give heartbeats and rpc preambles a higher priority
	local priority = (dataType == Constants.DATA_TYPES.HEARTBEAT or dataType == Constants.DATA_TYPES.RPC_PREAMBLE) and "ALERT" or nil
	-- send the message
	Comm:SendCommMessage("TSMSyncData", compressed, "WHISPER", targetCharacter, priority)
	return #compressed
end



-- ============================================================================
-- Private Helper Functions
-- ============================================================================

function private.OnCommReceived(_, packet, _, sourceCharacter)
	-- delay the processing to make sure it happens within a debuggable context (this function is called via pcall)
	tinsert(private.queuedPacket, packet)
	tinsert(private.queuedSourceCharacter, sourceCharacter)
	Delay.AfterFrame("commReceiveQueue", 0, private.ProcessReceiveQueue)
end

function private.ProcessReceiveQueue()
	assert(#private.queuedPacket == #private.queuedSourceCharacter)
	while #private.queuedPacket > 0 do
		local packet = tremove(private.queuedPacket, 1)
		local sourceCharacter = tremove(private.queuedSourceCharacter, 1)
		private.ProcessReceivedPacket(packet, sourceCharacter)
	end
end

function private.ProcessReceivedPacket(msg, sourceCharacter)
	-- remove realm name from source player
	sourceCharacter = strsplit("-", sourceCharacter)
	sourceCharacter = strtrim(sourceCharacter)
	local sourceCharacterAccountKey = Settings.GetCharacterSyncAccountKey(sourceCharacter)
	if sourceCharacterAccountKey and sourceCharacterAccountKey == Settings.GetCurrentSyncAccountKey() then
		Log.Err("We own the source character")
		Settings.ShowSyncSVCopyError()
		return
	end

	-- decode and decompress
	msg = LibDeflate:DecompressDeflate(LibDeflate:DecodeForWoWAddonChannel(msg))
	if not msg then
		Log.Err("Invalid packet")
		return
	end
	local success, packet = LibSerialize:Deserialize(msg)
	if not success then
		Log.Err("Invalid packet")
		return
	end

	-- validate the packet
	local dataType = packet.dt
	local sourceAccount = packet.sa
	local version = packet.v
	local data = packet.d
	if type(dataType) ~= "string" or #dataType > 1 or not sourceAccount or version ~= Constants.VERSION then
		Log.Info("Invalid message received")
		return
	elseif sourceAccount == Settings.GetCurrentSyncAccountKey() then
		Log.Err("We are the source account (SV copy)")
		Settings.ShowSyncSVCopyError()
		return
	elseif sourceCharacterAccountKey and sourceCharacterAccountKey ~= sourceAccount then
		-- the source player now belongs to a different account than what we expect
		Log.Err("Unexpected source account")
		Settings.ShowSyncSVCopyError()
		return
	end

	if private.handler[dataType] then
		private.handler[dataType](dataType, sourceAccount, sourceCharacter, data)
	else
		Log.Info("Received unhandled message of type: "..strbyte(dataType))
	end
end
