Quantcast
-- create global instance
ViragDevTool = {
    --static constant useed for metatable name
    METATABLE_NAME = "$metatable",
    ADDON_NAME = "ViragDevTool",

    --this 2 keyword are for cmd operations
    -- you can use /vdt find somestr parentname(can be in format _G.Frame.Button)
    -- for examle "/vdt find Virag" will find every variable in _G that has *Virag* pattern
    -- "/vdt find Data ViragDevTool" will find every variable that has *Data* in their name in _G.ViragDevTool object if it exists
    -- same for "startswith"
    CMD = {
        --"/vdt help"
        HELP = function(msg)
            local arg = function(txt) return "|cFF96C0CE" .. txt .. "|cFFFFFFFF" end
            local arg2 = function(txt) return "|cFFBEB9B5" .. txt .. "|cFFFFFFFF" end
            local arg3 = function(txt) return "|cFF00FF00" .. txt .. "|cFFFFFFFF" end
            -- todo print info to chat
            ViragDevTool:print("/vdt - toggle UI")
            ViragDevTool:print("/vdt " .. arg("name") .. " - add _G." .. arg("name") .. " to the list")
            ViragDevTool:print("/vdt " .. arg("name") .. " " .. arg2("parent")
                    .. " - add " .. arg2("parent") .. "." .. arg("name") .. " to the list.\n" ..
                    arg2("parent") .. " can be like A.B so this will look in _G.A.B." .. arg("name"))
            ViragDevTool:print("/vdt " .. arg3("find") .. " " .. arg("name") .. " " .. arg2("parent")
                    .. " - add " .. arg("name") .. " _G." .. arg("*name*") .. "to the list" ..
                    "Adds any field name that has " .. arg("name") .. " part in its name")
            ViragDevTool:print("/vdt " .. arg3("startswith") .. " " .. arg("name") .. " " .. arg2("parent")
                    .. " - same as find but will look only for name* ")
            ViragDevTool:print("/vdt " .. arg3("m") .. " add frame at mouse location to the list. Recommendation: use binds for this cmd")

            ViragDevTool:print("/vdt " .. arg3("eventadd") .. " " .. arg("event") .. " " .. arg2("unit")
                    .. "\nExample: /vdt eventadd UNIT_AURA player")

            ViragDevTool:print("/vdt " .. arg3("eventremove") .. " " .. arg("event")
                    .. "\nExample: /vdt eventremove UNIT_AURA \n /vdt eventremove ALL will reset events in events tab to default state")

            return ViragDevTool.CMD, msg
        end,

        -- "/vdt find Data ViragDevTool" or "/vdt find Data"
        FIND = function(msg)
            local tMsg = ViragDevTool.split(msg, " ")

            local parent = _G

            if tMsg[3] then
                parent = ViragDevTool:FromStrToObject(tMsg[3])
            end


            return ViragDevTool:FindIn(parent, tMsg[2], string.match), msg
        end,

        --"/vdt startswith Data ViragDevTool" or "/vdt startswith Data"
        STARTSWITH = function(msg)

            local tMsg = ViragDevTool.split(msg, " ")

            local parent = _G

            if tMsg[3] then
                parent = ViragDevTool:FromStrToObject(tMsg[3])
            end

            return ViragDevTool:FindIn(parent, tMsg[2], ViragDevTool.starts), msg
        end,

        --"/vdt m" --m stands for mouse focus
        M = function(msg)
            local resultTable = GetMouseFocus()
            return resultTable, resultTable:GetName()
        end,

        --"/vdt eventadd ADDON_LOADED"
        EVENTADD = function(msg)
            local tMsg = ViragDevTool.split(msg, " ")

            --register noraml event
            if tMsg[2] then
                -- ViragDevTool.wndRef.topFrame:RegisterAllEvents() for debug
                ViragDevTool:StartMonitorEvent(tMsg[2], tMsg[3])
            end
        end,

        --"/vdt eventremove ADDON_LOADED"
        EVENTREMOVE = function(msg)
            local tMsg = ViragDevTool.split(msg, " ")
            if tMsg[2] then
                if tMsg[2] == "ALL" then
                    ViragDevTool.settings.events = {}
                end

                ViragDevTool:StopMonitorEvent(tMsg[2], tMsg[3])
            end
        end,
    },

    -- stores arguments for fcunction calls --todo implement
    tArgs = {},

    -- mapping table is used to store searches and diferent values that are frequently used
    -- for example we may need some api or some variable so we can add it here
    mapping = {},

    -- Default settings
    -- this variable will be used only on first load so it is just default init with empty values.
    -- will be replaced with ViragDevTool_Settings at 2-nd start
    default_settings = {
        -- selected list in gui. one of 3 list from settings: history or favourites or events
        sideBarTabSelected = "history",

        -- UI saved state
        isWndOpen = true,
        isSideBarOpen = false,

        -- stores history of recent calls to /vdt
        MAX_HISTORY_SIZE = 50,
        history = {
            -- examples
            "find LFR",
            "find SLASH",
            "find Data ViragDevTool",
            "startswith Virag",
            "ViragDevTool.settings.history",
        },
        favourites = {}, --todo implement

        -- events to monitor
        -- format ({event = "EVENT_NAME", unit = "player", active = true}, ...)
        -- default events inactive
        events = {
            {
                event = "CURSOR_UPDATE",
                active = false
            },
            {
                event = "UNIT_AURA",
                unit = "player",
                active = false
            },
            {
                event = "CHAT_MSG_CHANNEL",
                active = false
            }
        },
    }
}


