local MDT = DungeonTools
local L = MDT.L
local Compresser = LibStub:GetLibrary("LibCompress")
local Encoder = Compresser:GetAddonEncodeTable()
local Serializer = LibStub:GetLibrary("AceSerializer-3.0")
local LibDeflate = LibStub:GetLibrary("LibDeflate")
local configForDeflate = {
    [1]= {level = 1},
    [2]= {level = 2},
    [3]= {level = 3},
    [4]= {level = 4},
    [5]= {level = 5},
    [6]= {level = 6},
    [7]= {level = 7},
    [8]= {level = 8},
    [9]= {level = 9},
}
MDT.commsObject = LibStub("AceAddon-3.0"):NewAddon("DungeonToolsCommsObject","AceComm-3.0","AceSerializer-3.0")
local commsObject = MDT.commsObject

-- Lua APIs
local tostring, string_char, strsplit,tremove,tinsert = tostring, string.char, strsplit,table.remove,table.insert
local pairs, type, unpack = pairs, type, unpack
local bit_band, bit_lshift, bit_rshift = bit.band, bit.lshift, bit.rshift

--Based on code from WeakAuras2, all credit goes to the authors
local bytetoB64 = {
    [0]="a","b","c","d","e","f","g","h",
    "i","j","k","l","m","n","o","p",
    "q","r","s","t","u","v","w","x",
    "y","z","A","B","C","D","E","F",
    "G","H","I","J","K","L","M","N",
    "O","P","Q","R","S","T","U","V",
    "W","X","Y","Z","0","1","2","3",
    "4","5","6","7","8","9","(",")"
}

local B64tobyte = {
    a =  0,  b =  1,  c =  2,  d =  3,  e =  4,  f =  5,  g =  6,  h =  7,
    i =  8,  j =  9,  k = 10,  l = 11,  m = 12,  n = 13,  o = 14,  p = 15,
    q = 16,  r = 17,  s = 18,  t = 19,  u = 20,  v = 21,  w = 22,  x = 23,
    y = 24,  z = 25,  A = 26,  B = 27,  C = 28,  D = 29,  E = 30,  F = 31,
    G = 32,  H = 33,  I = 34,  J = 35,  K = 36,  L = 37,  M = 38,  N = 39,
    O = 40,  P = 41,  Q = 42,  R = 43,  S = 44,  T = 45,  U = 46,  V = 47,
    W = 48,  X = 49,  Y = 50,  Z = 51,["0"]=52,["1"]=53,["2"]=54,["3"]=55,
    ["4"]=56,["5"]=57,["6"]=58,["7"]=59,["8"]=60,["9"]=61,["("]=62,[")"]=63
}

-- This code is based on the Encode7Bit algorithm from LibCompress
-- Credit goes to Galmok (galmok@gmail.com)
local decodeB64Table = {}

function decodeB64(str)
    local bit8 = decodeB64Table
    local decoded_size = 0
    local ch
    local i = 1
    local bitfield_len = 0
    local bitfield = 0
    local l = #str
    while true do
        if bitfield_len >= 8 then
            decoded_size = decoded_size + 1
            bit8[decoded_size] = string_char(bit_band(bitfield, 255))
            bitfield = bit_rshift(bitfield, 8)
            bitfield_len = bitfield_len - 8
        end
        ch = B64tobyte[str:sub(i, i)]
        bitfield = bitfield + bit_lshift(ch or 0, bitfield_len)
        bitfield_len = bitfield_len + 6
        if i > l then
            break
        end
        i = i + 1
    end
    return table.concat(bit8, "", 1, decoded_size)
end

function MDT:TableToString(inTable, forChat,level)
    local serialized = Serializer:Serialize(inTable)
    local compressed = LibDeflate:CompressDeflate(serialized, configForDeflate[level])
    -- prepend with "!" so that we know that it is not a legacy compression
    -- also this way, old versions will error out due to the "bad" encoding
    local encoded = "!"
    if(forChat) then
        encoded = encoded .. LibDeflate:EncodeForPrint(compressed)
    else
        encoded = encoded .. LibDeflate:EncodeForWoWAddonChannel(compressed)
    end
    return encoded
