local M,d=...d.DevTool=(LibStub("\065\099\101\065\100\100\111\110-\051\046\048")):NewAddon("\068\101\118\084\111\111\108","\065\099\101\067\111\110\115\111\108\101-\051\046\048","\065\099\101\069\118\101\110\116-\051\046\048","\065\099\101\072\111\111\107-\051\046\048","\065\099\101\084\105\109\101\114-\051\046\048")local l=d.DevTool _G.DevTool=d.DevTool l.colors={}l.colors.gray=CreateColorFromHexString("\070\070\066\069\066\057\066\053")l.colors.lightblue=CreateColorFromHexString("\070\070\057\054\067\048\067\069")l.colors.lightgreen=CreateColorFromHexString("\070\070\057\056\070\066\057\056")l.colors.red=CreateColorFromHexString("\070\070\070\070\048\048\048\048")l.colors.green=CreateColorFromHexString("\070\070\048\048\070\070\048\048")l.colors.darkred=CreateColorFromHexString("\070\070\067\050\053\066\053\054")l.colors.parent=CreateColorFromHexString("\070\070\066\069\066\057\066\053")l.colors.error=CreateColorFromHexString("\070\070\070\070\048\048\048\048")l.colors.ok=CreateColorFromHexString("\070\070\048\048\070\070\048\048")l.list={}function l.OnInitialize(M)M.db=(LibStub("\065\099\101\068\066-\051\046\048")):New("\068\101\118\084\111\111\108\068\097\116\097\098\097\115\101",M.DatabaseDefaults)end function l.OnEnable(M)M:CreateChatCommands()M.MainWindow=CreateFrame("\070\114\097\109\101","\068\101\118\084\111\111\108\070\114\097\109\101",UIParent,"\068\101\118\084\111\111\108\077\097\105\110\070\114\097\109\101")M.colors.table=CreateColor(unpack(M.db.profile.colorVals.table))M.colors.string=CreateColor(unpack(M.db.profile.colorVals.string))M.colors.number=CreateColor(unpack(M.db.profile.colorVals.number))M.colors["\102\117\110\099\116\105\111\110"]=CreateColor(unpack(M.db.profile.colorVals["\102\117\110\099\116\105\111\110"]))M.colors.default=CreateColor(unpack(M.db.profile.colorVals.default))M:LoadSettings()M:UpdateMainTableUI()M:UpdateSideBarUI()M.MainWindow.scrollFrame.update=function()M:UpdateMainTableUI()end M.MainWindow.sideFrame.sideScrollFrame.update=function()M:UpdateSideBarUI()end local function d(d)if not d or d==""then M:ToggleUI()else M:ExecuteCMD(d,true)end end M:RegisterChatCommand("\100\101\118",d)M:RegisterChatCommand("\100\101\118\116\111\111\108",d)M:RegisterChatCommand("\118\100\116",d)end function l.OnDisable(M) end function l.CreateChatCommands(M)M.CMD={HELP=function()print(" ")M:Print("\068\101\118\084\111\111\108 \105\115 \097 \087\111\114\108\100 \111\102 \087\097\114\099\114\097\102\116\194\174 \097\100\100\111\110 \100\101\118\101\108\111\112\109\101\110\116 \116\111\111\108\046")M:Print("\065\118\097\105\108\097\098\108\101 \099\111\109\109\097\110\100\115\058")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118\058 ").."\083\104\111\119\047\072\105\100\101 \116\104\101 \068\101\118\084\111\111\108 \105\110\116\101\114\102\097\099\101")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \104\101\108\112\058 ").."\068\105\115\112\108\097\121 \104\101\108\112 \105\110\102\111\114\109\097\116\105\111\110 \105\110 \116\104\101 \099\104\097\116 \119\105\110\100\111\119")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \060\110\097\109\101\062 \060\112\097\114\101\110\116 \040\111\112\116\105\111\110\097\108\041\062\058 ").."\065\100\100 \097 \116\097\098\108\101 \119\105\116\104\105\110 \095\071 \111\114 \095\071\046\112\097\114\101\110\116 \116\111 \116\104\101 \108\105\115\116")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \102\105\110\100 \060\112\097\114\116\095\111\102\095\110\097\109\101\062 \060\112\097\114\101\110\116 \040\111\112\116\105\111\110\097\108\041\062\058 ").."\065\100\100 \116\097\098\108\101\115 \099\111\110\116\097\105\110\105\110\103 \116\104\105\115 \112\097\114\116\105\097\108 \110\097\109\101 \119\105\116\104\105\110 \095\071 \111\114 \095\071\046\112\097\114\101\110\116 \116\111 \116\104\101 \108\105\115\116")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \115\116\097\114\116\115\119\105\116\104 \060\112\097\114\116\095\111\102\095\110\097\109\101\062 \060\112\097\114\101\110\116 \040\111\112\116\105\111\110\097\108\041\062\058 ").."\065\100\100 \116\097\098\108\101\115 \098\101\103\105\110\110\105\110\103 \119\105\116\104 \116\104\105\115 \112\097\114\116\105\097\108 \110\097\109\101 \119\105\116\104\105\110 \095\071 \111\114 \095\071\046\112\097\114\101\110\116 \116\111 \116\104\101 \108\105\115\116")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \101\118\101\110\116\097\100\100 \060\101\118\101\110\116\062 \060\117\110\105\116 \040\111\112\116\105\111\110\097\108\041\062\058 ").."\065\100\100 \101\118\101\110\116 \111\114 \117\110\105\116 \101\118\101\110\116 \116\111 \116\104\101 \108\105\115\116")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \101\118\101\110\116\115\116\097\114\116 \060\101\118\101\110\116\062 \060\117\110\105\116 \040\111\112\116\105\111\110\097\108\041\062\058 ").."\066\101\103\105\110 \109\111\110\105\116\111\114\105\110\103 \116\104\105\115 \101\118\101\110\116 \111\114 \117\110\105\116 \101\118\101\110\116")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \101\118\101\110\116\115\116\111\112 \060\101\118\101\110\116\062 \060\117\110\105\116 \040\111\112\116\105\111\110\097\108\041\062\058 ").."\083\116\111\112 \109\111\110\105\116\111\114\105\110\103 \116\104\105\115 \101\118\101\110\116 \111\114 \117\110\105\116 \101\118\101\110\116")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \108\111\103\102\110 \060\102\117\110\099\116\105\111\110\062 \060\112\097\114\101\110\116\062\058 ").."\076\111\103 \097\108\108 \099\097\108\108\115 \109\097\100\101 \116\111 \116\104\101 \102\117\110\099\116\105\111\110")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \109\111\117\115\101\111\118\101\114\058 ").."\065\100\100 \116\104\101 \099\117\114\114\101\110\116\108\121 \104\111\118\101\114\101\100 \102\114\097\109\101 \116\111 \116\104\101 \108\105\115\116")print(M.colors.lightblue:WrapTextInColorCode("\047\100\101\118 \114\101\112\111\115\105\116\105\111\110\058 ").."\082\101\115\101\116 \116\104\101 \112\111\115\105\116\105\111\110 \111\102 \116\104\101 \068\101\118\084\111\111\108 \119\105\110\100\111\119")print(" ")return end;FIND=function(M,d)local U=d and l.FromStrToObject(d)or _G return l.FindIn(U,M,string.match)end;STARTSWITH=function(M,d)local U=d and l.FromStrToObject(d)or _G return l.FindIn(U,M,l.starts)end,EVENTADD=function(M,d)l:StartMonitorEvent(M,d)end;EVENTSTART=function(M,d)l:StartMonitorEvent(M,d)end;EVENTSTOP=function(M,d)l:StopMonitorEvent(M,d)end;LOGFN=function(M,d)l:StartLogFunctionCalls(M,d)end;MOUSEOVER=function()local M if GetMouseFoci then M=(GetMouseFoci())[1]else M=GetMouseFocus()end return M,M:GetName()end,REPOSITION=function()M.MainWindow:ClearAllPoints()M.MainWindow:SetPoint("\067\069\078\084\069\082",UIParent)M.MainWindow:SetSize(750,400)end}end function l.LoadSettings(M)if M.db.profile.isWndOpen then M.MainWindow:Show()end if M.db.profile.isSideBarOpen then M.MainWindow.sideFrame:Show()end M:EnableSideBarTab(M.db.profile.sideBarTabSelected)for M,d in pairs(M.db.profile.logs)do d.active=false end for d,l in pairs(M.db.profile.events)do if l.active then M:StartMonitorEvent(l.event,l.unit)end end local d=""local l=""for M,U in pairs(M.db.profile.tArgs)do d=tostring(U)..(l..d)l="\044 "end M.MainWindow.editbox:SetText(d)M:SetMonitorEventScript()if M.db.profile.colorVals then for d,l in pairs(M.db.profile.colorVals)do M.colors[d]:SetRGBA(unpack(l))end end M:LoadInterfaceOptions()M:ResizeColumn(true)end function l.AddData(l,M,d)if not M then l:Print("\069\114\114\111\114\058 \084\104\101 \100\097\116\097 \098\101\105\110\103 \097\100\100\101\100 \100\111\101\115 \110\111\116 \101\120\105\115\116\046 \065\098\111\114\116\105\110\103\046")return end if not d then d=tostring(M)end if not l.list then print("\069\114\114\111\114\058 \068\101\118\084\111\111\108 \108\105\115\116 \105\115 \110\111\116 \102\117\108\108\121 \105\110\105\116\105\097\108\105\122\101\100\046 \080\108\101\097\115\101 \116\114\121 \097\103\097\105\110 \108\097\116\101\114 \105\110 \116\104\101 \108\111\097\100\105\110\103 \112\114\111\099\101\115\115\046 \065\098\111\114\116\105\110\103\046")end table.insert(l.list,l:NewElement(M,tostring(d)))l:UpdateMainTableUI()end function l.NewElement(j,M,d,l,U)return{name=d;value=M,indentation=l==nil and 0 or l,parent=U or j.list}end function l.ClearAllData(M)table.wipe(M.list)collectgarbage("\099\111\108\108\101\099\116")M:UpdateMainTableUI()end function l.ExpandCell(d,M)local U={}local j=M.indentation+1 local v for l,x in pairs(M.value)do if type(x)~="\117\115\101\114\100\097\116\097"then table.insert(U,d:NewElement(x,tostring(l),j,M))else v=getmetatable(x)if v then table.insert(U,d:NewElement(v,"\036\109\101\116\097\116\097\098\108\101 \102\111\114 "..tostring(l),j,M))else if l~=0 then table.insert(U,d:NewElement(x,"\036\109\101\116\097\116\097\098\108\101 \110\111\116 \102\111\117\110\100 \102\111\114 "..tostring(l),j,M))end end end end v=getmetatable(M.value)if v and type(v)=="\116\097\098\108\101"then table.insert(U,d:NewMetatableElement(v,j,M))end if#U+#d.list>=l.CountElements(_G)*1.05 then d:Print("\069\120\112\097\110\100\067\101\108\108\058 \084\111\111 \109\097\110\121 \101\108\101\109\101\110\116\115 \105\110 \116\097\098\108\101\046 \065\098\111\114\116\105\110\103")return end table.sort(U,l.SelectSortFunction(#U))local x=l.FindIndex(d.list,M)for M,l in ipairs(U)do table.insert(d.list,x+M,l)end M.expanded=true d:UpdateMainTableUI()end function l.CollapseCell(d,M)local U=l.FindIndex(d.list,M)local j for l=U+1,#d.list,1 do if d.list[l].indentation>M.indentation then j=l else break end end if j then for M=j,U+1,-1 do table.remove(d.list,M)end end M.expanded=nil d:UpdateMainTableUI()end function l.NewMetatableElement(U,M,d,l)if#M==1 and M.__index then return U:NewElement(M.__index,"\036\109\101\116\097\116\097\098\108\101\046\095\095\105\110\100\101\120",d,l)else return U:NewElement(M,"\036\109\101\116\097\116\097\098\108\101",d,l)end end function l.ExecuteCMD(U,M,d)if M==""then M="\095\071"end local j local v=l.split(M," ")local x=U.CMD[string.upper(v[1])]if x then local d j,d=x(v[2],v[3])if d then M=d end else j=l.FromStrToObject(M)if not j then U:Print("\067\097\110\110\111\116 \102\105\110\100 "..("\095\071\046"..M))end end if j then if d then l:AddToHistory(M)end U:AddData(j,M)end end function l.ToggleUI(M)if not M.MainWindow:IsVisible()then M.MainWindow:Show()M.db.profile.isWndOpen=true else M.MainWindow:Hide()M.db.profile.isWndOpen=false end if M.db.profile.isWndOpen then M:UpdateMainTableUI()M:UpdateSideBarUI()else M:CancelAllTimers()end end function l.ResizeUpdateTick(M)M:ResizeMainFrame()M:ResizeColumn()M:UpdateMainTableUI()M:UpdateSideBarUI()end function l.ColumnResizeUpdateTick(M)M:ResizeColumn()M:UpdateMainTableUI()M:UpdateSideBarUI()end function l.ResizeMainFrame(M)local d=M.MainWindow:GetLeft()local U=M.MainWindow:GetTop()local j,v=GetCursorPosition()local x=M.MainWindow:GetEffectiveScale()j=j/x v=v/x local C,i,A,m if M.MainWindow.SetResizeBounds then C,i,A,m=M.MainWindow:GetResizeBounds()else A,m=M.MainWindow:GetMaxResize()C,i=M.MainWindow:GetMinResize()end M.MainWindow:SetSize(l.CalculatePosition(j-d,C,A),l.CalculatePosition(U-v,i,m))end function l.ResizeColumn(d,M)if not M then local M=100 local U=d.MainWindow:GetWidth()-150 local j=d.MainWindow:GetRight()-d.MainWindow.columnResizer:GetRight()j=l.CalculatePosition(j,M,U)d.db.profile.collResizeWidth=j end d.MainWindow.columnResizer:ClearAllPoints()d.MainWindow.columnResizer:SetPoint("\084\079\080\082\073\071\072\084",d.MainWindow,"\084\079\080\082\073\071\072\084",d.db.profile.collResizeWidth*-1,-30)end function l.UpdateMainTableUI(M)if not M.MainWindow or not M.MainWindow:IsVisible()then return end M:AddScrollFrameButtons(M.MainWindow.scrollFrame,"\068\101\118\084\111\111\108\069\110\116\114\121\084\101\109\112\108\097\116\101")M:UpdateScrollFrameRowSize(M.MainWindow.scrollFrame)local d=HybridScrollFrame_GetOffset(M.MainWindow.scrollFrame)local l=#M.list local U=1 for j,v in pairs(M.MainWindow.scrollFrame.buttons)do local x=j+d if x<=l and(j-1)*M.MainWindow.scrollFrame.buttons[1]:GetHeight()<M.MainWindow.scrollFrame:GetHeight()then M:UIUpdateMainTableButton(v,M.list[d+U],x)U=U+1 v:Show()else v:Hide()end end HybridScrollFrame_Update(M.MainWindow.scrollFrame,l*M.MainWindow.scrollFrame.buttons[1]:GetHeight()+10,M.MainWindow.scrollFrame:GetHeight())M.MainWindow.scrollFrame.scrollChild:SetWidth(M.MainWindow.scrollFrame:GetWidth())end function l.UpdateScrollFrameRowSize(d,M)local l=d.db.profile.fontSize or 10 local U=l+l*.2 U=U%2==0 and U or U+1 for M,d in pairs(M.buttons)do d:SetHeight(U)local j=(d.nameButton:GetFontString()):GetFont();(d.nameButton:GetFontString()):SetFont(j,l);(d.rowNumberButton:GetFontString()):SetFont(j,l);(d.valueButton:GetFontString()):SetFont(j,l)end M.buttonHeight=U end function l.AddScrollFrameButtons(l,M,d)if not M.ScrollBarHeight or M:GetHeight()>M.ScrollBarHeight then M.ScrollBarHeight=M:GetHeight()HybridScrollFrame_CreateButtons(M,d,0,-2)M.scrollBar:SetValue(M.scrollBar:GetValue())end end function l.UIUpdateMainTableButton(j,M,d,U)local v=j.colors[type(d.value)]if not v then v=j.colors.default end if type(d.value)=="\116\097\098\108\101"and l.IsMetaTableNode(d)then v=j.colors.default end M.nameButton:SetPoint("\076\069\070\084",M.rowNumberButton,"\082\073\071\072\084",10*d.indentation-10,0)M.valueButton:SetText(l.ToUIString(d.value,d.name,true))M.nameButton:SetText(tostring(d.name))M.rowNumberButton:SetText(tostring(U));(M.nameButton:GetFontString()):SetTextColor(v:GetRGBA());(M.valueButton:GetFontString()):SetTextColor(v:GetRGBA());(M.rowNumberButton:GetFontString()):SetTextColor(v:GetRGBA())j:SetMainTableButtonScript(M.nameButton,d)j:SetMainTableButtonScript(M.valueButton,d)end function l.ToggleSidebar(M)if not M.MainWindow.sideFrame:IsVisible()then M.MainWindow.sideFrame:Show()M.db.profile.isSideBarOpen=true else M.MainWindow.sideFrame:Hide()M.db.profile.isSideBarOpen=false end M:UpdateSideBarUI()end function l.SubmitEditBoxSidebar(M)local d=M.db.profile.sideBarTabSelected local l=M.MainWindow.sideFrame.editbox:GetText()if d=="\108\111\103\115"then l="\108\111\103\102\110 "..l elseif d=="\101\118\101\110\116\115"then l="\101\118\101\110\116\097\100\100 "..l end M:ExecuteCMD(l,true)M:UpdateSideBarUI()end function l.EnableSideBarTab(d,M)local l=d.MainWindow.sideFrame l.history:SetChecked(false)l.events:SetChecked(false)l.logs:SetChecked(false)l[M]:SetChecked(true)d.db.profile.sideBarTabSelected=M d:UpdateSideBarUI()end function l.UpdateSideBarUI(M)M:AddScrollFrameButtons(M.MainWindow.sideFrame.sideScrollFrame,"\068\101\118\084\111\111\108\083\105\100\101\066\097\114\082\111\119\084\101\109\112\108\097\116\101")local d=HybridScrollFrame_GetOffset(M.MainWindow.sideFrame.sideScrollFrame)local l=M.db.profile[M.db.profile.sideBarTabSelected]local U=#l for j,v in pairs(M.MainWindow.sideFrame.sideScrollFrame.buttons)do local x=j+d if x<=U and j*M.MainWindow.sideFrame.sideScrollFrame.buttons[1]:GetHeight()<M.MainWindow.sideFrame.sideScrollFrame:GetHeight()then M:UpdateSideBarRow(v.mainButton,l,x)v.actionButton:SetScript("\079\110\077\111\117\115\101\085\112",function()table.remove(l,x)M:UpdateSideBarUI()end)v:Show()else v:Hide()end end HybridScrollFrame_Update(M.MainWindow.sideFrame.sideScrollFrame,U*M.MainWindow.sideFrame.sideScrollFrame.buttons[1]:GetHeight(),M.MainWindow.sideFrame.sideScrollFrame:GetHeight())end function l.UpdateSideBarRow(j,M,d,U)local v=j.db.profile.sideBarTabSelected local x=d[U]if v=="\104\105\115\116\111\114\121"then local j=tostring(x)M:SetText(j)M:SetScript("\079\110\077\111\117\115\101\085\112",function()l:ExecuteCMD(j)table.remove(d,U)table.insert(d,1,x)l:UpdateSideBarUI()end)elseif v=="\108\111\103\115"then local d=j:LogFunctionCallText(x)if x.active then M:SetText(d)else M:SetText(d.." \040\115\116\111\112\112\101\100\041")end M:SetScript("\079\110\077\111\117\115\101\085\112",function()l:ToggleFnLogger(x)l:UpdateSideBarUI()end)elseif v=="\101\118\101\110\116\115"then local d=x.event local U=x.unit local v=d if U then v=v..j.colors.lightblue:WrapTextInColorCode(" \040"..(U.."\041"))end if not x.active then v=v..j.colors.red:WrapTextInColorCode(" \040\115\116\111\112\112\101\100\041")end M:SetText(v)M:SetScript("\079\110\077\111\117\115\101\085\112",function()l:ToggleMonitorEvent(x)l:UpdateSideBarUI()end)end end function l.SetMainTableButtonScript(U,M,d)local j=type(d.value)local v=function() end if j=="\116\097\098\108\101"then v=function()if d.expanded then U:CollapseCell(d)else U:ExpandCell(d)end end elseif j=="\102\117\110\099\116\105\111\110"then v=function()U:TryCallFunction(d)end end M:SetScript("\079\110\077\111\117\115\101\085\112",function(M,d,U)if d=="\082\105\103\104\116\066\117\116\116\111\110"then local d=(M:GetParent()).nameButton local U=(M:GetParent()).valueButton l:Print(d:GetText()..(" - "..U:GetText()))else v(M,d,U)end end)end function l.TryCallFunction(d,M)local U=l.GetParentTable(M)local j=M.value local v={unpack(d.db.profile.tArgs)}for j,x in pairs(v)do if type(x)=="\115\116\114\105\110\103"and x=="\116\061\115\101\108\102"then if not U then local l,j=false,{"\116\061\115\101\108\102 \115\101\116 \097\115 \097\114\103\117\109\101\110\116\044 \098\117\116 \110\111 \112\097\114\101\110\116 \116\097\098\108\101 \101\120\105\115\116\115"}return d:ProcessCallFunctionData(l,M,U,v,j)end v[j]=U and U.value elseif type(x)=="\115\116\114\105\110\103"and l.starts(x,"\116\061")then local M=l.FromStrToObject(string.sub(x,3))if M then v[j]=M end end end local x,C=l.TryCallFunctionWithArgs(j,v)if not x and(U and v[1]~=U)then v={U.value;unpack(v)}x,C=l.TryCallFunctionWithArgs(j,v)end d:ProcessCallFunctionData(x,M,U,v,C)end function l.ProcessCallFunctionData(x,M,d,U,j,v)local C={}x:CollapseCell(d)local i=d.indentation+1 local A=function(M)if M then return x.colors.ok:WrapTextInColorCode("\079\075")end return x.colors.error:WrapTextInColorCode("\069\082\082\079\082")end local m=d.name..x.colors.lightblue:WrapTextInColorCode("\040"..(l.ArgsToString(j).."\041"))m=U and x.colors.gray:WrapTextInColorCode(U.name..("\058"..m))or m local f=""local K=false for M=10,1,-1 do if v[M]~=nil then K=true end if K or M==1 then table.insert(C,x:NewElement(v[M],string.format("  \114\101\116\117\114\110\058 \037\100",M),i))f=string.format(" \037\115 \040\037\115\041\037\115",tostring(v[M]),x.colors.lightblue:WrapTextInColorCode(type(v[M])),f)end end table.insert(C,1,x:NewElement(string.format("\037\115 - \037\115",A(M),m),date("\037\088").." \102\117\110\099\116\105\111\110 \099\097\108\108 \114\101\115\117\108\116\115\058",i))local P=l.FindIndex(x.list,d)for M,d in ipairs(C)do table.insert(x.list,P+M,d)end x:UpdateMainTableUI()x:Print(A(M)..(" "..(m..(x.colors.gray:WrapTextInColorCode(" \114\101\116\117\114\110\115\058")..f))))end function l.SetArgForFunctionCallFromString(d,M)local U=l.split(M,"\044")or{}local j=function(M)return string.gsub(M,"\094\037\115\042\040\046-\041\037\115\042\036","\037\049")end for M,d in pairs(U)do d=j(d)if tonumber(d)then U[M]=tonumber(d)elseif d=="\110\105\108"then U[M]=nil elseif d=="\116\114\117\101"then U[M]=true elseif d=="\102\097\108\115\101"then U[M]=false end end d.db.profile.tArgs=U d:AddData(U,"\078\101\119 \065\114\103\115 \102\111\114 \102\117\110\099\116\105\111\110 \099\097\108\108\115")end
-- DevTool is a World of Warcraft® addon development tool.
-- Copyright (c) 2021-2024 Britt W. Yazel
-- Copyright (c) 2016-2021 Peter aka "Varren"
-- This code is licensed under the MIT license (see LICENSE for details)