-- just remove global reference so it is easy to read with my ide
local ViragDevTool = ViragDevTool

local pairs, tostring, type, print, string, getmetatable, table, pcall =
pairs, tostring, type, print, string, getmetatable, table, pcall

local HybridScrollFrame_CreateButtons, HybridScrollFrame_GetOffset, HybridScrollFrame_Update =
HybridScrollFrame_CreateButtons, HybridScrollFrame_GetOffset, HybridScrollFrame_Update

-----------------------------------------------------------------------------------------------
-- ViragDevTool_Colors == ViragDevTool.colors
-----------------------------------------------------------------------------------------------

-- todo refactore this class

local ViragDevTool_Colors = {
    white = "|cFFFFFFFF",
    gray = "|cFFBEB9B5",
    lightblue = "|cFF96C0CE",
    red = "|cFFFF0000",
    green = "|cFF00FF00",
    darkred = "|cFFC25B56",
    parent = "|cFFBEB9B5",
    error = "|cFFFF0000",
    ok = "|cFF00FF00",
}

--this colors are used in main table text
ViragDevTool_Colors["table"] = { 0.41, 0.80, 0.94, 1 }
ViragDevTool_Colors["string"] = { 0.67, 0.83, 0.45, 1 }
ViragDevTool_Colors["number"] = { 1, 0.96, 0.41, 1 }
ViragDevTool_Colors["function"] = { 1, 0.49, 0.04, 1 }
ViragDevTool_Colors["default"] = { 1, 1, 1, 1 }

function ViragDevTool_Colors:forState(state)
    if state then return self.ok end
    return self.error
end

function ViragDevTool_Colors:stateStr(state)
    if state then return self.ok .. "OK" end
    return self.error .. "ERROR"
end

function ViragDevTool_Colors:errorText()
    return self:stateStr(false) .. self.white .. " function call failed"
end

function ViragDevTool_Colors:FNNameToString(name, args)
    -- Create function call string like myFunction(arg1, arg2, arg3)
    local fnNameWitArgs = ""
    local delimiter = ""
    local found = false
    for i = 10, 1, -1 do
        if args[i] ~= nil then found = true end

        if found then
            fnNameWitArgs = tostring(args[i]) .. delimiter .. fnNameWitArgs
            delimiter = ", "
        end
    end

    return name .. "(" .. fnNameWitArgs .. ")"
end

