local _, addon = ...

local EXPANDED_BAR_LEFT_OFFSET = 68;    --Constant
local COLLAPSED_BAR_LEFT_OFFSET = -120; --Variable

local GetTrackingInfo = addon.TransitionAPI.GetTrackingInfo;
local SetTracking = addon.TransitionAPI.SetTracking;

local outSine = addon.EasingFunctions.outSine;


local MainFrame;
local ToolbarButtons = {};

function ToolbarButtons:DisableButtons()
    for i = 1, #self do
        self[i]:Disable();
    end
end

function ToolbarButtons:EnableButtons()
    for i = 1, #self do
        self[i]:Enable();
    end
end

function ToolbarButtons:SetButtonVisiblity(state)
    if state then
        for i = 1, #self do
            self[i]:Enable();
            self[i]:SetAlpha(1);
        end
    else
        for i = 1, #self do
            self[i]:Disable();
            self[i]:SetAlpha(0);
        end
    end
end

function ToolbarButtons:ResetButtonState()
    for i = 1, #self do
        self[i].isOn = nil;
        self[i]:UpdateIcon();
    end
end

function ToolbarButtons:RunShowButtonCallback()
    for i = 1, #self do
        if self[i].onInitFunc then
            self[i].onInitFunc(self[i]);
        end
    end
end

ToolbarButtons.buttonTypeToButton = {};


local SwitchButtonScripts = {};

function SwitchButtonScripts.OnEnter(self)
    self.Icon:SetTexCoord(0, 0.1875, 0.4375, 0.625);
    MainFrame:OnEnter();
end

function SwitchButtonScripts.OnLeave(self)
    if not MainFrame.expanded then
        self.Icon:SetTexCoord(0, 0.1875, 0.25, 0.4375);
    end
    MainFrame:OnLeave();
end

function SwitchButtonScripts.OnClick(self)
    if MainFrame.expanded then
        MainFrame:Collapse();
    else
        MainFrame:Expand();
    end
end

function SwitchButtonScripts.OnMouseDown(self)
    self.Icon:SetSize(56, 56);
end

function SwitchButtonScripts.OnMouseUp(self)
    self.Icon:SetSize(60, 60);
end




---Set Graphics Settings to Ultra---
local CVAR_GRAPHICS_BACKUP = {};
local CVAR_GRAPHICS_VALUES = {
	["graphicsTextureResolution"] = 3,
	["graphicsTextureFiltering"] = 6,
	["graphicsProjectedTextures"] = 2,

	["graphicsViewDistance"] = 10,
	["graphicsEnvironmentDetail"] = 10,
	["graphicsGroundClutter"] = 10,

	["graphicsShadowQuality"] = 6,
	["graphicsLiquidDetail"] = 4,
	["graphicsSunshafts"] = 3,
	["graphicsParticleDensity"] = 5,
	["graphicsSSAO"] = 5,
	["graphicsDepthEffects"] = 4,
	--["graphicsLightingQuality"] = 3,
	["lightMode"] = 2,
    ["ffxAntiAliasingMode"] = 2,    --FXAA High
	["MSAAQuality"] = 4,	--4 is invalid. But used for backup
	["shadowrt"] = -1,		--invalid
}

---Hide Names and Bubbles---
local CVAR_UNIT_NAME_BACKUP = {};
local CVAR_UNIT_NAME_VALUES = {			--Unit Name CVars
	["UnitNameOwn"] = 0,
	["UnitNameNonCombatCreatureName"] = 0,
	["UnitNameFriendlyPlayerName"] = 0,
	["UnitNameFriendlyPetName"] = 0,
	["UnitNameFriendlyMinionName"] = 0,
	["UnitNameFriendlyGuardianName"] = 0,
	["UnitNameFriendlySpecialNPCName"] = 0,
	["UnitNameEnemyPlayerName"] = 0,
	["UnitNameEnemyPetName"] = 0,
	["UnitNameEnemyGuardianName"] = 0,
	["UnitNameNPC"] = 0,
	["UnitNameInteractiveNPC"] = 0,
	["UnitNameHostleNPC"] = 0,
	["chatBubbles"] = 0,
	["floatingCombatTextCombatDamage"] = 0,
	["floatingCombatTextCombatHealing"] = 0,
};


local CVAR_CAMERA_BACKUP = {};
local CVAR_CAMERA_VALUES = {
    ["cameraFov"] = 90,
    ["test_cameraOverShoulder"] = 0,
    ["test_cameraDynamicPitch"] = 0,
    ["test_cameraDynamicPitchBaseFovPad"] = 0,
    ["cameraViewBlendStyle"] = 1,
}


local SetCVar = SetCVar;
local GetCVar = GetCVar;
local ConsoleExec = ConsoleExec;

local CVarUtil = {};
CVarUtil.groups = {};

function CVarUtil:Backup(originalTable, backupTable)
    for k, v in pairs(originalTable) do
        backupTable[k] = GetCVar(k) or 0;
    end
end

