local UPDATE_PORTRAIT_DELAY = 0.2;


local _, addon = ...

local API = addon.API;
local StatManager = addon.StatManager;
local HotkeyManager = addon.HotkeyManager;

local FadeFrame = NarciFadeUI.Fade;
local TransitionAPI = NarciAPI.TransitionAPI;

local C_BarberShop = C_BarberShop;
local SetCustomizationChoice = C_BarberShop.SetCustomizationChoice;

local After = C_Timer.After;
local sin = math.sin;
local cos = math.cos;
local pi = math.pi;
local sqrt = math.sqrt;
local abs = math.abs;
local tremove = table.remove;
local tinsert = table.insert;
local unpack = unpack;
local wipe = wipe;

local IsMouseButtonDown = IsMouseButtonDown;
local GetMouseFocus = GetMouseFocus;
local PlaySound = PlaySound;

local function IsWidgetFocused(widget1, widget2)
    local focus = GetMouseFocus();
    if widget2 then
        return focus == widget1 or focus == widget2
    elseif widget1 then
        return focus == widget1
    end
end

local function linear(t, b, e, d)
	return (e - b) * t / d + b
end

local function outSine(t, b, e, d)
	return (e - b) * sin(t / d * (pi / 2)) + b
end

local function inOutSine(t, b, e, d)
	return (b - e) / 2 * (cos(pi * t / d) - 1) + b
end


local L = Narci.L;
-----------------------------------------------

local MainFrame, MaleButtons, FemaleButtons, AlternateMaleButtons, AlternateFemaleButtons, EditButton, EditBox, DeleteButton, PlusButton, SettingFrame, SettingButton, LoadingFrame;
local BarbershopModel, ExportEditBox;
local ACTIVE_BUTTON_POOL;

--[[
NarciBarberShopModelMixin = {};

local CameraUpdater = CreateFrame("Frame");

function CameraUpdater:ZoomTo(value)
    BarbershopModel:MakeCurrentCameraCustom();
    local currentDistance = BarbershopModel:GetCameraDistance();
    self.zoomDistance = value
    BarbershopModel:SetCameraDistance(value);
end

function NarciBarberShopModelMixin:OnLoad()
    self:SetUnit("player")
    self:SetLight(true, false, -0.707, 0, -0.707, 1, 1, 1, 1, 0.5, 1, 1, 1);
    self:SetCamera(0);
    self:SetPortraitZoom(0.9);
    self:SetAnimation(0, 0);
    self:SetPaused(true);
    self:SetKeepModelOnHide(true);
    
    local ScreenHeight = UIParent:GetHeight();
    self:SetSize(ScreenHeight, ScreenHeight)
    BarbershopModel = self;
    self:SetViewTranslation(0, -80);
    self:SetPosition(-0.5, 0.2, 0);
end

function NarciBarberShopModelMixin:OnShow()
    
end

function NarciBarberShopModelMixin:OnModelLoaded()
    self:SetPaused(true)
end

function NarciBarberShopModelMixin:ZoomTo(value)
    CameraUpdater:ZoomTo(value)
end
--]]



----------------------------------------------------------------------------------------------------
local CAMERA_PROFILES_BY_RACE = {
    --modelX, modelY, modelZ, facing
    bloodelf = {
        male = {3.38, 0.07, -1.88, 0.43},
        female = {3.54, -0.02, -1.75, 0.35},
    },

    voidelf = {
        male = {3.38, 0.07, -1.88, 0.43},
        female = {3.54, -0.02, -1.75, 0.35},
    },

    draenei = {
        male = {2.94, -0.19, -2.24, 0.43},
        female = {3.40, -0.09, -2.14, 0.35},
    },

    lightforgeddraenei = {
        male = {2.94, -0.19, -2.24, 0.43},
        female = {3.40, -0.09, -2.14, 0.35},
    },

    dwarf = {
        male = {3.24, -0.08, -1.34, 0.44},
        female = {3.43, -0.02, -1.34, 0.44}, 
    },

    darkirondwarf = {
        male = {3.24, -0.08, -1.34, 0.44},
        female = {3.43, -0.02, -1.34, 0.44}, 
    },

    gnome = {
        male = {3.23, -0.01, -0.92, 0.43},
        female = {3.37, -0.07, -0.9, 0.43},
    },

    mechagnome = {
        male = {3.23, -0.05, -0.93, 0.43},
        female = {3.37, -0.07, -0.9, 0.43},
    },

    goblin = {
        male = {3.24, 0, -1.08, 0.43},
        female = {3.40, -0.03, -1.14, 0.43},
    },

    human = {
        male = {3.38, -0.04, -1.87, 0.35},
        female = {3.51, -0.025, -1.745, 0.43},
    },

    kultiran = {
        male = {3.06, -0.05, -2.32, 0.43},
        female = {3.34, -0.01, -2.21, 0.43},
    },

    nightborne = {
        male = {3.37, 0.01, -2.26, 0.35},
        female = {3.40, 0.05, -2.09, 0.43},
    },

    nightelf = {
        male = {3.26, -0.07, -2.21, 0.43},
        female = {3.38, -0.06, -2.09, 0.43},
    },

    orc = {
        male = {2.79, -0.06, -1.84, 0.35},
        female = {3.59, -0.02, -1.87, 0.35},
    },

    uprightorc = {
        male = {3.11, -0.02, -2.08, 0.35},
        female = {3.59, -0.02, -1.87, 0.35},
    },

    magharorc = {
        male = {3.11, -0.02, -2.08, 0.35},
        female = {3.59, -0.02, -1.87, 0.35},
    },

    pandaren = {
        male = {3.14, -0.11, -2.11, 0.43},
        female = {3.1, -0.16, -1.95, 0.43},
    },

    tauren = {
        male = {1.97, -0.35, -2.2, 0.43},
        female = {2.86, -0.35, -2.4, 0.52},
    },

    highmountaintauren = {
        male = {1.97, -0.35, -2.2, 0.43},
        female = {2.86, -0.35, -2.4, 0.52},
    },

    troll = {
        male = {2.79, -0.19, -2.01, 0.43},
        female = {3.43, 0.035, -2.19, 0.35},
    },

    scourge = {
        male = {3.37, 0.03, -1.6, 0.43},
        female = {3.39, -0.035, -1.665, 0.43},
    },

    vulpera = {
        male = {3.33, -0.05, -1.09, 0.26},
        female = {3.33, -0.04, -1.09, 0.26},
    },

    worgen = {
        male = {2.41, -0.18, -1.93, 0.43},
        female = {3.06, -0.04, -2.19, 0.52},
    },

    zandalaritroll = {
        male = {3.16, 0.05, -2.55, 0.52},
        female = {3.35, 0.04, -2.51, 0.44},
    },

    dracthyr = {
        male = {3.1, -0.58, -2.6, 0.52},
        female = {3.1, -0.58, -2.6, 0.52},
    },
};


local HAS_ALTERNATE_FORM, IN_ALTERNATE_FORM = false, false;
local CAMEAR_KEY_TRUE_FORM;
local CAMEAR_KEY_ALTERNATE_FORM;

local RACE_WITH_ALTERNATE_FORM = {
    [22] = "human",       --worgen
    [52] = "bloodelf",    --dracthyr
    [70] = "bloodelf",    --dracthyr
};

do
    local _, _, raceID = UnitRace("player");
    if RACE_WITH_ALTERNATE_FORM[raceID] then
        CAMEAR_KEY_ALTERNATE_FORM = RACE_WITH_ALTERNATE_FORM[raceID];
        if raceID == 22 then
            CAMEAR_KEY_TRUE_FORM = "worgen"
        else
            CAMEAR_KEY_TRUE_FORM = "dracthyr"
        end
    end
end


local ALTERNATE_FORM_SAVED_ID = 220;   --custom
local MAX_SAVES = 10;
local NUM_ACTIVE_BUTTONS = 0;
local CAMERA_PROFILE;
local ACTIVE_CAMERA_PROFILE;
local SCROLLFRAME_CENTER_Y;

local function UpdateScrollButtonAlpha(buttons)
    local button;
    local alpha;
    local x, y, dy;

    for i = 1, NUM_ACTIVE_BUTTONS do
        button = buttons[i];
        x, y = button:GetCenter();
        dy = abs(y - SCROLLFRAME_CENTER_Y);
        if dy < 128 then
            alpha = 1;
            button:Show();
        else
            alpha = 1 - (dy - 128)/64;
            if alpha < 0 then
                alpha = 0;
                button:Hide();
            else
                button:Show();
            end
        end
        button:SetButtonAlpha(alpha);
    end
end

local ScrollButtonAlphaUpdater = CreateFrame("Frame");
ScrollButtonAlphaUpdater:Hide();
ScrollButtonAlphaUpdater.t = 0;
ScrollButtonAlphaUpdater.duration = 1/60;
ScrollButtonAlphaUpdater:SetScript("OnUpdate", function(self, elapsed)
    self.t = self.t + elapsed;
    if self.t >= self.duration then
        self.t = 0;
        UpdateScrollButtonAlpha(self.activeButtons);
    end
end);

function ScrollButtonAlphaUpdater:Start()
    self:Show();
end

function ScrollButtonAlphaUpdater:Stop()
    self:Hide();
end

function ScrollButtonAlphaUpdater:Refresh()
    UpdateScrollButtonAlpha(self.activeButtons);
end

local ScrollBoundMarkUpdater = CreateFrame("Frame");
ScrollBoundMarkUpdater:Hide();
ScrollBoundMarkUpdater.t = 0;
ScrollBoundMarkUpdater.duration = 0.2;
ScrollBoundMarkUpdater.lastAlpha = 0;
ScrollBoundMarkUpdater:SetScript("OnUpdate", function(self, elapsed)
    local alpha = self.lastAlpha + 2 * elapsed;
    self.lastAlpha = alpha;
    if alpha >= self.toAlpha then
        alpha = self.toAlpha;
        self:Hide();
    end
    self.object1:SetAlpha(alpha);
    self.object2:SetAlpha(alpha);
end);

function ScrollBoundMarkUpdater:Start()
    self.lastAlpha = 0; --self.object1:GetAlpha();
    self.toAlpha = 0.5;
    self:Show();
end

function ScrollBoundMarkUpdater:Stop()
    self:Hide();
end

local function UpdateScrollBoundMark(ScrollFrame)
    --local ScrollFrame = MainFrame.activeCategory;
    local scrollValue = ScrollFrame.scrollBar:GetValue();
    local a, b = ScrollFrame.scrollBar:GetMinMaxValues();
    local isTop = scrollValue <= 0.1;
    local isBottom = scrollValue + 0.1 >= b;
    local numButtons = NUM_ACTIVE_BUTTONS or 0;
    ScrollFrame.BoundTop:SetShown(not isTop);
    ScrollFrame.BoundBottom:SetShown(not isBottom);
    
    ScrollFrame.BoundTop:SetAlpha(0);
    ScrollFrame.BoundBottom:SetAlpha(0);
    
    ScrollBoundMarkUpdater:Start();
end

local function UpdateScrollRange(ScrollFrame)
    if not ScrollFrame then
        ScrollFrame = MainFrame.activeCategory;
    end

    local range;
    local numButtons = NUM_ACTIVE_BUTTONS;
    if numButtons == 0 then
        range = 0;
    else
        range = (64 + 16) * numButtons -16 - ScrollFrame:GetHeight() + 14;   --the active sex is not neccessarily male, just use the male buttons for height referencing
        if range < 0 then
            range = 0;
        end
    end

    local scrollBar = ScrollFrame.scrollBar;
    scrollBar:SetMinMaxValues(0, range);
    ScrollFrame.range = range;
    scrollBar:SetShown(range ~= 0);

    UpdateScrollButtonAlpha(ScrollButtonAlphaUpdater.activeButtons);
    UpdateScrollBoundMark(ScrollFrame);

    if numButtons >= MAX_SAVES then
        PlusButton:SetCase(3);
    end
    PlusButton.numSaves = numButtons;
