local MAJOR, MINOR = "LibElv-UIButtons-1.0", 3
local lib, oldminor = LibStub:NewLibrary(MAJOR, MINOR)

if not lib then return end
local E, L, V, P, G = unpack(ElvUI)
local S = E.Skins
local TT = E.Tooltip

--GLOBALS: CreateFrame
local _G = _G
local pairs, type, tinsert = pairs, type, tinsert
local UnitName = UnitName
local GameTooltip = GameTooltip
local GetAddOnEnableState = GetAddOnEnableState

--Upon creation menu.db does not account for values missing in E.db = being default.
--We are using this to fix it.
local function EqualizeDB(db, default)
	for key, value in pairs(default) do
		if not db[key] and type(default[key]) ~= 'table' then
			db[key] = default[key]
		elseif type(default[key]) == 'table' then
			EqualizeDB(db[key], default[key])
		end
	end
end

--Sets the size of menu's mover
local function MoverSize(menu)
	local db = menu.db
	if db.orientation == 'vertical' then
		menu:SetWidth(db.size + 2 + E.Spacing*2)
		menu:SetHeight((db.size*menu.NumBut)+((db.spacing + E.Spacing*2)*(menu.NumBut-1))+2)
	elseif db.orientation == 'horizontal' then
		menu:SetWidth((db.size*menu.NumBut)+((db.spacing + E.Spacing*2)*(menu.NumBut-1))+2)
		menu:SetHeight(db.size + 2 + E.Spacing*2)
	end
end