function CVarUtil:Restore(backupTable)
    for k, v in pairs(backupTable) do
        SetCVar(k, v);
    end
end

function CVarUtil:SetValues(cvarTable, value)
    for k, v in pairs(cvarTable) do
        SetCVar(k, value);
    end
end

function CVarUtil:Zero(cvarTable)
    for k, v in pairs(cvarTable) do
        SetCVar(k, 0);
    end
end

function CVarUtil:BackupAll()
    self:Backup(CVAR_GRAPHICS_VALUES, CVAR_GRAPHICS_BACKUP);
    self:Backup(CVAR_UNIT_NAME_VALUES, CVAR_UNIT_NAME_BACKUP);
    self:Backup(CVAR_CAMERA_VALUES, CVAR_CAMERA_BACKUP);
    self:SaveTrackingStatus();
end

function CVarUtil:RestoreAll()
    self:SetHideTextStatus(false);
    self:SetTopQualityStatus(false);
    self:SetCameraStatus(false);
end

function CVarUtil:SaveTrackingStatus()
	local _;
	_, _, self.isTrackingBattlePet = GetTrackingInfo(1);
end

function CVarUtil:SetTrackingStatus(state)
	SetTracking(1, state);
end

function CVarUtil:RestoreTrackingStatus()
	if self.isTrackingBattlePet then
		self:SetTrackingStatus(true);
	end
end

function CVarUtil:MarkCVarChanged(groupName, state)
    self.groups[groupName] = state;
end

function CVarUtil:IsCVarChanged(groupName)
    return self.groups[groupName]
end


function CVarUtil:SetHideTextStatus(state)
    if state then
        if not self:IsCVarChanged("HideTexts") then
            CVarUtil:Backup(CVAR_UNIT_NAME_VALUES, CVAR_UNIT_NAME_BACKUP);
            CVarUtil:Zero(CVAR_UNIT_NAME_BACKUP);
            CVarUtil:SaveTrackingStatus();
            CVarUtil:SetTrackingStatus(false);
            self:MarkCVarChanged("HideTexts", true);
        end
    else
        if self:IsCVarChanged("HideTexts") then
            CVarUtil:RestoreTrackingStatus();
            CVarUtil:Restore(CVAR_UNIT_NAME_BACKUP);	--causes "Interface action failed" when used in combat programmatically, still functioning, no workaround
            self:MarkCVarChanged("HideTexts", false)
        end
    end
end

function CVarUtil:SetTopQualityStatus(state)
    if state then
        if not self:IsCVarChanged("TopQuality") then
            CVarUtil:Backup(CVAR_GRAPHICS_VALUES, CVAR_GRAPHICS_BACKUP);
            CVarUtil:Restore(CVAR_GRAPHICS_VALUES);
            self:MarkCVarChanged("TopQuality", true);
        end
    else
        if self:IsCVarChanged("TopQuality") then
            CVarUtil:Restore(CVAR_GRAPHICS_BACKUP);
            self:MarkCVarChanged("TopQuality", false)
        end
    end
end

function CVarUtil:SetCameraStatus(state)
    if state then
        if not self:IsCVarChanged("Camera") then
            CVarUtil:Backup(CVAR_CAMERA_VALUES, CVAR_CAMERA_BACKUP);
            self:MarkCVarChanged("Camera", true);
        end
    else
        if self:IsCVarChanged("Camera") then
            CVarUtil:Restore(CVAR_CAMERA_BACKUP);
            self:MarkCVarChanged("Camera", false)
        end
    end
end


local function MogButton_OnClick(self)
    self.isOn = not self.isOn;
    self:UpdateIcon();
end

local function EmoteButton_OnClick(self)
    local state = not MainFrame.EmoteFrame:IsShown();
    if state then
        MainFrame.EmoteFrame:ClearAllPoints();
        if MainFrame.CameraSettingFrame:IsShown() then
            MainFrame.EmoteFrame:SetPoint("BOTTOMLEFT", MainFrame.CameraSettingFrame, "BOTTOMRIGHT", 0, 0);
        elseif MainFrame.TransmogListFrame:IsShown() then
            MainFrame.EmoteFrame:SetPoint("BOTTOMLEFT", MainFrame.TransmogListFrame, "BOTTOMLEFT", MainFrame.TransmogListFrame.collapsedWidth + 6, 0);
        else
            MainFrame.EmoteFrame:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 4, 6);
        end

        MainFrame.EmoteFrame:ShowUI();
        MainFrame.EmoteFrame.parentButton = self;
        self.isOn = true;
    else
        MainFrame.EmoteFrame:HideUI();
        self.isOn = nil;
    end

    self:UpdateIcon();
end

local function HideTextsButton_OnClick(self)
	self.isOn = not CVarUtil:IsCVarChanged("HideTexts");
	NarcissusDB.HideTextsWithUI = self.isOn;
	CVarUtil:SetHideTextStatus(self.isOn);

    self:UpdateIcon();
end