end

local function CreateSavedLooksButton(ScrollFrame, sex, isAlternateForm)
    local button;
    local buttons = {};
    local ScrollChild = ScrollFrame.ScrollChild;
    local buttonHeight = 64;
    local frameHeight = 4 * (buttonHeight + 16) - 2;
    ScrollFrame:SetSize(280, frameHeight);
    ScrollChild:SetSize(280, frameHeight);

    for i = 1, MAX_SAVES do
        button = CreateFrame("Button", nil, ScrollFrame.ClipFrame, "NarciBarberShopSavedLooksTemplate");
        tinsert(buttons, button);
        button:SetPoint("TOPLEFT", ScrollChild, "TOPLEFT", 0, -8 + (buttonHeight + 16)*(1 - i));
        button.order = i;
        button:Hide();
    end

    local deltaRatio = 1;
    local speedRatio = 0.14;
    local range = buttons[1]:GetTop() - buttons[MAX_SAVES]:GetBottom() - ScrollFrame:GetHeight() + 14;
    local parentScrollFunc;
    local positionFunc = function(endValue, delta, scrollBar, isTop, isBottom)
        ScrollButtonAlphaUpdater:Start();
        ScrollFrame.BoundTop:SetShown(not isTop);
        ScrollFrame.BoundBottom:SetShown(not isBottom);
        ScrollBoundMarkUpdater:Stop();
        ScrollFrame.BoundTop:SetAlpha(0.0);
        ScrollFrame.BoundBottom:SetAlpha(0.0);
        ScrollFrame.BoundTop:StopAnimating();
        ScrollFrame.BoundBottom:StopAnimating();
    end;

    local onScrollFinishedFunc = function()
        ScrollButtonAlphaUpdater:Stop();
        ScrollBoundMarkUpdater:Start();
        ScrollFrame.BoundTop.BoundTopArrow.spring:Play();
        ScrollFrame.BoundBottom.BoundBottomArrow.spring:Play();
    end

    ScrollBoundMarkUpdater.object1 = ScrollFrame.BoundTop;
    ScrollBoundMarkUpdater.object2 = ScrollFrame.BoundBottom;

    NarciAPI_ApplySmoothScrollToScrollFrame(ScrollFrame, deltaRatio, speedRatio, positionFunc, (buttonHeight + 16), range, parentScrollFunc, onScrollFinishedFunc);


    if isAlternateForm then
        if sex == "male" then
            AlternateMaleButtons = buttons;
        else
            AlternateFemaleButtons = buttons;
        end
    else
        if sex == "male" then
            MaleButtons = buttons;
        else
            FemaleButtons = buttons;
        end
    end
end

local function GetOrcCameraProfile(model)
    local fileID = model:GetModelFileID();
    if fileID == 1968587 then
        --Upright
        return CAMERA_PROFILES_BY_RACE.uprightorc.male
    elseif fileID == 917116 then
        --Regular Orc and Maghar: 917116
        return CAMERA_PROFILES_BY_RACE.orc.male
    else
        return ACTIVE_CAMERA_PROFILE
    end
end

local function GetTrueFormCameraProfile(model)
    if IN_ALTERNATE_FORM then
        print(CAMEAR_KEY_ALTERNATE_FORM)
        return CAMERA_PROFILES_BY_RACE[CAMEAR_KEY_ALTERNATE_FORM].male
    else
        --[[
        local fileID = model:GetModelFileID();
        if fileID == 307453 then
            return CAMERA_PROFILES_BY_RACE.worgen.female
        elseif fileID == 307454 then
            return CAMERA_PROFILES_BY_RACE.worgen.male
        elseif fileID == 1000764 then
            return CAMERA_PROFILES_BY_RACE.human.female
        else
            return CAMERA_PROFILES_BY_RACE.human.male
        end--]]
        print(CAMEAR_KEY_TRUE_FORM)
        return CAMERA_PROFILES_BY_RACE[CAMEAR_KEY_TRUE_FORM].male
    end
end

local function UpdatePortraitCameraGeneric(model, profile)
    if not profile then
        profile = ACTIVE_CAMERA_PROFILE;
    end
    if not profile then return end;

    local modelX, modelY, modelZ, modelFacing = unpack(profile);
    if modelFacing then
        model:MakeCurrentCameraCustom();
        model:SetFacing(modelFacing);
        model:SetPosition(modelX, modelY, modelZ);
        local cameraX, cameraY, cameraZ = TransitionAPI.TransformCameraSpaceToModelSpace(model, 4, 0, 0);
        local targetX, targetY, targetZ = TransitionAPI.TransformCameraSpaceToModelSpace(model, 0, 0, 0);
        TransitionAPI.SetCameraPosition(model, cameraX, cameraY, cameraZ);
        TransitionAPI.SetCameraTarget(model, targetX, targetY, targetZ);
        return true
    end
end

local UpdatePortraitCamera = UpdatePortraitCameraGeneric;

local function UpdatePortraitCameraForOrc(model)
    UpdatePortraitCameraGeneric(model, GetOrcCameraProfile(model))
end

local function UpdatePortraitCameraForWorgen(model)
    UpdatePortraitCameraGeneric(model, GetTrueFormCameraProfile(model))
end



local DataProvider = {};
DataProvider.numMales = 0;
DataProvider.numFemales = 0;
DataProvider.numAlternateMales = 0;
DataProvider.numAlternateFemales = 0;
DataProvider.maleButtonOrder = {};
DataProvider.femaleButtonOrder = {};
DataProvider.alternateMaleButtonOrder = {};
DataProvider.alternateFemaleButtonOrder = {};

function DataProvider:GetCurrentSelection(customizationData, generateDescription)
    customizationData = customizationData or C_BarberShop.GetAvailableCustomizations();
    if not customizationData then
        return
    end
    local numCatetroy = #customizationData;
    local options, optionName, optionID, cuurentChoiceIndex, choice, choiceName, choiceID;
    local selectedOptions = {};
    local description = "";
    local addComma = false;

    local total = 0;

    for i = 1, numCatetroy do
        options = customizationData[i].options;
        local numOptions = #options;
        for j = 1, numOptions do
            optionName = options[j].name;
            optionID = options[j].id;
            cuurentChoiceIndex = options[j].currentChoiceIndex or 1;
            choice = options[j].choices[cuurentChoiceIndex];
            choiceName = choice.name or "";
            choiceID = choice.id;
            tinsert(selectedOptions, {optionID, choiceID} );
            total = total + 1;
            if generateDescription then
                if choiceName ~= "" and choiceName ~= "None" then
                    if addComma then
                        description = description..", ";
                    else
                        addComma = true;
                    end
                    description = description.. choiceName.." "..optionName;
                end
            end
        end
    end

    return selectedOptions, description
end

--sex 0 - Male 1- Female C_BarberShop.SetSelectedSex;

local RaceAtlas = {};

RaceAtlas.fixedRaceAtlasNames = {
    ["highmountaintauren"] = "highmountain",
    ["lightforgeddraenei"] = "lightforged",
    ["scourge"] = "undead",
    ["zandalaritroll"] = "zandalari",
};

RaceAtlas.alternateFormAtlasNames = {
    ["dracthyr"] = "dracthyrvisage",    --visage
    ["worgen"] = "human",
};

function RaceAtlas:GetAtlas(raceName, gender, useHiRez, alternateForm)
    if alternateForm and self.alternateFormAtlasNames[raceName] then
        raceName = self.alternateFormAtlasNames[raceName];
    end

    if (self.fixedRaceAtlasNames[raceName]) then
        raceName = self.fixedRaceAtlasNames[raceName];
    end

    local formatingString = useHiRez and "raceicon128-%s-%s" or "raceicon-%s-%s";
    return formatingString:format(raceName, gender);
end

--[[
local f = CreateFrame("Frame");
f:SetSize(96, 96);
f:SetPoint("CENTER", 0, 0);
local tex = f:CreateTexture(nil, "OVERLAY");
tex:SetAllPoints(true);
tex:SetAtlas( RaceAtlas:GetAtlas("dracthyr", "male", true, true) )
--]]


local function SetUpSavedLooksButton(buttonPool, dataSource, atlas)
    for i = 1, #dataSource do
        if i <= 4 then
            buttonPool[i]:Show();
        end
        buttonPool[i]:SetInfo(dataSource[i]);
        buttonPool[i].Portrait:SetAtlas(atlas);
    end
end

function DataProvider:LoadData()
    local raceID = API.GetPlayerRaceID();
    self.raceID = raceID or 1;

    if not NarciBarberShopDB then
        NarciBarberShopDB = {};
    end

    local DB = NarciBarberShopDB;

    --wipe(DB) --!!TEST
    
    if not DB.PlayerData then
        DB.PlayerData = {};
    end

    local unitType, realmID, playerID = string.split("-", UnitGUID("player"));
    if not DB.PlayerData[playerID] then
        local playerName = UnitName("player");
        local realmName = GetRealmName();
        DB.PlayerData[playerID] = { SavedLooks = {} , realmID = realmID, playerName = playerName, realmName = realmName};
    end

    if not DB.PlayerData[playerID].SavedLooks[raceID] then
        DB.PlayerData[playerID].SavedLooks[raceID] = {male = {}, female = {}};
    end
    self.savedLooksByRace = DB.PlayerData[playerID].SavedLooks[raceID];

    local currentCharacterData = C_BarberShop.GetCurrentCharacterData();
    local raceName;
    if currentCharacterData then
        raceName = currentCharacterData.raceData.fileName;
    else
        raceName = "human";
    end

    raceName = string.lower(raceName);

    CreateSavedLooksButton(MainFrame.SavedLooksFrame.CategoryMale, "male");
    CreateSavedLooksButton(MainFrame.SavedLooksFrame.CategoryFemale, "female");
    
    --Worgen in human form. Dracthyr in visage form.
    if RACE_WITH_ALTERNATE_FORM[raceID] then
        HAS_ALTERNATE_FORM = true;
        local id = ALTERNATE_FORM_SAVED_ID;

        if not DB.PlayerData[playerID].SavedLooks[id] then
            DB.PlayerData[playerID].SavedLooks[id] = { male = {}, female = {} };
        end
        self.savedLooksInAlternateForm = DB.PlayerData[playerID].SavedLooks[id];
        CreateSavedLooksButton(MainFrame.SavedLooksFrame.CategoryAlternateFormMale, "male", true);
        CreateSavedLooksButton(MainFrame.SavedLooksFrame.CategoryAlternateFormFemale, "female", true);

        local alternateMaleDB = DB.PlayerData[playerID].SavedLooks[id].male;
        local alternateFemaleDB = DB.PlayerData[playerID].SavedLooks[id].female;
        local numMales =  #alternateMaleDB;
        local numFemales = #alternateFemaleDB;
        self.numAlternateMales = numMales;
        self.numAlternateFemales = numFemales;
        local useHiRez = true;
        local raceAtlasMale = RaceAtlas:GetAtlas(raceName, "male", useHiRez, HAS_ALTERNATE_FORM);
        local raceAtlasFemale = RaceAtlas:GetAtlas(raceName, "female", useHiRez, HAS_ALTERNATE_FORM);
        --NarciDevReferencePortrait:SetAtlas(raceAtlasFemale);
        SetUpSavedLooksButton(AlternateMaleButtons, alternateMaleDB, raceAtlasMale);
        SetUpSavedLooksButton(AlternateFemaleButtons, alternateFemaleDB, raceAtlasFemale);
    end

    if CAMERA_PROFILES_BY_RACE[raceName] then
        CAMERA_PROFILE = CAMERA_PROFILES_BY_RACE[raceName];
    else
        CAMERA_PROFILE = CAMERA_PROFILES_BY_RACE["human"];
    end

    local useHiRez = true;
    
    local raceAtlasMale = RaceAtlas:GetAtlas(raceName, "male", useHiRez);
    local raceAtlasFemale = RaceAtlas:GetAtlas(raceName, "female", useHiRez);

    if raceName == "orc" or raceName == "magharorc" then
        UpdatePortraitCamera = UpdatePortraitCameraForOrc;
    end

    self.raceAtlasMale = raceAtlasMale;
    self.raceAtlasFemale = raceAtlasFemale;

    local maleDB = DB.PlayerData[playerID].SavedLooks[raceID].male;
    local femaleDB = DB.PlayerData[playerID].SavedLooks[raceID].female;
    local numMales =  #maleDB;
    local numFemales = #femaleDB;

    self.numMales = numMales;
    self.numFemales = numFemales;
    
    for i = 1, MAX_SAVES do
        self.maleButtonOrder[i] = i; 
        self.femaleButtonOrder[i] = i; 
        self.alternateMaleButtonOrder[i] = i;
        self.alternateFemaleButtonOrder[i] = i;
    end

    SetUpSavedLooksButton(MaleButtons, maleDB, raceAtlasMale);
    SetUpSavedLooksButton(FemaleButtons, femaleDB, raceAtlasFemale);
