local W=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local q=W.AvatarEditor local e=require(q.Shared.QueryMap)local t=require(q.Shared.Util.Signal)local V=require(q.Shared.Util.Maid)local d=require(q.Shared.Settings)local E=require(q.Shared.Cache)local L=require(q.Client.Theme)local v=(require(q.Gui.ButtonTemplateToLua))()local F=E.new(d.CACHE_SIZE)local U={}U.__index=U local function z(W)local q={}table.move(W,1,#W,1,q)return q end local function C(W,q,t,V)local d=string.format("\037\115\044 \037\115\044 \037\115\044 \037\115",e.GetOptionName("\065\115\115\101\116\084\121\112\101",q),tostring(t),V,W)return d end local function G(W,q,e,t)local V=C(W,q,e,t)return F:Get(V)end local function p(W,q,e,t,V)local d=C(W,q,e,t)V=z(V)F:Set(d,V)end local function c(W)table.sort(W,function(W,q)return W._name<q._name end)end function stringMatch(W,q)local e=1 local t=#q local V=1 local d=#W while e<=t and V<=d do local t=(q:sub(e,e)):lower()local d=(W:sub(V,V)):lower()if t==d then e=e+1 end V=V+1 end return t>0 and(d>0 and e-1==t)end local function y()local W=v:Clone()return W end local function i(W,q)return W[q]end function U.new(W,q,e,E)local v=setmetatable({},U)v.ScrollingFrame=W v.All=q v.List=q v.Wearing=e v.PageSize=d.PAGE_SIZE v.MaxPageSize=d.PAGE_SIZE v.GridLayout=W.UIGridLayout v.Maid=V.new()v.Listeners=V.new()v.Children={}v.Pool={}v.State=E v.ItemSelected=t.new()v.ViewDetails=t.new()for W=1,50,1 do v.Pool[W]=y()end v.Maid:GiveTask(L.Changed:Connect(function()v:Render()end))v.Maid:GiveTask(L:Bind(W,"\083\099\114\111\108\108\066\097\114\073\109\097\103\101\067\111\108\111\114\051","\083\099\114\111\108\108\098\097\114"))v.Maid:GiveTask((v.ScrollingFrame:GetPropertyChangedSignal("\067\097\110\118\097\115\080\111\115\105\116\105\111\110")):Connect(function()local q=v.PageSize local e=v.GridLayout local t=W.CanvasPosition.Y>=(e.AbsoluteContentSize.Y-W.AbsoluteSize.Y)-10 if t then local W=math.min(q+d.PAGE_SIZE,v.MaxPageSize)if W~=q then v.PageSize=W v:Update()end end end))for W,q in ipairs(v.All)do q._name=q.name:lower()q._wearing=i(v.Wearing,q.id)end return v end function U.GetItem(W)local q=W.Pool local e=table.remove(q,#q)if not e then e=y()end return e end function U.Render(W)local q=W.Listeners local e=W.List local t=W.Children local V=W.ScrollingFrame local E=W.GridLayout local v=W.Pool local F=table.create(1000)q:DoCleaning()for e,E in ipairs(e)do local v=t[e]or W:GetItem()v.LayoutOrder=e v.ItemLabel.Text=E.name v.Icon.Image="\114\098\120\116\104\117\109\098\058\047\047\116\121\112\101\061\065\115\115\101\116\038\105\100\061"..(E.id.."\038\119\061\049\053\048\038\104\061\049\053\048")v.ItemLabel.Visible=false v.Wearing.Visible=E._wearing v.More.Visible=false F[e]=v q:GiveTask(L:Bind(v,"\066\097\099\107\103\114\111\117\110\100\067\111\108\111\114\051","\066\117\116\116\111\110"))q:GiveTask(L:Bind(v.ItemLabel,"\084\101\120\116\067\111\108\111\114\051","\084\101\120\116"))q:GiveTask(L:Bind(v.ItemLabel,"\084\101\120\116\083\116\114\111\107\101\067\111\108\111\114\051","\084\101\120\116\083\116\114\111\107\101"))q:GiveTask(L:Bind(v.More,"\066\097\099\107\103\114\111\117\110\100\067\111\108\111\114\051","\068\101\116\097\105\108\115"))q:GiveTask(L:Bind(v.More,"\084\101\120\116\067\111\108\111\114\051","\084\101\120\116"))q:GiveTask(L:Bind(v.Wearing,"\066\097\099\107\103\114\111\117\110\100\067\111\108\111\114\051","\087\101\097\114\105\110\103"))q:GiveTask(v.More.Activated:Connect(function(q)W.ViewDetails:Fire(E)end))q:GiveTask(v.TouchLongPress:Connect(function(q,e)v.ItemLabel.Visible=false v.More.Visible=false W.ViewDetails:Fire(E)end))q:GiveTask(v.Activated:Connect(function(q)W.ItemSelected:Fire(E,E._wearing)end))q:GiveTask(v.InputBegan:Connect(function(W)if not d.HOVER_INPUTS[W.UserInputType]then return end v.ItemLabel.Visible=true if W.UserInputType==Enum.UserInputType.MouseMovement then v.More.Visible=true end end))q:GiveTask(v.InputEnded:Connect(function(W)if not d.HOVER_INPUTS[W.UserInputType]then return end v.ItemLabel.Visible=false v.More.Visible=false end))v.Parent=V end for W=#e+1,#t,1 do local q=t[W]q.Parent=nil table.insert(v,q)end W.Children=F V.CanvasSize=UDim2.new(0,0,0,E.AbsoluteContentSize.Y)end function U.Search(d,W,q,e,t,V)local E=table.create(10000)if q==""then if t then for W,q in ipairs(W)do if q._wearing then table.insert(E,q)end end else for W,q in ipairs(W)do if q.assetType==e then table.insert(E,q)end end end else if t then for W,e in ipairs(W)do if stringMatch(e._name,q)and e._wearing then table.insert(E,e)end end else for W,t in ipairs(W)do if stringMatch(t._name,q)and t.assetType==e then table.insert(E,t)end end end end return E end function U.Update(q,W)local e=q.ScrollingFrame local t=q.State if W then q.PageSize=d.PAGE_SIZE q.MaxPageSize=d.PAGE_SIZE e.CanvasPosition=Vector2.new(0,0)end local V={}local E=G(t.search,t.assetType,t.wearing,t.uid)if E then q.MaxPageSize=#E for W=1,q.PageSize,1 do table.insert(V,W,E[W])end p(t.search,t.assetType,t.wearing,t.uid,E)else local W=q:Search(q.All,t.search,t.assetType,t.wearing,t.uid)c(W)q.MaxPageSize=#W for q=1,q.PageSize,1 do table.insert(V,q,W[q])end p(t.search,t.assetType,t.wearing,t.uid,W)end q.List=V q:Render()end function U.UpdateWearing(q,W)q.Wearing=W for W,e in ipairs(q.All)do e._wearing=i(q.Wearing,e.id)end q:Render()end function U.Destroy(W)W.ScrollingFrame.CanvasPosition=Vector2.new(0,0)W.ScrollingFrame=nil W.All=nil W.List=nil W.Wearing=nil W.PageSize=nil W.MaxPageSize=nil W.GridLayout=nil W.Maid:DoCleaning()W.Maid=nil W.Listeners:DoCleaning()W.Listeners=nil for W,q in ipairs(W.Children)do q:Destroy()end table.clear(W.Children)for W,q in ipairs(W.Pool)do q:Destroy()end table.clear(W.Pool)W.State=nil W.ItemSelected:Destroy()W.ItemSelected=nil W.ViewDetails:Destroy()W.ViewDetails=nil end return U
-- Class
-- 0_1195
-- January 10, 2021

--[[



]]

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local AvatarEditor = ReplicatedStorage.AvatarEditor
local QueryMap = require(AvatarEditor.Shared.QueryMap)
local Signal = require(AvatarEditor.Shared.Util.Signal)
local Maid = require(AvatarEditor.Shared.Util.Maid)
local Settings = require(AvatarEditor.Shared.Settings)
local Cache = require(AvatarEditor.Shared.Cache)
local Theme = require(AvatarEditor.Client.Theme)

local buttonTemplate = require(AvatarEditor.Gui.ButtonTemplateToLua)()

local cache = Cache.new(Settings.CACHE_SIZE)

local Class = {}
Class.__index = Class


local function copy(array)
	local new = {}
	table.move(array, 1, #array, 1, new)
	return new
end


local function getCacheKey(pattern, assetType, wearing, uid)
	local key = string.format("%s, %s, %s, %s", QueryMap.GetOptionName("AssetType", assetType), tostring(wearing), uid, pattern)
	return key
end


local function getFromCache(pattern, assetType, wearing, uid)
	local key = getCacheKey(pattern, assetType, wearing, uid)
	return cache:Get(key)
end


local function createCache(pattern, assetType, wearing, uid, list)
	local key = getCacheKey(pattern, assetType, wearing, uid)
	list = copy(list)
	cache:Set(key, list)
end


local function sort(list)
	table.sort(list, function(a, b)
		return a._name < b._name
	end)
end


function stringMatch(str, pattern)
	local patternIndex = 1
	local patternLength = #pattern
	local strIndex = 1
	local strLength = #str

	while patternIndex <= patternLength and strIndex <= strLength do
		local patternChar = pattern:sub(patternIndex, patternIndex):lower()
		local strChar = str:sub(strIndex, strIndex):lower()

		if patternChar == strChar then
			patternIndex = patternIndex + 1
		end

		strIndex = strIndex + 1
	end

	return patternLength > 0 and strLength > 0 and (patternIndex - 1) == patternLength
end


local function createButton()
	local button = buttonTemplate:Clone()
	return button
end


local function isWearing(assets, id)
	return assets[id]
end


function Class.new(scrollingFrame, catalogData, wearingAssets, searchState)
	local self = setmetatable({}, Class)
	
	self.ScrollingFrame = scrollingFrame
	self.All = catalogData
	self.List = catalogData
	self.Wearing = wearingAssets
	self.PageSize = Settings.PAGE_SIZE
	self.MaxPageSize = Settings.PAGE_SIZE
	self.GridLayout = scrollingFrame.UIGridLayout
	self.Maid = Maid.new()
	self.Listeners = Maid.new()
	self.Children = {}
	self.Pool = {}
	self.State = searchState
	self.ItemSelected = Signal.new()
	self.ViewDetails = Signal.new()
	
	for i = 1, 50 do
		self.Pool[i] = createButton()
	end
	
	self.Maid:GiveTask(Theme.Changed:Connect(function()
		self:Render()
	end))
	
	self.Maid:GiveTask(Theme:Bind(scrollingFrame, "ScrollBarImageColor3", "Scrollbar"))
	
	self.Maid:GiveTask(self.ScrollingFrame:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
		local currentPageSize = self.PageSize
		local uiGridLayout = self.GridLayout
		-- i think indexing AbsoluteContentSize is really intensive especially on larger AbsoluteContentSize
		local scrollEnough = scrollingFrame.CanvasPosition.Y >= uiGridLayout.AbsoluteContentSize.Y - scrollingFrame.AbsoluteSize.Y - 10
		if scrollEnough then
			local newPageSize = math.min(currentPageSize + Settings.PAGE_SIZE, self.MaxPageSize)
			if newPageSize ~= currentPageSize then
				self.PageSize = newPageSize
				self:Update()
			end
		end
	end))
	
	for i, v in ipairs(self.All) do
		v._name = v.name:lower()
		v._wearing = isWearing(self.Wearing, v.id)
	end
	
	return self
end


function Class:GetItem()
	local pool = self.Pool
	local button = table.remove(pool, #pool)
	if not button then
		button = createButton()
	end
	return button
end


function Class:Render()
	local maid = self.Listeners
	local catalogList = self.List
	local children = self.Children
	local scrollingFrame = self.ScrollingFrame
	local uiGridLayout = self.GridLayout
	local pool = self.Pool
	local newChildren = table.create(1000)
	
	maid:DoCleaning()

	for i, v in ipairs(catalogList) do
		local button = children[i] or self:GetItem()
		button.LayoutOrder = i
		button.ItemLabel.Text = v.name
		button.Icon.Image = "rbxthumb://type=Asset&id=" .. v.id .. "&w=150&h=150"
		button.ItemLabel.Visible = false
		button.Wearing.Visible = v._wearing
		button.More.Visible = false
		newChildren[i] = button
		
		maid:GiveTask(Theme:Bind(button, "BackgroundColor3", "Button"))
		maid:GiveTask(Theme:Bind(button.ItemLabel, "TextColor3", "Text"))
		maid:GiveTask(Theme:Bind(button.ItemLabel, "TextStrokeColor3", "TextStroke"))
		maid:GiveTask(Theme:Bind(button.More, "BackgroundColor3", "Details"))
		maid:GiveTask(Theme:Bind(button.More, "TextColor3", "Text"))
		maid:GiveTask(Theme:Bind(button.Wearing, "BackgroundColor3", "Wearing"))
		
		maid:GiveTask(button.More.Activated:Connect(function(inputObject)
			self.ViewDetails:Fire(v)
		end))
		
		maid:GiveTask(button.TouchLongPress:Connect(function(touchPositions, inputState)
			button.ItemLabel.Visible = false
			button.More.Visible = false
			self.ViewDetails:Fire(v)
		end))

		maid:GiveTask(button.Activated:Connect(function(inputObject)
			self.ItemSelected:Fire(v, v._wearing)
		end))

		maid:GiveTask(button.InputBegan:Connect(function(inputObject)
			if not Settings.HOVER_INPUTS[inputObject.UserInputType] then
				return
			end

			button.ItemLabel.Visible = true
			if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
				button.More.Visible = true
			end
		end))

		maid:GiveTask(button.InputEnded:Connect(function(inputObject)
			if not Settings.HOVER_INPUTS[inputObject.UserInputType] then
				return
			end

			button.ItemLabel.Visible = false
			button.More.Visible = false
		end))
		
		button.Parent = scrollingFrame
	end

	for i = #catalogList + 1, #children do
		local button = children[i]
		button.Parent = nil
		table.insert(pool, button)
	end
	
	self.Children = newChildren
	scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, uiGridLayout.AbsoluteContentSize.Y)
end


function Class:Search(list, pattern, assetType, wearing, uid)
	local results = table.create(10000)
	-- umm yeaaa
	if pattern == "" then
		if wearing then
			for i, v in ipairs(list) do
				if v._wearing then
					table.insert(results, v)
				end
			end
		else
			for i, v in ipairs(list) do
				if v.assetType == assetType then
					table.insert(results, v)
				end
			end
		end
	else
		if wearing then
			for i, v in ipairs(list) do
				if stringMatch(v._name, pattern) and v._wearing then
					table.insert(results, v)
				end
			end
		else
			for i, v in ipairs(list) do
				if stringMatch(v._name, pattern) and v.assetType == assetType then
					table.insert(results, v)
				end
			end
		end
	end
	
	return results
end


function Class:Update(reset)
	local scrollingFrame = self.ScrollingFrame
	local state = self.State

	if reset then
		self.PageSize = Settings.PAGE_SIZE
		self.MaxPageSize = Settings.PAGE_SIZE
		scrollingFrame.CanvasPosition = Vector2.new(0, 0)
	end

	local filteredItems = {}
	local cacheResults = getFromCache(state.search, state.assetType, state.wearing, state.uid)
	if cacheResults then
		self.MaxPageSize = #cacheResults
		
		for i = 1, self.PageSize do
			table.insert(filteredItems, i, cacheResults[i])
		end
		
		createCache(state.search, state.assetType, state.wearing, state.uid, cacheResults)
	else
		local searchResults = self:Search(self.All, state.search, state.assetType, state.wearing, state.uid)
		sort(searchResults)
		
		self.MaxPageSize = #searchResults
		
		for i = 1, self.PageSize do
			table.insert(filteredItems, i, searchResults[i])
		end
		
		createCache(state.search, state.assetType, state.wearing, state.uid, searchResults)
	end

	self.List = filteredItems
	self:Render()
end


function Class:UpdateWearing(wearingAssets)
	self.Wearing = wearingAssets
	for i, v in ipairs(self.All) do
		v._wearing = isWearing(self.Wearing, v.id)
	end
	self:Render()
end


function Class:Destroy()
	self.ScrollingFrame.CanvasPosition = Vector2.new(0, 0)
	self.ScrollingFrame = nil
	self.All = nil
	self.List = nil
	self.Wearing = nil
	self.PageSize = nil
	self.MaxPageSize = nil
	self.GridLayout = nil
	self.Maid:DoCleaning()
	self.Maid = nil
	self.Listeners:DoCleaning()
	self.Listeners = nil
	for i, v in ipairs(self.Children) do
		v:Destroy()
	end
	table.clear(self.Children)
	for i, v in ipairs(self.Pool) do
		v:Destroy()
	end
	table.clear(self.Pool)
	self.State = nil
	self.ItemSelected:Destroy()
	self.ItemSelected = nil
	self.ViewDetails:Destroy()
	self.ViewDetails = nil
end


return Class