local function HideTextsButton_OnInit(self)
    self.isOn = NarcissusDB.HideTextsWithUI;

    if self.isOn then
        CVarUtil:SetHideTextStatus(true);
    end

    self:UpdateIcon();
end

local function TopQualityButton_OnClick(self)
	self.isOn = not CVarUtil:IsCVarChanged("TopQuality");
	CVarUtil:SetTopQualityStatus(self.isOn);

    if self.isOn then
        MainFrame.TopQualityFrame:ClearAllPoints();
        if MainFrame.Switch:IsShown() then
            MainFrame.TopQualityFrame:SetPoint("TOPRIGHT", self, "BOTTOMRIGHT", 4, -11);
        else
            MainFrame.TopQualityFrame:SetPoint("TOPRIGHT", self, "BOTTOMRIGHT", 28, -11);
        end
        MainFrame.showTopQualityFrame = true;
        MainFrame.TopQualityFrame:Show();
	else
        MainFrame.showTopQualityFrame = nil;
        MainFrame.TopQualityFrame:Hide();
	end

    self:UpdateIcon();
end

local function CameraButton_OnClick(self)
    local state = not MainFrame.CameraSettingFrame:IsShown();
    if state then
        MainFrame.CameraSettingFrame:ClearAllPoints();
        MainFrame.CameraSettingFrame:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 0, 6);
        MainFrame.CameraSettingFrame:Show();
        self.isOn = true;
    else
        MainFrame.CameraSettingFrame:Hide();
        self.isOn = nil;
    end

    CVarUtil:SetCameraStatus(self.isOn);

    self:UpdateIcon();
end

local function LocationButton_OnClick(self)
    local state = not MainFrame.PlayerLocationFrame:IsShown();
    MainFrame.PlayerLocationFrame:SetShown(state);
    MainFrame.PlayerLocationFrame.enabled = state;
    if state then
        self.isOn = true;
    else
        self.isOn = nil;
    end
    self:UpdateIcon();
end

local function LocationButton_OnInit(self)
    self.isOn = MainFrame.PlayerLocationFrame.enabled;
    if self.isOn then
        MainFrame.PlayerLocationFrame:Show();
    end
    self:UpdateIcon();
end

local ToolbarButtonInfo = {
    Mog = {
        onClickFunc = MogButton_OnClick,
    },

    Emote = {
        onClickFunc = EmoteButton_OnClick,
    },

    HideTexts = {
        onClickFunc = HideTextsButton_OnClick,
        onInitFunc = HideTextsButton_OnInit,
    },

    TopQuality = {
        onClickFunc = TopQualityButton_OnClick,
    },

    Camera = {
        onClickFunc = CameraButton_OnClick,
    },

    Location = {
        onClickFunc = LocationButton_OnClick,
        onInitFunc = LocationButton_OnInit,
    },
};

local Layouts = {
    Narcissus = {"Mog", "Emote", "HideTexts", "TopQuality",
        showSwitch = true,
    },

    Blizzard = {"Camera", "Emote", "HideTexts", "TopQuality", "Location",
        customScale = 1,
    },
};


local function OverrideToolbarButtonOnClickFunc(key, newFunc)
    if ToolbarButtonInfo[key] and newFunc then
        ToolbarButtonInfo[key].onClickFunc = newFunc;
    end
end

local function OverrideToolbarButtonOnInitFunc(key, newFunc)
    if ToolbarButtonInfo[key] and newFunc then
        ToolbarButtonInfo[key].onInitFunc = newFunc;
    end
end

local function  GetToolbarButtonByButtonType(buttonKey)
    if buttonKey and ToolbarButtons.buttonTypeToButton[buttonKey] then
        return ToolbarButtons.buttonTypeToButton[buttonKey]
    end
end

addon.OverrideToolbarButtonOnClickFunc = OverrideToolbarButtonOnClickFunc;
addon.OverrideToolbarButtonOnInitFunc = OverrideToolbarButtonOnInitFunc;
addon.GetToolbarButtonByButtonType = GetToolbarButtonByButtonType;


local function MSAASlider_OnValueChanged(self, value, userInput)
    local valueText = "";
    value = tonumber(value);
    if value ~= 0 then
        if value == 1 then
            valueText = "2x";
        elseif value == 2 then
            valueText = "4x";
        elseif value == 3 then
            valueText = "8x";
        end
        valueText = "|cff3fc7eb"..valueText;
    else
        valueText = "|cff808080".."OFF";
    end

    --self:SetLabel("MSAA "..valueText);
    self:SetValueText(valueText);

    if userInput then
        if value ~= 0 then
            ConsoleExec("MSAAQuality "..value..",0");
        else
            ConsoleExec("MSAAQuality 0");
        end
    end
end

local function MSAASlider_GetValue(self)
    local level = tostring(GetCVar("MSAAQuality") or 0);
    local value = tonumber(string.sub(level, 1, 1));
    self.value = nil;

    if not value or value > 3 then
        value = 0;
    end

    self:SetValue(value, false);