local _, addonTable = ... --make use of the default addon namespace

---@class DevTool : AceAddon-3.0 @define The main addon object for the DevTool addon
addonTable.DevTool = LibStub("AceAddon-3.0"):NewAddon("DevTool", "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0", "AceTimer-3.0")
local DevTool = addonTable.DevTool

--add global reference to the addon object
_G["DevTool"] = addonTable.DevTool

--store the colors outside the database in a class level table
DevTool.colors = {}
DevTool.colors["gray"] = CreateColorFromHexString("FFBEB9B5")
DevTool.colors["lightblue"] = CreateColorFromHexString("FF96C0CE")
DevTool.colors["lightgreen"] = CreateColorFromHexString("FF98FB98")
DevTool.colors["red"] = CreateColorFromHexString("FFFF0000")
DevTool.colors["green"] = CreateColorFromHexString("FF00FF00")
DevTool.colors["darkred"] = CreateColorFromHexString("FFC25B56")
DevTool.colors["parent"] = CreateColorFromHexString("FFBEB9B5")
DevTool.colors["error"] = CreateColorFromHexString("FFFF0000")
DevTool.colors["ok"] = CreateColorFromHexString("FF00FF00")

-- Holds the contents of the current view window.
DevTool.list = {}


-------------------------------------------------------------------------
--------------------Start of Functions-----------------------------------
-------------------------------------------------------------------------