end

function DataProvider:GetRandomAppearance()
    local customizationData = C_BarberShop.GetAvailableCustomizations();
    if not customizationData then    --Not at the Barber
        return
    end

    local random = math.random;
    local selectedOptions = {};

    local option, options, optionName, optionID, cuurentChoiceIndex, choice, choiceName, choiceID;
    local numCatetroy = #customizationData;
    for i = 1, numCatetroy do
        options = customizationData[i].options;
        local numOptions = #options;
        for j = 1, numOptions do
            option = options[j];
            optionName = option.name;
            optionID = option.id;
            local numChoices = #option.choices;
            choice = option.choices[ random(numChoices) ]
            choiceID = choice.id;
            tinsert(selectedOptions, {optionID, choiceID} );
        end
    end

    return selectedOptions
end

function DataProvider:IsNewLooksUnique()
    local newLooks, generatedDescription = DataProvider:GetCurrentSelection();
    if not newLooks then
        return
    end
    local checkOnly = true;
    local isUnique = self:CheckAndSaveLooks(newLooks, nil, nil, checkOnly);
    return isUnique
end

function DataProvider:IsCharacterDataUnique(customizationData)
    local sex = self.currentSex or 0;
    local SavedLooks;
    local newLooks = self:GetCurrentSelection(customizationData);
    
    if HAS_ALTERNATE_FORM then
        if IN_ALTERNATE_FORM then
            if sex == 0 then
                SavedLooks = self.savedLooksInAlternateForm.male;
            else
                SavedLooks = self.savedLooksInAlternateForm.female;
            end
        else
            if sex == 0 then
                SavedLooks = self.savedLooksByRace.male;
            else
                SavedLooks = self.savedLooksByRace.female;
            end
        end
    else
        if sex == 0 then
            SavedLooks = self.savedLooksByRace.male;
        else
            SavedLooks = self.savedLooksByRace.female;
        end
    end

    local isUnique = true;
    local tempTable = {};
    local numLooks = #SavedLooks;
    local data;
    local matchID;
    local profileName;

    for i = 1, numLooks do
        wipe(tempTable);
        data = SavedLooks[i].data;
        local numData = #data;
        for j = 1, numData do
            tempTable[ data[j][1] ] = data[j][2];
        end

        local numSame = 0;
        for j = 1, #newLooks do
            if tempTable[ newLooks[j][1] ] == newLooks[j][2] then
                numSame = numSame + 1;
            end
        end

        if numSame == numData then
            isUnique = false;
            matchID = i;
            profileName = SavedLooks[i].name;
            break
        end
    end

    if isUnique then
        if numLooks < MAX_SAVES then
            PlusButton:SetCase(1);
        else
            PlusButton:SetCase(3);
        end
    else
        PlusButton:SetCase(2);
    end
    PlusButton.numSaves = numLooks;

    if ACTIVE_BUTTON_POOL then
        for i, portraitButton in ipairs(ACTIVE_BUTTON_POOL) do
            portraitButton:SetSelection(i == matchID);
        end
    end

    API.SetActiveAppearanceName(profileName);
end


function DataProvider:CheckAndSaveLooks(newLooks, generatedDescription, customName, checkOnly)
    if not newLooks then return false end;

    local currentCharacterData = C_BarberShop.GetCurrentCharacterData();
    if not currentCharacterData then return false end;
    
    local sex = currentCharacterData.sex;

    local SavedLooks;

    if HAS_ALTERNATE_FORM then
        if IN_ALTERNATE_FORM then
            if sex == 0 then
                SavedLooks = self.savedLooksInAlternateForm.male;
            else
                SavedLooks = self.savedLooksInAlternateForm.female;
            end
        else
            if sex == 0 then
                SavedLooks = self.savedLooksByRace.male;
            else
                SavedLooks = self.savedLooksByRace.female;
            end
        end
    else
        if sex == 0 then
            SavedLooks = self.savedLooksByRace.male;
        else
            SavedLooks = self.savedLooksByRace.female;
        end
    end

    local isUnique = true;
    local data;

    local tempTable = {};
    local numLooks = #SavedLooks;
    for i = 1, numLooks do
        wipe(tempTable);
        data = SavedLooks[i].data;
        local numData = #data;
        for j = 1, numData do
            tempTable[ data[j][1] ] = data[j][2];
        end

        local numSame = 0;
        for j = 1, #newLooks do
            if tempTable[ newLooks[j][1] ] == newLooks[j][2] then
                numSame = numSame + 1;
            end
        end

        if numSame == numData then
            isUnique = false;
            break
        end
    end

    wipe(tempTable);
    numLooks = numLooks + 1;

    if numLooks > MAX_SAVES then
        return false
    end

    local looksName = customName or ("New Look #"..(numLooks));
    if isUnique then
        if checkOnly then
            return true;
        else
            if numLooks < MAX_SAVES then
                PlusButton:SetCase(2);
            else
                PlusButton:SetCase(3);
            end
            local currentTime = time();
            tinsert(SavedLooks, 1, {name = looksName , description = generatedDescription, data = newLooks, timeCreated = currentTime});
            return SavedLooks[1], numLooks
        end
    else
        PlusButton:SetCase(2);
    end
    PlusButton.numSaves = numLooks;
end

function DataProvider:SaveNewLooks(customName)
    if NUM_ACTIVE_BUTTONS >= MAX_SAVES then
        return
    end
    local generateDescription = true;
    local newLooks, generatedDescription = DataProvider:GetCurrentSelection(nil, generateDescription);
    local data, numLooks = self:CheckAndSaveLooks(newLooks, generatedDescription, customName);
    if data then
        NUM_ACTIVE_BUTTONS = numLooks;
        if numLooks >= 4 then
            ScrollBoundMarkUpdater:Start();
        end

        return data
    else

    end
end

local function RepositionButtons(buttonPool)
    local _, relativeTo = buttonPool[1]:GetPoint();
    local buttonHeight = 64;
    local button;
    for i = 1, MAX_SAVES do
        --buttonPool[ orderTable[i] ]:SetPoint("TOPLEFT", relativeTo, "TOPLEFT", 0, -8 + (buttonHeight + 16)*(1 - i));
        button = buttonPool[i];
        button:ClearAllPoints();
        button:SetPoint("TOPLEFT", relativeTo, "TOPLEFT", 0, -8 + (buttonHeight + 16)*(1 - i));
        button.order = i;
    end
end

local function InsertButtonToTop(buttonPool, position)
    local removedButton = tremove(buttonPool, position);
    if removedButton then
        for i = #buttonPool, 1, -1  do
            buttonPool[i + 1] = buttonPool[i];
        end
        buttonPool[1] = removedButton;
        --RepositionButtons(buttonPool);
    end
end

function DataProvider:DeleteLooks(dataSource)
    if not dataSource then return end;
    local sex = self.currentSex;

    local SavedLooks, OrderTable, ButtonPool, categoryID;
    if HAS_ALTERNATE_FORM then
        if not IN_ALTERNATE_FORM then
            if sex == 0 then
                SavedLooks = self.savedLooksByRace.male;
                OrderTable = self.maleButtonOrder;
                ButtonPool = MaleButtons;
                categoryID = 1;
            else
                SavedLooks = self.savedLooksByRace.female;
                OrderTable = self.femaleButtonOrder;
                ButtonPool = FemaleButtons;
                categoryID = 2;
            end
        else
            if sex == 0 then
                SavedLooks = self.savedLooksInAlternateForm.male;
                OrderTable = self.alternateMaleButtonOrder;
                ButtonPool = AlternateMaleButtons;
                categoryID = 3;
            else
                SavedLooks = self.savedLooksInAlternateForm.female;
                OrderTable = self.alternateFemaleButtonOrder;
                ButtonPool = AlternateFemaleButtons;
                categoryID = 4;
            end
        end
    else
        if sex == 0 then
            SavedLooks = self.savedLooksByRace.male;
            OrderTable = self.maleButtonOrder;
            ButtonPool = MaleButtons;
            categoryID = 1;
        else
            SavedLooks = self.savedLooksByRace.female;
            OrderTable = self.femaleButtonOrder;
            ButtonPool = FemaleButtons;
            categoryID = 2;
        end
    end

    local numLooks = #SavedLooks;
    local position;
    for i = 1, numLooks do
        if SavedLooks[i] == dataSource then
            position = i;
            break
        end
    end
    if position then
        for i = position, numLooks do
            SavedLooks[i] = SavedLooks[i + 1];
        end

        local removedIndex = tremove(OrderTable, position);
        tinsert(OrderTable, removedIndex);

        local removedButton = tremove(ButtonPool, position);
        removedButton.isPortraitLoaded = false;
        removedButton.appearanceData = nil;
        tinsert(ButtonPool, removedButton);

        if categoryID == 1 then
            self.numMales = self.numMales - 1;
        elseif categoryID == 2 then
            self.numFemales = self.numFemales - 1;
        elseif categoryID == 3 then
            self.numAlternateMales = self.numAlternateMales - 1;
        elseif categoryID == 4 then
            self.numAlternateFemales = self.numAlternateFemales - 1;
        end
        NUM_ACTIVE_BUTTONS = NUM_ACTIVE_BUTTONS - 1;

        return ButtonPool, removedButton, position
    end
end