end


local function IsValueZero(value)
    return value < 0.0001 and value > -0.0001
end

local function ShoulderSlider_OnValueChanged(self, value, userInput)
    if IsValueZero(value) then
        value = 0;
    end

    --self:SetLabel( string.format("Offset |cff62c497%.1f|r", value) );
    self:SetValueText(string.format("|cff62c497%.1f|r", value));

    if userInput then
        SetCVar("test_cameraOverShoulder", -value);
    end
end

local function ShoulderSlider_GetValue(self)
    local value = tonumber(GetCVar("test_cameraOverShoulder") or 0);
    self:SetValue(-value);
end


local function FoVSlider_OnValueChanged(self, value, userInput)
    --self:SetLabel( string.format("FoV |cff62c497%.1f|r", value) );
    self:SetValueText(string.format("|cff62c497%.1f|r", value));

    if userInput then
        SetCVar("camerafov", value);
    end
end

local function FoVSlider_GetValue(self)
    local value = tonumber(GetCVar("camerafov") or 90);
    self:SetValue(value);
end


local function PitchSlider_OnValueChanged(self, value, userInput)
    if IsValueZero(value) then
        --self:SetLabel("Pitch |cff62c497|r |cff808080OFF|r");
        self:SetValueText("|cff808080OFF|r");
    else
        --self:SetLabel( string.format("Pitch |cff62c497%.1f|r", value) );
        self:SetValueText( string.format("|cff62c497%.1f|r", value) );
    end


    if userInput then
        if IsValueZero(value) then
            SetCVar("test_cameraDynamicPitch", 0);
        else
            SetCVar("test_cameraDynamicPitch", 1);
        end
        SetCVar("test_cameraDynamicPitchBaseFovPad", value);
    end
end

local function PitchSlider_GetValue(self)
    local pitchOn = tonumber(GetCVar("test_cameraDynamicPitch") or 0) >= 1;

    local value;

    if pitchOn then
        value = tonumber(GetCVar("test_cameraDynamicPitchBaseFovPad") or 0);
    else
        value = 0;
    end

    self:SetValue(value);
end


local GetCameraZoom = GetCameraZoom;
local CameraZoomIn = CameraZoomIn;
local CameraZoomOut = CameraZoomOut;

local function ZoomToDistance(value)
    local diff = value - GetCameraZoom();
    if diff > 0 then
        CameraZoomOut(diff);
    elseif diff < 0 then
        CameraZoomIn(-diff);
    end
end

local function ZoomSlider_OnValueChanged(self, value, userInput)
    --self:SetLabel( string.format("Zoom |cff62c497%.1f|r", value) );
    self:SetValueText( string.format("|cff62c497%.1f|r", value) );
    if userInput then
        ZoomToDistance(value);
    end
end

local function ZoomSlider_GetValue(self)
    local value = GetCameraZoom();
    self:SetValue(value);
end

local function ZoomSlider_OnUpdate(self, elapsed)
    if self.isDragging then return end;

    if not self.t then
        self.t = 1;
    end

    self.t = self.t + elapsed;

    if self.t > 0.2 then
        self.t = 0;
    else
        return
    end

    ZoomSlider_GetValue(self);
end


local function PlayCheckSound(self)
	if self.isOn then
		PlaySound(856);     --SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON
	else
		PlaySound(857);     --SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF
	end
end

local function ToolbarButton_OnEnter(self)
    MainFrame:OnEnter();
    MainFrame.Tooltip:ShowTooltip(self);
end

local function ToolbarButton_OnLeave(self)
    MainFrame:OnLeave();
    MainFrame.Tooltip:HideTooltip();
end


NarciScreenshotToolbarButtonMixin = {};

function NarciScreenshotToolbarButtonMixin:OnEnter()
    self.Icon:SetVertexColor(1, 1, 1);
    ToolbarButton_OnEnter(self);
    --self.Highlight.AnimFlash:Play();
end

function NarciScreenshotToolbarButtonMixin:OnLeave()
    --[[
    if self.isOn then
        self.Icon:SetVertexColor(0.9, 0.9, 0.9);
    else
        self.Icon:SetVertexColor(0.72, 0.72, 0.72);
    end
    --]]
    self.Icon:SetVertexColor(0.9, 0.9, 0.9);
    ToolbarButton_OnLeave(self);
end

function NarciScreenshotToolbarButtonMixin:OnClick(mouseButton)
    if self.onClickFunc then
        self.onClickFunc(self);
    end
    self:UpdateIcon();
    if mouseButton then
        PlayCheckSound(self);
    end
end

function NarciScreenshotToolbarButtonMixin:OnMouseDown()
    self.Icon:SetSize(36, 36);
    MainFrame.Tooltip:HideTooltip();
end

function NarciScreenshotToolbarButtonMixin:OnMouseUp()
    self.Icon:SetSize(40, 40);
end

