--[[
AceConfigDialog-3.0

]]
local LibStub = LibStub
local MAJOR, MINOR = "AceConfigDialog-3.0", 1
local lib = LibStub:NewLibrary(MAJOR, MINOR)

if not lib then return end

lib.OpenFrames = lib.OpenFrames or {}
lib.Status = lib.Status or {}
lib.frame = lib.frame or CreateFrame("Frame")

local gui = LibStub("AceGUI-3.0")
local reg = LibStub("AceConfigRegistry-3.0")
local con = LibStub("AceConsole-3.0")

local select = select
local pairs = pairs
local type = type
local assert = assert
local tinsert = tinsert
local tremove = tremove
local error = error
local table = table
local unpack = unpack
local string = string
local next = next
local math = math
local _

--[[
	 xpcall safecall implementation
]]
local xpcall = xpcall

local function errorhandler(err)
	return geterrorhandler()(err)
end

local function CreateDispatcher(argCount)
	local code = [[
		local xpcall, eh = ...
		local method, ARGS
		local function call() return method(ARGS) end
	
		local function dispatch(func, ...)
			 method = func
			 if not method then return end
			 ARGS = ...
			 return xpcall(call, eh)
		end
	
		return dispatch
	]]
	
	local ARGS = {}
	for i = 1, argCount do ARGS[i] = "arg"..i end
	code = code:gsub("ARGS", table.concat(ARGS, ", "))
	return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler)
end

local Dispatchers = setmetatable({}, {__index=function(self, argCount)
	local dispatcher = CreateDispatcher(argCount)
	rawset(self, argCount, dispatcher)
	return dispatcher
end})
Dispatchers[0] = function(func)
	return xpcall(func, errorhandler)
end
 
local function safecall(func, ...)
	return Dispatchers[select('#', ...)](func, ...)
end

--[[
Group Types
  Tree 	- All Descendant Groups will all become nodes on the tree, direct child options will appear above the tree
  		- Descendant Groups with inline=true and thier children will not become nodes

  Tab	- Direct Child Groups will become tabs, direct child options will appear above the tab control
  		- Grandchild groups will default to inline unless specified otherwise

  Select- Same as Tab but with entries in a dropdown rather than tabs


  Inline Groups
  	- Will not become nodes of a select group, they will be effectivly part of thier parent group seperated by a border
  	- If declared on a direct child of a root node of a select group, they will appear above the group container control
  	- When a group is displayed inline, all descendants will also be inline members of the group

]]

-- Recycling functions
local new, del, copy
--newcount, delcount,createdcount,cached = 0,0,0
do
	local pool = setmetatable({},{__mode='k'})
	function new()
		--newcount = newcount + 1
		local t = next(pool)
		if t then
			pool[t] = nil
			return t
		else
			--createdcount = createdcount + 1
			return {}
		end
	end
	function copy(t)
		local c = new()
		for k, v in pairs(t) do
			c[k] = v
		end
		return c
	end
	function del(t)
		--delcount = delcount + 1
		for k in pairs(t) do
			t[k] = nil
		end
		pool[t] = true
	end
--	function cached()
--		local n = 0
--		for k in pairs(pool) do
--			n = n + 1
--		end
--		return n
--	end
end

-- picks the first non-nil value and returns it
local function pickfirstset(...)
  for i=1,select("#",...) do
    if select(i,...)~=nil then
      return select(i,...)
    end
  end
end

--gets an option from a given group, checking plugins
local function GetSubOption(group, key)
	if group.plugins then
		for plugin, t in pairs(group.plugins) do
			if t[key] then
				return t[key]
			end
		end
	end

	return group.args[key]
end

--Option member type definitions, used to decide how to access it

--Is the member Inherited from parent options
local isInherited = {
	set = true,
	get = true,
	func = true,
	confirm = true,
	validate = true,
	disabled = true,
	hidden = true
}

--Does a string type mean a literal value, instead of the default of a method of the handler
local stringIsLiteral = {
	name = true,
	desc = true,
	icon = true,
	usage = true,
	width = true,
}

--Is Never a function or method
local allIsLiteral = {
	type = true,
}