function ViragDevTool_Colors:functionStr(parent, name, args)
    local resultStr = self:FNNameToString(name, args)

    if parent then
        return self.parent .. parent.name .. ":" .. self.white .. resultStr
    else
        return self.white .. resultStr
    end
end

-----------------------------------------------------------------------------------------------
-- ViragDevToolLinkedList == ViragDevTool.list
-----------------------------------------------------------------------------------------------

--- Linked List
-- @field size
-- @field first
-- @field last
--
-- Each node has:
-- @field name - string name
-- @field value - any object
-- @field next - nil/next node
-- @field padding - int expanded level( when you click on table it expands  so padding = padding + 1)
-- @field parent - parent node after it expanded
-- @field expanded - true/false/nil


local ViragDevToolLinkedList = {}


function ViragDevToolLinkedList:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    self.size = 0
    return o
end

function ViragDevToolLinkedList:GetInfoAtPosition(position)
    if self.size < position or self.first == nil then
        return nil
    end

    local node = self.first
    while position > 0 do
        node = node.next
        position = position - 1
    end

    return node
end

function ViragDevToolLinkedList:AddNodesAfter(nodeList, parentNode)
    local tempNext = parentNode.next
    local currNode = parentNode;

    for _, node in pairs(nodeList) do
        currNode.next = node
        currNode = node
        self.size = self.size + 1;
    end

    currNode.next = tempNext

    if tempNext == nil then
        self.last = currNode
    end
end

function ViragDevToolLinkedList:AddNode(data, dataName)
    local node = self:NewNode(data, dataName)

    if self.first == nil then
        self.first = node
        self.last = node
    else
        if self.last ~= nil then
            self.last.next = node
        end
        self.last = node
    end

    self.size = self.size + 1;
end

function ViragDevToolLinkedList:NewNode(data, dataName, padding, parent)
    return {
        name = dataName,
        value = data,
        next = nil,
        padding = padding == nil and 0 or padding,
        parent = parent
    }
end

function ViragDevToolLinkedList:RemoveChildNodes(node)
    local currNode = node

    while true do

        currNode = currNode.next

        if currNode == nil then
            node.next = nil
            self.last = node
            break
        end

        if currNode.padding <= node.padding then
            node.next = currNode
            break
        end

        self.size = self.size - 1
    end
end

function ViragDevToolLinkedList:Clear()
    self.size = 0
    self.first = nil
    self.last = nil
end

-----------------------------------------------------------------------------------------------
-- ViragDevTool main
-----------------------------------------------------------------------------------------------

ViragDevTool.list = ViragDevToolLinkedList:new()
ViragDevTool.colors = ViragDevTool_Colors

---
-- Main (and the only) function you can use in ViragDevTool API
-- Will add data to the list so you can explore its values in UI list
-- @usage
-- Lets suppose you have MyModFN function in yours addon
-- function MyModFN()
-- local var = {}
-- ViragDevTool_AddData(var, "My local var in MyModFN")
-- end
-- This will add var as new var in our list
-- @param data (any type)- is object you would like to track.
-- Default behavior is shallow copy
-- @param dataName (string or nil) - name tag to show in UI for you variable.
-- Main purpose is to give readable names to objects you want to track.
function ViragDevTool_AddData(data, dataName)
    if dataName == nil then
        dataName = tostring(data)
    end

    ViragDevTool.list:AddNode(data, tostring(dataName))
    ViragDevTool:UpdateMainTableUI()
end

function ViragDevTool:Add(data, dataName)
    ViragDevTool_AddData(data, dataName)
end

function ViragDevTool:AddDataFromString(msg, bAddToHistory)
    if msg == "" then msg = "_G" end
    local resultTable

    local msgs = self.split(msg, " ")
    local cmd = self.CMD[string.upper(msgs[1])]

    if cmd then
        resultTable, msg = cmd(msg)
    else
        resultTable = self:FromStrToObject(msg)
        if not resultTable then
            self:print("_G." .. msg .. " == nil, so can't add")
        end
    end

    if resultTable then
        if bAddToHistory then
            ViragDevTool:AddToHistory(msg)
        end

        self:Add(resultTable, msg)
    end