function NarciScreenshotToolbarButtonMixin:UpdateIcon()
    if self.isOn then
        self.Icon:SetTexCoord(0.5, 1, 0, 1);
    else
        self.Icon:SetTexCoord(0, 0.5, 0, 1);
    end

    if self:IsVisible() and self:IsMouseOver() then
        self.Icon:SetVertexColor(1, 1, 1);
    else
        self.Icon:SetVertexColor(0.9, 0.9, 0.9);
    end
end

function NarciScreenshotToolbarButtonMixin:OnDoubleClick()
    --do nothing
end


local TRANSITION_TIME = 0.5;
local AnimExpand = CreateFrame("Frame");

local function AnimExpand_OnUpdate(self, elapsed)
    self.t = self.t + elapsed;

    local offsetX;
    local radian;

    if self.t >= self.duration then
        offsetX = self.toX;
        radian = self.toRadian;
        self:SetScript("OnUpdate", nil);
    else
        offsetX = outSine(self.t, self.fromX, self.toX, self.duration);
        radian = outSine(self.t, self.fromRadian, self.toRadian, self.duration);
    end

    MainFrame.BarLeft:SetPoint("LEFT", offsetX, 0);
    MainFrame.Switch.Ring:SetRotation(radian);

    local diff, alpha;
    for i = 1, 4 do
        diff = ToolbarButtons[i].relativeOffset + offsetX - 40;
        if diff > 20 then
            alpha = 1;
        elseif diff > 4 then
            alpha = diff/20;
        else
            alpha = 0;
        end
        ToolbarButtons[i]:SetAlpha(alpha);
    end
end

function AnimExpand:Start(toX, toRadian, snap)
    local _, _, _, fromX = MainFrame.BarLeft:GetPoint();
    local fullOffset = EXPANDED_BAR_LEFT_OFFSET - COLLAPSED_BAR_LEFT_OFFSET;

    local duration;
    if snap then
        duration = 0;
    else
        duration = TRANSITION_TIME * math.abs(fromX - toX)/fullOffset;
        if duration < 0.15 then
            duration = 0.15;
        end
    end

    self.fromRadian = MainFrame.Switch.Ring:GetRotation();
    self.toRadian = toRadian;
    self.fromX = fromX;
    self.toX = toX;
    self.t = 0;
    self.duration = duration;
    self:SetScript("OnUpdate", AnimExpand_OnUpdate);
end

function AnimExpand:Stop()
    self:SetScript("OnUpdate", nil);
end


local function SubFrame_OnEnter(self)
    MainFrame:OnEnter();
end

local function SubFrame_OnLeave(self)
    MainFrame:OnLeave();
end


local function UpdateAnchor()
    if MainFrame.layout == "Narcissus" then
        local scale = MainFrame.defaultScale or 1;
        if scale >= 1 then
            MainFrame:SetPoint("BOTTOMLEFT", 0, -80);
            MainFrame.adaptivePosition = true;
        else
            MainFrame:SetPoint("BOTTOMLEFT", 0, 0);
            MainFrame.adaptivePosition = nil;
        end
    else
        MainFrame.adaptivePosition = nil;
        MainFrame:SetPoint("BOTTOMLEFT", 0, 0);
    end
end

NarciScreenshotToolbarMixin = {};

function NarciScreenshotToolbarMixin:OnLoad()
    MainFrame = self;

    --Expand Panel Switch
    for methodName, method in pairs(SwitchButtonScripts) do
        self.Switch:SetScript(methodName, method);
    end

    self.TopQualityFrame.MSAASlider.onValueChangedFunc = MSAASlider_OnValueChanged;
    self.TopQualityFrame.MSAASlider.onShowCallback = MSAASlider_GetValue;

    self.CameraSettingFrame.ShoulderSlider.onValueChangedFunc = ShoulderSlider_OnValueChanged;
    self.CameraSettingFrame.ShoulderSlider.onShowCallback = ShoulderSlider_GetValue;

    self.CameraSettingFrame.FoVSlider.onValueChangedFunc = FoVSlider_OnValueChanged;
    self.CameraSettingFrame.FoVSlider.onShowCallback = FoVSlider_GetValue;

    self.CameraSettingFrame.ZoomSlider.onValueChangedFunc = ZoomSlider_OnValueChanged;
    self.CameraSettingFrame.ZoomSlider.onShowCallback = ZoomSlider_GetValue;
    self.CameraSettingFrame.ZoomSlider:SetScript("OnUpdate", ZoomSlider_OnUpdate);
    self.CameraSettingFrame.ZoomSlider.alwaysUpdate = true;

    self.CameraSettingFrame.PitchSlider.onValueChangedFunc = PitchSlider_OnValueChanged;
    self.CameraSettingFrame.PitchSlider.onShowCallback = PitchSlider_GetValue;

    self.CameraSettingFrame:SetScript("OnEnter", SubFrame_OnEnter);
    self.CameraSettingFrame:SetScript("OnLeave", SubFrame_OnLeave);

    --self:SetLayout("Narcissus");
    self:SetLayout("Blizzard");

    self:FadeOut(true);