end

function MDT:StringToTable(inString, fromChat)
    -- if gsub strips off a ! at the beginning then we know that this is not a legacy encoding
    local encoded, usesDeflate = inString:gsub("^%!", "")
    local decoded
    if(fromChat) then
        if usesDeflate == 1 then
            decoded = LibDeflate:DecodeForPrint(encoded)
        else
            decoded = decodeB64(encoded)
        end
    else
        decoded = LibDeflate:DecodeForWoWAddonChannel(encoded)
    end

    if not decoded then
        return "Error decoding."
    end

    local decompressed, errorMsg = nil, "unknown compression method"
    if usesDeflate == 1 then
        decompressed = LibDeflate:DecompressDeflate(decoded)
    else
        decompressed, errorMsg = Compresser:Decompress(decoded)
    end
    if not(decompressed) then
        return "Error decompressing: " .. errorMsg
    end

    local success, deserialized = Serializer:Deserialize(decompressed)
    if not(success) then
        return "Error deserializing "..deserialized
    end
    return deserialized
end

local function filterFunc(_, event, msg, player, l, cs, t, flag, channelId, ...)
    if flag == "GM" or flag == "DEV" or (event == "CHAT_MSG_CHANNEL" and type(channelId) == "number" and channelId > 0) then
        return
    end
    local newMsg = ""
    local remaining = msg
    local done
    repeat
        local start, finish, characterName, displayName = remaining:find("%[MythicDungeonTools: ([^%s]+) %- ([^%]]+)%]")
        local startLive, finishLive, characterNameLive, displayNameLive = remaining:find("%[MDTLive: ([^%s]+) %- ([^%]]+)%]")
        if(characterName and displayName) then
            characterName = characterName:gsub("|c[Ff][Ff]......", ""):gsub("|r", "")
            displayName = displayName:gsub("|c[Ff][Ff]......", ""):gsub("|r", "")
            newMsg = newMsg..remaining:sub(1, start-1)
            newMsg = "|cfff49d38|Hgarrmission:mdt-"..characterName.."|h["..displayName.."]|h|r"
            remaining = remaining:sub(finish + 1)
        elseif (characterNameLive and displayNameLive) then
            characterNameLive = characterNameLive:gsub("|c[Ff][Ff]......", ""):gsub("|r", "")
            displayNameLive = displayNameLive:gsub("|c[Ff][Ff]......", ""):gsub("|r", "")
            newMsg = newMsg..remaining:sub(1, startLive-1)
            newMsg = newMsg.."|Hgarrmission:mdtlive-"..characterNameLive.."|h[".."|cFF00FF00Live Session: |cfff49d38"..""..displayNameLive.."]|h|r"
            remaining = remaining:sub(finishLive + 1)
        else
            done = true
        end
    until(done)
    if newMsg ~= "" then
        return false, newMsg, player, l, cs, t, flag, channelId, ...
    end
end

local presetCommPrefix = "MDTPreset"

MDT.liveSessionPrefixes = {
    ["enabled"] = "MDTLiveEnabled",
    ["request"] = "MDTLiveReq",
    ["ping"] = "MDTLivePing",
    ["obj"] = "MDTLiveObj",
    ["objOff"] = "MDTLiveObjOff",
    ["objChg"] = "MDTLiveObjChg",
    ["cmd"] = "MDTLiveCmd",
    ["note"] = "MDTLiveNote",
    ["preset"] = "MDTLivePreset",
    ["pull"] = "MDTLivePull",
    ["week"] = "MDTLiveWeek",
    ["free"] = "MDTLiveFree",
    ["bora"] = "MDTLiveBora",
    ["mdi"] = "MDTLiveMDI",
    ["reqPre"] = "MDTLiveReqPre",
    ["corrupted"] = "MDTLiveCor",
    ["difficulty"] = "MDTLiveLvl",
}