--- Called directly after the addon is fully loaded.
--- We do initialization tasks here, such as loading our saved variables or setting up slash commands.
function DevTool:OnInitialize()

	self.db = LibStub("AceDB-3.0"):New("DevToolDatabase", self.DatabaseDefaults)
	
end

--- Called during the PLAYER_LOGIN event when most of the data provided by the game is already present.
--- We perform more startup tasks here, such as registering events, hooking functions, creating frames, or getting 
--- information from the game that wasn't yet available during :OnInitialize()
function DevTool:OnEnable()
	
	self:CreateChatCommands()

	self.MainWindow = CreateFrame("Frame", "DevToolFrame", UIParent, "DevToolMainFrame")

	--create the colors from the values stored in the database
	self.colors["table"] = CreateColor(unpack(self.db.profile.colorVals["table"]))
	self.colors["string"] = CreateColor(unpack(self.db.profile.colorVals["string"]))
	self.colors["number"] = CreateColor(unpack(self.db.profile.colorVals["number"]))
	self.colors["function"] = CreateColor(unpack(self.db.profile.colorVals["function"]))
	self.colors["default"] = CreateColor(unpack(self.db.profile.colorVals["default"]))

	self:LoadSettings()
	self:UpdateMainTableUI()
	self:UpdateSideBarUI()

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

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

	local function chatFunction(message)
		if not message or message == "" then
			self:ToggleUI()
		else
			self:ExecuteCMD(message, true)
		end
	end

	self:RegisterChatCommand("dev", chatFunction)
	self:RegisterChatCommand("devtool", chatFunction)
	-- legacy command for muscle memory reasons
	self:RegisterChatCommand("vdt", chatFunction)