end

function NarciScreenshotToolbarMixin:SetLayout(layoutName)
    if self.layout == layoutName then
        return
    end

    local layout = Layouts[layoutName];
    if layout then
        self.layout = layoutName;
    else
        return
    end

    local BUTTON_SIZE = 40;
    local SIDE_SIZE = 40;
    local GAP = 2;
    local SIDE_OFFSET = -22;
    local FROM_OFFSET = (layout.showSwitch and EXPANDED_BAR_LEFT_OFFSET) or 0;
    local numButtons = #layout;
    local button;
    for i, buttonKey in ipairs(layout) do
        if not ToolbarButtons[i] then
            ToolbarButtons[i] = CreateFrame("Button", nil, self, "NarciScreenshotToolbarButtonTemplate");
            ToolbarButtons[i].relativeOffset = (i - 1) * (BUTTON_SIZE + GAP);
        end

        button = ToolbarButtons[i];
        button:ClearAllPoints();
        local offsetX = FROM_OFFSET + SIDE_SIZE + SIDE_OFFSET + (i-1)*(BUTTON_SIZE + GAP);
        button:SetPoint("LEFT", self.BarLeft, "LEFT", offsetX - FROM_OFFSET, 0);
        button.relativeOffset = offsetX - FROM_OFFSET;
        button:Show();

        if button.type ~= buttonKey then
            button.type = buttonKey;
            button.Icon:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Modules\\ScreenshotTool\\Icon"..buttonKey);
            button.Icon:SetTexCoord(0, 0.5, 0, 1);
            button:UpdateIcon();
            button.onClickFunc = ToolbarButtonInfo[buttonKey].onClickFunc;
            button.onInitFunc = ToolbarButtonInfo[buttonKey].onInitFunc;
        end

        ToolbarButtons.buttonTypeToButton[buttonKey] = button;
    end

    for i = numButtons + 1, #ToolbarButtons do
        ToolbarButtons[i]:Hide();
        ToolbarButtons[i].type = nil;
        ToolbarButtons[i].onInitFunc = nil;
    end

    local centralWidth = numButtons * (BUTTON_SIZE + GAP) - GAP + 2*SIDE_OFFSET;
    COLLAPSED_BAR_LEFT_OFFSET = -centralWidth + 10;
    self.BarCenter:SetWidth(centralWidth);

    --self.BarLeft:ClearAllPoints();
    --self.BarLeft:SetPoint("LEFT", self, "LEFT", FROM_OFFSET, 0);
    --/run NarciScreenshotToolbar:SetLayout("Narcissus")

    local PADDING_H;

    if layout.showSwitch then
        PADDING_H = 0;
        self.Switch:Show();
        self.BarLeft:SetTexCoord(0.375, 0.5, 0, 0.25);
        self.BarMask:Show();
        if self.expanded then
            self:Expand(true);
        else
            self:Collapse(true);
        end
        self.ExitButton:Show();
        self.PlayerLocationFrame:Hide();

        if self.PreferenceToggle then
            self.PreferenceToggle:Show();
            if self.PreferenceToggle.scriptNotSet then
                self.PreferenceToggle.scriptNotSet = nil;
                self.PreferenceToggle:SetScript("OnEnter", ToolbarButton_OnEnter);
                self.PreferenceToggle:SetScript("OnLeave", ToolbarButton_OnLeave);
                self.PreferenceToggle:SetScript("OnMouseDown", function()
                    MainFrame.Tooltip:HideTooltip();
                end);
            end
        end
    else
        PADDING_H = 16;
        self.Switch:Hide();
        self.BarLeft:SetTexCoord(0.25, 0.375, 0, 0.25);
        self.BarLeft:SetPoint("LEFT", self, "LEFT", PADDING_H, 0);
        self.BarMask:Hide();
        AnimExpand:Stop();
        ToolbarButtons:SetButtonVisiblity(true);
        self.ExitButton:Hide();

        if self.PreferenceToggle then
            self.PreferenceToggle:Hide();
        end
    end

    local fullWidth = centralWidth + 2*SIDE_SIZE + FROM_OFFSET + 2*PADDING_H;
    self:SetWidth(fullWidth);
    self.fullWidth = fullWidth;

    if layout.customScale then
        self:SetScale(layout.customScale);
        self.EmoteFrame.newFontSize = 10;
        self.Tooltip:UseSmallFont(true);
    else
        self:SetScale(self.defaultScale or 1);
        self.EmoteFrame.newFontSize = 12;
        self.Tooltip:UseSmallFont(false);
    end
    UpdateAnchor();

    self.TransmogListFrame:Hide();
    self:StopAnimating();
end