end

function ViragDevTool:FromStrToObject(str)
    local vars = self.split(str, ".") or {}

    local var = _G
    for _, name in pairs(vars) do
        if var then
            var = var[name]
        end
    end

    return var
end


function ViragDevTool:ClearData()
    self.list:Clear()
    self:UpdateMainTableUI()
end

function ViragDevTool:ExpandCell(info)

    local nodeList = {}
    local padding = info.padding + 1
    local couner = 1
    for k, v in pairs(info.value) do
        if type(v) ~= "userdata" then
            nodeList[couner] = self.list:NewNode(v, tostring(k), padding, info)
        else
            local mt = getmetatable(info.value)
            if mt then
                nodeList[couner] = self.list:NewNode(mt.__index, self.METATABLE_NAME, padding, info)
            end
        end
        couner = couner + 1
    end

    table.sort(nodeList, self:SortFnForCells(nodeList))

    self.list:AddNodesAfter(nodeList, info)

    info.expanded = true

    ViragDevTool:UpdateMainTableUI()
end

function ViragDevTool:SortFnForCells(nodeList)

    local cmpFn = function(a, b)
        if a.name == "__index" then return true
        elseif b.name == "__index" then return false
        else
            return a.name < b.name
        end
    end

    if #nodeList > 20000 then --  just optimisation for _G
        cmpFn = function(a, b) return a.name < b.name end
    end
    --lets try some better sorting if we have small number of records
    --numbers will be sorted not like 1,10,2 but like 1,2,10
    if #nodeList < 100 then
        cmpFn = function(a, b)
            if a.name == "__index" then return true
            elseif b.name == "__index" then return false
            else

                if tonumber(a.name) ~= nil and tonumber(b.name) ~= nil then
                    return tonumber(a.name)< tonumber(b.name)
                else
                    return a.name < b.name
                end
            end
        end
    end

    return cmpFn
end

function ViragDevTool:ColapseCell(info)
    self.list:RemoveChildNodes(info)
    info.expanded = nil
    self:UpdateMainTableUI()
end

-----------------------------------------------------------------------------------------------
-- UI
-----------------------------------------------------------------------------------------------
function ViragDevTool:ToggleUI()
    self:Toggle(self.wndRef)
    self.settings.isWndOpen = self.wndRef:IsVisible()
end

function ViragDevTool:Toggle(view)
    self:SetVisible(view, not view:IsVisible())
end

function ViragDevTool:SetVisible(view, isVisible)
    if not view then return end

    if isVisible then
        view:Show()
    else
        view:Hide()
    end
end

-----------------------------------------------------------------------------------------------
-- Main table UI
-----------------------------------------------------------------------------------------------
function ViragDevTool:UpdateMainTableUI()

    local scrollFrame = self.wndRef.scrollFrame
    self:MainTableScrollBar_AddChildren(scrollFrame)

    local buttons = scrollFrame.buttons;
    local offset = HybridScrollFrame_GetOffset(scrollFrame)
    local totalRowsCount = self.list.size
    local lineplusoffset;

    local nodeInfo = self.list:GetInfoAtPosition(offset)
    for k, view in pairs(buttons) do
        lineplusoffset = k + offset;
        if lineplusoffset <= totalRowsCount then
            self:UIUpdateMainTableButton(view, nodeInfo, lineplusoffset)
            nodeInfo = nodeInfo.next
            view:Show();
        else
            view:Hide();
        end
    end

    HybridScrollFrame_Update(scrollFrame, totalRowsCount * buttons[1]:GetHeight(), scrollFrame:GetHeight());
end

function ViragDevTool:MainTableScrollBar_AddChildren(scrollFrame)
    if self.ScrollBarHeight == nil or scrollFrame:GetHeight() > self.ScrollBarHeight then
        self.ScrollBarHeight = scrollFrame:GetHeight()

        local scrollBarValue = scrollFrame.scrollBar:GetValue()
        HybridScrollFrame_CreateButtons(scrollFrame, "ViragDevToolEntryTemplate", 0, -2)
        scrollFrame.scrollBar:SetValue(scrollBarValue);
    end