--gets the value for a member that could be a function
--function refs are called with an info arg
--every other type is returned
local function GetOptionsMemberValue(membername, option, options, path, appName, ...)
	--get definition for the member
	local inherits = isInherited[membername]
	

	--get the member of the option, traversing the tree if it can be inherited
	local member
	
	if inherits then
		local group = options
		if group[membername] ~= nil then
			member = group[membername]
		end
		for i = 1, #path do
			group = GetSubOption(group, path[i])
			if group[membername] ~= nil then
				member = group[membername]
			end
		end
	else
		member = option[membername]
	end
	
	--check if we need to call a functon, or if we have a literal value
	if ( not allIsLiteral[membername] ) and ( type(member) == "function" or ((not stringIsLiteral[membername]) and type(member) == "string") ) then
		--We have a function to call
		local info = new()
		--traverse the options table, picking up the handler and filling the info with the path
		local handler
		local group = options
		handler = group.handler or handler
		
		for i = 1, #path do
			group = GetSubOption(group, path[i])
			info[i] = path[i]
			handler = group.handler or handler
		end
		
		info.options = options
		info[0] = appName
		info.arg = option.arg
	
		local a, b, c ,d 
		--using 4 returns for the get of a color type, increase if a type needs more
		if type(member) == "function" then
			--Call the function
			a,b,c,d = member(info, ...)
		else
			--Call the method
			if handler and handler[member] then
				a,b,c,d = handler[member](handler, info, ...)
			else
				error(string.format("Method %s doesn't exist in handler for type %s", member, membername))
			end
		end
		del(info)
		return a,b,c,d
	else
		--The value isnt a function to call, return it
		return member	
	end	
end

--[[calls an options function that could be inherited, method name or function ref
local function CallOptionsFunction(funcname ,option, options, path, appName, ...)
	local info = new()

	local func
	local group = options
	local handler

	--build the info table containing the path
	-- pick up functions while traversing the tree
	if group[funcname] ~= nil then
		func = group[funcname]
	end
	handler = group.handler or handler

	for i, v in ipairs(path) do
		group = GetSubOption(group, v)
		info[i] = v
		if group[funcname] ~= nil then
			func =  group[funcname]
		end
		handler = group.handler or handler
	end

	info.options = options
	info[0] = appName
	info.arg = option.arg

	local a, b, c ,d
	if type(func) == "string" then
		if handler and handler[func] then
			a,b,c,d = handler[func](handler, info, ...)
		else
			error(string.format("Method %s doesn't exist in handler for type func", func))
		end
	elseif type(func) == "function" then
		a,b,c,d = func(info, ...)
	end
	del(info)
	return a,b,c,d
end
--]]

--tables to hold orders and names for options being sorted, will be created with new()
--prevents needing to call functions repeatedly while sorting
local tempOrders
local tempNames

local function compareOptions(a,b)
	if not a then
		return true
	end
	if not b then
		return false
	end
	local OrderA, OrderB = tempOrders[a] or 100, tempOrders[b] or 100
	if OrderA == OrderB then
		local NameA = (type(tempNames[a] == "string") and tempNames[a]) or ""
		local NameB = (type(tempNames[b] == "string") and tempNames[b]) or ""
		return NameA:upper() < NameB:upper()
	end
	if OrderA < 0 then
		if OrderB > 0 then
			return false
		end
	else
		if OrderB < 0 then
			return true
		end
	end
	return OrderA < OrderB
end