function NarciScreenshotToolbarMixin:Expand(snap)
    self.expanded = true;
    self.Switch.Icon:SetTexCoord(0, 0.1875, 0.4375, 0.625);
    self:SetWidth(self.fullWidth);
    --self.BarLeft:ClearAllPoints();
    --self.BarLeft:SetPoint("LEFT", self, "LEFT", EXPANDED_BAR_LEFT_OFFSET, 0);
    AnimExpand:Start(EXPANDED_BAR_LEFT_OFFSET, 1.25*math.pi, snap);
    ToolbarButtons:EnableButtons();

    if self.showTransmogFrame then
        self.TransmogListFrame:Show();
    end
    if self.showTopQualityFrame then
        self.TopQualityFrame:Show();
    end
end

function NarciScreenshotToolbarMixin:Collapse(snap)
    self.expanded = false;
    self:SetWidth(100);
    --self.BarLeft:ClearAllPoints();
    --self.BarLeft:SetPoint("LEFT", self, "LEFT", COLLAPSED_BAR_LEFT_OFFSET, 0);
    AnimExpand:Start(COLLAPSED_BAR_LEFT_OFFSET, 0, snap);
    ToolbarButtons:DisableButtons();

    self.TransmogListFrame:Hide();
    self.TopQualityFrame:Hide();
end


local AnimFade = CreateFrame("Frame");

local function AnimFadeIn_OnUpdate(self, elapsed)
    self.alpha = self.alpha + elapsed * 4;
    if self.alpha >= 1 then
        self:SetScript("OnUpdate", nil);
        self.alpha = 1;
    end
    MainFrame:SetAlpha(self.alpha);
end

local function AnimFadeOut_OnUpdate(self, elapsed)
    self.alpha = self.alpha - elapsed * 4;
    if self.alpha <= 0 then
        self:SetScript("OnUpdate", nil);
        self.alpha = 0;
    end
    MainFrame:SetAlpha(self.alpha);
end

function AnimFade:FadeTo(toAlpha)
    if self:IsShown() and (toAlpha == self.toAlpha) then
        return
    else
        self.toAlpha = toAlpha;
    end

    self.alpha = MainFrame:GetAlpha();

    if self.alpha == toAlpha then
        return
    else
        if toAlpha > self.alpha then
            self:SetScript("OnUpdate", AnimFadeIn_OnUpdate);
        else
            self:SetScript("OnUpdate", AnimFadeOut_OnUpdate);
        end
    end

    if MainFrame.adaptivePosition then
        MainFrame.FlyDown:Stop();
        MainFrame.FlyUp:Stop();
        if toAlpha == 1 then
            MainFrame.FlyUp:Play();
        elseif toAlpha == 0 then
            MainFrame.EmoteFrame:Hide();
            MainFrame.FlyDown:Play();
        end
    end
end

function AnimFade:Stop()
    self:SetScript("OnUpdate", nil);
end


function NarciScreenshotToolbarMixin:FadeIn()
    AnimFade:FadeTo(1);
    if self.adaptivePosition then
        self:SetPoint("BOTTOMLEFT", 0, 0);
    end
end

function NarciScreenshotToolbarMixin:FadeOut(instant)
    if instant then
        AnimFade:Stop();
        AnimFade.toAlpha = 0;
        self:SetAlpha(0);
    else
        AnimFade:FadeTo(0);
    end

    if self.adaptivePosition then
        self:SetPoint("BOTTOMLEFT", 0, -80);
    end
end

function NarciScreenshotToolbarMixin:OnEnter()
    self:FadeIn();
end

function NarciScreenshotToolbarMixin:OnLeave()
    if not self:IsFocused() then
        self:FadeOut();
    end
end

local function IsFrameFocused(frame)
    return frame and frame:IsShown() and frame:IsMouseOver()
end

function NarciScreenshotToolbarMixin:IsFocused()
    return self:IsMouseOver() or self.EmoteFrame:IsFocused() or self.CameraSettingFrame:IsFocused() or self.TransmogListFrame:IsFocused() or IsFrameFocused(self.PreferenceToggle);
end

function NarciScreenshotToolbarMixin:OnShow()
    self:RegisterEvent("PLAYER_LOGOUT");
    self:RegisterEvent("PLAYER_QUITING");
    self:RegisterEvent("PLAYER_CAMPING");
    self:RegisterEvent("SCREENSHOT_STARTED");
end

function NarciScreenshotToolbarMixin:OnHide()
    self:UnregisterEvent("PLAYER_LOGOUT");
    self:UnregisterEvent("PLAYER_QUITING");
    self:UnregisterEvent("PLAYER_CAMPING");
    self:UnregisterEvent("SCREENSHOT_STARTED");
    self:FadeOut(true);
    self:UseLowerLevel(false);
end

function NarciScreenshotToolbarMixin:OnEvent(event, ...)
    if event == "SCREENSHOT_STARTED" then
        self:FadeOut(true);
    else
        self:OnExit();
    end
end

function NarciScreenshotToolbarMixin:OnExit()
    CVarUtil:RestoreAll();
    ToolbarButtons:ResetButtonState();

    --Hide Subframes
    self.CameraSettingFrame:Hide();
    self.EmoteFrame:Hide();
    self.TopQualityFrame:Hide();
    self.TransmogListFrame:Hide();