end


function ViragDevTool:UIUpdateMainTableButton(node, info, id)
    local nameButton = node.nameButton;
    local typeButton = node.typeButton
    local valueButton = node.valueButton
    local rowNumberButton = node.rowNumberButton

    local value = info.value
    local name = info.name
    local padding = info.padding

    nameButton:SetPoint("LEFT", node.typeButton, "RIGHT", 20 * padding, 0)

    local valueType = type(value)

    valueButton:SetText(tostring(value))
    nameButton:SetText(tostring(name))
    typeButton:SetText(valueType)
    rowNumberButton:SetText(tostring(id))

    -- local color = "ViragDevToolBaseFont"
    local color = self.colors[valueType]
    if not color then color = self.colors.default end

    if valueType == "table" then
        if name ~= self.METATABLE_NAME then
            local objectType = self:GetObjectTypeFromWoWAPI(value)
            if objectType then
                valueButton:SetText(objectType .. "  " .. tostring(value))
            end
        else
            color = self.colors.default
        end

        local resultStringName = tostring(name)
        local MAX_STRING_SIZE = 60
        if #resultStringName >= MAX_STRING_SIZE then
            resultStringName = string.sub(resultStringName, 0, MAX_STRING_SIZE) .. "..."
        end

        nameButton:SetText(resultStringName .. "   (" .. self:tablelength(value) .. ") ");
    elseif valueType == "string" then
        valueButton:SetText(string.gsub(string.gsub(tostring(value), "|n", ""), "\n", ""))
    end

    nameButton:GetFontString():SetTextColor(unpack(color))
    typeButton:GetFontString():SetTextColor(unpack(color))
    valueButton:GetFontString():SetTextColor(unpack(color))
    rowNumberButton:GetFontString():SetTextColor(unpack(color))

    self:SetMainTableButtonScript(nameButton, info)
    self:SetMainTableButtonScript(valueButton, info)
end

-----------------------------------------------------------------------------------------------
-- Sidebar UI
-----------------------------------------------------------------------------------------------
function ViragDevTool:ToggleSidebar()
    self:Toggle(self.wndRef.sideFrame)
    self.settings.isSideBarOpen = self.wndRef.sideFrame:IsVisible()
    self:UpdateSideBarUI()
end

function ViragDevTool:SubmitEditBoxSidebar()
    local edditBox = self.wndRef.sideFrame.editbox
    local msg = edditBox:GetText()
    local selectedTab = self.settings.sideBarTabSelected
    if selectedTab == "history" then
        self:AddDataFromString(msg, true)
    elseif selectedTab == "favourites" then
        self:AddDataFromString(msg, true)
    elseif selectedTab == "events" then
        self:AddDataFromString("eventadd " .. msg, true)
    end
    self:UpdateSideBarUI()
end

function ViragDevTool:EnableSideBarTab(tabStrName)
    --Update ui
    local sidebar = self.wndRef.sideFrame
    sidebar.history:SetChecked(false)
    sidebar.events:SetChecked(false)
    sidebar.favourites:SetChecked(false)
    sidebar[tabStrName]:SetChecked(true)

    -- update selected tab  and function to update cell items
    self.settings.sideBarTabSelected = tabStrName

    -- refresh ui
    self:UpdateSideBarUI()
end