end

--- Called when our addon is manually being disabled during a running session.
--- We primarily use this to unhook scripts, unregister events, or hide frames that we created.
function DevTool:OnDisable()
	-- Empty --
end

-------------------------------------------------

function DevTool:CreateChatCommands()
	-- you can use "/dev find <some string> <parent name>" (can be in format _G.Frame.Button)
	-- for example "/dev find DevTool" will find every variable in _G that has *DevTool* pattern
	-- "/dev find Data DevTool" will find every variable that has *Data* in their name in _G.DevTool object if it exists
	-- same for "startswith"
	self.CMD = {
		--"/dev help"
		HELP = function()
			print(" ")
			self:Print("DevTool is a World of Warcraft® addon development tool.")
			self:Print("Available commands:")
			print(self.colors.lightblue:WrapTextInColorCode("/dev: ") .. "Show/Hide the DevTool interface")
			print(self.colors.lightblue:WrapTextInColorCode("/dev help: ") .. "Display help information in the chat window")
			print(self.colors.lightblue:WrapTextInColorCode("/dev <name> <parent (optional)>: ") .. "Add a table within _G or _G.parent to the list")
			print(self.colors.lightblue:WrapTextInColorCode("/dev find <part_of_name> <parent (optional)>: ") .. "Add tables containing this partial name within _G or _G.parent to the list")
			print(self.colors.lightblue:WrapTextInColorCode("/dev startswith <part_of_name> <parent (optional)>: ") .. "Add tables beginning with this partial name within _G or _G.parent to the list")
			print(self.colors.lightblue:WrapTextInColorCode("/dev eventadd <event> <unit (optional)>: ") .. "Add event or unit event to the list")
			print(self.colors.lightblue:WrapTextInColorCode("/dev eventstart <event> <unit (optional)>: ") .. "Begin monitoring this event or unit event")
			print(self.colors.lightblue:WrapTextInColorCode("/dev eventstop <event> <unit (optional)>: ") .. "Stop monitoring this event or unit event")
			print(self.colors.lightblue:WrapTextInColorCode("/dev logfn <function> <parent>: ") .. "Log all calls made to the function")
			print(self.colors.lightblue:WrapTextInColorCode("/dev mouseover: ") .. "Add the currently hovered frame to the list")
			print(self.colors.lightblue:WrapTextInColorCode("/dev reposition: ") .. "Reset the position of the DevTool window")
			print(" ")
			return

		end,

		FIND = function(message2, message3)
			local parent = message3 and DevTool.FromStrToObject(message3) or _G
			return DevTool.FindIn(parent, message2, string.match)
		end,

		STARTSWITH = function(message2, message3)
			local parent = message3 and DevTool.FromStrToObject(message3) or _G
			return DevTool.FindIn(parent, message2, DevTool.starts)
		end,

		EVENTADD = function(message2, message3)
			DevTool:StartMonitorEvent(message2, message3)
		end,

		EVENTSTART = function(message2, message3)
			DevTool:StartMonitorEvent(message2, message3)
		end,

		EVENTSTOP = function(message2, message3)
			DevTool:StopMonitorEvent(message2, message3)
		end,

		LOGFN = function(message2, message3)
			DevTool:StartLogFunctionCalls(message2, message3)
		end,

		MOUSEOVER = function()
			local focusedFrame
			--WoW 11.0 added GetMouseFoci() which now returns a table of frames in order of their on-screen stacking
			if GetMouseFoci then
				focusedFrame = GetMouseFoci()[1]
			else
				focusedFrame = GetMouseFocus()
			end
			return focusedFrame, focusedFrame:GetName()
		end,

		REPOSITION = function()
			self.MainWindow:ClearAllPoints()
			self.MainWindow:SetPoint("CENTER", UIParent)
			self.MainWindow:SetSize(750, 400)
		end
	}