function DataProvider:GetButton()
    local currentCharacterData =  C_BarberShop.GetCurrentCharacterData();
    if not currentCharacterData then
        print("Error: No Character Data");
        return
    end
    local sex = currentCharacterData.sex;
    if sex == 0 then
        if HAS_ALTERNATE_FORM then
            if not IN_ALTERNATE_FORM then
                self.numMales = self.numMales + 1;
                if self.numMales > MAX_SAVES then
                    self.numMales = MAX_SAVES;
                end
                InsertButtonToTop(MaleButtons, self.numMales);
                return MaleButtons[1], MaleButtons;
            else
                self.numAlternateMales = self.numAlternateMales + 1;
                if self.numAlternateMales > MAX_SAVES then
                    self.numAlternateMales = MAX_SAVES;
                end
                InsertButtonToTop(AlternateMaleButtons, self.numAlternateMales);
                return AlternateMaleButtons[1], AlternateMaleButtons;
            end
        else
            self.numMales = self.numMales + 1;
            if self.numMales > MAX_SAVES then
                self.numMales = MAX_SAVES;
            end
            InsertButtonToTop(MaleButtons, self.numMales);
            return MaleButtons[1], MaleButtons;
        end
    elseif sex == 1 then
        if HAS_ALTERNATE_FORM then
            if not IN_ALTERNATE_FORM then
                self.numFemales = self.numFemales + 1;
                if self.numFemales > MAX_SAVES then
                    self.numFemales = MAX_SAVES;
                end
                InsertButtonToTop(FemaleButtons, self.numFemales);
                return FemaleButtons[1], FemaleButtons;
            else
                self.numAlternateFemales = self.numAlternateFemales + 1;
                if self.numAlternateFemales > MAX_SAVES then
                    self.numAlternateFemales = MAX_SAVES;
                end
                local index = self.alternateFemaleButtonOrder[self.numAlternateFemales];
                InsertButtonToTop(AlternateFemaleButtons, self.numAlternateFemales);
                return AlternateFemaleButtons[1], AlternateFemaleButtons;
            end
        else
            self.numFemales = self.numFemales + 1;
            if self.numFemales > MAX_SAVES then
                self.numFemales = MAX_SAVES;
            end
            local index = self.femaleButtonOrder[self.numFemales];
            InsertButtonToTop(FemaleButtons, self.numFemales);
            return FemaleButtons[1], FemaleButtons;
        end
    else
        print("Error: Unknown Gender");
        return
    end
end


local function RandomizeApperance()
    local data = DataProvider:GetRandomAppearance();
    if not data then return end;

    for i = 1, #data do
        local optionID, choiceID = unpack(data[i]);
        SetCustomizationChoice(optionID, choiceID);
    end
    BarberShopFrame:UpdateCharCustomizationFrame()
end

local function SetFontStringShadow(fontString)
    fontString:SetShadowColor(0, 0, 0);
    fontString:SetShadowOffset(1, -1);
end

-------------------------------------------------------------

local CustomizationUtil = {};
CustomizationUtil.f = CreateFrame("Frame");

function CustomizationUtil.repeater_OnUpdate(f, elapsed)
    --Dracthyr Notes:
    --some options are only valid when certain options are selected
    --so we need to apply the same appearance profile again to ensure everything we need is selected
    --Arbitrarily use a 2-frame delay

    f.t = f.t + elapsed;

    if f.t > 0.033 then
        f:SetScript("OnUpdate", nil);
        for i = 1, #CustomizationUtil.appearanceData do
            local optionID, choiceID = unpack(CustomizationUtil.appearanceData[i]);
            SetCustomizationChoice(optionID, choiceID);
        end
        BarberShopFrame:UpdateCharCustomizationFrame();
    end
end

function CustomizationUtil:UseCustomization(appearanceData)
    if not appearanceData then return end;

    self.f:SetScript("OnUpdate", nil);
    self.appearanceData = appearanceData;

    local optionID, choiceID;
    for i = 1, #appearanceData do
        optionID, choiceID = unpack(appearanceData[i]);
        SetCustomizationChoice(optionID, choiceID);
    end

    self.f.t = 0;
    self.f:SetScript("OnUpdate", self.repeater_OnUpdate);
end



NarciBarberShopSavedLooksMixin = {};

local function PortaitModel_OnModelLoaded(self)
    self:SetCamera(0);
    self:SetAnimation(0, 0);
    self:SetPaused(true);
    local p = self:GetParent();
    p:SetPortraitZoom(0.975);
    p:SetPortraitZoom(1);
    p.isPortraitLoaded = true;

    LoadingFrame:LoadNextPortrait(p.order);
end

function NarciBarberShopSavedLooksMixin:OnLoad()
    self.Portrait:SetVertexColor(0.5, 0.5, 0.5);
    self.Portrait:SetDesaturation(0.6);
    SetFontStringShadow(self.Description);

    local model = self.Model;
    model:SetUnit("player");
    model:SetKeepModelOnHide(true);
    model:SetFacing(pi/24);
    TransitionAPI.SetModelLight(model, true, false, cos(pi/4)*sin(-pi/4) ,  cos(pi/4)*cos(-pi/4) , -cos(pi/4), 1, 0.5, 0.5, 0.5, 1, 0.9, 0.9, 0.9);
    model:SetCamera(0);
    self:SetPortraitZoom(1);
    model:SetAnimation(0, 0);
    model:SetPaused(true);
    model:SetScript("OnModelLoaded", PortaitModel_OnModelLoaded);
    model:SetViewTranslation(0, 0);

    self:OnLeave();
end

function NarciBarberShopSavedLooksMixin:SetPortraitZoom(value)
    self.Model:SetPortraitZoom(value);
    self.portraitZoom = value;
end

function NarciBarberShopSavedLooksMixin:GetPortraitZoom(value)
    return self.portraitZoom or 1;
end

function NarciBarberShopSavedLooksMixin:RefreshPortrait(forcedRefresh)
    if (not self.isPortraitLoaded) or (forcedRefresh) then
        self.Model:Show();
        self.Portrait:Hide();
        self.PortraitText:Hide();
        self.isPortraitLoaded = true;
        self.Model:SetUnit("player");
        UpdatePortraitCamera(self.Model);
    end
end

function NarciBarberShopSavedLooksMixin:UpdateText()
    local textHeight = self.Name:GetHeight() + self.Description:GetHeight() + 6;
    self.Reference:SetHeight(textHeight);
end

function NarciBarberShopSavedLooksMixin:SetInfo(dataSource)
    self.dataSource = dataSource;
    self.Name:SetText(dataSource.name);
    self.Description:SetText(dataSource.description);
    self.appearanceData = dataSource.data;
    self:UpdateText()
end

function NarciBarberShopSavedLooksMixin:OnEnter()
    self.Name:SetAlpha(1);
    self.Description:SetAlpha(1);
    FadeFrame(self.BorderHighlight, 0.15, 1);
    MainFrame:FadeIn(0.2);
    --self.Model:SetPaused(false);  --Playing character idle animation seems distractive, disabled

    EditButton:SetParentObject(self);
    DeleteButton:SetParentObject(self);
end

function NarciBarberShopSavedLooksMixin:OnLeave()
    if self:IsMouseOver() and IsWidgetFocused(self) then
        return
    end
    self.Name:SetAlpha(0.66);
    self.Description:SetAlpha(0.66);
    FadeFrame(self.BorderHighlight, 0.25, 0);
    MainFrame:OnLeave();

    EditButton:Hide();
    DeleteButton:Hide();
end

function NarciBarberShopSavedLooksMixin:OnClick()
    --self:UseCustomization();
    CustomizationUtil:UseCustomization(self.appearanceData);

    if not self.isPortraitLoaded then
        After(UPDATE_PORTRAIT_DELAY, function()
            self:RefreshPortrait();
        end)
    end

    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
end

function NarciBarberShopSavedLooksMixin:LoadPortrait()
    self:UseCustomization(true);
    if not self.isPortraitLoaded then
        After(UPDATE_PORTRAIT_DELAY, function()
            self:RefreshPortrait();
        end)
    end
end

function NarciBarberShopSavedLooksMixin:UseCustomization(dontUpdateButton)
    if not self.appearanceData then return end

    local BarberShopFrame = BarberShopFrame;
    for i = 1, #self.appearanceData do
        local optionID, choiceID = unpack(self.appearanceData[i]);
        SetCustomizationChoice(optionID, choiceID);
        --C_BarberShop.PreviewCustomizationChoice(optionID, choiceID);
    end

    if not dontUpdateButton then
        --determine if it should update the option buttons on the right side of the screen
        BarberShopFrame:UpdateCharCustomizationFrame();
    end
end

function NarciBarberShopSavedLooksMixin:SetSelection(state)
    if state then
        if not self.isSelected then
            self.isSelected = true;
            self.Border:SetTexCoord(0.5, 1, 0, 1);
        end
    else
        if self.isSelected then
            self.isSelected = nil;
            self.Border:SetTexCoord(0, 0.5, 0, 1);
        end
    end
end

if addon.IsDragonflight() then
    function NarciBarberShopSavedLooksMixin:SetButtonAlpha(alpha)
        --Dragonflight Beta: Model no longer inherts parent alpha.
        --Probably a driver issue that only took place in beta but still
        if not self.modelIgnoreAlpha then
            self.modelIgnoreAlpha = true;
            self.Model:SetIgnoreParentAlpha(true);
        end
        self:SetAlpha(alpha);
        self.Model:SetModelAlpha(alpha);
    end
else
    function NarciBarberShopSavedLooksMixin:SetButtonAlpha(alpha)
        self:SetAlpha(alpha);
    end
end


----------------------------------
local DURATION_FADE_OUT = 1.5;

local autoHideTimer = NarciAPI_CreateAnimationFrame(4);
autoHideTimer:SetScript("OnUpdate", function(self, elapsed)
    self.total = self.total + elapsed;
    if self.total >= self.duration then
        self.total = 0;
        if MainFrame:IsVisible() then
            if not MainFrame:IsMouseOver() then
                self:Hide();
                MainFrame:FadeOut(DURATION_FADE_OUT);
            end
        else
            self:Hide();
        end
    end
end)

local animScrollFrame = NarciAPI_CreateAnimationFrame(0.25);
animScrollFrame:SetScript("OnUpdate", function(self, elapsed)
    self.total = self.total + elapsed;
    local offsetY = linear(self.total, self.fromY, 0, self.duration);
    if self.total >= self.duration then
        self:Hide();
        offsetY = 0;
        ScrollButtonAlphaUpdater:Stop();
        if self.onFinishedFunc then
            self.onFinishedFunc();
            self.onFinishedFunc = nil;
        end
    end
    self.parentScrollBar:SetValue(offsetY);
end);

function animScrollFrame:ScrollToTop(ScrollFrame)
    self:Hide();
    local value = ScrollFrame:GetVerticalScroll();
    if value == 0 then
        return
    end
    self.parentScrollBar = ScrollFrame.scrollBar;
    self.fromY = value;
    local duration = sqrt( value/ 400) * 0.25;
    self.duration = duration;
    ScrollButtonAlphaUpdater:Start();
    self:Show();
end


local animScrollButtons = NarciAPI_CreateAnimationFrame(0.4);