function ViragDevTool:UpdateSideBarUI()
    local scrollFrame = self.wndRef.sideFrame.sideScrollFrame

    local buttons = scrollFrame.buttons;


    local data = self.settings[self.settings.sideBarTabSelected]
    local selectedTab = self.settings.sideBarTabSelected

    data = data and data or {}

    if not buttons then
        HybridScrollFrame_CreateButtons(scrollFrame, "ViragDevToolSideBarRowTemplate", 0, -2)
    end

    buttons = scrollFrame.buttons;
    local offset = HybridScrollFrame_GetOffset(scrollFrame)
    local totalRowsCount = self:tablelength(data)

    for k, frame in pairs(buttons) do
        local view = frame.mainButton
        local sideButton = frame.actionButton
        local lineplusoffset = k + offset;
        if lineplusoffset <= totalRowsCount then
            local currItem = data[lineplusoffset]
            --history update
            if selectedTab == "history" then

                local name = tostring(currItem)

                view:SetText(name)
                view:SetScript("OnMouseUp", function()
                    ViragDevTool:AddDataFromString(name)

                    --move to top
                    table.remove(data, lineplusoffset)
                    table.insert(data, 1, currItem)

                    ViragDevTool:UpdateSideBarUI()
                end)
                --favourites update
            elseif selectedTab == "favourites" then
                view:SetText("")
                view:SetScript("OnMouseUp", nil)
                --events update
            elseif selectedTab == "events" and type(currItem) == "table" and currItem.event then
                local color = currItem.active and ViragDevTool.colors.white or ViragDevTool.colors.gray
                view:SetText(color .. currItem.event)
                view:SetScript("OnMouseUp", function()
                    --move to top
                    ViragDevTool:ToggleMonitorEvent(currItem)
                    local color = currItem.active and ViragDevTool.colors.white or ViragDevTool.colors.gray
                    view:SetText(color .. currItem.event)
                end)
            end
            sideButton:SetScript("OnMouseUp", function()
                --move to top
                table.remove(data, lineplusoffset)
                self:UpdateSideBarUI()
            end)
            frame:Show();
        else
            frame:Hide();
        end
    end

    HybridScrollFrame_Update(scrollFrame, totalRowsCount * buttons[1]:GetHeight(), scrollFrame:GetHeight());
end

-----------------------------------------------------------------------------------------------
-- Main table row button clicks setup
-----------------------------------------------------------------------------------------------
function ViragDevTool:SetMainTableButtonScript(button, info)
    local valueType = type(info.value)
    if valueType == "table" then
        button:SetScript("OnMouseUp", function(this, button, down)
            if info.expanded then
                self:ColapseCell(info)
            else
                self:ExpandCell(info)
            end
        end)
    elseif valueType == "function" then
        button:SetScript("OnMouseUp", function(this, button, down)
            self:TryCallFunction(info)
        end)
    else
        button:SetScript("OnMouseUp", nil)
    end
end

function ViragDevTool:TryCallFunction(info)
    -- info.value is just our function to call
    local parent, ok
    local fn = info.value
    local args = self:shallowcopyargs(self.tArgs)
    local results = {}

    -- lets try safe call first
    ok, results[1], results[2], results[3], results[4], results[5] = pcall(fn, unpack(args, 1, 10))

    if not ok then
        -- if safe call failed we probably could try to find self and call self:fn()
        parent = info.parent


        if parent and parent.value == _G then
            -- this fn is in global namespace so no parent
            parent = nil
        end

        if parent then

            if parent.name == self.METATABLE_NAME then
                -- metatable has real object 1 level higher
                parent = parent.parent
            end
            fn = parent.value[info.name]
            table.insert(args, 1, parent.value)
            ok, results[1], results[2], results[3], results[4], results[5] = pcall(fn, unpack(args, 1, 10))
        end
    end

    self:ProcessCallFunctionData(ok, info, parent, args, results)
end