--builds 2 tables out of an options group
-- keySort, sorted keys
-- opts, combined options from .plugins and args
local function BuildSortedOptionsTable(group, keySort, opts, options, path, appName)
	tempOrders = new()
	tempNames = new()
	
	if group.plugins then
		for plugin, t in pairs(group.plugins) do
			for k, v in pairs(t) do
				if not opts[k] then
					tinsert(keySort, k)
					opts[k] = v

					path[#path+1] = k
					tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
					tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
					path[#path] = nil
				end
			end
		end
	end
	
	for k, v in pairs(group.args) do
		if not opts[k] then
			tinsert(keySort, k)
			opts[k] = v

			path[#path+1] = k
			tempOrders[k] = GetOptionsMemberValue("order", v, options, path, appName)
			tempNames[k] = GetOptionsMemberValue("name", v, options, path, appName)
			path[#path] = nil
		end
	end

	table.sort(keySort, compareOptions)

	del(tempOrders)
	del(tempNames)
end

local function DelTree(tree)
	if tree.children then
		local childs = tree.children
		for i = 1, #childs do
			DelTree(childs[i])
			del(childs[i])
		end
		del(childs)
	end
end

local function CleanUserData(widget, event)
	local user = widget.userdata

	if user.path then
		del(user.path)
	end

	if widget.type == "TreeGroup" then
		local tree = widget.tree
		if tree then
			for i = 1, #tree do
				DelTree(tree[i])
				del(tree[i])
			end
			del(tree)
			widget.tree = nil
		end
	end

	if widget.type == "TabGroup" then
		del(widget.tablist)
		del(widget.text)
		widget.tablist = nil
		widget.text = nil
	end

	if widget.type == "DropdownGroup" then
		if widget.dropdown.list then
			del(widget.dropdown.list)
			widget.dropdown.list = nil
		end
	end
end

--[[
	Gets a status table for the given appname and options path
]]
function lib:GetStatusTable(appName, path)
	local status = self.Status

	if not status[appName] then
		status[appName] = {}
		status[appName].status = {}
		status[appName].children = {}
	end

	status = status[appName]

	if path then
		for i = 1, #path do
			local v = path[i]
			if not status.children[v] then
				status.children[v] = {}
				status.children[v].status = {}
				status.children[v].children = {}
			end
			status = status.children[v]
		end
	end

	return status.status
end

local function OptionOnMouseOver(widget, event)
	--show a tooltip/set the status bar to the desc text
	local user = widget.userdata
	local opt = user.option
	local options = user.options
	local path = user.path
	local appName = user.appName

	GameTooltip_SetDefaultAnchor(GameTooltip, widget.frame)
	local name = GetOptionsMemberValue("name", opt, options, path, appName)
	local desc = GetOptionsMemberValue("desc", opt, options, path, appName)
	local usage = GetOptionsMemberValue("usage", opt, options, path, appName)
	
	GameTooltip:SetText(name, 1, 1, 1, 1)
	
	if opt.type == 'multiselect' then
		GameTooltip:AddLine(user.text,0.5, 0.5, 0.8, 1)
	end	
	if type(desc) == "string" then
		GameTooltip:AddLine(desc, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
	end
	if type(usage) == "string" then
		GameTooltip:AddLine("Usage: "..usage, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
	end

	GameTooltip:Show()
end

local function OptionOnMouseLeave(widget, event)
	GameTooltip:Hide()
end

local function GetFuncName(option)
	local type = option.type
	if type == 'execute' then
		return 'func'
	else
		return 'set'
	end
end
local function confirmPopup(appName, rootframe, info, message, func, ...)
	if not StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] then
		StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"] = {}
	end
	local t = StaticPopupDialogs["ACECONFIGDIALOG30_CONFIRM_DIALOG"]
	for k in pairs(t) do
		t[k] = nil
	end
	t.text = message
	t.button1 = ACCEPT
	t.button2 = CANCEL
	t.OnAccept = function()
		safecall(func, unpack(t))
		lib:Open(appName, rootframe)
		del(info)
	end
	t.OnCancel = function()
		del(info)
	end
	for i = 1, select('#', ...) do
		t[i] = select(i, ...) or false
	end
	t.timeout = 0
	t.whileDead = 1
	t.hideOnEscape = 1

	StaticPopup_Show("ACECONFIGDIALOG30_CONFIRM_DIALOG")
end

local function ActivateControl(widget, event, ...)
	--This function will call the set / execute handler for the widget
	--widget.userdata contains the needed info
	local user = widget.userdata
	local option = user.option
	local options = user.options
	local path = user.path
	local info = new()

	local func
	local group = options
	local funcname = GetFuncName(option)
	local handler
	local confirm
	local validate
	--build the info table containing the path
	-- pick up functions while traversing the tree
	if group[funcname] ~= nil then
		func =  group[funcname]
	end
	handler = group.handler or handler
	confirm = group.confirm
	validate = group.validate
	for i = 1, #path do
		local v = path[i]
		group = GetSubOption(group, v)
		info[i] = v
		if group[funcname] ~= nil then
			func =  group[funcname]
		end
		handler = group.handler or handler
		if group.confirm ~= nil then
			confirm = group.confirm
		end
		if group.validate ~= nil then
			validate = group.validate
		end
	end

	info.options = options
	info[0] = user.appName
	info.arg = option.arg

	local name
	if type(option.name) == "function" then
		name = option.name(info)
	elseif type(option.name) == "string" then
		name = option.name
	else
		name = ""
	end
	local usage = option.usage
	local pattern = option.pattern

	local validated = true

	if option.type == "input" then
		if type(pattern)=="string" then
			if not strmatch(..., pattern) then
				validated = false
			end
		end
	end
	
	local success
	if validated and option.type ~= "execute" then
		if type(validate) == "string" then
			if handler and handler[validate] then
				success, validated = safecall(handler[validate], handler, info, ...)
				if not success then validated = false end
			else
				error(string.format("Method %s doesn't exist in handler for type execute", validate))
			end
		elseif type(validate) == "function" then
			success, validated = safecall(validate, info, ...)
			if not success then validated = false end
		end
	end
	
	local rootframe = user.rootframe
	if type(validated) == "string" then
		--validate function returned a message to display
		if rootframe.SetStatusText then
			rootframe:SetStatusText(validated)
		end
		PlaySound("igPlayerInviteDecline")
		del(info)
		return true
	elseif not validated then
		--validate returned false	
		if rootframe.SetStatusText then
			if usage then
				rootframe:SetStatusText(name..": "..usage)
			else
				if pattern then
					rootframe:SetStatusText(name..": Expected "..pattern)
				else
					rootframe:SetStatusText(name..": Invalid Value")
				end
			end
		end
		PlaySound("igPlayerInviteDecline")
		del(info)
		return true
	else
		local confirmText = option.confirmText
		--call confirm func/method
		if type(confirm) == "string" then
			if handler and handler[confirm] then
				success, confirm = safecall(handler[confirm],handler, info)
				if success and type(confirm) == "string" then
					confirmText = confirm
					confirm = true
				elseif not success then
					confirm = false
				end
			else
				error(string.format("Method %s doesn't exist in handler for type confirm", confirm))
			end
		elseif type(confirm) == "function" then
			success, confirm = safecall(confirm,info)
			if success and type(confirm) == "string" then
				confirmText = confirm
				confirm = true
			elseif not success then
				confirm = false
			end
		end

		--confirm if needed
		if type(confirm) == "boolean" then
			if confirm then
				if not confirmText then
					local name, desc = option.name, option.desc
					if type(name) == "function" then
						name = name(info)
					end
					if type(desc) == "function" then
						desc = desc(info)
					end
					confirmText = name
					if desc then
						confirmText = confirmText.." - "..desc
					end
				end
				local iscustom = user.rootframe.userdata.iscustom
				local rootframe
				if iscustom then
					rootframe = user.rootframe
				end
				if type(func) == "string" then
					if handler and handler[func] then
						confirmPopup(user.appName, rootframe, info, confirmText, handler[func], handler, info, ...)
					else
						error(string.format("Method %s doesn't exist in handler for type func", func))
					end
				elseif type(func) == "function" then
					confirmPopup(user.appName, rootframe, info, confirmText, func, info, ...)
				end
				--func will be called and info deleted when the confirm dialog is responded to
				return
			end
		end

		--call the function
		if type(func) == "string" then
			if handler and handler[func] then
				safecall(handler[func],handler, info, ...)
			else
				error(string.format("Method %s doesn't exist in handler for type func", func))
			end
		elseif type(func) == "function" then
			safecall(func,info, ...)
		end



		local iscustom = user.rootframe.userdata.iscustom
		--full refresh of the frame, some controls dont cause this on all events
		if option.type == "color" then
			if event == "OnValueConfirmed" then
				if iscustom then
					lib:Open(user.appName, user.rootframe)
				else
					lib:Open(user.appName)
				end
			end
		elseif option.type == "range" then
			if event == "OnMouseUp" then
				if iscustom then
					lib:Open(user.appName, user.rootframe)
				else
					lib:Open(user.appName)
				end
			end
		else
			if iscustom then
				lib:Open(user.appName, user.rootframe)
			else
				lib:Open(user.appName)
			end
		end

	end
	del(info)
end

local function ActivateSlider(widget, event, value)
	local option = widget.userdata.option
	local min, max, step = option.min or 0, option.max or 100, option.step
	if step then
		value = math.floor((value - min) / step + 0.5) * step + min
	else
		value = math.max(math.min(value,max),min)
	end
	ActivateControl(widget,event,value)
end

local function ActivateMultiControl(widget, event, ...)
	ActivateControl(widget, event, widget.userdata.value, ...)
end

local function FrameOnClose(widget, event)
	local appName = widget.userdata.appName
	lib.OpenFrames[appName] = nil
	gui:Release(widget)
end

local function CheckOptionHidden(option, options, path, appName)
	--check for a specific boolean option
	local hidden = pickfirstset(option.dialogHidden,option.guiHidden)
	if hidden ~= nil then
		return hidden
	end

	return GetOptionsMemberValue("hidden", option, options, path, appName)
end

local function CheckOptionDisabled(option, options, path, appName)
	--check for a specific boolean option
	local disabled = pickfirstset(option.dialogDisabled,option.guiDisabled)
	if disabled ~= nil then
		return disabled
	end

	return GetOptionsMemberValue("disabled", option, options, path, appName)
end

local function BuildTabs(group, options, path, appName)
	local tabs = new()
	local text = new()
	local keySort = new()
	local opts = new()

	BuildSortedOptionsTable(group, keySort, opts, options, path, appName)

	for i = 1, #keySort do
		local k = keySort[i]
		local v = opts[k]
		if v.type == "group" then
			path[#path+1] = k
			local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
			local hidden = CheckOptionHidden(v, options, path, appName)
			if not inline and not hidden then
				tinsert(tabs, k)
				text[k] = GetOptionsMemberValue("name", v, options, path, appName)
			end
			path[#path] = nil
		end
	end

	del(keySort)
	del(opts)

	return tabs, text
end

local function BuildSelect(group, options, path, appName)
	local groups = new()
	local keySort = new()
	local opts = new()

	BuildSortedOptionsTable(group, keySort, opts, options, path, appName)

	for i = 1, #keySort do
		local k = keySort[i]
		local v = opts[k]
		if v.type == "group" then
			path[#path+1] = k
			local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
			local hidden = CheckOptionHidden(v, options, path, appName)
			if not inline and not hidden then
				groups[k] = GetOptionsMemberValue("name", v, options, path, appName)
			end
			path[#path] = nil
		end
	end

	del(keySort)
	del(opts)

	return groups
end

local function BuildSubTree(group, tree, options, path, appName)
	local keySort = new()
	local opts = new()

	BuildSortedOptionsTable(group, keySort, opts, options, path, appName)

	for i = 1, #keySort do
		local k = keySort[i]
		local v = opts[k]
		if v.type == "group" then
			path[#path+1] = k
			local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
			local hidden = CheckOptionHidden(v, options, path, appName)
			if not inline and not hidden then
				local entry = new()
				entry.value = k
				entry.text = GetOptionsMemberValue("name", v, options, path, appName)
				entry.disabled = CheckOptionDisabled(v, options, path, appName)
				if not tree.children then tree.children = new() end
				tinsert(tree.children,entry)
				if (v.childGroups or "tree") == "tree" then
					BuildSubTree(v,entry, options, path, appName)
				end
			end
			path[#path] = nil
		end
	end

	del(keySort)
	del(opts)
end

local function BuildTree(group, options, path, appName)
	local tree = new()
	local keySort = new()
	local opts = new()

	BuildSortedOptionsTable(group, keySort, opts, options, path, appName)

	for i = 1, #keySort do
		local k = keySort[i]
		local v = opts[k]
		if v.type == "group" then
			path[#path+1] = k
			local inline = pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
			local hidden = CheckOptionHidden(v, options, path, appName)
			if not inline and not hidden then
				local entry = new()
				entry.value = k
				entry.text = GetOptionsMemberValue("name", v, options, path, appName)
				entry.disabled = CheckOptionDisabled(v, options, path, appName)
				tinsert(tree,entry)
				if (v.childGroups or "tree") == "tree" then
					BuildSubTree(v,entry, options, path, appName)
				end
			end
			path[#path] = nil
		end
	end
	del(keySort)
	del(opts)
	return tree
end

local function InjectInfo(control, options, option, path, rootframe, appName)
	local user = control.userdata
	for i = 1, #path do
		user[i] = path[i]
	end
	user.rootframe = rootframe
	user.option = option
	user.options = options
	user.path = copy(path)
	user.appName = appName
	control:SetCallback("OnRelease", CleanUserData)
	control:SetCallback("OnLeave", OptionOnMouseLeave)
	control:SetCallback("OnEnter", OptionOnMouseOver)
end


--[[
	options - root of the options table being fed
	container - widget that controls will be placed in
	rootframe - Frame object the options are in
	path - table with the keys to get to the group being fed
--]]

local function FeedOptions(appName, options,container,rootframe,path,group,inline)
	local keySort = new()
	local opts = new()

	BuildSortedOptionsTable(group, keySort, opts, options, path, appName)

	for i = 1, #keySort do
		local k = keySort[i]
		local v = opts[k]
		tinsert(path, k)
		local hidden = CheckOptionHidden(v, options, path, appName)
		local name = GetOptionsMemberValue("name", v, options, path, appName)
		if not hidden then
			if v.type == "group" then
				if inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then
					--Inline group
					local GroupContainer
					if name and name ~= "" then
						GroupContainer = gui:Create("InlineGroup")
						GroupContainer:SetTitle(name or "")
					else
						GroupContainer = gui:Create("SimpleGroup")
					end
					
					GroupContainer.width = "fill"
					GroupContainer:SetLayout("flow")
					container:AddChild(GroupContainer)
					FeedOptions(appName,options,GroupContainer,rootframe,path,v,true)
				end
			else
				--Control to feed
				local control
				
				local name = GetOptionsMemberValue("name", v, options, path, appName)
				
				if v.type == "execute" then
					control = gui:Create("Button")
					control:SetText(name)
					control:SetCallback("OnClick",ActivateControl)

				elseif v.type == "input" then
					control = gui:Create("EditBox")
					control:SetLabel(name)
					control:SetCallback("OnEnterPressed",ActivateControl)
					control:SetText(GetOptionsMemberValue("get",v, options, path, appName))

				elseif v.type == "toggle" then
					control = gui:Create("CheckBox")
					control:SetLabel(name)
					control:SetTriState(v.tristate)
					control:SetValue(GetOptionsMemberValue("get",v, options, path, appName))
					control:SetCallback("OnValueChanged",ActivateControl)

				elseif v.type == "range" then
					control = gui:Create("Slider")
					control:SetLabel(name)
					control:SetSliderValues(v.min or 0,v.max or 100, v.bigStep or v.step or 0)
					control:SetIsPercent(v.isPercent)
					control:SetValue(GetOptionsMemberValue("get",v, options, path, appName) or 0)
					control:SetCallback("OnValueChanged",ActivateSlider)
					control:SetCallback("OnMouseUp",ActivateSlider)

				elseif v.type == "select" then
					local values = GetOptionsMemberValue("values", v, options, path, appName)
					
					control = gui:Create("Dropdown")
					control:SetLabel(name)
					control:SetList(values)
					control:SetValue(GetOptionsMemberValue("get",v, options, path, appName))
					control:SetCallback("OnValueChanged",ActivateControl)

				elseif v.type == "multiselect" then
					local values = GetOptionsMemberValue("values", v, options, path, appName)
					
					control = gui:Create("InlineGroup")
					control:SetLayout("Flow")
					control:SetTitle(name)
					control.width = "fill"

					local valuesort = new()

					local disabled = CheckOptionDisabled(v, options, path, appName)

					if values then
						for value, text in pairs(values) do
							tinsert(valuesort, value)
						end

						table.sort(valuesort)
						control:PauseLayout()
						local width = GetOptionsMemberValue("width",v,options,path,appName)
						for i = 1, #valuesort do
							local value = valuesort[i]
							local text = values[value]
							local check = gui:Create("CheckBox")
							check:SetLabel(text)
							check.userdata.value = value
							check.userdata.text = text
							check:SetDisabled(disabled)
							check:SetTriState(v.tristate)
							check:SetValue(GetOptionsMemberValue("get",v, options, path, appName, value))
							check:SetCallback("OnValueChanged",ActivateMultiControl)
							InjectInfo(check, options, v, path, rootframe, appName)
							control:AddChild(check)
							if width == "double" then
								check:SetWidth(400)
							elseif width == "half" then
								check:SetWidth(100)
							elseif width == "full" then
								check.width = "fill"
							else
								check:SetWidth(200)
							end
						end
						control:ResumeLayout()
						control:DoLayout()
					end
					del(valuesort)

				elseif v.type == "color" then
					control = gui:Create("ColorPicker")
					control:SetLabel(name)
					control:SetColor(GetOptionsMemberValue("get",v, options, path, appName))
					control:SetCallback("OnValueChanged",ActivateControl)
					control:SetCallback("OnValueConfirmed",ActivateControl)

				elseif v.type == "keybinding" then
					control = gui:Create("Keybinding")
					control:SetLabel(name)
					control:SetKey(GetOptionsMemberValue("get",v, options, path, appName))
					control:SetCallback("OnKeyChanged",ActivateControl)

				elseif v.type == "header" then
					control = gui:Create("Heading")
					control:SetText(name)
					control.width = "fill"

				elseif v.type == "description" then
					control = gui:Create("Label")
					control:SetText(name)
					control.width = "fill"					
				end

				--Common Init
				if control then
					if control.width ~= "fill" then
						local width = GetOptionsMemberValue("width",v,options,path,appName)
						if width == "double" then
							control:SetWidth(400)
						elseif width == "half" then
							control:SetWidth(100)
						elseif width == "full" then
							control.width = "fill"
						else
							control:SetWidth(200)
						end
					end
					if control.SetDisabled then
						local disabled = CheckOptionDisabled(v, options, path, appName)
						control:SetDisabled(disabled)
					end

					InjectInfo(control, options, v, path, rootframe, appName)
					container:AddChild(control)
				end
				
			end
		end
		tremove(path)
	end
	container:ResumeLayout()
	container:DoLayout()
	del(keySort)
	del(opts)
end

local function BuildPath(path, ...)
	for i = 1, select('#',...)  do
		tinsert(path, (select(i,...)))
	end
end

local function GroupExists(appName, options, path, uniquevalue)
	if not uniquevalue then return false end
	
	local feedpath = new()
	local temppath = new()
	for i = 1, #path do
		feedpath[i] = path[i]
	end
	
	BuildPath(feedpath, string.split("\001", uniquevalue))
	
	local group = options
	for i = 1, #feedpath do
		local v = feedpath[i]
		temppath[i] = v
		group = GetSubOption(group, v)
		
		if not group or CheckOptionHidden(group, options, temppath, appName) then 
			del(feedpath)
			return false 
		end
	end
	del(feedpath)
	del(temppath)
	return true
end

local function GroupSelected(widget, event, uniquevalue)

	local user = widget.userdata

	local options = user.options
	local option = user.option
	local path = user.path
	local rootframe = user.rootframe

	local feedpath = new()
	for i = 1, #path do
		feedpath[i] = path[i]
	end

	BuildPath(feedpath, string.split("\001", uniquevalue))
	local group = options
	for i = 1, #feedpath do
		group = GetSubOption(group, feedpath[i])
	end

	widget:ReleaseChildren()
	lib:FeedGroup(user.appName,options,widget,rootframe,feedpath,group)

	del(feedpath)
end




--[[
This function will feed one group, and any inline child groups into the given container
Select Groups will only have the selection control (tree, tabs, dropdown) fed in
and have a group selected, this event will trigger the feeding of child groups

Rules:
	If the group is Inline, FeedOptions
	If the group has no child groups, FeedOptions

	If the group is a tab or select group, FeedOptions then add the Group Control
	If the group is a tree group FeedOptions then
		its parent isn't a tree group:  then add the tree control containing this and all child tree groups
		if its parent is a tree group, its already a node on a tree
--]]

function lib:FeedGroup(appName,options,container,rootframe,path)
	local group = options
	--follow the path to get to the curent group
	local inline
	local grouptype, parenttype = options.childGroups, "none"


	--temp path table to pass to callbacks as we traverse the tree
	local temppath = new()
	for i = 1, #path do
		local v = path[i]
		temppath[i] = v
		group = GetSubOption(group, v)
		inline = inline or pickfirstset(v.dialogInline,v.guiInline,v.inline, false)
		parenttype = grouptype
		grouptype = group.childGroups
	end
	del(temppath)

	if not parenttype then
		parenttype = "tree"
	end

	--check if the group has child groups
	local hasChildGroups
	for k, v in pairs(group.args) do
		if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then
			hasChildGroups = true
		end
	end
	if group.plugins then
		for plugin, t in pairs(group.plugins) do
			for k, v in pairs(t) do
				if v.type == "group" and not pickfirstset(v.dialogInline,v.guiInline,v.inline, false) then
					hasChildGroups = true
				end
			end
		end
	end

	container:SetLayout("flow")
	local scroll

	--Add a scrollframe if we are not going to add a group control, this is the inverse of the conditions for that later on
	if (not (hasChildGroups and not inline)) or (grouptype ~= "tab" and grouptype ~= "select" and parenttype == "tree") then
		if container.type ~= "InlineGroup" then
			scroll = gui:Create("ScrollFrame")
			scroll:SetLayout("flow")
			scroll.width = "fill"
			scroll.height = "fill"
			container:SetLayout("fill")
			container:AddChild(scroll)
			container = scroll
		end
	end

	FeedOptions(appName,options,container,rootframe,path,group,nil)

	if scroll then
		container:PerformLayout()
		local status = self:GetStatusTable(appName, path)
		if not status.scroll then
			status.scroll = {}
		end
		scroll:SetStatusTable(status.scroll)
	end

	if hasChildGroups and not inline then

		if grouptype == "tab" then

			local tab = gui:Create("TabGroup")
			InjectInfo(tab, options, group, path, rootframe, appName)
			tab:SetCallback("OnGroupSelected", GroupSelected)
			local status = lib:GetStatusTable(appName, path)
			if not status.groups then
				status.groups = {}
			end
			tab:SetStatusTable(status.groups)
			tab.width = "fill"
			tab.height = "fill"

			local tabs, text = BuildTabs(group, options, path, appName)
			tab:SetTabs(tabs, text)

			container:AddChild(tab)

		elseif grouptype == "select" then

			local select = gui:Create("DropdownGroup")
			InjectInfo(select, options, group, path, rootframe, appName)
			select:SetCallback("OnGroupSelected", GroupSelected)
			local status = lib:GetStatusTable(appName, path)
			if not status.groups then
				status.groups = {}
			end
			select:SetStatusTable(status.groups)
			local grouplist = BuildSelect(group, options, path, appName)
			select:SetGroupList(grouplist)
			local firstgroup
			for k, v in pairs(grouplist) do
				if not firstgroup or k < firstgroup then
					firstgroup = k
				end
			end

			select:SetGroup( (GroupExists(appName, options, path,status.groups.selectedgroup) and status.groups.selectedgroup) or firstgroup)

			select.width = "fill"
			select.height = "fill"

			container:AddChild(select)

		--assume tree group by default
		--if parenttype is tree then this group is already a node on that tree
		elseif parenttype ~= "tree" then

			local tree = gui:Create("TreeGroup")
			InjectInfo(tree, options, group, path, rootframe, appName)

			tree.width = "fill"
			tree.height = "fill"

			tree:SetCallback("OnGroupSelected", GroupSelected)

			local status = lib:GetStatusTable(appName, path)
			if not status.groups then
				status.groups = {}
			end
			local treedefinition = BuildTree(group, options, path, appName)
			tree:SetStatusTable(status.groups)

			tree:SetTree(treedefinition)

			for i = 1, #treedefinition do
				local entry = treedefinition[i]
				if not entry.disabled then
					tree:SelectByValue((GroupExists(appName, options, path,status.groups.selected) and status.groups.selected) or entry.value)
					break
				end
			end

			container:AddChild(tree)
		end
	end
end

local old_CloseSpecialWindows

function lib:CloseAll()
	local closed
	for k, v in pairs(self.OpenFrames) do
		v:Hide()
		closed = true
	end
	return closed
end

function lib:Close(appName)
	if self.OpenFrames[appName] then
		self.OpenFrames[appName]:Hide()
		return true
	end
end

local function RefreshOnUpdate(this)
	for appName in pairs(this.apps) do
		if lib.OpenFrames[appName] then
			lib:Open(appName)
		end
		this.apps[appName] = nil
	end
	this:SetScript("OnUpdate", nil)
end

function lib:ConfigTableChanged(event, appName)
	if not lib.frame.apps then
		lib.frame.apps = {}
	end
	lib.frame.apps[appName] = true
	lib.frame:SetScript("OnUpdate", RefreshOnUpdate)
end

reg.RegisterCallback(lib, "ConfigTableChange", "ConfigTableChanged")

function lib:Open(appName, container)
	if not old_CloseSpecialWindows then
		old_CloseSpecialWindows = CloseSpecialWindows
		CloseSpecialWindows = function()
			local found = old_CloseSpecialWindows()
			return self:CloseAll() or found
		end
	end
	local app = reg:GetOptionsTable(appName)
	if not app then
		error(("%s isn't registed with AceConfigRegistry, unable to open config"):format(appName), 2)
	end
	local options = app("dialog", MAJOR)

	local f
	
	local path = new()
	local name = GetOptionsMemberValue("name", options, options, path, appName)
	
	--if a container is given feed into that
	if container then
		f = container
		f:ReleaseChildren()
		f.userdata.appName = appName
		f.userdata.iscustom = true
		if f.SetTitle then
			f:SetTitle(name or "")
		end
		local status = lib:GetStatusTable(appName)
		if f.SetStatusTable then
			f:SetStatusTable(status)
		end
	else
		if not self.OpenFrames[appName] then
			f = gui:Create("Frame")
			self.OpenFrames[appName] = f
		else
			f = self.OpenFrames[appName]
		end
		f:ReleaseChildren()
		f:SetCallback("OnClose", FrameOnClose)
		f.userdata.appName = appName
		f:SetTitle(name or "")
		local status = lib:GetStatusTable(appName)
		f:SetStatusTable(status)
	end

	

	self:FeedGroup(appName,options,f,f,path)
	if f.Show then
		f:Show()
	end
	del(path)
end
