GUILib = {};
local gui = GUILib;

local scripts = {
    checkButton = {
        OnShow = function(self) 
            local setting = self.Get and self.Get(self.setting) or self.module:Get(self.setting);
            self:SetChecked(setting and true or false); 
        end,
        OnClick = function(self) 
            local val = self:GetChecked() and true or false;
            if self.Set then
                self.Set(self.setting, val);
            else
                self.module:Set(self.setting, val);
            end
            if self.toggle then self.toggle:SetChecked(true); end
        end,
    },
    slider = {
		OnShow = function(self) 
            self.real = false;
            self:SetValue((self.Get and (self.Get(self.setting) or 0)) or self.module:Get(self.setting)); 
            self.real = true; 
        end,
		OnValueChanged = function(self)
			local value = self:GetValue();
			local formatstring;
			if math.floor(value) == value then 
				formatstring = "%d";
			else
				formatstring = "%.2f";
			end
            local text = string.format(formatstring, value);
            text = string.gsub(text, "(%.%d+)(0+)$", "%1");
			getglobal(this:GetName() .. "Value"):SetText(text);
			if this.real then
                if self.Set then
                    self.Set(self.setting, value);
                else
                    self.module:Set(self.setting, value);
                end
                if self.toggle then self.toggle:SetChecked(true); end
			end
		end,
	},
    editBoxString = {
		OnEditFocusGained = function(self) 
            local text;
            if self.Get then
                text = self.Get(self.setting);
            else
                text = self.module:Get(self.setting);
            end
            self:SetText(text or ""); 
            self:HighlightText(); 
        end,
		OnEditFocusLost = function(self) self:SetText(self.title or ""); self:SetCursorPosition(0); end,
		OnShow = function(self) self:SetText(self.title or ""); self:SetCursorPosition(0); end,
		OnEscapePressed = function(self)
            self:SetText(self.title or ""); self:SetCursorPosition(0);
			self:ClearFocus();
		end,
		OnEnterPressed = function(self) 
			local text = self:GetText();
            if self.Set then
                self.Set(this.setting, text);
            else
                self.module:Set(this.setting, text); 
            end
            if self.toggle then self.toggle:SetChecked(true); end
            self:ClearFocus();
		end,
	},
    editBoxDropDown = {
		OnEditFocusGained = function(self) self:SetText(""); self:HighlightText() end,
		OnEditFocusLost = function(self) self:SetText(self.title or ""); self:SetCursorPosition(0); end,
		OnShow = function(self) self:SetText(self.title or ""); self:SetCursorPosition(0); end,
		OnEscapePressed = function(self)
            self:SetText(self.title or ""); self:SetCursorPosition(0);
			self:ClearFocus();
		end,
		OnEnterPressed = function(self) 
			local text = self:GetText();
            if self.Set then
                self.Set(this.setting, string.lower(text), 1);
            else
                self.module:Set(this.setting, string.lower(text), 1); 
            end
            if self.toggle then self.toggle:SetChecked(true); end
            self:ClearFocus();
		end,
	},
    colorSelect = {
        OnClick = function(self)
            ColorPickerFrame.func = function() 
                local r, g, b = ColorPickerFrame:GetColorRGB();
                getglobal(self:GetName() .. "NormalTexture"):SetVertexColor(r, g, b);
                if self.Set then
                    self.Set(self.setting, {r = r, g = g, b = b});
                else
                    self.module:Set(self.setting, {r = r, g = g, b = b});
                end
                if self.toggle then self.toggle:SetChecked(true); end
            end
            local r, g, b = getglobal(self:GetName().."NormalTexture"):GetVertexColor();
            ColorPickerFrame:SetColorRGB(r, g, b);
            ColorPickerFrame.previousValues = {r = r, g = g, b = b, isReal = self.isReal};
            ColorPickerFrame.cancelFunc = function(prev)
                if prev.isReal then
                    if self.Set then
                        self.Set(self.setting, {r = r, g = g, b = b});
                    else
                        self.module:Set(self.setting, {r = r, g = g, b = b});
                    end
                    if self.toggle then self.toggle:SetChecked(true); end
                else
                    if self.Set then
                        self.Set(self.setting, nil);
                    else
                        self.module:Set(self.setting, nil);
                    end
                    if self.toggle then self.toggle:SetChecked(false); end
                end
                getglobal(self:GetName().."NormalTexture"):SetVertexColor(prev.r, prev.g, prev.b);
            end
            ShowUIPanel(ColorPickerFrame);
        end,
        OnEnter = function(self)
            getglobal(self:GetName() .. "SwatchBg"):SetVertexColor(NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b);
            if self.tooltipText then
                GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
                GameTooltip:SetText(self.tooltipText, nil, nil, nil, nil, 1);
            end
        end,
        OnLeave = function(self)
            getglobal(self:GetName() .. "SwatchBg"):SetVertexColor(HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
            GameTooltip:Hide();
        end,
        OnShow = function(self)
            local color;
            local isReal = true;
            if self.Get then
                color, isReal = self.Get(self.setting);
            else
                color = self.module:Get(self.setting);
            end
            if color then
                self.isReal = isReal;
                getglobal(self:GetName() .. "NormalTexture"):SetVertexColor(color.r, color.g, color.b);
            end
        end,
    },
    button = {
        OnClick = function(self)
            if self.func then
                self.func();
            end
        end,
    },
    toggle = {
        OnShow = function(self)
            local value, isChecked;
            if self.Get then
                value, isChecked = self.Get(self.setting);
            else
                value = self.module:Get(self.setting);
            end
            if isChecked == nil then
                isChecked = (value ~= nil and true or false);
            end
            self:SetChecked(isChecked);
        end,
        OnClick = function(self) 
            if self:GetChecked() then
                self:SetChecked(false);
                AsheylaLib:AlertUser("You cannot check this button.  It becomes checked when you change the setting to the right.");
            else
                if self.Set then
                    self.Set(self.setting, nil);
                else
                    self.module:Set(self.setting, nil);
                end
                local ownerShowFunc = self.owner:GetScript("OnShow");
                if ownerShowFunc then 
                    ownerShowFunc(self.owner);
                end
            end
        end,
    },
};

function gui:SetGUIPanel(panel, subCat, module, options, extras)
    extras = extras or {};
    self.panels = self.panels or {};
    self.panels[panel] = self.panels[panel] or {};
    self.panels[panel][subCat] = { options = options, }; 
    local panelTable = self.panels[panel][subCat];
    local f = CreateFrame("Frame");
    panelTable.panel = f;
    if subCat ~= "main" then
        f.parent = panel;
        f.name = subCat;
    else
        f.name = panel;
    end
    self:GenerateMenu(f, module, options, extras);
    f.default = extras.default or function() module:ClearSettings(); InterfaceOptionsFrame:Hide(); gui:ShowPanel(panel, subCat); end
    f.okay = extras.okay;
    InterfaceOptions_AddCategory(f);
end

function gui:GenerateMenu(frame, module, options, extras)
    local anchor = frame;
    local x, y, rel = 5, -5, "TOPLEFT";
    for _, section in ipairs(options) do
        local sectionFrame = self:GenerateSection(module, frame, section, extras);
        sectionFrame:SetPoint("TOPLEFT", anchor, rel, x, y);
        anchor = sectionFrame;
        x, y = 0, -5;
        rel = "BOTTOMLEFT";
    end
    
    if extras.help then
        local helpFrame = CreateFrame("Button", module:GetName() .. "HelpButton", frame, "Ash_CoreButtonTemplate");
        getglobal(helpFrame:GetName() .. "Text"):SetText("Help");
        helpFrame:SetPoint("BOTTOM", 0, 20);
        UIDropDownMenu_Initialize(getglobal(helpFrame:GetName() .. "DropDown"), extras.help);
    end
    local donationText = frame:CreateFontString();
    donationText:SetFontObject(GameFontNormalSmall);
    donationText:SetPoint("BOTTOM", 0, 6);
    donationText:SetJustifyV("BOTTOM");
    donationText:SetText("Donations are appreciated at ross456@gmail.com via PayPal.");
end

function gui:GenerateSection(module, frame, section, extras)
    local sectionFrame = CreateFrame("Frame", nil, frame);
    sectionFrame.columns = {};
    local height = 0;
    local anchor = sectionFrame;
    local x, y, rel = 0, 0, "TOPLEFT";
    for _, column in ipairs(section) do
        local columnFrame = self:GenerateColumn(module, sectionFrame, column, extras);
        table.insert(sectionFrame.columns, columnFrame);
        height = math.max(height, columnFrame:GetHeight());
        columnFrame:SetPoint("TOPLEFT", anchor, rel, x, y);
        anchor = columnFrame;
        x, y = 8, 0;
        rel = "TOPRIGHT";
    end
    
    for _, column in ipairs(sectionFrame.columns) do
        local difference = height - column:GetHeight();
        if difference > 0 then
            if #column.breaks > 0 then
                difference = difference / #column.breaks;
                for _, breakFrame in ipairs(column.breaks) do
                    breakFrame:SetHeight(difference);
                end
            else
                difference = difference / (#column.items - 1);
                for i = 2, #column.items do
                    column.items[i]:ClearAllPoints();
                    column.items[i]:SetPoint("TOPLEFT", column.items[i - 1], "BOTTOMLEFT", 0, -difference);
                end
            end
        end
    end
    
    sectionFrame:SetWidth(1);
    sectionFrame:SetHeight(height);
    return sectionFrame;
end

function gui:GenerateColumn(module, frame, column, extras)
    local columnFrame = CreateFrame("Frame", nil, frame);
    columnFrame.items = {};
    columnFrame.breaks = {};
    local height, width = 0, 0;
    local anchor = columnFrame;
    local x, y, rel = 0, 0, "TOPLEFT";
    for _, item in ipairs(column) do
        if item == "BREAK" then
            local breakFrame = CreateFrame("Frame", nil, frame);
            table.insert(columnFrame.breaks, breakFrame);
            breakFrame:SetWidth(1);
            breakFrame:SetHeight(1);
            height = height + 1;
            width = math.max(width, 1);
            breakFrame:SetPoint("TOPLEFT", anchor, rel, x, y);
            x, y = 0, 0;
            anchor = breakFrame;
            rel = "BOTTOMLEFT";
        elseif (extras.allSimple and not item.notSimple) or item.simple or not AsheylaLib:InSimpleMode() then
            local itemFrame = item[1] and self:GenerateRow(module, columnFrame, item, extras) or self:GenerateItem(module, columnFrame, item, extras);
            table.insert(columnFrame.items, itemFrame);
            height = height + itemFrame:GetHeight();
            width = math.max(width, itemFrame:GetWidth());
            itemFrame:SetPoint("TOPLEFT", anchor, rel, x, y);
            x, y = 0, 0;
            anchor = itemFrame;
            rel = "BOTTOMLEFT";
        end
    end
    columnFrame:SetWidth(width);
    columnFrame:SetHeight(height);
    --local t = columnFrame:CreateTexture() t:SetAllPoints(true) t:SetTexture(0, 0, 1, .5);
    --local bg = CreateFrame("Frame", nil, columnFrame, "Ash_CoreColumnBackgroundTemplate");
    --bg:SetPoint("TOPLEFT", -3, 3);
    --bg:SetPoint("BOTTOMRIGHT", 3, -3);
    return columnFrame;
end

function gui:GenerateRow(module, frame, row, extras)
    local rowFrame = CreateFrame("Frame", nil, frame);
    local height, width = 0, -5;
    local anchor = rowFrame;
    local x, y, rel = 0, 0, "LEFT";
    for _, item in ipairs(row) do
        local itemFrame = self:GenerateItem(module, rowFrame, item, extras);
        height = math.max(height, itemFrame:GetHeight());
        width = width + itemFrame:GetWidth() + 5;
        itemFrame:SetPoint("LEFT", anchor, rel, x, y);
        x, y = 5, 0;
        anchor = itemFrame;
        rel = "RIGHT";
    end
    rowFrame:SetWidth(width);
    rowFrame:SetHeight(height);
    return rowFrame;
end

local counter = 0;
function gui:GenerateItem(module, frame, item, extras)
    module = item.module or module;
    counter = counter + 1;
    local holderFrame = CreateFrame("Frame", nil, frame);
    local itemFrame, width, height, point;
    
    local x, y = 0, 0;
    if item.type == "checkButton" then
        itemFrame = CreateFrame("CheckButton", "Ash_MenuItem" .. counter, frame, "Ash_CoreCheckButtonTemplate");
        getglobal(itemFrame:GetName() .. "Text"):SetText(item.title);
        width = itemFrame:GetWidth() + getglobal(itemFrame:GetName() .. "Text"):GetWidth();
        height = itemFrame:GetHeight() * .8;
        point = "LEFT";
    elseif item.type == "slider" then
        itemFrame = CreateFrame("Slider", "Ash_MenuItem" .. counter, frame, "Ash_CoreSliderTemplate");
        getglobal(itemFrame:GetName() .. "Title"):SetText(item.title);
        getglobal(itemFrame:GetName() .. "Value"):SetText("aaaa");
        getglobal(itemFrame:GetName() .. "Low"):SetText("");
        getglobal(itemFrame:GetName() .. "High"):SetText("");
        itemFrame:SetMinMaxValues(item.minValue, item.maxValue);
        itemFrame:SetValueStep(item.valueStep);
		width = itemFrame:GetWidth() + getglobal(itemFrame:GetName() .. "Value"):GetWidth();
		height = itemFrame:GetHeight() + getglobal(itemFrame:GetName() .. "Title"):GetHeight() + 5;
        point = "BOTTOMLEFT";
    elseif item.type == "editBoxString" then
        itemFrame = CreateFrame("EditBox", "Ash_MenuItem" .. counter, frame, "Ash_CoreEditBoxTemplate");
        itemFrame.title = item.title;
		width = itemFrame:GetWidth() + 5;
		height = itemFrame:GetHeight() + 6;
		point = "BOTTOMRIGHT";
        y = 3;
    elseif item.type == "dropDownSelector" then
        itemFrame = CreateFrame("Button", "Ash_MenuItem" .. counter, frame, "Ash_CoreDropDownTemplate");
        getglobal(itemFrame:GetName() .. "Text"):SetText(item.title);
        local func = function()
            local info;
            for _, value in ipairs(item.values) do
                info = UIDropDownMenu_CreateInfo();
                info.text = value;
                if extras.get then
                    info.checked = extras.get(item.setting) == value;
                else
                    info.checked = module:Get(item.setting) == value;
                end
                info.func = function() 
                    if extras.set then
                       extras.set(item.setting, value);
                    else
                        module:Set(item.setting, value); 
                    end
                    if itemFrame.toggle then itemFrame.toggle:SetChecked(true); end
                    CloseDropDownMenus(); 
                end
                UIDropDownMenu_AddButton(info);
            end
        end
        UIDropDownMenu_Initialize(getglobal(itemFrame:GetName() .. "DropDown"), func);
		width = itemFrame:GetWidth() + getglobal(itemFrame:GetName() .. "Text"):GetWidth();
		height = itemFrame:GetHeight();
		point = "LEFT";
    elseif item.type == "dropDownMultiSelector" then
        itemFrame = CreateFrame("Button", "Ash_MenuItem" .. counter, frame, "Ash_CoreDropDownTemplate");
        getglobal(itemFrame:GetName() .. "Text"):SetText(item.title);
        local func = function()
            local info;
            for _, value in ipairs(item.values) do
                info = UIDropDownMenu_CreateInfo();
                info.text = value;
                if extras.get then
                    info.checked = extras.get(item.setting, value);
                else
                    info.checked = module:Get(item.setting, value);
                end
                info.func = function(frame, _, _, wasChecked) 
                    local val = not wasChecked and 1 or nil;
                    if extras.set then
                        extras.set(item.setting, value, val);
                    else
                        module:Set(item.setting, value, val); 
                    end
                    if itemFrame.toggle then itemFrame.toggle:SetChecked(true); end
                    CloseDropDownMenus(); 
                end
                UIDropDownMenu_AddButton(info);
            end
        end
        UIDropDownMenu_Initialize(getglobal(itemFrame:GetName() .. "DropDown"), func);
		width = itemFrame:GetWidth() + getglobal(itemFrame:GetName() .. "Text"):GetWidth();
		height = itemFrame:GetHeight();
		point = "LEFT";
    elseif item.type == "dropDown" then
        itemFrame = CreateFrame("Button", "Ash_MenuItem" .. counter, frame, "Ash_CoreDropDownTemplate");
        getglobal(itemFrame:GetName() .. "Text"):SetText(item.title);
        UIDropDownMenu_Initialize(getglobal(itemFrame:GetName() .. "DropDown"), item.func);
		width = itemFrame:GetWidth() + getglobal(itemFrame:GetName() .. "Text"):GetWidth();
		height = itemFrame:GetHeight();
		point = "TOPLEFT";
    elseif item.type == "editBoxDropDown" then
        itemFrame = CreateFrame("EditBox", "Ash_MenuItem" .. counter, frame, "Ash_CoreEditBoxDropDownTemplate");
        itemFrame.title = item.title;
        local func = item.func or function()
            local info;
            for value in pairs(extras.get and extras.get(item.setting) or module:Get(item.setting)) do
                info = UIDropDownMenu_CreateInfo();
                info.text = value;
                info.notCheckable = 1;
                info.func = function() 
                    if extras.set then
                        extras.set(item.setting, value, nil);
                    else
                        module:Set(item.setting, value, nil); 
                    end
                    if itemFrame.toggle then itemFrame.toggle:SetChecked(true); end
                    CloseDropDownMenus(); 
                end
                UIDropDownMenu_AddButton(info);
            end
        end
        UIDropDownMenu_Initialize(getglobal(itemFrame:GetName() .. "ButtonDropDown"), func);
        UIDropDownMenu_SetAnchor(getglobal(itemFrame:GetName() .. "ButtonDropDown"), nil, nil, "TOPRIGHT", getglobal(itemFrame:GetName() .. "Button"), "BOTTOMLEFT");
		width = itemFrame:GetWidth() + getglobal(itemFrame:GetName() .. "Button"):GetWidth() + 5;
		height = itemFrame:GetHeight() + 6;
		point = "LEFT";
        x = 5;
    elseif item.type == "text" then
        itemFrame = CreateFrame("Frame", "Ash_MenuItem" .. counter, frame);
        local s = itemFrame:CreateFontString();
        itemFrame.text = s;
        s:SetFontObject(item.small and GameFontNormalSmall or GameFontNormal);
        s:SetJustifyH("LEFT");
        s:SetText(item.text);
        s:SetAllPoints(itemFrame);
        itemFrame:SetWidth(s:GetWidth() + 5);
        itemFrame:SetHeight(s:GetHeight());
        width = itemFrame:GetWidth();
        height = itemFrame:GetHeight() + 4;
        point = "LEFT";
    elseif item.type == "colorSelect" then
        itemFrame = CreateFrame("Button", "Ash_MenuItem" .. counter, frame, "Ash_CoreColorSwatchTemplate");
        getglobal(itemFrame:GetName() .. "Title"):SetText(item.title);
        width = itemFrame:GetWidth() + getglobal(itemFrame:GetName() .. "Title"):GetWidth() + 5;
        height = itemFrame:GetHeight() + 4;
        point = "LEFT";
    elseif item.type == "button" then
        itemFrame = CreateFrame("Button", "Ash_MenuItem" .. counter, frame, "Ash_CoreButtonTemplate");
        itemFrame.func = item.func;
        getglobal(itemFrame:GetName() .. "Text"):SetText(item.title);
        width = itemFrame:GetWidth();
        height = itemFrame:GetHeight();
        point = "LEFT";
    end
    if not item.type then DoTimer:Print(item.title) end
    
    itemFrame.tooltipText = item.tooltipText;
    itemFrame.data = item.data;
    itemFrame.module = module;
    itemFrame.setting = item.setting;
    itemFrame.Get = extras.get;
    itemFrame.Set = extras.set;
    if scripts[item.type] then
        for name, func in pairs(scripts[item.type]) do
            itemFrame:SetScript(name, func);
        end
    end
    if item.scripts then
        for name, func in pairs(item.scripts) do
            itemFrame:SetScript(name, func);
        end
    end
    if item.hasToggle then
        local toggleFrame = CreateFrame("CheckButton", nil, frame, "Ash_CoreToggleTemplate");
        toggleFrame:SetPoint("LEFT", holderFrame);
        toggleFrame.tooltipText = extras.toggleTooltip;
        toggleFrame.Set = extras.set;
        toggleFrame.Get = extras.get;
        for name, func in pairs(scripts.toggle) do
            toggleFrame:SetScript(name, func);
        end
        toggleFrame.setting = item.setting;
        toggleFrame.module = module;
        toggleFrame.owner = itemFrame;
        itemFrame.toggle = toggleFrame;
        x = x + toggleFrame:GetWidth();
    end
    itemFrame:SetPoint(point, holderFrame, point, x, y);
    holderFrame:SetWidth(width);
    holderFrame:SetHeight(height);
    --local t = holderFrame:CreateTexture() t:SetAllPoints(true) t:SetTexture(0, 1, 0, .5);
    return holderFrame;
end

function gui:ShowPanel(panel, subCat)
    subCat = subCat or "main";
    for panelName, panelTable in pairs(self.panels) do
        if string.find(panelName, panel .. "$") then
            local f = panelTable[subCat].panel;
            InterfaceOptionsFrame_OpenToCategory(f);
            break;
        end
    end
end

local SHOWN_PANEL, SHOWN_SUBCAT;
hooksecurefunc("InterfaceOptionsList_DisplayPanel", function(f) 
    if f.parent then
        SHOWN_PANEL = f.parent;
        SHOWN_SUBCAT = f.name;
    else
        SHOWN_PANEL = f.name;
        SHOWN_SUBCAT = nil;
    end
end);

local creationFuncs = {};
function gui:AddGUICreationScript(func)
    table.insert(creationFuncs, func);
end

function gui:ReloadGUI()
    local cats = INTERFACEOPTIONS_ADDONCATEGORIES;
    for _, panel in pairs(self.panels) do
        for subCat, panelTable in pairs(panel) do
            for i = #cats, 1, -1 do
                if cats[i] == panelTable.panel then
                    table.remove(cats, i);
                    break;
                end
            end
        end
    end
    self.panels = {};
    for i, func in ipairs(creationFuncs) do
        func();
    end
    InterfaceOptionsFrame:Hide(); 
    gui:ShowPanel(SHOWN_PANEL, SHOWN_SUBCAT);
end

local profileFuncs = {};
function gui:AddProfileChangeScript(func)
    table.insert(profileFuncs, func);
end

hooksecurefunc(AsheylaLib, "ProfileUpdate", function()
    for i, func in ipairs(profileFuncs) do
        func();
    end
    InterfaceOptionsFrame:Hide(); 
    gui:ShowPanel(SHOWN_PANEL, SHOWN_SUBCAT);
end);