end

-----------------------------------------------------------------------------------------------
--- LIFECYCLE
-----------------------------------------------------------------------------------------------

function DevTool:LoadSettings()
	-- setup open or closed main window
	if self.db.profile.isWndOpen then
		self.MainWindow:Show()
	end

	-- setup open or closed sidebar
	if self.db.profile.isSideBarOpen then
		self.MainWindow.sideFrame:Show()
	end

	-- setup selected sidebar tab history/events/logs
	self:EnableSideBarTab(self.db.profile.sideBarTabSelected)

	-- setup logs. Just disable all of them for now on startup
	for _, tLog in pairs(self.db.profile.logs) do
		tLog.active = false
	end

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

	-- show in UI fn saved args if you have them
	local args = ""
	local delim = ""
	for _, arg in pairs(self.db.profile.tArgs) do
		args = tostring(arg) .. delim .. args
		delim = ", "
	end

	self.MainWindow.editbox:SetText(args)

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

	--we store colors not in saved settings for now
	if self.db.profile.colorVals then
		for k, v in pairs(self.db.profile.colorVals) do
			self.colors[k]:SetRGBA(unpack(v))
		end
	end

	self:LoadInterfaceOptions()

	self:ResizeColumn(true)
end

-----------------------------------------------------------------------------------------------
--- DevTool main
-----------------------------------------------------------------------------------------------