MDT.dataCollectionPrefixes = {
    ["request"] = "MDTDataReq",
    ["distribute"] = "MDTDataDist",
}

function commsObject:OnEnable()
    self:RegisterComm(presetCommPrefix)
    for _,prefix in pairs(MDT.liveSessionPrefixes) do
        self:RegisterComm(prefix)
    end
    for _,prefix in pairs(MDT.dataCollectionPrefixes) do
        self:RegisterComm(prefix)
    end
    MDT.transmissionCache = {}
    ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY", filterFunc)
    ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY_LEADER", filterFunc)
    ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID", filterFunc)
    ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID_LEADER", filterFunc)
end

--handle preset chat link clicks
hooksecurefunc("SetItemRef", function(link, text)
    if(link and link:sub(0, 19) == "garrmission:mdtlive") then
        local sender = link:sub(21, string.len(link))
        local name,realm = string.match(sender,"(.*)+(.*)")
        sender = name.."-"..realm
        --ignore importing the live preset when sender is player, open MDT only
        local playerName,playerRealm = UnitFullName("player")
        playerName = playerName.."-"..playerRealm
        if sender==playerName then
            MDT:ShowInterface(true)
        else
            MDT:ShowInterface(true)
            MDT:LiveSession_Enable()
        end
        return
    elseif (link and link:sub(0, 15) == "garrmission:mdt") then
        local sender = link:sub(17, string.len(link))
        local name,realm = string.match(sender,"(.*)+(.*)")
        if (not name) or (not realm) then
            print(string.format(L["receiveErrorUpdate"],sender))
            return
        end
        sender = name.."-"..realm
        local preset = MDT.transmissionCache[sender]
        if preset then
            MDT:ShowInterface(true)
            MDT:OpenChatImportPresetDialog(sender,preset)
        end
        return
    end
end)