animScrollButtons:SetScript("OnUpdate", function(self, elapsed)
    self.total = self.total + elapsed;
    local offsetY = outSine(self.total, self.fromY, self.toY, self.duration);

    if self.newButton then
        local alpha = self.total/0.35;
        if alpha > 1 then
            alpha = 1;
        end
        local offsetX = inOutSine(self.total, -32, 0, self.duration);
        self.newButton:Show();
        self.newButton:SetAlpha(alpha);
        if self.total >= self.duration then
            offsetX = 0;
            offsetY = self.toY;
        end
        self.newButton:SetPoint("TOPLEFT", self.relativeTo, "TOPLEFT", offsetX, -8);
    end

    if self.oldButton then
        local alpha = 1 - self.total/0.35;
        
        if alpha < 0 then
            alpha = 0;
            self.oldButton:Hide();
        end
        local offsetX = outSine(self.total, 0, -120, self.duration);
        self.oldButton:SetAlpha(alpha);
        if self.total >= self.duration then
            offsetX = -64;
        end
        self.oldButton:SetPoint("TOPLEFT", self.relativeTo, "TOPLEFT", offsetX, self.oldButtonPosY);
    end

    if self.forthButton then
        local alpha = self.fromAlpha - self.total/0.25;
        if alpha < 0 then
            alpha = 0;
        end
        self.forthButton:SetAlpha(alpha);
        if alpha == 0 then
            self.forthButton:Hide();
        end
    end

    if self.total >= self.duration then
        offsetY = self.toY;
        self:Hide();
        MainFrame.ScrollBlocker:Hide();
        RepositionButtons(self.buttonPool);
        UpdateScrollRange();
        ScrollButtonAlphaUpdater:Stop();
    end
    
    for i = self.buttonIndex, self.numButtons do
        self.buttonPool[i]:SetPoint("TOPLEFT", self.relativeTo, "TOPLEFT", 0, -8 + 80*(1 - i) + offsetY);
    end
end)

function animScrollButtons:InsertNewButton(buttonPool, newButton)
    self:Hide();
    local _, ScrollChild = buttonPool[1]:GetPoint();
    self.relativeTo = ScrollChild;
    self.buttonPool = buttonPool;
    self.numButtons = #buttonPool;
    self.buttonIndex = 2;
    self.newButton = newButton;
    self.forthButton = buttonPool[5];
    self.oldButton = nil;
    self.fromAlpha = self.forthButton:GetAlpha();
    self.fromY = 80;
    self.toY = 0;

    local ScrollFrame = ScrollChild:GetParent();
    if ScrollFrame:GetVerticalScroll() > 0.1 then
        --ScrollFrame.scrollBar:SetValue(0);
        animScrollFrame.onFinishedFunc = function()
            self:Show();
            EditBox:SetParentObject(newButton, true);
        end
        animScrollFrame:ScrollToTop(ScrollFrame);
    else
        self:Show();
    end
    MainFrame.ScrollBlocker:Show();
end

function animScrollButtons:RemoveOldButton(buttonPool, button)
    self:Hide();
    self.forthButton = nil;
    self.newButton = nil;
    self.oldButton = button;
    local _, ScrollChild, _, _, posY = button:GetPoint();
    self.oldButtonPosY = posY;
    self.buttonIndex = button.order;
    self.relativeTo = ScrollChild;
    self.buttonPool = buttonPool;
    self.numButtons = #buttonPool - 1;
    self.fromY = -80;
    self.toY = 0;
    self:Show();
    ScrollButtonAlphaUpdater:Start();
    MainFrame.ScrollBlocker:Show();
end


--Click to save new looks
NarciBarberShopPlusButtonMixin = {};

function NarciBarberShopPlusButtonMixin:OnLoad()
    PlusButton = self;
    self:OnLeave();
    self.tooltipDefault = L["Save New Look"];
    self.tooltipReachMax = L["No Available Slot"];
    self.tooltipSaved = L["Look Saved"];
    self.tooltipShapeShifted = L["Cannot Save Forms"];
    self:SetCase(1);
end

function NarciBarberShopPlusButtonMixin:OnMouseDown()
    if self:IsEnabled() then
        self.Background:SetTexCoord(0.25, 0.5, 0, 1);
    end
end

function NarciBarberShopPlusButtonMixin:OnMouseUp()
    if self:IsEnabled() then
        self.Background:SetTexCoord(0, 0.25, 0, 1);
    end
end

function NarciBarberShopPlusButtonMixin:OnEnable()
    self.Background:SetTexCoord(0, 0.25, 0, 1);
    self.Label:SetTextColor(1, 1, 1);
    self.Label:SetAlpha(0.66);
end

function NarciBarberShopPlusButtonMixin:OnDisable()
    self.Background:SetTexCoord(0.5, 0.75, 0, 1);
    self.Label:SetTextColor(0.5, 0.5, 0.5);
end

function NarciBarberShopPlusButtonMixin:SetCase(caseID)
    if caseID == 1 then
        --Can be added
        self.Label:SetText(self.tooltipDefault);
        self:Enable();
    elseif caseID == 2 then
        --Already saved
        self.Label:SetText(self.tooltipSaved);
        self:Disable();
    elseif caseID == 3 then
        --Cannot save more
        self.Label:SetText(self.tooltipReachMax);
        self:Disable();
    elseif caseID == 4 then
        --Cannot add shapes
        self.Label:SetText(self.tooltipShapeShifted);
        self:Disable();
    end

    self.caseID = caseID;
end

function NarciBarberShopPlusButtonMixin:GetCase()
    return self.caseID
end

function NarciBarberShopPlusButtonMixin:OnEnter()
    self.Label:SetAlpha(1);
    MainFrame:OnEnter();
    EditButton:Hide();
    DeleteButton:Hide();

    local labelWidth = self.Label:GetWrappedWidth();
    self.Count:SetPoint("LEFT", self.Label, "LEFT", labelWidth + 8, 0);
    if self.numSaves then
        self.Count:SetText(self.numSaves.." / "..MAX_SAVES);
        self.Count:Show();
    else
        self.Count:Hide();
    end
end

function NarciBarberShopPlusButtonMixin:OnLeave()
    self.Label:SetAlpha(0.66);
    self.Count:Hide();
end

local function SaveCurrentAppearance(customName)
    if not PlusButton:IsEnabled() then return end;

    local data = DataProvider:SaveNewLooks(customName);
    if data then
        local button, buttonPool = DataProvider:GetButton();
        if button then
            animScrollButtons:InsertNewButton(buttonPool, button);
            button:SetInfo(data);
            button:Show();
            EditBox:SetParentObject(button, true);
            After(UPDATE_PORTRAIT_DELAY, function()
                button:RefreshPortrait(true);
                button:SetSelection(true);
            end)
        end
        return true
    end
end

API.SaveCurrentAppearance = SaveCurrentAppearance;

function NarciBarberShopPlusButtonMixin:OnClick()
    --Save new Looks
    SaveCurrentAppearance();
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
end

function NarciBarberShopPlusButtonMixin:Glow()
    self.GlowTexture.AnimGlow:Play();
    self.GlowTexture:Show();
    --PlaySound(SOUNDKIT.MAP_PING);
    PlaySound(23404)
end


NarciBarberShopEditButtonMixin = {};

function NarciBarberShopEditButtonMixin:OnLoad()
    EditButton = self;
    self.Icon:SetTexCoord(0.25, 0.5, 0, 1);
    self.Icon:SetVertexColor(0.8, 0.8, 0.8);
    self.Ring:SetVertexColor(0.8, 0.8, 0.8);
    self.Tooltip:SetText(L["Edit Name"]);
    SetFontStringShadow(self.Tooltip);
end

function NarciBarberShopEditButtonMixin:SetParentObject(object)
    self:ClearAllPoints();
    self:SetParent(object);
    self:SetPoint("BOTTOMRIGHT", object, "RIGHT", 0, 0);
    self:Show();
end

function NarciBarberShopEditButtonMixin:EditName()
    EditBox:SetParentObject(self:GetParent());
end

function NarciBarberShopEditButtonMixin:OnClick()
    self:EditName();
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
end

function NarciBarberShopEditButtonMixin:OnEnter()
    self:GetParent():OnEnter();
    self.Tooltip:Show();
end

function NarciBarberShopEditButtonMixin:OnLeave()
    MainFrame:OnLeave();
    self:GetParent():OnLeave();
    self.Tooltip:Hide();
end

function NarciBarberShopEditButtonMixin:OnHide()
    self:Hide();
    self:OnMouseUp();
    self.Tooltip:Hide();
end

function NarciBarberShopEditButtonMixin:OnMouseDown()
    self.Icon:SetSize(16, 16);
end

function NarciBarberShopEditButtonMixin:OnMouseUp()
    self.Icon:SetSize(20, 20);
end


NarciBarberShopDeleteButtonMixin = {};

function NarciBarberShopDeleteButtonMixin:OnLoad()
    DeleteButton = self;
    self.Icon:SetTexCoord(0, 0.25, 0, 1);
    self.Ring:SetTexCoord(0.25, 0.5, 0, 1);
    self.Ring:SetVertexColor(0.85, 0, 0);
    self.SemiCircleRight:SetVertexColor(0.85, 0, 0);
    self.SemiCircleLeft:SetVertexColor(0.85, 0, 0);
    self.Tooltip:SetText(L["Delete Look"]);
    self.Tooltip:SetTextColor(1, 0.31, 0.31);
    SetFontStringShadow(self.Tooltip);

    --self.Ring:SetDrawLayer("BORDER");
end

function NarciBarberShopDeleteButtonMixin:SetParentObject(object)
    self:SetParent(object);
    self:Show();
    self:OnMouseUp();
end

function NarciBarberShopDeleteButtonMixin:OnClick()
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
end

function NarciBarberShopDeleteButtonMixin:OnLongClick()
    local ButtonPool, removedButton = DataProvider:DeleteLooks(self:GetParent().dataSource);
    if ButtonPool then
        DataProvider:IsCharacterDataUnique();
        animScrollButtons:RemoveOldButton(ButtonPool, removedButton);
    end
end

function NarciBarberShopDeleteButtonMixin:OnEnter()
    self:GetParent():OnEnter();
    self.Tooltip:Show();
end

function NarciBarberShopDeleteButtonMixin:OnLeave()
    MainFrame:OnLeave();
    self:GetParent():OnLeave();
    self.Tooltip:Hide();
end

function NarciBarberShopDeleteButtonMixin:OnHide()
    self:Hide();
    self:OnMouseUp();
    self.Tooltip:Hide();
end

function NarciBarberShopDeleteButtonMixin:OnMouseDown()
    self.Icon:SetSize(14, 14);
    self:LockHighlight();
    self.SemiCircleLeft:Show();
    self.SemiCircleRight:Show();
    self.SemiCircleLeft.rotation:Play();
    self.SemiCircleRight.rotation:Play();
    self.Ring:SetVertexColor(0.25, 0, 0);
end

function NarciBarberShopDeleteButtonMixin:OnMouseUp()
    self.Icon:SetSize(20, 20);
    self:UnlockHighlight();
    self.SemiCircleRight:Hide();
    self.SemiCircleLeft:Hide();
    self:StopAnimating();
    self.Ring:SetVertexColor(0.85, 0, 0);
end


NarciBarberShopEditBoxMixin = {};

function NarciBarberShopEditBoxMixin:OnLoad()
    EditBox = self;
end

function NarciBarberShopEditBoxMixin:SetParentObject(object, alsoHighlightText)
    if self.parentObject then
        self.parentObject.Name:Show();
        self.parentObject:OnLeave();
    end
    self:ClearAllPoints();
    self:SetParent(object);
    self:SetPoint("LEFT", object.Name, "LEFT", 0, 0);
    self:SetText(object.Name:GetText());
    self:Show();
    self.parentObject = object;
    object.Name:Hide();
    if alsoHighlightText then
        After(0, function()
            self:HighlightText();
        end)
    end
end

function NarciBarberShopEditBoxMixin:OnEscapePressed()
    self.exitByPressingEscape = true;
    self:DiscardChanges();
end

function NarciBarberShopEditBoxMixin:OnEnterPressed()
    self:ConfirmChanges();
end

function NarciBarberShopEditBoxMixin:OnTextChanged()
    local remainingLetters = 36 - self:GetNumLetters(true);
    self.LetterCount:SetText(remainingLetters);
    if remainingLetters == 0 then
        self.LetterCount:SetTextColor(0.85, 0, 0);
    elseif remainingLetters <= 12 then
        self.LetterCount:SetTextColor(0.96, 0.64, 0.13);
    else
        self.LetterCount:SetTextColor(0.66, 0.66, 0.66);
    end