--Creates a mover for the menu
local function SetupMover(menu, name, own)
	local exist = false
	for i = 1, #E.ConfigModeLayouts do
		if E.ConfigModeLayouts[i] == 'UIButtons' then
			exist = true
			break
		end
	end
	if not exist then --Create a new config group for movers
		tinsert(E.ConfigModeLayouts, #(E.ConfigModeLayouts)+1, 'UIButtons')
		E.ConfigModeLocalizedStrings['UIButtons'] = L["UI Buttons"]
	end
	E:CreateMover(menu, menu:GetName()..'Mover', name, nil, nil, nil, own and own..',UIButtons' or 'ALL,UIButtons')
end

--A function to toggle dropdown categories to show/hide then on button click
local function ToggleCats(menu)
	for i = 1, #menu.ToggleTable do
		if menu.ToggleTable[i].opened then
			menu.HoldersTable[i]:Show()
		else
			menu.HoldersTable[i]:Hide()
		end
	end
end

local function OnEnter(menu)
	menu:SetAlpha(1)
end

local function OnLeave(menu)
	if menu.db.mouse then
		menu:SetAlpha(0)
	end
end

--Creating main buttons to execute stuff in classic mode and toggle categories n dropdown
local function CreateCoreButton(menu, name, text, onClick)
	if _G[menu:GetName()..'_Core_'..name] then return end
	local button, holder
	if menu.style == 'classic' then
		menu[name] = CreateFrame('Button', menu:GetName()..'_Core_'..name, menu, 'BackdropTemplate')
		button = menu[name]
		if onClick then
			button:SetScript('OnClick', onClick)
		end
	elseif menu.style == 'dropdown' then
		menu[name] = CreateFrame('Frame', menu:GetName()..'_Core_'..name, menu)
		menu[name].Toggle = CreateFrame('Button', menu:GetName()..'_Core_'..name..'Toggle', menu, 'BackdropTemplate')
		holder = menu[name]
		holder.width = 0
		holder:CreateBackdrop('Transparent')
		button = menu[name].Toggle
		if not menu[name..'Table'] then
			menu[name..'Table'] = {}
		end
		menu:ToggleSetup(button, holder)
		tinsert(menu.HoldersTable, holder)
		tinsert(menu.GroupsTable, name)
	else
		lib:CustomStyleCoreButton(menu, name, text)
	end
	button:SetScript('OnEnter', function(self) menu:OnEnter() end)
	button:SetScript('OnLeave', function(self) menu:OnLeave() end)
	-- button:SetSize(17, 17) --For testing purposes
	button.text = button:CreateFontString(nil, 'OVERLAY')
	button.text:SetPoint('CENTER', button, 'CENTER', 0, 0)

	if menu.transparent == 'Transparent' then
		button:CreateBackdrop('Transparent')
	elseif menu.transparent == 'Default' then
		S:HandleButton(button)
	end

	if text then
		local t = button:CreateFontString(nil, 'OVERLAY')
		t:FontTemplate()
		t:SetPoint('CENTER', button, 'CENTER', 0, -1)
		t:SetJustifyH('CENTER')
		t:SetText(text)
		button:SetFontString(t)
	end

	tinsert(menu.ToggleTable, button)

	menu.NumBut = menu.NumBut + 1
end

--Creating buttons to populate dropdowns
local function CreateDropdownButton(menu, core, name, text, tooltip1, tooltip2, click, addon, always)
	if addon then --Check if addon specified as dependancy is enabled to load (not loaded cause it can start to load way after our code is executed)
		local enabled = GetAddOnEnableState(menu.myname, addon)
		if enabled == 0 then return end
	end
	if _G[menu:GetName()..'_Core_'..core..name] or not menu[core..'Table'] then return end
	menu[core][name] = CreateFrame('Button', menu:GetName()..'_Core_'..core..name, menu[core], 'BackdropTemplate')
	local b = menu[core][name]
	local toggle = menu[core].Toggle

	b:SetScript('OnClick', function(self)
		click()
		toggle.opened = false
		menu:ToggleCats()
	end)

	if tooltip1 then
		b:SetScript('OnEnter', function(self)
			menu:OnEnter()
			GameTooltip:SetOwner(self)
			GameTooltip:AddLine(tooltip1, 1, .96, .41, .6, .6, 1)
			if tooltip2 then GameTooltip:AddLine(tooltip2, 1, 1, 1, 1, 1, 1) end
			GameTooltip:Show()
		end)
		b:SetScript('OnLeave', function(self)
			menu:OnLeave()
			GameTooltip:Hide()
		end)
	else
		b:SetScript('OnEnter', function(self) menu:OnEnter() end)
		b:SetScript('OnLeave', function(self) menu:OnLeave() end)
	end
	if menu.transparent == 'Transparent' then
		b:CreateBackdrop('Transparent')
	elseif menu.transparent == 'Default' then
		S:HandleButton(b)
	end

	if text and type(text) == 'string' then
		b.text = b:CreateFontString(nil, 'OVERLAY')
		b.text:FontTemplate()
		b.text:SetPoint('CENTER', b, 'CENTER', 0, -1)
		b.text:SetJustifyH('CENTER')
		b.text:SetText(text)
		b:SetFontString(b.text)
	end

	tinsert(menu[core..'Table'], b)
end

--Creating separator frame
local function CreateSeparator(menu, core, name, size, space)
	if _G[menu:GetName()..'_Core_'..core..name..'_Separator'] or not menu[core..'Table'] then return end
	menu[core][name] = CreateFrame('Frame', menu:GetName()..'_Core_'..core..name..'_Separator', menu[core], 'BackdropTemplate')
	local f = menu[core][name]
	f.isSeparator = true
	f.size = size or 1
	f.space = space or 2
	f:CreateBackdrop('Transparent')
	f:SetScript('OnEnter', function(self) menu:OnEnter() end)
	f:SetScript('OnLeave', function(self) menu:OnLeave() end)
	tinsert(menu[core..'Table'], f)
end

--Setup for core buttons in dropdown mode to act like toggles
local function ToggleSetup(menu, button, holder)
	local db = menu.db
	button.opened = false
	holder:Hide()
	button:SetScript('OnClick', function(self, button, down)
		if button == 'LeftButton' then
			if self.opened then
				self.opened = false
			else
				self.opened = true
				for i = 1, #menu.ToggleTable do
					if menu.ToggleTable[i] ~= self then menu.ToggleTable[i].opened = false end
				end
			end
			menu:ToggleCats()
		end
	end)
	holder:SetScript('OnEnter', function(self) menu:OnEnter() end)
	holder:SetScript('OnLeave', function(self) menu:OnLeave() end)
end

--Updating the positioning and order of dropdown category
local function UpdateDropdownLayout(menu, group)
	local count = -1
	local sepS, sepC = 0, 0
	local header = menu[group]
	local db = menu.db
	header:ClearAllPoints()
	header:SetPoint(db.point, header.Toggle, db.anchor, db.xoffset, db.yoffset)
	local T = menu[group..'Table']
	for i = 1, #T do
		local button, prev, next = T[i], T[i-1], T[i+1]
		local y_offset = prev and (-(db.spacing + E.Spacing*2) - (prev.isSeparator and prev.space or 0) - (button.isSeparator and button.space or 0)) or 0
		button:SetPoint('TOP', (prev or header), (prev and 'BOTTOM' or 'TOP'), 0, y_offset)
		count = button.isSeparator and count or count + 1
		sepS = (button.isSeparator and sepS + ((prev and 2 or 1)*button.space + button.size)) or sepS
		sepC = button.isSeparator and sepC + 1 or sepC
	end

	header:SetSize(header.width, (db.size * (count+1))+(db.spacing*(count))+sepS+(.5*sepC))
	if menu.db.dropdownBackdrop then
		header.backdrop:Show()
	else
		header.backdrop:Hide()
	end
end

local function UpdateMouseOverSetting(menu)
	if menu.db.mouse then
		menu:SetAlpha(0)
	else
		menu:SetAlpha(1)
	end
end

--Updating positions of the menu
local function Positioning(menu)
	local db = menu.db

	--position check
	local header = menu
	if db.orientation == 'vertical' then
		for i = 1, #menu.ToggleTable do
			local button, prev = menu.ToggleTable[i], menu.ToggleTable[i-1]
			menu.ToggleTable[i]:ClearAllPoints()
			menu.ToggleTable[i]:SetPoint('TOP', (prev or header), prev and 'BOTTOM' or 'TOP', 0, prev and -(db.spacing + E.Spacing*2) or -(1 + E.Spacing))
		end
	elseif db.orientation == 'horizontal' then
		for i = 1, #menu.ToggleTable do
			local button, prev = menu.ToggleTable[i], menu.ToggleTable[i-1]
			menu.ToggleTable[i]:ClearAllPoints()
			menu.ToggleTable[i]:SetPoint('LEFT', (prev or header), prev and 'RIGHT' or 'LEFT', prev and (db.spacing + E.Spacing*2) or (1 + E.Spacing), 0)
		end
	end
	--Calling for dropdown updates
	if menu.style == 'dropdown' then
		for i = 1, #menu.GroupsTable do
			menu:UpdateDropdownLayout(menu.GroupsTable[i])
		end
	end
end

--Resizing frames. Need to run after a new button was created to let the menu know it exist
local function FrameSize(menu)
	local db = menu.db
	if not db.size then return end
	menu:MoverSize()

	for i = 1, #menu.ToggleTable do
		menu.ToggleTable[i]:SetSize(db.size, db.size)
	end

	if menu.style == 'dropdown' then
		for i = 1, #menu.GroupsTable do
			local group = menu.GroupsTable[i]
			local mass = menu[group..'Table']
			for w = 1, #mass do
				if mass[w].text and mass[w].text:GetWidth() > menu.HoldersTable[i].width and not menu.fixedDropdownWidth then
					menu.HoldersTable[i].width = mass[w].text:GetWidth() + 10
				elseif menu.fixedDropdownWidth then
					menu.HoldersTable[i].width = menu.fixedDropdownWidth
					if mass[w].text and mass[w].text:GetWidth() > menu.fixedDropdownWidth then
						mass[w].text:SetWidth(menu.fixedDropdownWidth - 10)
						mass[w].text:SetWordWrap(false)
					end
				end
			end
			for n = 1, #mass do
				if mass[n].isSeparator then
					mass[n]:SetSize(menu.HoldersTable[i].width - 3, mass[n].size)
				else
					mass[n]:SetSize(menu.HoldersTable[i].width, db.size)
				end
			end
		end
	end

	menu:Positioning()
end

local function UpdateBackdrop(menu)
	if menu.db.menuBackdrop then
		menu.backdrop:Show()
	else
		menu.backdrop:Hide()
	end
end

--Enable/disable call
local function ToggleShow(menu)
	if not menu.db.enable then
		menu:Hide()
		E:DisableMover(menu.mover:GetName())
		UnregisterStateDriver(menu, 'visibility')
	else
		menu:Show()
		menu:UpdateMouseOverSetting()
		menu:UpdateBackdrop()
		E:EnableMover(menu.mover:GetName())
		if menu.db.visibility then RegisterStateDriver(menu, 'visibility', menu.db.visibility) end
	end
end

--Creating of the menu
function lib:CreateFrame(name, db, default, style, styleDefault, strata, level, transparent)
	--Checks to prevent a shitload of errors cause of wrong arguments passed
	if _G[name] then return end
	if not strata then strata = 'MEDIUM' end
	local menu = CreateFrame('Frame', name, E.UIParent, 'BackdropTemplate')
	menu.db = db --making menu db table so we can actually keep unified settings calls in other functions
	menu.default = default --same for defaults
	EqualizeDB(menu.db, menu.default)
	if not style and styleDefault then style = styleDefault end
	menu.style = style
	menu.transparent = transparent

	menu:SetFrameStrata(strata)
	menu:SetFrameLevel(level or 5)
	menu:SetClampedToScreen(true)
	menu:SetPoint('LEFT', E.UIParent, 'LEFT', -2, 0)
	menu:SetSize(17, 17) --Cause the damn thing doesn't want to show up without default size lol
	menu.myname = UnitName('player') --used in checks for addon deps
	menu:CreateBackdrop()

	menu.NumBut = 0
	menu.ToggleTable = {}
	menu.HoldersTable = {}
	menu.GroupsTable = {}

	--Setting API
	menu.CreateCoreButton = CreateCoreButton
	menu.CreateDropdownButton = CreateDropdownButton
	menu.CreateSeparator = CreateSeparator
	menu.ToggleSetup = ToggleSetup
	menu.ToggleCats = ToggleCats
	menu.UpdateDropdownLayout = UpdateDropdownLayout
	menu.Positioning = Positioning
	menu.MoverSize = MoverSize
	menu.UpdateMouseOverSetting = UpdateMouseOverSetting
	menu.FrameSize = FrameSize
	menu.SetupMover = SetupMover
	menu.ToggleShow = ToggleShow
	menu.UpdateBackdrop = UpdateBackdrop
	menu.OnEnter = OnEnter
	menu.OnLeave = OnLeave


	menu:SetScript('OnEnter', function(self) menu:OnEnter() end)
	menu:SetScript('OnLeave', function(self) menu:OnLeave() end)

	return menu
end

--Default options table structure
local function GenerateTable(menu, coreGroup, groupName, groupTitle)
	local positionValues = {
		TOPLEFT = 'TOPLEFT',
		LEFT = 'LEFT',
		BOTTOMLEFT = 'BOTTOMLEFT',
		RIGHT = 'RIGHT',
		TOPRIGHT = 'TOPRIGHT',
		BOTTOMRIGHT = 'BOTTOMRIGHT',
		CENTER = 'CENTER',
		TOP = 'TOP',
		BOTTOM = 'BOTTOM',
	}

	local isDefault = coreGroup == true and true or false
	if isDefault and not E.Options.args.uibuttons then
		E.Options.args.uibuttons = {
			type = 'group',
			name = L["UI Buttons"],
			order = 77,
			childGroups = 'select',
			args = {
			},
		}
		coreGroup = E.Options.args.uibuttons
	end
	coreGroup.args[groupName] = {
		type = 'group',
		name = groupTitle,
		order = 1,
		args = {
			header = {
				order = 1,
				type = 'header',
				name = groupTitle,
			},
			enabled = {
				order = 3,
				type = 'toggle',
				name = L["Enable"],
				desc = L["Show/Hide UI buttons."],
				get = function(info) return menu.db.enable end,
				set = function(info, value) menu.db.enable = value; menu:ToggleShow() end
			},
			style = {
				order = 4,
				name = L["UI Buttons Style"],
				type = 'select',
				values = {
					['classic'] = L["Classic"],
					['dropdown'] = L["Dropdown"],
				},
				disabled = function() return not menu.db.enable end,
				get = function(info) return E.private.sle.uiButtonStyle end,
				set = function(info, value) E.private.sle.uiButtonStyle = value; E:StaticPopup_Show('PRIVATE_RL') end,
			},
			space = {
				order = 5,
				type = 'description',
				name = '',
			},
			size = {
				order = 6,
				type = 'range',
				name = L["Size"],
				desc = L["Sets size of buttons"],
				min = 12, max = 25, step = 1,
				disabled = function() return not menu.db.enable end,
				get = function(info) return menu.db.size end,
				set = function(info, value) menu.db.size = value; menu:FrameSize() end,
			},
			spacing = {
				order = 7,
				type = 'range',
				name = L["Button Spacing"],
				desc = L["The spacing between buttons."],
				min = 1, max = 10, step = 1,
				disabled = function() return not menu.db.enable end,
				get = function(info) return menu.db.spacing end,
				set = function(info, value) menu.db.spacing = value; menu:FrameSize() end,
			},
			mouse = {
				order = 8,
				type = 'toggle',
				name = L["Mouseover"],
				desc = L["Show on mouse over."],
				disabled = function() return not menu.db.enable end,
				get = function(info) return menu.db.mouse end,
				set = function(info, value) menu.db.mouse = value; menu:UpdateMouseOverSetting() end
			},
			menuBackdrop = {
				order = 9,
				type = 'toggle',
				name = L["Backdrop"],
				disabled = function() return not menu.db.enable end,
				get = function(info) return menu.db.menuBackdrop end,
				set = function(info, value) menu.db.menuBackdrop = value; menu:UpdateBackdrop() end
			},
			dropdownBackdrop = {
				order = 10,
				type = 'toggle',
				name = L["Dropdown Backdrop"],
				disabled = function() return not menu.db.enable or E.private.sle.uiButtonStyle == 'classic' end,
				get = function(info) return menu.db.dropdownBackdrop end,
				set = function(info, value) menu.db.dropdownBackdrop = value; menu:FrameSize() end
			},
			orientation = {
				order = 11,
				name = L["Buttons position"],
				desc = L["Layout for UI buttons."],
				type = 'select',
				values = {
					['horizontal'] = L["Horizontal"],
					['vertical'] = L["Vertical"],
				},
				disabled = function() return not menu.db.enable end,
				get = function(info) return menu.db.orientation end,
				set = function(info, value) menu.db.orientation = value; menu:FrameSize() end,
			},
			point = {
				type = 'select',
				order = 13,
				name = L["Anchor Point"],
				desc = L["What point of dropdown will be attached to the toggle button."],
				disabled = function() return not menu.db.enable or E.private.sle.uiButtonStyle == 'classic' end,
				get = function(info) return menu.db.point end,
				set = function(info, value) menu.db.point = value; menu:FrameSize() end,
				values = positionValues,
			},
			anchor = {
				type = 'select',
				order = 14,
				name = L["Attach To"],
				desc = L["What point to anchor dropdown on the toggle button."],
				disabled = function() return not menu.db.enable or E.private.sle.uiButtonStyle == 'classic' end,
				get = function(info) return menu.db.anchor end,
				set = function(info, value) menu.db.anchor = value; menu:FrameSize() end,
				values = positionValues,
			},
			xoffset = {
				order = 15,
				type = 'range',
				name = L["X-Offset"],
				desc = L["Horizontal offset of dropdown from the toggle button."],
				min = -10, max = 10, step = 1,
				disabled = function() return not menu.db.enable or E.private.sle.uiButtonStyle == 'classic' end,
				get = function(info) return menu.db.xoffset end,
				set = function(info, value) menu.db.xoffset = value; menu:FrameSize() end,
			},
			yoffset = {
				order = 16,
				type = 'range',
				name = L["Y-Offset"],
				desc = L["Vertical offset of dropdown from the toggle button."],
				min = -10, max = 10, step = 1,
				disabled = function() return not menu.db.enable or E.private.sle.uiButtonStyle == 'classic' end,
				get = function(info) return menu.db.yoffset end,
				set = function(info, value) menu.db.yoffset = value; menu:FrameSize() end,
			},
		},
	}
end

function lib:CreateOptions(menu, default, groupName, groupTitle)
	menu:RegisterEvent('ADDON_LOADED')
	menu:SetScript('OnEvent', function(self, event, addon)
		if addon ~= 'ElvUI_OptionsUI' then return end
		self:UnregisterEvent('ADDON_LOADED')
		GenerateTable(self, default, groupName, groupTitle)
	end)
end

--This function is for creating own menu style. Replacing is not encouraged, hooking recommended.
function lib:CustomStyleCoreButton(menu, name, text)
end