--- The main (and the only) function you can use in DevTool API
--- Adds data to the list so you can explore its values in UI 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.
function DevTool:AddData(data, dataName)
	-- If the data is nil, print an error and abort
	if not data then
		self:Print("Error: The data being added does not exist. Aborting.")
		return
	end

	if not dataName then
		dataName = tostring(data)
	end

	if not self.list then
		print("Error: DevTool list is not fully initialized. Please try again later in the loading process. Aborting.")
	end

	table.insert(self.list, self:NewElement(data, tostring(dataName)))
	self:UpdateMainTableUI()
end

function DevTool:NewElement(data, dataName, indentation, parent)
	return {
		name = dataName,
		value = data,
		indentation = indentation == nil and 0 or indentation,
		parent = parent or self.list
	}
end

function DevTool:ClearAllData()
	table.wipe(self.list)
	collectgarbage("collect")
	self:UpdateMainTableUI()
end

function DevTool:ExpandCell(info)
	local elementList = {}
	local indentation = info.indentation + 1
	local metatable
	for k, v in pairs(info.value) do
		if type(v) ~= "userdata" then
			table.insert(elementList, self:NewElement(v, tostring(k), indentation, info))
		else
			metatable = getmetatable(v)
			if metatable then
				table.insert(elementList, self:NewElement(metatable, "$metatable for " .. tostring(k), indentation, info))
			else
				if k ~= 0 then
					table.insert(elementList, self:NewElement(v, "$metatable not found for " .. tostring(k), indentation, info))
				end
			end
		end
	end

	metatable = getmetatable(info.value)
	if metatable and type(metatable) == "table" then
		table.insert(elementList, self:NewMetatableElement(metatable, indentation, info))
	end

	--this is a somewhat hacky safety check to make sure we don't overwhelm the UI with too many elements
	--checks to see if the current list + the new elements is more than 5% larger than the total number of elements in _G
	--do this check before the sort to not waste cycles unnecessarily
	if #elementList + #self.list >= DevTool.CountElements(_G) * 1.05 then
		self:Print("ExpandCell: Too many elements in table. Aborting")
		return
	end

	table.sort(elementList, DevTool.SelectSortFunction(#elementList))

	local parentIndex = DevTool.FindIndex(self.list, info)
	for i, element in ipairs(elementList) do
		table.insert(self.list, parentIndex + i, element)
	end

	info.expanded = true

	self:UpdateMainTableUI()
end

function DevTool:CollapseCell(info)
	local parentIndex = DevTool.FindIndex(self.list, info)
	local endIndex
	for i = parentIndex + 1, #self.list do
		if self.list[i].indentation > info.indentation then
			endIndex = i
		else
			break
		end
	end

	--loop backwards as it is WAY faster to remove from the end first
	if endIndex then
		for i = endIndex, parentIndex + 1, -1 do
			table.remove(self.list, i)
		end
	end

	info.expanded = nil
	self:UpdateMainTableUI()
end

function DevTool:NewMetatableElement(metatable, indentation, info)
	if #metatable == 1 and metatable.__index then
		return self:NewElement(metatable.__index, "$metatable.__index", indentation, info)
	else
		return self:NewElement(metatable, "$metatable", indentation, info)
	end
end

function DevTool:ExecuteCMD(message, bAddToHistory)
	if message == "" then
		message = "_G"
	end
	local resultTable

	local messages = DevTool.split(message, " ")
	local command = self.CMD[string.upper(messages[1])]

	if command then
		local title
		resultTable, title = command(messages[2], messages[3])

		if title then
			message = title
		end
	else
		resultTable = DevTool.FromStrToObject(message)
		if not resultTable then
			self:Print("Cannot find " .. "_G." .. message)
		end
	end

	if resultTable then
		if bAddToHistory then
			DevTool:AddToHistory(message)
		end

		self:AddData(resultTable, message)
	end
end

-----------------------------------------------------------------------------------------------
--- UI
-----------------------------------------------------------------------------------------------
function DevTool:ToggleUI()
	if not self.MainWindow:IsVisible() then
		self.MainWindow:Show()
		self.db.profile.isWndOpen = true
	else
		self.MainWindow:Hide()
		self.db.profile.isWndOpen = false
	end

	if self.db.profile.isWndOpen then
		self:UpdateMainTableUI()
		self:UpdateSideBarUI()
	else
		--just in case a timer slipped through
		self:CancelAllTimers()
	end
end

function DevTool:ResizeUpdateTick()
	self:ResizeMainFrame()
	self:ResizeColumn()
	self:UpdateMainTableUI()
	self:UpdateSideBarUI()
end

function DevTool:ColumnResizeUpdateTick()
	self:ResizeColumn()
	self:UpdateMainTableUI()
	self:UpdateSideBarUI()
end

-- I do manual resizing and not the default
-- self:GetParent():StartSizing("BOTTOMRIGHT");
-- self:GetParent():StopMovingOrSizing();
-- Because I don't like default behaviour. --Varren
function DevTool:ResizeMainFrame()
	local left = self.MainWindow:GetLeft()
	local top = self.MainWindow:GetTop()

	local x, y = GetCursorPosition()
	local s = self.MainWindow:GetEffectiveScale()
	x = x / s
	y = y / s

	local minX, minY, maxX, maxY

	if self.MainWindow.SetResizeBounds then
		-- WoW 10.0
		minX, minY, maxX, maxY = self.MainWindow:GetResizeBounds()
	else
		maxX, maxY = self.MainWindow:GetMaxResize()
		minX, minY = self.MainWindow:GetMinResize()

	end

	self.MainWindow:SetSize(DevTool.CalculatePosition(x - left, minX, maxX),
			DevTool.CalculatePosition(top - y, minY, maxY))
end

function DevTool:ResizeColumn(firstRun)
	if not firstRun then
		-- 150 and 50 are just const values. safe to change
		local minWidth = 100
		local maxWidth = self.MainWindow:GetWidth() - 150

		local width = self.MainWindow:GetRight() - self.MainWindow.columnResizer:GetRight()
		width = DevTool.CalculatePosition(width, minWidth, maxWidth)

		-- save pos so we can restore it on reload ui or logout
		self.db.profile.collResizeWidth = width
	end

	self.MainWindow.columnResizer:ClearAllPoints()
	self.MainWindow.columnResizer:SetPoint("TOPRIGHT", self.MainWindow, "TOPRIGHT", self.db.profile.collResizeWidth * -1, -30) -- 30 is offset from above (top buttons)
end

-----------------------------------------------------------------------------------------------
--- Main table UI
-----------------------------------------------------------------------------------------------
function DevTool:UpdateMainTableUI()
	if not self.MainWindow or not self.MainWindow:IsVisible() then
		return
	end

	self:AddScrollFrameButtons(self.MainWindow.scrollFrame, "DevToolEntryTemplate")
	self:UpdateScrollFrameRowSize(self.MainWindow.scrollFrame)

	local offset = HybridScrollFrame_GetOffset(self.MainWindow.scrollFrame)
	local totalRowsCount = #self.list

	local counter = 1
	for k, button in pairs(self.MainWindow.scrollFrame.buttons) do
		local linePlusOffset = k + offset;
		if linePlusOffset <= totalRowsCount and (k - 1) * self.MainWindow.scrollFrame.buttons[1]:GetHeight() <
				self.MainWindow.scrollFrame:GetHeight() then
			self:UIUpdateMainTableButton(button, self.list[offset + counter], linePlusOffset)
			counter = counter + 1
			button:Show();
		else
			button:Hide();
		end
	end

	HybridScrollFrame_Update(self.MainWindow.scrollFrame, totalRowsCount *
			self.MainWindow.scrollFrame.buttons[1]:GetHeight() + 10, self.MainWindow.scrollFrame:GetHeight());

	self.MainWindow.scrollFrame.scrollChild:SetWidth(self.MainWindow.scrollFrame:GetWidth())
end

function DevTool:UpdateScrollFrameRowSize(scrollFrame)
	local currFontSize = self.db.profile.fontSize or 10

	local cellHeight = currFontSize + currFontSize * 0.2
	cellHeight = cellHeight % 2 == 0 and cellHeight or cellHeight + 1
	for _, button in pairs(scrollFrame.buttons) do
		button:SetHeight(cellHeight)
		local font = button.nameButton:GetFontString():GetFont()
		button.nameButton:GetFontString():SetFont(font, currFontSize)
		button.rowNumberButton:GetFontString():SetFont(font, currFontSize)
		button.valueButton:GetFontString():SetFont(font, currFontSize)
	end

	scrollFrame.buttonHeight = cellHeight
end

function DevTool:AddScrollFrameButtons(scrollFrame, strTemplate)
	if not scrollFrame.ScrollBarHeight or scrollFrame:GetHeight() > scrollFrame.ScrollBarHeight then
		scrollFrame.ScrollBarHeight = scrollFrame:GetHeight()
		HybridScrollFrame_CreateButtons(scrollFrame, strTemplate, 0, -2)
		scrollFrame.scrollBar:SetValue(scrollFrame.scrollBar:GetValue());
	end
end

function DevTool:UIUpdateMainTableButton(element, info, id)
	local color = self.colors[type(info.value)]
	if not color then
		color = self.colors.default
	end
	if type(info.value) == "table" and DevTool.IsMetaTableNode(info) then
		color = self.colors.default
	end

	element.nameButton:SetPoint("LEFT", element.rowNumberButton, "RIGHT", 10 * info.indentation - 10, 0)

	element.valueButton:SetText(DevTool.ToUIString(info.value, info.name, true))
	element.nameButton:SetText(tostring(info.name))
	element.rowNumberButton:SetText(tostring(id))

	element.nameButton:GetFontString():SetTextColor(color:GetRGBA())
	element.valueButton:GetFontString():SetTextColor(color:GetRGBA())
	element.rowNumberButton:GetFontString():SetTextColor(color:GetRGBA())

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

-----------------------------------------------------------------------------------------------
--- Sidebar UI
-----------------------------------------------------------------------------------------------
function DevTool:ToggleSidebar()
	if not self.MainWindow.sideFrame:IsVisible() then
		self.MainWindow.sideFrame:Show()
		self.db.profile.isSideBarOpen = true
	else
		self.MainWindow.sideFrame:Hide()
		self.db.profile.isSideBarOpen = false
	end
	self:UpdateSideBarUI()
end

function DevTool:SubmitEditBoxSidebar()
	local selectedTab = self.db.profile.sideBarTabSelected
	local command = self.MainWindow.sideFrame.editbox:GetText()

	if selectedTab == "logs" then
		command = "logfn " .. command
	elseif selectedTab == "events" then
		command = "eventadd " .. command
	end

	self:ExecuteCMD(command, true)
	self:UpdateSideBarUI()
end

function DevTool:EnableSideBarTab(tabStrName)
	--Update ui
	local sidebar = self.MainWindow.sideFrame
	sidebar.history:SetChecked(false)
	sidebar.events:SetChecked(false)
	sidebar.logs:SetChecked(false)
	sidebar[tabStrName]:SetChecked(true)

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

	-- refresh ui
	self:UpdateSideBarUI()
end

function DevTool:UpdateSideBarUI()
	self:AddScrollFrameButtons(self.MainWindow.sideFrame.sideScrollFrame, "DevToolSideBarRowTemplate")

	local offset = HybridScrollFrame_GetOffset(self.MainWindow.sideFrame.sideScrollFrame)
	local data = self.db.profile[self.db.profile.sideBarTabSelected]
	local totalRowsCount = #data

	for k, button in pairs(self.MainWindow.sideFrame.sideScrollFrame.buttons) do
		local linePlusOffset = k + offset;

		if linePlusOffset <= totalRowsCount and k * self.MainWindow.sideFrame.sideScrollFrame.buttons[1]:GetHeight() <
				self.MainWindow.sideFrame.sideScrollFrame:GetHeight() then
			self:UpdateSideBarRow(button.mainButton, data, linePlusOffset)

			--setup remove button for every row
			button.actionButton:SetScript("OnMouseUp", function()
				table.remove(data, linePlusOffset)
				self:UpdateSideBarUI()
			end)
			button:Show();
		else
			button:Hide();
		end
	end

	HybridScrollFrame_Update(self.MainWindow.sideFrame.sideScrollFrame,
			totalRowsCount * self.MainWindow.sideFrame.sideScrollFrame.buttons[1]:GetHeight(),
			self.MainWindow.sideFrame.sideScrollFrame:GetHeight());
end

function DevTool:UpdateSideBarRow(view, data, linePlusOffset)
	local selectedTab = self.db.profile.sideBarTabSelected

	local currItem = data[linePlusOffset]

	if selectedTab == "history" then
		-- history update
		local name = tostring(currItem)
		view:SetText(name)
		view:SetScript("OnMouseUp", function()
			DevTool:ExecuteCMD(name)
			--move this item to the top of the list
			table.remove(data, linePlusOffset)
			table.insert(data, 1, currItem)
			DevTool:UpdateSideBarUI()
		end)

	elseif selectedTab == "logs" then
		local text = self:LogFunctionCallText(currItem)
		if currItem.active then
			view:SetText(text)
		else
			view:SetText(text .. " (stopped)")
		end

		-- logs update
		view:SetScript("OnMouseUp", function()
			DevTool:ToggleFnLogger(currItem)
			DevTool:UpdateSideBarUI()
		end)

	elseif selectedTab == "events" then
		local name = currItem.event
		local unit = currItem.unit

		--set label to be the name
		local label = name

		--append a unit to the label if one exists
		if unit then
			label = label .. self.colors.lightblue:WrapTextInColorCode(" (" .. unit .. ")")
		end

		--append a 'stopped' tag to the label if the event is not active
		if not currItem.active then
			label = label .. self.colors.red:WrapTextInColorCode(" (stopped)")
		end

		view:SetText(label)

		-- events  update
		view:SetScript("OnMouseUp", function()
			DevTool:ToggleMonitorEvent(currItem)
			DevTool:UpdateSideBarUI()
		end)
	end
end

-----------------------------------------------------------------------------------------------
-- Main table row button clicks setup
-----------------------------------------------------------------------------------------------
function DevTool:SetMainTableButtonScript(button, info)
	local valueType = type(info.value)
	local leftClickFn = function()
	end

	if valueType == "table" then
		leftClickFn = function()
			if info.expanded then
				self:CollapseCell(info)
			else
				self:ExpandCell(info)
			end
		end
	elseif valueType == "function" then
		leftClickFn = function()
			self:TryCallFunction(info)
		end
	end

	button:SetScript("OnMouseUp", function(this, mouseButton, down)
		if mouseButton == "RightButton" then
			local nameButton = this:GetParent().nameButton
			local valueButton = this:GetParent().valueButton
			DevTool:Print(nameButton:GetText() .. " - " .. valueButton:GetText())
		else
			leftClickFn(this, mouseButton, down)
		end
	end)
end

function DevTool:TryCallFunction(info)
	-- info.value is just our function to call
	local parent = DevTool.GetParentTable(info)
	local fn = info.value
	local args = { unpack(self.db.profile.tArgs) }
	for k, v in pairs(args) do
		if type(v) == "string" and v == "t=self" then
			if not parent then
				local ok, results = false, { "t=self set as argument, but no parent table exists" }
				return self:ProcessCallFunctionData(ok, info, parent, args, results)
			end
			args[k] = parent and parent.value
		elseif type(v) == "string" and DevTool.starts(v, "t=") then
			local obj = DevTool.FromStrToObject(string.sub(v, 3))
			if obj then
				args[k] = obj
			end
		end
	end

	-- lets try safe call first
	local ok, results = DevTool.TryCallFunctionWithArgs(fn, args)

	if not ok and parent and args[1] ~= parent then
		-- if safe call failed we probably could try to find self and call self:fn(), but only if user didn't explicitly specify t=self already
		args = { parent.value, unpack(args) } --shallow copy and add parent table
		ok, results = DevTool.TryCallFunctionWithArgs(fn, args)
	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 DevTool:ProcessCallFunctionData(ok, info, parent, args, results)
	local elements = {}

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

	local indentation = info.indentation + 1

	local stateStr = function(state)
		if state then
			return self.colors.ok:WrapTextInColorCode("OK")
		end
		return self.colors.error:WrapTextInColorCode("ERROR")
	end

	--construct colored full function call name
	local fnNameWithArgs = info.name .. self.colors.lightblue:WrapTextInColorCode("(" .. DevTool.ArgsToString(args) .. ")")

	fnNameWithArgs = parent and self.colors.gray:WrapTextInColorCode(parent.name .. ":" .. fnNameWithArgs) or fnNameWithArgs

	local returnFormattedStr = ""

	-- iterate backwards because we want to include every meaningful nil result
	-- and with default iteration 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
			table.insert(elements, self:NewElement(results[i], string.format("  return: %d", i), indentation))

			returnFormattedStr = string.format(" %s (%s)%s", tostring(results[i]),
					self.colors.lightblue:WrapTextInColorCode(type(results[i])), returnFormattedStr)
		end
	end

	-- create first element of result info no need for now. will use debug
	table.insert(elements, 1, self:NewElement(string.format("%s - %s", stateStr(ok), fnNameWithArgs), -- element value
			date("%X") .. " function call results:", indentation))

	-- adds call result to our UI list
	local parentIndex = DevTool.FindIndex(self.list, info)
	for i, element in ipairs(elements) do
		table.insert(self.list, parentIndex + i, element)
	end

	self:UpdateMainTableUI()

	--print info to chat
	self:Print(stateStr(ok) .. " " .. fnNameWithArgs .. self.colors.gray:WrapTextInColorCode(" returns:") .. returnFormattedStr)
end

-----------------------------------------------------------------------------------------------
--- BOTTOM PANEL Function Arguments button  and arguments input edit box
-----------------------------------------------------------------------------------------------
function DevTool:SetArgForFunctionCallFromString(argStr)
	local args = DevTool.split(argStr, ",") or {}

	local trim = function(s)
		return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
	end

	for k, arg in pairs(args) do

		arg = trim(arg)
		if tonumber(arg) then
			args[k] = tonumber(arg)
		elseif arg == "nil" then
			args[k] = nil
		elseif arg == "true" then
			args[k] = true
		elseif arg == "false" then
			args[k] = false
		end
	end

	self.db.profile.tArgs = args
	self:AddData(args, "New Args for function calls")
end