end

function NarciScreenshotToolbarMixin:ShowUI(layoutName)
    CVarUtil:SetCameraStatus(true); --save camera cvars

    self:SetLayout(layoutName);
    self:Show();
    ToolbarButtons:RunShowButtonCallback();
end

function NarciScreenshotToolbarMixin:HideUI()
    self:OnExit();
    self:Hide();

    self.showTransmogFrame = nil;
    self.showTopQualityFrame = nil;
end

function NarciScreenshotToolbarMixin:UseLowerLevel(state)
	local strata;
    local level = 10;
	if state then
		strata = "BACKGROUND";
		self:SetAlpha(0);
        AnimFade.toAlpha = 0;
	else
		strata = "HIGH";
		if self:IsFocused() then
            self:SetAlpha(1);
            AnimFade.toAlpha = 1;
        end
	end
	self:SetFrameStrata(strata);
    self:SetFrameLevel(level);
    self.Switch:SetFrameLevel(level + 4);
	self.MotionBlock:SetFrameStrata("BACKGROUND");
	self.MotionBlock:SetFrameLevel(level + 6);
	self.MotionBlock:SetShown(state);
end


function NarciScreenshotToolbarMixin:SetDefaultScale(scale)
    --set scale to 1 when in screenshot mode (Alt+Z)
    self.defaultScale = scale;
    if self.layout == "Narcissus" then
        self:SetScale(scale);
        if scale >= 1 then
            self.adaptivePosition = true;
        else
            self.adaptivePosition = nil;
        end
    else
        self:SetScale(1);
        self:SetPoint("BOTTOMLEFT", 0, 0);
        self.adaptivePosition = nil;
    end
    UpdateAnchor();
end



NarciRayTracingToggleMixin = {};

function NarciRayTracingToggleMixin:SetVisual(level)
	if level and level > 0 then
		self.ButtonText:SetText("RTX ".. level);
        self.ButtonText:SetTextColor(1, 1, 1);
        self.Background:SetTexCoord(0.5, 0.8125, 0.25, 0.375);
	else
		self.ButtonText:SetText("RTX |cff808080OFF|r");
        self.ButtonText:SetTextColor(0.8, 0.8, 0.8);
        self.Background:SetTexCoord(0.5, 0.8125, 0.375, 0.5);
	end
end

function NarciRayTracingToggleMixin:SetLevel(level)
	level = tonumber(level) or 0;
	if level > 3 then
		level = 3;
	end
	self:SetVisual(level);
	SetCVar("shadowrt", level);
end

function NarciRayTracingToggleMixin:Restore()
	if self.oldValue then
		self:SetLevel(self.oldValue);
	end
end

function NarciRayTracingToggleMixin:OnClick()
	self.isOn = not self.isOn;
	if self.isOn then
		self:SetLevel(3);
	else
		self:Restore();
	end
end

function NarciRayTracingToggleMixin:OnShow()
	local level = tonumber(GetCVar("shadowrt"));
	self.oldValue = level;
	self:SetVisual(level);
end

function NarciRayTracingToggleMixin:OnHide()
	self.isOn = false;
end

function NarciRayTracingToggleMixin:OnEnter()
	self.Background:SetVertexColor(1, 1, 1);
end

function NarciRayTracingToggleMixin:OnLeave()
	self.Background:SetVertexColor(0.72, 0.72, 0.72);
end

function NarciRayTracingToggleMixin:OnLoad()
	local validity = addon.TransitionAPI.IsRTXSupported();

    self:OnLeave();

	self.isValid = validity;
	if not validity then
		self:Hide();
		self:Disable();
        self:GetParent():SetWidth(134);
        --
	end

	self:SetScript("OnLoad", nil);
	self.OnLoad = nil;
end



do
    local SettingFunctions = addon.SettingFunctions;

    function SettingFunctions.UseEscapeKeyForExit(state, db)
        if state == nil then
            state = db["UseEscapeButton"];
        end

        if state then
            MainFrame.KeyListener.escapeKey = "ESCAPE";
        else
            MainFrame.KeyListener.escapeKey = "HELLOWORLD";
        end
    end
end
--[[
--Doolly Zoom:
--d = width / (2*math.tan(0.5 * fov))    --fov ~ degree
--fov = 2*math.atan(0.5 * width / d)

local atan = math.atan;
local deg = math.deg;

function GetFoVByZoomDistance(distance, width)
    local fov = deg(2*atan(0.5 * width / distance));

    if fov > 90 then
        fov = 90;
    elseif fov < 50 then
        fov = 50;
    end

    return fov
end

local UpdateFrame = CreateFrame("Frame");

UpdateFrame:SetScript("OnUpdate", function(self, elapsed)
    local d = GetCameraZoom();
    if d ~= self.distance then
        self.distance = d;
        local fov = GetFoVByZoomDistance(d, 12);  --4
        print(string.format("%.2f  %.2f", d, fov));
        SetCVar("camerafov", fov);
    end
end);
--]]