end

function NarciBarberShopEditBoxMixin:DiscardChanges()
    self:Hide();
end

function NarciBarberShopEditBoxMixin:ConfirmChanges()
    local text = strtrim(self:GetText() or "");
    if text ~= "" then
        self.parentObject.Name:SetText(text);
        self.parentObject:UpdateText();
        self.parentObject.dataSource.name = text;
    end
    self:Hide();
end

function NarciBarberShopEditBoxMixin:OnShow()
    self:SetFocus();
    self:SetCursorPosition(100);
end

function NarciBarberShopEditBoxMixin:OnHide()
    self:HighlightText(0, 0);
    self:ClearFocus();
    self:Hide();
    if self.parentObject then
        self.parentObject.Name:Show();
        self.parentObject:OnLeave();
    end
end

function NarciBarberShopEditBoxMixin:OnEditFocusGained()
    autoHideTimer:Hide();
end

function NarciBarberShopEditBoxMixin:OnEditFocusLost()
    if self.exitByPressingEscape then
        self.exitByPressingEscape = nil;
        self:Hide();
    else
        self:ConfirmChanges();
    end
    autoHideTimer:Show();
end

----------------------------------
--Main Frame




NarciBarberShopMixin = {};

function NarciBarberShopMixin:OnLoad()
    MainFrame = self;
    
    NarciAPI_CreateFadingFrame(self);
end

function NarciBarberShopMixin:OnKeyDown(key)
    if HotkeyManager:RunCommandByKeyState(key, true) then
        self:SetPropagateKeyboardInput(false);
    else
        self:SetPropagateKeyboardInput(true);
    end
end

function NarciBarberShopMixin:ToggleNotification(state)
    self.checkUniqueness = state;
end

function NarciBarberShopMixin:ToggleRandomizeAppearanceButton(visible)
    local button = CharCustomizeFrame.RandomizeAppearanceButton;
    if button then
        button:SetShown(visible);
        button:SetScript("OnClick", RandomizeApperance);
    end
end

function NarciBarberShopMixin:OnKeyUp(key)
    if HotkeyManager:RunCommandByKeyState(key, false) then
        self:SetPropagateKeyboardInput(false);
    else
        self:SetPropagateKeyboardInput(true);
    end
end

function NarciBarberShopMixin:OnShow()
    local _;
    _, SCROLLFRAME_CENTER_Y = self.SavedLooksFrame.CategoryMale:GetCenter();
    UpdateScrollButtonAlpha(ScrollButtonAlphaUpdater.activeButtons);
end

function NarciBarberShopMixin:OnHide()
    ScrollButtonAlphaUpdater:Stop();
    HotkeyManager:StopMovingCamera();
    autoHideTimer:Hide();
end

function NarciBarberShopMixin:OnEnter()
    self:FadeIn(0.2);
end

function NarciBarberShopMixin:OnLeave()
    if not self:IsMouseOver() and not IsMouseButtonDown() then
        autoHideTimer:Show();
    end
end

function NarciBarberShopMixin:ToggleSaves(visible)
    --For Shapeshifter
    if visible then
        self.SavedLooksFrame:Show();
        self:FadeIn(0.2);
    else
        PlusButton:SetCase(4);
        self.SavedLooksFrame:Hide();
        self:SetAlpha(0);
    end
end

function NarciBarberShopMixin:UpdateGenderCategory(sex)
    self:FadeIn(0.2);
    autoHideTimer:Hide();
    autoHideTimer:Show();

    if not sex then
        local currentCharacterData =  C_BarberShop.GetCurrentCharacterData();
        if currentCharacterData then
            sex = currentCharacterData.sex;
        else
            print("Error: No Character Data");
            return
        end
    end
    
    DataProvider.currentSex = sex;

    if HAS_ALTERNATE_FORM then
        IN_ALTERNATE_FORM = C_BarberShop.IsViewingAlteredForm();
        if sex == self.lastSex and IN_ALTERNATE_FORM == self.lastFrom then
            return
        else
            self.lastSex = sex;
            self.lastFrom = IN_ALTERNATE_FORM;
        end
    else
        if sex == self.lastSex then
            return
        else
            self.lastSex = sex;
        end
    end

    local activeCategory;
    if sex == 0 then
        if HAS_ALTERNATE_FORM then
            if IN_ALTERNATE_FORM then
                activeCategory = self.SavedLooksFrame.CategoryAlternateFormMale;
                self.SavedLooksFrame.CategoryMale:Hide();
                self.SavedLooksFrame.CategoryFemale:Hide();
                self.SavedLooksFrame.CategoryAlternateFormFemale:Hide();
                ScrollButtonAlphaUpdater.activeButtons = AlternateMaleButtons;
                NUM_ACTIVE_BUTTONS = DataProvider.numAlternateMales;
                ACTIVE_CAMERA_PROFILE = CAMERA_PROFILES_BY_RACE[CAMEAR_KEY_ALTERNATE_FORM].male;
            else
                activeCategory = self.SavedLooksFrame.CategoryMale;
                self.SavedLooksFrame.CategoryFemale:Hide();
                self.SavedLooksFrame.CategoryAlternateFormMale:Hide();
                self.SavedLooksFrame.CategoryAlternateFormFemale:Hide();
                ScrollButtonAlphaUpdater.activeButtons = MaleButtons;
                NUM_ACTIVE_BUTTONS = DataProvider.numMales;
                ACTIVE_CAMERA_PROFILE = CAMERA_PROFILES_BY_RACE[CAMEAR_KEY_TRUE_FORM].male;
            end
        else
            activeCategory = self.SavedLooksFrame.CategoryMale;
            self.SavedLooksFrame.CategoryFemale:Hide();
            ScrollButtonAlphaUpdater.activeButtons = MaleButtons;
            NUM_ACTIVE_BUTTONS = DataProvider.numMales;
            ACTIVE_CAMERA_PROFILE = CAMERA_PROFILE.male;
        end
    else
        if HAS_ALTERNATE_FORM then
            if IN_ALTERNATE_FORM then
                activeCategory = self.SavedLooksFrame.CategoryAlternateFormFemale;
                self.SavedLooksFrame.CategoryMale:Hide();
                self.SavedLooksFrame.CategoryFemale:Hide();
                self.SavedLooksFrame.CategoryAlternateFormMale:Hide();
                ScrollButtonAlphaUpdater.activeButtons = AlternateFemaleButtons;
                NUM_ACTIVE_BUTTONS = DataProvider.numAlternateFemales;
                ACTIVE_CAMERA_PROFILE = CAMERA_PROFILES_BY_RACE[CAMEAR_KEY_ALTERNATE_FORM].female;
            else
                activeCategory = self.SavedLooksFrame.CategoryFemale;
                self.SavedLooksFrame.CategoryMale:Hide();
                self.SavedLooksFrame.CategoryAlternateFormMale:Hide();
                self.SavedLooksFrame.CategoryAlternateFormFemale:Hide();
                ScrollButtonAlphaUpdater.activeButtons = FemaleButtons;
                NUM_ACTIVE_BUTTONS = DataProvider.numFemales;
                ACTIVE_CAMERA_PROFILE = CAMERA_PROFILES_BY_RACE[CAMEAR_KEY_TRUE_FORM].female;
            end
        else
            activeCategory = self.SavedLooksFrame.CategoryFemale;
            self.SavedLooksFrame.CategoryMale:Hide();
            ScrollButtonAlphaUpdater.activeButtons = FemaleButtons;
            NUM_ACTIVE_BUTTONS = DataProvider.numFemales;
            ACTIVE_CAMERA_PROFILE = CAMERA_PROFILE.female;
        end
    end

    self.activeCategory = activeCategory;
    ACTIVE_BUTTON_POOL = ScrollButtonAlphaUpdater.activeButtons;

    activeCategory:Show();
    ScrollBoundMarkUpdater:Hide();
    ScrollBoundMarkUpdater.object1 = activeCategory.BoundTop;
    ScrollBoundMarkUpdater.object2 = activeCategory.BoundBottom;

    UpdateScrollRange(activeCategory);

    LoadingFrame:LoadPortraits();
end

-----------------------------------------------------------------
local function InitializeBarberShopFrame()
    local frame = Narci_BarbershopFrame;
    frame:ClearAllPoints();
    frame:SetParent(BarberShopFrame);
    frame:SetPoint("TOPLEFT", BarberShopFrame, "TOPLEFT", 0, -95);
    frame:ToggleRandomizeAppearanceButton(true);
    frame:Show();

    --The WoW default action is automatically closing the BarberShopFrame
    --But here we want to check if the newly applied appearance is unique and notifiy user to save it
    BarberShopFrame:UnregisterEvent("BARBER_SHOP_APPEARANCE_APPLIED");

    SettingFrame:Initialize();
end

--/run TestPlayerModel:SetZoomDistance()


local function HookMixin()
    --[[
    hooksecurefunc(CharCustomizeFrame, "SetCustomizationChoice", function(self, optionID, choiceID)
        print("Set ",optionID, choiceID)
    end)
    hooksecurefunc(CharCustomizeFrame, "PreviewCustomizationChoice", function(self, optionID, choiceID)
        print("Preview ", optionID, choiceID)
    end)
    --]]
    
    hooksecurefunc(C_BarberShop, "SetSelectedSex", function(sexID)
        MainFrame:UpdateGenderCategory(sexID);
    end);

    hooksecurefunc(C_BarberShop, "SetViewingShapeshiftForm", function(formID)
        if formID then
            MainFrame:ToggleSaves(false);
        else
            MainFrame:ToggleSaves(true);
        end
    end);

    if HAS_ALTERNATE_FORM then
        hooksecurefunc(C_BarberShop, "SetViewingAlteredForm", function(viewingAlteredForm)
            MainFrame:UpdateGenderCategory();
        end);
    end

    --Override the default method to:
    --1. Add looks uniqueness check
    --Constantly monitor this mixin!!

    if true then
        function BarberShopFrame:UpdateCharCustomizationFrame(alsoReset)
            local customizationCategoryData = C_BarberShop.GetAvailableCustomizations();
            if not customizationCategoryData then
                return;
            end

            DataProvider:IsCharacterDataUnique(customizationCategoryData);

            if alsoReset then
                CharCustomizeFrame:Reset();
            end

            CharCustomizeFrame:SetCustomizations(customizationCategoryData);

            if self.UpdatePrice then
                --TransitionAPI *barbershop is free in Dragonflight
                self:UpdatePrice();
            end

            if self.UpdateButtons then
                self:UpdateButtons();
            end
            --ExportEditBox.profileString = nil; --It'll be updated when that editbox becomes visible
        end
    end
end



local EventListener = CreateFrame("Frame");
local events = {"BARBER_SHOP_COST_UPDATE", "BARBER_SHOP_FORCE_CUSTOMIZATIONS_UPDATE", "BARBER_SHOP_RESULT", "BARBER_SHOP_OPEN", "BARBER_SHOP_CLOSE", "BARBER_SHOP_APPEARANCE_APPLIED", "ADDON_LOADED"};    --"UNIT_MODEL_CHANGED"
for i = 1, #events do
    EventListener:RegisterEvent(events[i])
end