-- this function is kinda hard to read but it just adds new items to list and prints log in chat.
-- will add 1 row for call result(ok or error) and 1 row for each return value
function ViragDevTool:ProcessCallFunctionData(ok, info, parent, args, results)
    local nodes = {}

    self:ColapseCell(info) -- if we already called this fn remove old results

    local C = self.colors
    local list = self.list
    local padding = info.padding + 1

    --constract full function call name
    local fnNameWitArgs = C:functionStr(parent, info.name, args)
    local returnFormatedStr = ""

    -- itterate backwords because we want to include every meaningfull nil result
    -- and with default itteration like pairs() we will just skip them so
    -- for example 1, 2, nil, 4 should return only this 4 values nothing more, nothing less.
    local found = false
    for i = 10, 1, -1 do
        if results[i] ~= nil then found = true
        end

        if found or i == 1 then -- if found some return or if return is nil
        nodes[i] = list:NewNode(results[i], string.format("  return: %d", i), padding)

        returnFormatedStr = string.format(" %s%s %s(%s)%s", C.white, tostring(results[i]),
            C.lightblue, type(results[i]), returnFormatedStr)
        end
    end

    -- create fist node of result info no need for now. will use debug
    table.insert(nodes, 1, list:NewNode(string.format("%s - %s", C:stateStr(ok), fnNameWitArgs), -- node value
        C.white .. date("%X") .. " function call results:", padding))


    -- adds call result to our UI list
    list:AddNodesAfter(nodes, info)
    self:UpdateMainTableUI()

    --print info to chat
    self:print(C:stateStr(ok) .. " " .. fnNameWitArgs .. C.gray .. " returns:" .. returnFormatedStr)
end

-----------------------------------------------------------------------------------------------
-- HISTORY
-----------------------------------------------------------------------------------------------
function ViragDevTool:AddToHistory(strValue)
    if self.settings and self.settings.history then
        local hist = self.settings.history

        -- if already contains value then just move it to top
        for k, v in pairs(hist or {}) do
            if v == strValue then
                table.remove(hist, k)
                table.insert(hist, 1, strValue)
                self:UpdateSideBarUI()
                return
            end
        end


        table.insert(hist, 1, strValue)

        local maxSize = self.default_settings.MAX_HISTORY_SIZE
        if self.settings and self.settings.MAX_HISTORY_SIZE then
            maxSize = self.settings.MAX_HISTORY_SIZE
        end

        while #hist > maxSize do -- can have only 10 values in history
        table.remove(hist, maxSize)
        end

        self:UpdateSideBarUI()
    end
end


-----------------------------------------------------------------------------------------------
-- EVENTS
-----------------------------------------------------------------------------------------------
function ViragDevTool:OnEvent(this, event, ...)
    local arg = { ... }
    if event == "ADDON_LOADED" and arg[1] == self.ADDON_NAME then
        ViragDevTool_Settings = self:SetupForSettings(ViragDevTool_Settings)
    end
end

function ViragDevTool:StartMonitorEvent(event, unit)
    local tEvent = self:GetMonitoredEvent(event, unit)

    if not tEvent then
        tEvent = { event = event, unit = unit, active = true }
        table.insert(self.settings.events, tEvent)
    end

    local f = self.wndRef.listenerFrame

    if type(unit) == "string" then
        f:RegisterUnitEvent(event, unit)
    else
        f:RegisterEvent(event)
    end

    tEvent.active = true

    local eventName = event
    if unit then eventName = eventName .. " " .. tostring(unit) end
    self:print(self.colors.green.."Start"..self.colors.white.." event monitoring: " .. self.colors.lightblue.. eventName)
end

function ViragDevTool:StopMonitorEvent(event, unit)
    local tEvent = self:GetMonitoredEvent(event, unit)

    if tEvent and tEvent.active then
        local f = self.wndRef.listenerFrame
        f:UnregisterEvent(event)
        tEvent.active = false

        local eventName = event
        if unit then eventName = eventName .. " " .. tostring(unit) end

        self:print(self.colors.red.."Stop"..self.colors.white.." event monitoring: ".. self.colors.lightblue .. eventName)
    end
end

function ViragDevTool:ToggleMonitorEvent(tEvent)
    if tEvent then
        if tEvent.active then
            self:StopMonitorEvent(tEvent.event, tEvent.unit)
        else
            self:StartMonitorEvent(tEvent.event, tEvent.unit)
        end
    end
end

function ViragDevTool:SetMonitorEventScript()
    local f = self.wndRef.listenerFrame

    f:SetScript("OnEvent", function(this, ...)
        local args = { ... }
        local event = args[1]
        if ViragDevTool:GetMonitoredEvent(event) then
            if #args == 1 then args = args[1] end
            ViragDevTool:Add(args, date("%X") .." " .. event)
        end
    end);