function commsObject:OnCommReceived(prefix, message, distribution, sender)
    --[[
        Sender has no realm name attached when sender is from the same realm as the player
        UnitFullName("Nnoggie") returns no realm while UnitFullName("player") does
        UnitFullName("Nnoggie-TarrenMill") returns realm even if you are not on the same realm as Nnoggie
        We append our realm if there is no realm
    ]]
    local name, realm = UnitFullName(sender)
    if not name then return end
    if not realm or string.len(realm)<3 then
        local _,r = UnitFullName("player")
        realm = r
    end
    local fullName = name.."-"..realm

    --standard preset transmission
    --we cache the preset here already
    --the user still decides if he wants to click the chat link and add the preset to his db
    if prefix == presetCommPrefix then
        local preset = MDT:StringToTable(message,false)
        MDT.transmissionCache[fullName] = preset
        --live session preset
        if MDT.liveSessionActive and MDT.liveSessionAcceptingPreset and preset.uid == MDT.livePresetUID then
            if MDT:ValidateImportPreset(preset) then
                MDT:ImportPreset(preset,true)
                MDT.liveSessionAcceptingPreset = false
                MDT.main_frame.SendingStatusBar:Hide()
                if MDT.main_frame.LoadingSpinner then
                    MDT.main_frame.LoadingSpinner:Hide()
                    MDT.main_frame.LoadingSpinner.Anim:Stop()
                end
                MDT.liveSessionRequested = false
            end
        end
    end

    if prefix == MDT.dataCollectionPrefixes.request then
        MDT.DataCollection:DistributeData()
    end

    if prefix == MDT.dataCollectionPrefixes.distribute then
        local package = MDT:StringToTable(message,false)
        MDT.DataCollection:MergeReceiveData(package)
    end

    if prefix == MDT.liveSessionPrefixes.enabled then
        if MDT.liveSessionRequested == true then
            MDT:LiveSession_SessionFound(fullName,message)
        end
    end

    --pulls
    if prefix == MDT.liveSessionPrefixes.pull then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local pulls = MDT:StringToTable(message,false)
            preset.value.pulls = pulls
            if not preset.value.pulls[preset.value.currentPull] then
                preset.value.currentPull = #preset.value.pulls
                preset.value.selection = {#preset.value.pulls}
            end
            if preset == MDT:GetCurrentPreset() then
                MDT:ReloadPullButtons()
                MDT:SetSelectionToPull(MDT:GetCurrentPull())
                MDT:POI_UpdateAll() --for corrupted spires
                MDT:UpdateProgressbar()
            end
        end
    end

    --corrupted
    if prefix == MDT.liveSessionPrefixes.corrupted then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local offsets = MDT:StringToTable(message,false)
            --only reposition if no blip is currently moving
            if not MDT.draggedBlip then
                preset.value.riftOffsets = offsets
                MDT:UpdateMap()
            end
        end
    end

    --difficulty
    if prefix == MDT.liveSessionPrefixes.difficulty then
        if MDT.liveSessionActive then
            local db = MDT:GetDB()
            local difficulty = tonumber(message)
            if difficulty and difficulty~= db.currentDifficulty then
                local updateSeasonal
                if ((difficulty>=10 and db.currentDifficulty<10) or (difficulty<10 and db.currentDifficulty>=10)) then
                    updateSeasonal = true
                end
                db.currentDifficulty = difficulty
                MDT.main_frame.sidePanel.DifficultySlider:SetValue(difficulty)
                MDT:UpdateProgressbar()
                if MDT.EnemyInfoFrame and MDT.EnemyInfoFrame.frame:IsShown() then MDT:UpdateEnemyInfoData() end
                MDT:ReloadPullButtons()
                if updateSeasonal then
                    MDT:DungeonEnemies_UpdateSeasonalAffix()
                    MDT.main_frame.sidePanel.difficultyWarning:Toggle(difficulty)
                    MDT:POI_UpdateAll()
                    MDT:KillAllAnimatedLines()
                    MDT:DrawAllAnimatedLines()
                end
            end
        end
    end

    --week
    if prefix == MDT.liveSessionPrefixes.week then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local week = tonumber(message)
            if preset.week ~= week then
                preset.week = week
                local teeming = MDT:IsPresetTeeming(preset)
                preset.value.teeming = teeming
                if preset == MDT:GetCurrentPreset() then
                    local affixDropdown = MDT.main_frame.sidePanel.affixDropdown
                    affixDropdown:SetValue(week)
                    if not MDT:GetCurrentAffixWeek() then
                        MDT.main_frame.sidePanel.affixWeekWarning.image:Hide()
                        MDT.main_frame.sidePanel.affixWeekWarning:SetDisabled(true)
                    elseif MDT:GetCurrentAffixWeek() == week then
                        MDT.main_frame.sidePanel.affixWeekWarning.image:Hide()
                        MDT.main_frame.sidePanel.affixWeekWarning:SetDisabled(true)
                    else
                        MDT.main_frame.sidePanel.affixWeekWarning.image:Show()
                        MDT.main_frame.sidePanel.affixWeekWarning:SetDisabled(false)
                    end
                    MDT:DungeonEnemies_UpdateTeeming()
                    MDT:DungeonEnemies_UpdateInspiring()
                    MDT:UpdateFreeholdSelector(week)
                    MDT:DungeonEnemies_UpdateBlacktoothEvent(week)
                    MDT:DungeonEnemies_UpdateSeasonalAffix()
                    MDT:DungeonEnemies_UpdateBoralusFaction(preset.faction)
                    MDT:POI_UpdateAll()
                    MDT:UpdateProgressbar()
                    MDT:ReloadPullButtons()
                    MDT:KillAllAnimatedLines()
                    MDT:DrawAllAnimatedLines()
                end
            end
        end
    end

    --live session messages that ignore concurrency from here on, we ignore our own messages
    if sender == UnitFullName("player") then return end


    if prefix == MDT.liveSessionPrefixes.request then
        if MDT.liveSessionActive then
            MDT:LiveSession_NotifyEnabled()
        end
    end

    --request preset
    if prefix == MDT.liveSessionPrefixes.reqPre then
        local playerName,playerRealm = UnitFullName("player")
        playerName = playerName.."-"..playerRealm
        if playerName == message then
            MDT:SendToGroup(MDT:IsPlayerInGroup(),true,MDT:GetCurrentLivePreset())
        end
    end


    --ping
    if prefix == MDT.liveSessionPrefixes.ping then
        local currentUID = MDT:GetCurrentPreset().uid
        if MDT.liveSessionActive and (currentUID and currentUID==MDT.livePresetUID) then
            local x,y,sublevel = string.match(message,"(.*):(.*):(.*)")
            x = tonumber(x)
            y = tonumber(y)
            sublevel = tonumber(sublevel)
            local scale = MDT:GetScale()
            if sublevel == MDT:GetCurrentSubLevel() then
                MDT:PingMap(x*scale,y*scale)
            end
        end
    end

    --preset objects
    if prefix == MDT.liveSessionPrefixes.obj then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local obj = MDT:StringToTable(message,false)
            MDT:StorePresetObject(obj,true,preset)
            if preset == MDT:GetCurrentPreset() then
                local scale = MDT:GetScale()
                local currentPreset = MDT:GetCurrentPreset()
                local currentSublevel = MDT:GetCurrentSubLevel()
                MDT:DrawPresetObject(obj,nil,scale,currentPreset,currentSublevel)
            end
        end
    end

    --preset object offsets
    if prefix == MDT.liveSessionPrefixes.objOff then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local objIdx,x,y = string.match(message,"(.*):(.*):(.*)")
            objIdx = tonumber(objIdx)
            x = tonumber(x)
            y = tonumber(y)
            MDT:UpdatePresetObjectOffsets(objIdx,x,y,preset,true)
            if preset == MDT:GetCurrentPreset() then MDT:DrawAllPresetObjects() end
        end
    end

    --preset object changed (deletions, partial deletions)
    if prefix == MDT.liveSessionPrefixes.objChg then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local changedObjects = MDT:StringToTable(message,false)
            for objIdx,obj in pairs(changedObjects) do
                preset.objects[objIdx] = obj
            end
            if preset == MDT:GetCurrentPreset() then MDT:DrawAllPresetObjects() end
        end
    end

    --various commands
    if prefix == MDT.liveSessionPrefixes.cmd then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            if message == "deletePresetObjects" then MDT:DeletePresetObjects(preset, true) end
            if message == "undo" then MDT:PresetObjectStepBack(preset, true) end
            if message == "redo" then MDT:PresetObjectStepForward(preset, true) end
            if message == "clear" then MDT:ClearPreset(preset,true) end
        end
    end

    --note text update, delete, move
    if prefix == MDT.liveSessionPrefixes.note then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local action,noteIdx,text,y = string.match(message,"(.*):(.*):(.*):(.*)")
            noteIdx = tonumber(noteIdx)
            if action == "text" then
                preset.objects[noteIdx].d[5]=text
            elseif action == "delete" then
                tremove(preset.objects,noteIdx)
            elseif action == "move" then
                local x = tonumber(text)
                y = tonumber(y)
                preset.objects[noteIdx].d[1]=x
                preset.objects[noteIdx].d[2]=y
            end
            if preset == MDT:GetCurrentPreset() then MDT:DrawAllPresetObjects() end
        end
    end

    --preset
    if prefix == MDT.liveSessionPrefixes.preset then
        if MDT.liveSessionActive then
            local preset = MDT:StringToTable(message,false)
            MDT.transmissionCache[fullName] = preset
            if MDT:ValidateImportPreset(preset) then
                MDT.livePresetUID = preset.uid
                MDT:ImportPreset(preset,true)
            end
        end
    end

    --freehold
    if prefix == MDT.liveSessionPrefixes.free then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local value,week = string.match(message,"(.*):(.*)")
            value = value == "T" and true or false
            week = tonumber(week)
            preset.freeholdCrew = (value and week) or nil
            if preset == MDT:GetCurrentPreset() then
                MDT:DungeonEnemies_UpdateFreeholdCrew(preset.freeholdCrew)
                MDT:UpdateFreeholdSelector(week)
                MDT:ReloadPullButtons()
                MDT:UpdateProgressbar()
            end
        end
    end

    --Siege of Boralus
    if prefix == MDT.liveSessionPrefixes.bora then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local faction = tonumber(message)
            preset.faction = faction
            if preset == MDT:GetCurrentPreset() then
                MDT:UpdateBoralusSelector()
                MDT:ReloadPullButtons()
                MDT:UpdateProgressbar()
            end
        end
    end

    --MDI
    if prefix == MDT.liveSessionPrefixes.mdi then
        if MDT.liveSessionActive then
            local preset = MDT:GetCurrentLivePreset()
            local updateUI = preset == MDT:GetCurrentPreset()
            local action,data = string.match(message,"(.*):(.*)")
            data = tonumber(data)
            if action == "toggle" then
                MDT:GetDB().MDI.enabled = data == 1 or false
                MDT:DisplayMDISelector()
            elseif action == "beguiling" then
                preset.mdi.beguiling = data
                if updateUI then
                    MDT.MDISelector.BeguilingDropDown:SetValue(preset.mdi.beguiling)
                    MDT:DungeonEnemies_UpdateSeasonalAffix()
                    MDT:DungeonEnemies_UpdateBoralusFaction(preset.faction)
                    MDT:UpdateProgressbar()
                    MDT:ReloadPullButtons()
                    MDT:POI_UpdateAll()
                    MDT:KillAllAnimatedLines()
                    MDT:DrawAllAnimatedLines()
                end
            elseif action == "freehold" then
                preset.mdi.freehold = data
                if updateUI then
                    MDT.MDISelector.FreeholdDropDown:SetValue(preset.mdi.freehold)
                    if preset.mdi.freeholdJoined then
                        MDT:DungeonEnemies_UpdateFreeholdCrew(preset.mdi.freehold)
                    end
                    MDT:DungeonEnemies_UpdateBlacktoothEvent()
                    MDT:UpdateProgressbar()
                    MDT:ReloadPullButtons()
                end
            elseif action == "join" then
                preset.mdi.freeholdJoined = data == 1 or false
                if updateUI then
                    MDT:DungeonEnemies_UpdateFreeholdCrew()
                    MDT:ReloadPullButtons()
                    MDT:UpdateProgressbar()
                end
            end

        end
    end

end


---MakeSendingStatusBar
---Creates a bar that indicates sending progress when sharing presets with your group
---Called once from initFrames()
function MDT:MakeSendingStatusBar(f)
    f.SendingStatusBar = CreateFrame("StatusBar", nil, f)
    local statusbar = f.SendingStatusBar
    statusbar:SetMinMaxValues(0, 1)
    statusbar:SetPoint("LEFT", f.bottomPanel, "LEFT", 5, 0)
    statusbar:SetWidth(200)
    statusbar:SetHeight(20)
    statusbar:SetStatusBarTexture("Interface\\TARGETINGFRAME\\UI-StatusBar")
    statusbar:GetStatusBarTexture():SetHorizTile(false)
    statusbar:GetStatusBarTexture():SetVertTile(false)
    statusbar:SetStatusBarColor(0.26,0.42,1)

    statusbar.bg = statusbar:CreateTexture(nil, "BACKGROUND")
    statusbar.bg:SetTexture("Interface\\TARGETINGFRAME\\UI-StatusBar")
    statusbar.bg:SetAllPoints(true)
    statusbar.bg:SetVertexColor(0.26,0.42,1)

    statusbar.value = statusbar:CreateFontString(nil, "OVERLAY")
    statusbar.value:SetPoint("CENTER", statusbar, "CENTER", 0, 0)
    statusbar.value:SetFontObject("GameFontNormalSmall")
    statusbar.value:SetJustifyH("CENTER")
    statusbar.value:SetJustifyV("CENTER")
    statusbar.value:SetShadowOffset(1, -1)
    statusbar.value:SetTextColor(1, 1, 1)
    statusbar:Hide()

    if IsAddOnLoaded("ElvUI") then
        local E, L, V, P, G = unpack(ElvUI)
        statusbar:SetStatusBarTexture(E.media.normTex)
    end
end

--callback for SendCommMessage
local function displaySendingProgress(userArgs,bytesSent,bytesToSend)
    MDT.main_frame.SendingStatusBar:Show()
    MDT.main_frame.SendingStatusBar:SetValue(bytesSent/bytesToSend)
    MDT.main_frame.SendingStatusBar.value:SetText(string.format(L["Sending: %.1f"],bytesSent/bytesToSend*100).."%")
    --done sending
    if bytesSent == bytesToSend then
        local distribution = userArgs[1]
        local preset = userArgs[2]
        local silent = userArgs[3]
        --restore "Send" and "Live" button
        if MDT.liveSessionActive then
            MDT.main_frame.LiveSessionButton:SetText(L["*Live*"])
        else
            MDT.main_frame.LiveSessionButton:SetText(L["Live"])
            MDT.main_frame.LiveSessionButton.text:SetTextColor(1,0.8196,0)
            MDT.main_frame.LinkToChatButton:SetDisabled(false)
            MDT.main_frame.LinkToChatButton.text:SetTextColor(1,0.8196,0)
        end
        MDT.main_frame.LinkToChatButton:SetText(L["Share"])
        MDT.main_frame.LiveSessionButton:SetDisabled(false)
        MDT.main_frame.SendingStatusBar:Hide()
        --output chat link
        if not silent then
            local prefix = "[MythicDungeonTools: "
            local dungeon = MDT:GetDungeonName(preset.value.currentDungeonIdx)
            local presetName = preset.text
            local name, realm = UnitFullName("player")
            local fullName = name.."+"..realm
            SendChatMessage(prefix..fullName.." - "..dungeon..": "..presetName.."]",distribution)
            MDT:SetThrottleValues(true)
        end
    end
end

---generates a unique random 11 digit number in base64 and assigns it to a preset if it does not have one yet
---credit to WeakAuras2
function MDT:SetUniqueID(preset)
    if not preset.uid then
        local s = {}
        for i=1,11 do
            tinsert(s, bytetoB64[math.random(0, 63)])
        end
        preset.uid = table.concat(s)
    end
end

---SendToGroup
---Send current preset to group/raid
function MDT:SendToGroup(distribution,silent,preset)
    MDT:SetThrottleValues()
    preset = preset or MDT:GetCurrentPreset()
    --set unique id
    MDT:SetUniqueID(preset)
    --gotta encode mdi mode / difficulty into preset
    local db = MDT:GetDB()
    preset.mdiEnabled = db.MDI.enabled
    preset.difficulty = db.currentDifficulty
    local export = MDT:TableToString(preset,false,5)
    commsObject:SendCommMessage("MDTPreset", export, distribution, nil, "BULK",displaySendingProgress,{distribution,preset,silent})
end

---GetPresetSize
---Returns the number of characters the string version of the preset contains
function MDT:GetPresetSize(forChat,level)
    local preset = MDT:GetCurrentPreset()
    local export = MDT:TableToString(preset,forChat,level)
    return string.len(export)
end

local defaultCPS = tonumber(_G.ChatThrottleLib.MAX_CPS)
local defaultBURST = tonumber(_G.ChatThrottleLib.BURST)
function MDT:SetThrottleValues(default)
    if not _G.ChatThrottleLib then return end
    if default then
        _G.ChatThrottleLib.MAX_CPS = defaultCPS
        _G.ChatThrottleLib.BURST = defaultBURST
    else --4000/16000 is fine but we go safe with 2000/10000
        _G.ChatThrottleLib.MAX_CPS= 2000
        _G.ChatThrottleLib.BURST = 10000
    end
end