EventListener:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" then
        local name = ...;
        --Blizzard_CharacterCustomize
        --Blizzard_BarbershopUI
        if name == "Narcissus_Barbershop" then --Narcissus_Barbershop
            self:UnregisterEvent(event);
            if not IsAddOnLoaded("Blizzard_BarbershopUI") then
                print("Narcissus Error: Blizzard_BarbershopUI not loaded!");
                self:UnregisterAllEvents();
                return
            end
            DataProvider:LoadData();
            HotkeyManager:LoadHotkeys();
            StatManager:LoadData();
            HookMixin();
            InitializeBarberShopFrame();
            MainFrame:UpdateGenderCategory();
            StatManager:OnBarberShopOpen();

            if true then
                C_Timer.After(0.5 , function()
                    BarberShopFrame:SetPropagateKeyboardInput(true);    --DEBUG
                end)
            end
        end
    elseif event == "BARBER_SHOP_OPEN" then
        MainFrame:UpdateGenderCategory();
        StatManager:OnBarberShopOpen();

    elseif event == "BARBER_SHOP_CLOSE" then
        StatManager:OnBarberShopClose();

    elseif event == "BARBER_SHOP_RESULT" then
        --MainFrame:UpdateGenderCategory();
        if BarberShopFrame.UpdateButtons then
            BarberShopFrame:UpdateButtons();
        end
    elseif event == "BARBER_SHOP_APPEARANCE_APPLIED" then
        if MainFrame.checkUniqueness and DataProvider:IsNewLooksUnique() then
            BarberShopFrame:UpdateCharCustomizationFrame();
            PlusButton:Glow();
        else
            BarberShopFrame:Cancel();
        end
        StatManager:UpdateMoney();
    end
end)





-------------------------------------------------------
--Settings
local TabButtons = {};

NarciBarberShopSettingTabButtonMixin = {};

function NarciBarberShopSettingTabButtonMixin:OnLoad()
    tinsert(TabButtons, self);
end

function NarciBarberShopSettingTabButtonMixin:OnEnter()
    if not self.isSelected then
        self.ButtonText:SetTextColor(1, 1, 1);
    end
end

function NarciBarberShopSettingTabButtonMixin:OnLeave()
    if not self.isSelected then
        self.ButtonText:SetTextColor(0.5, 0.5, 0.5);
    end
end

function NarciBarberShopSettingTabButtonMixin:SetSelection(isSelected)
    self.isSelected = isSelected;
    SettingFrame.ScrollFrame:SetOffset(0);
    if isSelected then
        self.ButtonText:SetTextColor(1, 0.82, 0);
        SettingFrame:SelectTab(self.Tab);
    else
        self:OnLeave();
        if self.Tab then
            self.Tab:Hide();
        end
    end
end

function NarciBarberShopSettingTabButtonMixin:OnClick()
    if self.isSelected then return end;
    
    for i = 1, #TabButtons do
        TabButtons[i]:SetSelection( TabButtons[i] == self );
    end

    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
end


NarciBarberShopSettingCheckBoxMixin = {};

function NarciBarberShopSettingCheckBoxMixin:OnLoad()
    self.Box:SetVertexColor(0.8, 0.8, 0.8);
    self.Highlight:SetVertexColor(0.5, 0.5, 1);
end

function NarciBarberShopSettingCheckBoxMixin:SetChecked(state)
    self.isOn = state;
    self.Check:SetShown(state);
    if self.onClickFunc then
        self.onClickFunc(self.isOn);
    end
end

function NarciBarberShopSettingCheckBoxMixin:OnClick()
    self.isOn = not self.isOn;
    self:SetChecked(self.isOn);
    if self.name then
        NarciBarberShopDB[self.name] = self.isOn;
    end

    if self.isOn then
        PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
    else
        PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF);
    end
end


local function ProfilesTab_Setup(tab)

end


local TabData = {
    { name= "General", order = 1, localizedName = GENERAL,
        layout = {
            { name = "ToggleNotification", type = "checkbox", localizedName = L["Save Notify"], defaultState = true,
                onClickFunc = function(state) MainFrame:ToggleNotification(state) end,
                tooltip = L["Save Notify Tooltip"];
            },
            { name = "ToggleRandomAppearance", type = "checkbox", localizedName = L["Show Randomize Button"], defaultState = false, onClickFunc = function(state) MainFrame:ToggleRandomizeAppearanceButton(state) end }, --RANDOMIZE_APPEARANCE
        },
    },

    { name = "Shortcuts", order = 2, localizedName = L["Hotkey"],
        layout = {
            { name = "Camera", type = "header", localizedName = CAMERA_LABEL},
            { name = "RotateLeft", type = "keybinding", localizedName = ROTATE_LEFT},
            { name = "RotateRight", type = "keybinding", localizedName = ROTATE_RIGHT},
            { name = "ZoomIn", type = "keybinding", localizedName = ZOOM_IN},
            { name = "ZoomOut", type = "keybinding", localizedName = ZOOM_OUT},
        },
    },

    { name = "Profiles", order = 3, localizedName = L["Profiles"],
        manuallyCreated = true,
        setupFunc = ProfilesTab_Setup,
    },

    { name = "Statistics", order = 0, localizedName = STATISTICS,
        layout = {
            { name = "Money", type = "header", localizedName = L["Coins Spent"] },
            { name = "CoinsSpentSinceShadowlands", type="money", localizedName = "9.0+", tooltip = "Coins spent since 9.0"},
            { name = "CoinsSpentLifetime", type="money", localizedName = HONOR_LIFETIME, tooltip = "Coins spent during lifetime"},
            { name = "Blank", type="header", localizedName=" ",},
            { name = "LocationHeader", type = "location", localizedName = L["Locations"] },
        },
    },
}


local function CreateTabs(frame)
    local Data;
    for i = 1, #TabData do
        Data = TabData[i];
        local button = CreateFrame("Button", nil, frame, "NarciBarberShopSettingTabButtonTemplate");
        local order = Data.order;
        button.order = order;
        if order ~= 0 then
            button:SetPoint("TOPLEFT", frame, "TOPLEFT", 12, -12 + 16 *(1 - i));
        else
            button:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 12, 12);
        end
        button:SetText(Data.localizedName);

        if Data.layout then
            local totalHeight = 8;
            local objects = {};
            local ScrollFrame = frame.ScrollFrame;
            local Tab = CreateFrame("Frame", nil, ScrollFrame);
            button.Tab = Tab;
            if order == 0 then
                StatManager.StatFrame = Tab;
            end
            Tab:SetSize(ScrollFrame:GetSize());
            Tab:SetPoint("TOPLEFT", frame.ScrollFrame.ScrollChild, "TOPLEFT", 0, 0);
            for j, objectData in ipairs(Data.layout) do
                local type = objectData.type;
                local object;
                if type == "checkbox" then
                    object = CreateFrame("Button", nil, Tab, "NarciBarberShopSettingCheckBoxTemplate");
                    object.onClickFunc = objectData.onClickFunc;
                    object.Label:SetText(objectData.localizedName);
                    object:SetPoint("TOPLEFT", Tab, "TOPLEFT", 8, -totalHeight);
                    local textHeight = object.Label:GetHeight() or 12;
                    object:SetHeight(textHeight + 2);
                    totalHeight = totalHeight + textHeight + 12;
                    --Load settings
                    local dbName = objectData.name;
                    object.name = dbName;
                    if NarciBarberShopDB[dbName] == nil then
                        NarciBarberShopDB[dbName] = objectData.defaultState;
                    end
                    object:SetChecked(NarciBarberShopDB[dbName]);
                    if objectData.tooltip then
                        local infoButton = CreateFrame("Frame", nil, object, "NarciBarberShopInfoButtonTemplate");
                        infoButton:SetPoint("LEFT", object, "RIGHT", 12, 0);
                        infoButton.tooltip = objectData.tooltip;
                        objectData.tooltip = nil;
                    end
                elseif type == "keybinding" then
                    object = CreateFrame("Button", nil, Tab, "NarciBarberShopSettingKeyBindingButtonTemplate");
                    object.Label:SetText(objectData.localizedName);
                    object.command = objectData.name;
                    object:SetPoint("TOPRIGHT", Tab, "TOPRIGHT", -60, -totalHeight);
                    local textHeight = object.Label:GetHeight() or 12;
                    totalHeight = totalHeight + textHeight + 12;
                    object:SetText(HotkeyManager:GetHotkey(object.command));

                elseif type == "header" then
                    object = Tab:CreateFontString(nil, "OVERLAY", "SystemFont_Tiny");
                    object:SetText(objectData.localizedName);
                    if objectData.anchor then
                        object:SetJustifyH("LEFT");
                    else
                        object:SetJustifyH("CENTER");
                    end
                    object:SetSpacing(2);
                    object:SetJustifyV("TOP");
                    object:SetPoint("TOP", Tab, "TOP", 0, -totalHeight);
                    object:SetTextColor(0.5, 0.5, 0.5);
                    object:SetWidth(270);
                    totalHeight = totalHeight + 16;

                elseif type == "money" then
                    object = CreateFrame("Frame", nil, Tab, "NarciBarberShopStatsMoneyFrameTemplate");
                    object:SetPoint("TOPLEFT", Tab, "TOPLEFT", 8, -totalHeight);
                    object:SetLabel(objectData.localizedName);
                    local textHeight = object.Label:GetHeight() or 12;
                    totalHeight = totalHeight + textHeight + 8;

                    StatManager.widgets[objectData.name] = object;
                elseif type == "location" then
                    object = CreateFrame("Frame", nil, Tab, "NarciBarberShopStatsLocationFrameTemplate");
                    object:SetPoint("TOPLEFT", Tab, "TOPLEFT", 8, -totalHeight);
                    object:SetHeader();
                    totalHeight = totalHeight + 16;
                    StatManager.widgets[objectData.name] = object;
                    
                end
            end

            Tab.tabHeight = totalHeight;
            Tab.basicHeight = totalHeight;

        elseif Data.manuallyCreated then
            local Tab = frame[Data.name.."Tab"];
            if Tab then
                button.Tab = Tab;
                Tab:ClearAllPoints();
                Tab:SetPoint("TOPLEFT", frame.ScrollFrame, "TOPLEFT", 0, 0);
                if Data.setupFunc then
                    Data.setupFunc(Tab);
                end
            end
        end
    end

    wipe(TabData);
end

NarciBarberShopSettingsMixin = CreateFromMixins(NarciChamferedFrameMixin);

function NarciBarberShopSettingsMixin:OnLoad()
    SettingFrame = self;
    StatManager.SettingFrame = self;
    ExportEditBox = self.ProfilesTab.ExportEditBox;

    local v = 0.2;
    self:SetBorderColor(v, v, v, 1);
    self:SetBackgroundColor(0, 0, 0, 1);
    self.Divider:SetVertexColor(v, v, v);
    self.ScrollFrame.ScrollBar.Background:SetVertexColor(0.5, 0.5, 0.5);

    NarciAPI.CreateSmoothScroll(self.ScrollFrame);
    self.ScrollFrame:SetStepSize(40);
end

function NarciBarberShopSettingsMixin:Initialize()
    CreateTabs(self);
    TabButtons[1]:Click();
end

function NarciBarberShopSettingsMixin:OnEvent(event)
    --GLOBAL_MOUSE_DOWN
    if not self:IsMouseOver() and not SettingButton:IsMouseOver() then
        self:Hide();
    end
end

function NarciBarberShopSettingsMixin:OnShow()
    self:RegisterEvent("GLOBAL_MOUSE_DOWN");
end

function NarciBarberShopSettingsMixin:OnHide()
    self:Hide();
    self:UnregisterEvent("GLOBAL_MOUSE_DOWN");

    autoHideTimer:Show();
end

function NarciBarberShopSettingsMixin:Toggle()
    self:SetShown(not self:IsShown());