end

function ViragDevTool:GetMonitoredEvent(event, args)

    if self.settings == nil or self.settings.events == nil then return end

    local found

    for _, tEvent in pairs(self.settings.events) do
        if tEvent.event == event then
            found = tEvent
            break
        end
    end

    if found then
        return found
    end
end

function ViragDevTool:SetupForSettings(s)

    if s == nil then
        s = self.default_settings
    else
        -- validating current settings and updating if version changed

        for k, defaultValue in pairs(self.default_settings) do
            local savedValue = s[k] -- saved value from "newSettings"

            -- if setting is a table of size 0 or if value is nil set it to default
            -- for now we have only arrays in settings so its fine to use #table
            if (type(savedValue) == "table" and #savedValue == 0)
                    or savedValue == nil then

                s[k] = defaultValue
            end
        end
    end

    --save to local var, so it is easy to use
    self.settings = s

    -- refresh gui

    -- setup open o closed main wnd
    self:SetVisible(self.wndRef, s.isWndOpen)

    -- setup open o closed sidebar
    self:SetVisible(self.wndRef.sideFrame, s.isSideBarOpen)

    -- setup selected sidebar tab history/events/ favourites
    self:EnableSideBarTab(s.sideBarTabSelected)

    --setup events part 1 register listeners
    for _, tEvent in pairs(self.settings.events) do
        if tEvent.active then
            self:StartMonitorEvent(tEvent.event, tEvent.unit)
        end
    end

    -- setup events part 2 set scripts on frame to listen registered events
    self:SetMonitorEventScript()

    return s
end

-----------------------------------------------------------------------------------------------
-- LIFECICLE
-----------------------------------------------------------------------------------------------
function ViragDevTool:OnLoad(mainFrame)
    self.wndRef = mainFrame

    self.wndRef:RegisterEvent("ADDON_LOADED")
    self.wndRef:SetScript("OnEvent", function(this, event, ...)
        ViragDevTool:OnEvent(this, event, ...); -- call one of the functions above
    end);

    --register update scrollFrame
    self.wndRef.scrollFrame.update = function()
        self:UpdateMainTableUI()
    end
    self:UpdateMainTableUI()

    self.wndRef.sideFrame.sideScrollFrame.update = function()
        self:UpdateSideBarUI()
    end


    -- register slash cmd
    SLASH_VIRAGDEVTOOLS1 = '/vdt';
    function SlashCmdList.VIRAGDEVTOOLS(msg, editbox)
        if msg == "" or msg == nil then
            self:ToggleUI()
        else
            self:AddDataFromString(msg, true)
        end
    end
end


-----------------------------------------------------------------------------------------------
-- UTILS
-----------------------------------------------------------------------------------------------
function ViragDevTool:print(strText)
    print(self.colors.darkred .. "[Virag's DT]: " .. self.colors.white .. strText)
end

function ViragDevTool:shallowcopyargs(orig)
    local copy = {}
    for k, v in pairs(orig) do copy[k] = orig[v]
    end
    return copy
end

function ViragDevTool:split(sep)
    local sep, fields = sep or ".", {}
    local pattern = string.format("([^%s]+)", sep)
    self:gsub(pattern, function(c) fields[#fields + 1] = c
    end)
    return fields
end

function ViragDevTool:tablelength(T)
    local count = 0
    for _ in pairs(T) do count = count + 1
    end
    return count
end

function ViragDevTool:printtable(T)
    print(tostring(T))
    if type(T) ~= "table" then return end
    for k, v in pairs(T) do
        print(tostring(k) .. " => " .. tostring(v))
    end
end


function ViragDevTool:GetObjectTypeFromWoWAPI(value)
    if ACP and value == ACP.L then return end --todo fix this later throws exception

    if type(value) == "table" and value.GetObjectType and value.IsForbidden then

        local ok, forbidden = pcall(value.IsForbidden, value)
        if ok and not forbidden then

            local ok, result = pcall(value.GetObjectType, value)

            if ok then
                return result
            end
        end
    end
end