if not WeakAuras.IsCorrectVersion() then return end

local Type, Version = "WeakAurasTwoColumnDropdown", 3
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end

local tconcat = table.concat
local select = select
local assert, loadstring = assert, loadstring
local setmetatable, rawset = setmetatable, rawset
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", tconcat(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

AceGUI:RegisterLayout("TwoColumn",
  function(content, children)
    local height = 0
    local width = content.width or content:GetWidth() or 0
    for i = 1, #children do
      local child = children[i]

      local frame = child.frame
      frame:ClearAllPoints()
      if child.userdata.hideMe then
        frame:Hide()
      else
        frame:Show()
      end
      if i == 1 then
        frame:SetPoint("TOPLEFT", content)
      else
        frame:SetPoint("TOPLEFT", children[i-1].frame, "TOPRIGHT")
      end

      if child.width == "relative" then
        child:SetWidth(width * child.relWidth)

        if child.DoLayout then
          child:DoLayout()
        end
      end

      height = max(height, frame.height or frame:GetHeight() or 0)
    end
    safecall(content.obj.LayoutFinished, content.obj, nil, height)
  end)

local methods = {
  ["OnAcquire"] = function(widget)
    local firstDropdown = AceGUI:Create("Dropdown")
    local secondDropDown = AceGUI:Create("Dropdown")

    firstDropdown:SetRelativeWidth(0.5)
    firstDropdown:SetPulloutWidth(200)
    secondDropDown:SetRelativeWidth(0.5)
    secondDropDown:SetLabel(" ")
    secondDropDown:SetPulloutWidth(200)
    secondDropDown.userdata.defaultSelection = {}

    widget.firstDropdown = firstDropdown
    widget.secondDropDown = secondDropDown

    widget:SetLayout("TwoColumn")
    widget.SetLayout = function()
      -- AceGui wants to set a default layout, but we don't want that
    end

    widget:AddChild(firstDropdown)
    widget:AddChild(secondDropDown)

    local OnFirstDropdownValueChanged = function(self, event, value)
      local displayName = widget.userdata.firstList[value]
      local treeValue = widget.userdata.tree[displayName]
      if type(treeValue) == "table" then
        local oldValue
        if widget.userdata.secondList then
          local v = widget.secondDropDown:GetValue()
          if v then
            oldValue = widget.userdata.secondList[v]
          end
        end
        local secondList = {}
        for displayName, _ in pairs(treeValue) do
          tinsert(secondList, displayName)
        end
        table.sort(secondList)

        local oldValueIndex = tIndexOf(secondList, oldValue)
        widget.userdata.secondList = secondList
        widget.secondDropDown:SetList(secondList)
        widget.firstDropdown:SetRelativeWidth(0.5)
        widget.secondDropDown:SetRelativeWidth(0.5)
        widget.secondDropDown.userdata.hideMe = false
        widget:DoLayout()

        if (oldValueIndex) then
          widget.secondDropDown:SetValue(oldValueIndex)

          local v = widget:GetValue()
          if (v) then
            widget:Fire("OnValueChanged", v)
          end
        else
          local default = widget.secondDropDown.userdata.defaultSelection[displayName]
          if default then
            local index = tIndexOf(secondList, default)
            widget.secondDropDown:SetValue(index)
            local v = widget:GetValue()
            if (v) then
              widget:Fire("OnValueChanged", v)
            end
          else
            widget.secondDropDown:SetValue()
          end
        end
      else
        widget.firstDropdown:SetRelativeWidth(1)
        widget.secondDropDown.userdata.hideMe = true
        widget.userdata.secondList = nil
        widget:DoLayout()
        widget:Fire("OnValueChanged", treeValue)
      end
    end

    firstDropdown.OnFirstDropdownValueChanged = OnFirstDropdownValueChanged

    local OnSecondDropdownValueChanged = function(self, event, value)
      widget:Fire("OnValueChanged", widget:GetValue())
    end

    firstDropdown:SetCallback("OnValueChanged", OnFirstDropdownValueChanged)
    secondDropDown:SetCallback("OnValueChanged", OnSecondDropdownValueChanged)
  end,
  ["OnRelease"] = function(self)
  end,
  ["SetLabel"] = function(self, ...)
    self.firstDropdown:SetLabel(...)
  end,
  ["SetValue"] = function(self, value)
    for displayName, treeValue in pairs(self.userdata.tree) do
      if treeValue == value then
        self.firstDropdown:SetRelativeWidth(1)
        self.secondDropDown.userdata.hideMe = true
        self:DoLayout()
        self.firstDropdown:SetValue(tIndexOf(self.userdata.firstList, displayName))
        return
      elseif type(treeValue) == "table" then
        for displayName2, key in pairs(treeValue) do
          if (key == value) then
            self.firstDropdown:SetRelativeWidth(0.5)
            self.secondDropDown:SetRelativeWidth(0.5)
            self.secondDropDown.userdata.hideMe = false
            self:DoLayout()
            local index = tIndexOf(self.userdata.firstList, displayName);
            self.firstDropdown:SetValue(index)
            self.firstDropdown:OnFirstDropdownValueChanged("", index)
            self.secondDropDown:SetValue(tIndexOf(self.userdata.secondList, displayName2))
            return
          end
        end
      end
    end
    self.firstDropdown:SetRelativeWidth(1)
    self.secondDropDown.userdata.hideMe = true
    self:DoLayout()
    self.firstDropdown:SetValue(nil)
  end,
  ["GetValue"] = function(self)
    local displayName1 = self.userdata.firstList[self.firstDropdown:GetValue()]

    local treeValue = self.userdata.tree[displayName1]
    if not treeValue then
      return nil
    end
    if type(treeValue) ~= "table" then
      return treeValue
    end

    local displayName2 = self.userdata.secondList[self.secondDropDown:GetValue()]
    treeValue = treeValue[displayName2]
    if not treeValue then
      return nil
    end
    return treeValue
  end,
  ["SetList"] = function(self, list, order, itemType)
    local tree = {}
    for key, displayName in pairs(list) do
      if type(displayName) == "table" then
        local base = displayName[1]
        local suffix = displayName[2]
        tree[base] = tree[base] or {}
        tree[base][suffix] = key
        if displayName[3] == true then
          self.secondDropDown.userdata.defaultSelection[base] = suffix
        end
      else
        tree[displayName] = key
      end
    end
    self.userdata.tree = tree

    local firstList = {}
    for displayName, _ in pairs(tree) do
      tinsert(firstList, displayName)
    end
    table.sort(firstList)
    self.userdata.firstList = firstList
    self.firstDropdown:SetList(firstList, order, itemType)
  end
}

local function Constructor()
  local frame = CreateFrame("Frame", nil, UIParent)
  frame:SetHeight(50)
  frame:SetWidth(50)

  local content = CreateFrame("Frame", nil, frame)
  content:SetPoint("TOPLEFT", 0, 0)
  content:SetPoint("BOTTOMRIGHT", 0, 0)

  local widget = {
    frame = frame,
    content = content,
    type = Type
  }
  for method, func in pairs(methods) do
    widget[method] = func
  end
  return AceGUI:RegisterAsContainer(widget)
end

AceGUI:RegisterWidgetType(Type, Constructor, Version)