end

function NarciBarberShopSettingsMixin:SelectTab(tab)
    --Update Scroll Range
    local frameHeight = math.floor(self.ScrollFrame:GetHeight() + 0.5);
    local range;
    if tab then
        tab:Show();
        if tab.tabHeight then
            range = tab.tabHeight - frameHeight;
            if range < 4 then
                range = 0;
            end
        else
            range = 0;
        end
        self.ScrollFrame:SetScrollRange(range);
    end
end

--Click to open Settings
NarciBarberShopSettingButtonMixin = {};

function NarciBarberShopSettingButtonMixin:OnLoad()
    SettingButton = self;
    self.Label:SetText(L["Settings And Share"]);
end

function NarciBarberShopSettingButtonMixin:OnMouseDown()
    self.Background:SetTexCoord(0.25, 0.5, 0, 1);
end

function NarciBarberShopSettingButtonMixin:OnMouseUp()
    self.Background:SetTexCoord(0, 0.25, 0, 1);
end

function NarciBarberShopSettingButtonMixin:OnEnter()
    self.Label:Show();
    EditButton:Hide();
    DeleteButton:Hide();
end

function NarciBarberShopSettingButtonMixin:OnLeave()
    self.Label:Hide();
end

function NarciBarberShopSettingButtonMixin:OnClick()
    SettingFrame:Toggle();
    PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
end

-----------------------------------------------
--Dev Tool
local function round(number, digit)
    digit = digit or 0;
    local fold = 10^digit;
    return math.floor((number * fold + 0.5))/fold
end

NarciDevToolPortraitMixin = {};

function NarciDevToolPortraitMixin:OnLoad()
    if false then return end;
    
    local model = self.Model;
    model:SetUnit("player");
    model:SetKeepModelOnHide(true);
    model:SetDoBlend(true);
    model:SetFacing(0);
    TransitionAPI.SetModelLight(model, true, false, cos(pi/4)*sin(-pi/4) ,  cos(pi/4)*cos(-pi/4) , -cos(pi/4), 1, 0.5, 0.5, 0.5, 1, 0.9, 0.9, 0.9);
    model:SetCamera(0);
    model:SetPortraitZoom(1);
    model:SetAnimation(0, 0);
    model:SetPaused(true);
    self.facing = 0;
    self.cameraDistance = 1;
    self.cameraPitch = pi/2;
    self.translationX, self.translationY = 0, 0;

    self.FacingButton:SetScript("OnClick", function(frame, button)
        local delta = frame.delta;
        if button == "LeftButton" then
            delta = - delta;
        end
        if IsShiftKeyDown() then
            delta = 4 * delta;
        end
        self.facing = model:GetFacing() + delta;
        model:SetFacing(self.facing);
        frame.Value:SetText( round(self.facing, 2) );
    end)

    --[[
    self.CameraPitchButton:SetScript("OnClick", function(frame, button)
        local delta = frame.delta;
        if button == "RightButton" then
            delta = - delta;
        end
        local cameraPitch = self.cameraPitch + delta;
        self.cameraPitch = cameraPitch;
        TransitionAPI.SetCameraPosition(model, self.cameraDistance*sin(cameraPitch), 0, self.cameraDistance*cos(cameraPitch) + 0.8);
        frame.Value:SetText(cameraPitch);
    end)
    --]]



    self.ModelXButton:SetScript("OnClick", function(frame, button)
        local delta = frame.delta;
        if button == "RightButton" then
            delta = - delta;
        end
        if IsShiftKeyDown() then
            delta = 16 * delta;
        end
        local x, y, z = model:GetPosition();
        model:SetPosition(x + delta, y, z);
        frame.Value:SetText( round(x + delta, 2) );
    end)

    self.ModelYButton:SetScript("OnClick", function(frame, button)
        local delta = frame.delta;
        if button == "RightButton" then
            delta = - delta;
        end
        if IsShiftKeyDown() then
            delta = 16 * delta;
        end
        local x, y, z = model:GetPosition();
        model:SetPosition(x, y + delta, z);
        frame.Value:SetText( round(y + delta, 2) );
    end)

    self.ModelZButton:SetScript("OnClick", function(frame, button)
        local delta = frame.delta;
        if button == "RightButton" then
            delta = - delta;
        end
        if IsShiftKeyDown() then
            delta = 16 * delta;
        end
        local x, y, z = model:GetPosition();
        model:SetPosition(x, y, z + delta);
        frame.Value:SetText( round(z + delta, 2) );
    end)

    self.DistanceButton:SetScript("OnClick", function(frame, button)
        local delta = frame.delta;
        if button == "LeftButton" then
            delta = - delta;
        end
        if IsShiftKeyDown() then
            delta = 4 * delta;
        end
        local cameraDistance = self.cameraDistance + delta;
        self.cameraDistance = cameraDistance;
        TransitionAPI.SetCameraPosition(model, cameraDistance*sin(self.cameraPitch), 0, cameraDistance*cos(self.cameraPitch) + 0.8);
        frame.Value:SetText( round(cameraDistance, 4) );
    end)

    self.OffsetXButton:SetScript("OnClick", function(frame, button)
        local delta = frame.delta;
        if button == "LeftButton" then
            delta = - delta;
        end
        if IsShiftKeyDown() then
            delta = 4 * delta;
        end
        local x, y = model:GetViewTranslation();
        model:SetViewTranslation(x + delta, y);
        frame.Value:SetText( round(x + delta) );
    end)

    self.OffsetYButton:SetScript("OnClick", function(frame, button)
        local delta = frame.delta;
        if button == "RightButton" then
            delta = - delta;
        end
        if IsShiftKeyDown() then
            delta = 4 * delta;
        end
        local x, y = model:GetViewTranslation();
        model:SetViewTranslation(x, y + delta);
        frame.Value:SetText( round(y + delta) );
    end)

    self.ReloadButton:SetScript("OnClick", function()
        self:LoadProfile();
    end)
end

function NarciDevToolPortraitMixin:OnShow()
    local model = self.Model;
    model:MakeCurrentCameraCustom();
    self.cameraDistance = model:GetCameraDistance();
    self.cameraPitch = pi/2;
    model:SetPosition(0, 0, 0);
    self.FacingButton.Value:SetText(model:GetFacing());
    self.DistanceButton.Value:SetText(self.cameraDistance);
    local x, y = model:GetViewTranslation();
    self.OffsetXButton.Value:SetText(x);
    self.OffsetYButton.Value:SetText(y);
end

function NarciDevToolPortraitMixin:LoadProfile(race, sex)
    local model = self.Model;
    model:RefreshUnit();
    model:SetAnimation(0, 0);
    model:SetPaused(true);
    model:MakeCurrentCameraCustom();
    if not UpdatePortraitCamera(model) then
        model:SetFacing(0.52);
        local modelX, modelY, modelZ = 3.4, -0.07, -2.09;
        model:SetPosition(modelX, modelY, modelZ);
    end
    local cameraX, cameraY, cameraZ = TransitionAPI.TransformCameraSpaceToModelSpace(model, 4, 0, 0);
    local targetX, targetY, targetZ = TransitionAPI.TransformCameraSpaceToModelSpace(model, 0, 0, 0);
    TransitionAPI.SetCameraTarget(model, targetX, targetY, targetZ);
    TransitionAPI.SetCameraPosition(model, cameraX, cameraY, cameraZ);
    local modelFacing = model:GetFacing();
    local modelPosX, modelPosY, modelPosZ = model:GetPosition();
    self.ModelXButton.Value:SetText(modelPosX);
    self.ModelYButton.Value:SetText(modelPosY);
    self.ModelZButton.Value:SetText(modelPosZ);
    self.FacingButton.Value:SetText(modelFacing);
end

--[[
    Statistics
    Gold spent at barber shops GetStatistic(1147)
    C_MapExplorationInfo.GetExploredAreaIDsAtPosition(109, C_Map.GetPlayerMapPosition(109, "player"))
    C_Map.GetMapInfoAtPosition(109, C_Map.GetPlayerMapPosition(109, "player"):GetXY())
New Look #10: Q.0.52k.i.5.c.1h.6.7.g.1.a.4.p.2.5.7.9.2.7.4.2.8.2g1.2.nD.J.9.B.16.7.7.c.f.w.3.5.4.5.C
Dracthyr Male: Q.0.52E.3.d.1i.4.9.g.3.6.3.j.9.6.3.3.8.3.4.8.1.6.1Th.g.mw.1.3.nV.p.9.J.19.3.b.1.O.1.8.5.C.aQ
Dracthyr Male: Q.0.52E.3.d.1i.4.9.g.3.6.3.j.9.6.3.3.8.3.4.8.1.6.1Th.h.P.lG.1.3.nV.p.9.J.19.3.b.1.O.1.8.5.C.aQ
--]]

local PORTRAIT_LOAD_DELAY = 0.1;
NarciBarberShopLoadingFrameMixin = {};

local function LoadingFrame_UpdatePortraitDelay(self, elapsed)
    self.t = self.t + elapsed;
    if self.t > PORTRAIT_LOAD_DELAY then
        --self:SetScript("OnUpdate", nil);
        self.t = 0;
        self.button:RefreshPortrait(true);
    end
end

local function LoadingFrame_InitiateLoadingDelay(self, elapsed)
    self.t = self.t + elapsed;
    if self.t > 0.2 then
        self.t = 0;
        self:LoadNextPortrait(self.current);
    end
end

function NarciBarberShopLoadingFrameMixin:OnLoad()
    LoadingFrame = self;

    self.Name:SetText(Narci.L["Loading Portraits"] .."...");
    self.Name:SetTextColor(1, 1, 1);
    self.Progress:SetTextColor(0.67, 0.67, 0.67);
end

function NarciBarberShopLoadingFrameMixin:LoadPortraits()
    local fromID;
    local total = 0;
    if ACTIVE_BUTTON_POOL then
        for i = 1, #ACTIVE_BUTTON_POOL do
            if (not ACTIVE_BUTTON_POOL[i].isPortraitLoaded) and ACTIVE_BUTTON_POOL[i].appearanceData then
                total = total + 1;
                if not fromID  then
                    fromID = i;
                end
            end
        end
    end

    if total == 0 then
        self:Hide();
        self:SetScript("OnUpdate", nil);
        return
    else
        self.total = total;
    end

    self.Name:SetText(Narci.L["Loading Portraits"] .."...");
    self.current = fromID - 1;
    self.Ring.AnimSpin:Play();
    self.t = 0;
    self:SetScript("OnUpdate", LoadingFrame_InitiateLoadingDelay);

    FadeFrame(self, 0.25, 1, 0);
end

function NarciBarberShopLoadingFrameMixin:OnHide()
    self:SetScript("OnUpdate", nil);
end

function NarciBarberShopLoadingFrameMixin:LoadNextPortrait(buttonID)
    if buttonID and buttonID ~= self.current then
        return
    end

    self.current = self.current + 1;
    if self.current > self.total then
        --complete
        self:OnLoadingComplete();
    else
        local button = ACTIVE_BUTTON_POOL[self.current];
        if button then
            button:UseCustomization(true);
            self.t = 0;
            self.button = button;
            self:SetScript("OnUpdate", LoadingFrame_UpdatePortraitDelay);
            self.Progress:SetText(self.current .. " / " ..self.total);
        else
            self:OnLoadingComplete();
        end
    end
end

function NarciBarberShopLoadingFrameMixin:OnLoadingComplete()
    --self:StopAnimating();
    self:SetScript("OnUpdate", nil);
    --self:Hide();
    FadeFrame(self, 0.5, 0);
end