--[[
				// CarouselView.lua

				// View for a carousel. Used for GameGenre screen
				// TODO: Support Vertical?
				//
				// Current this supports a focus that is aligned to the left (0, 0), in the future we
				// could do other alignments if we need them
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ShellModules = Modules:FindFirstChild("Shell")

local Utility = require(ShellModules:FindFirstChild('Utility'))

local function createCarouselView()
	local this = {}

	local items = {}
	local padding = 0
	local itemSizePercentOfContainer = 1
	local focusItem = nil

	local BASE_TWEEN_TIME = 0.2

	local container = Utility.Create'ScrollingFrame'
	{
		Name = "CarouselContainer";
		BackgroundTransparency = 1;
		ClipsDescendants = false;
		ScrollingEnabled = false;
		Selectable = false;
		ScrollBarThickness = 0;
	}

	local function isVisible(item)
		return item.AbsolutePosition.x + item.AbsoluteSize.x >= 0 and
			item.AbsolutePosition.x < GuiRoot.AbsoluteSize.x
	end
	local function getFocusSize()
		local size = container.Size.Y.Offset
		return UDim2.new(0, size, 0, size)
	end
	local function getNonFocusSize()
		local size = container.Size.Y.Offset * itemSizePercentOfContainer
		return UDim2.new(0, size, 0, size)
	end
	local function getItemSize(item)
		if item == focusItem then
			return getFocusSize()
		else
			return getNonFocusSize()
		end
	end

	local function getItemLayoutPosition(index)
		local focusIndex = this:GetItemIndex(focusItem)
		local offsetFromFocus = index - focusIndex
		local x, y = 0, 0

		if index > focusIndex then
			-- items to the right of focus need additional buffer due to focus size being larger
			x = getFocusSize().X.Offset + offsetFromFocus * padding + (offsetFromFocus - 1) * getNonFocusSize().X.Offset
		else
			x = offsetFromFocus * padding + offsetFromFocus * getNonFocusSize().X.Offset
		end
		y = (index == focusIndex and 0) or (container.Size.Y.Offset - getNonFocusSize().Y.Offset) / 2

		return UDim2.new(0, x, 0, y)
	end

	local function recalcLayout()
		for i = 1, #items do
			local item = items[i]
			local size = getItemSize(item)
			local position = getItemLayoutPosition(i)
			if item:IsDescendantOf(game.Workspace) then
				item:TweenSizeAndPosition(size, position, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0, true)
			else
				item.Size = size
				item.Position = position
			end
		end
	end

	--[[ Public API ]]--
	function this:ChangeFocus(newFocus)
		-- We don't use SetFocus() as that function will do a recalc. We want to get the next position
		-- from current position, not recalcd postion for each item
		if self:ContainsItem(newFocus) then
			focusItem = newFocus
			for i = 1, #items do
				local item = items[i]
				item:TweenSizeAndPosition(getItemSize(item), getItemLayoutPosition(i), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, BASE_TWEEN_TIME, true)
			end
		end
	end

	function this:SetSize(newSize)
		if newSize ~= container.Size then
			container.Size = newSize
			container.CanvasSize = UDim2.new(0, container.Size.X.Offset * 2, 1, 0)
			recalcLayout()
		end
	end

	function this:SetPosition(newPosition)
		container.Position = newPosition
	end

	function this:SetPadding(newPadding)
		if newPadding ~= padding then
			padding = newPadding
			recalcLayout()
		end
	end

	function this:SetItemSizePercentOfContainer(value)
		if value ~= itemSizePercentOfContainer then
			itemSizePercentOfContainer = value
			recalcLayout()
		end
	end

	function this:SetParent(newParent)
		container.Parent = newParent
	end

	function this:SetFocus(newFocusItem)
		if self:ContainsItem(newFocusItem) and newFocusItem ~= focusItem then
			focusItem = newFocusItem
			recalcLayout()
		end
	end

	function this:GetFocusItem()
		return focusItem
	end

	function this:GetItemAt(index)
		return items[index]
	end

	function this:GetFront()
		return items[1]
	end

	function this:GetBack()
		return items[#items]
	end

	function this:GetItemIndex(item)
		for i = 1, #items do
			if items[i] == item then
				return i
			end
		end

		return 0
	end

	function this:GetCount()
		return #items
	end

	function this:GetVisibleCount()
		local visibleItemCount = 0
		for i = 1, #items do
			if isVisible(items[i]) then
				visibleItemCount = visibleItemCount + 1
			end
		end

		return visibleItemCount
	end

	function this:GetFirstVisibleItemIndex()
		for i = 1, #items do
			if isVisible(items[i]) then
				return i
			end
		end
	end

	function this:GetLastVisibleItemIndex()
		for i = #items, 1, -1 do
			if isVisible(items[i]) then
				return i
			end
		end
	end

	function this:GetFullVisibleItemCount()
		local containerSizeX = container.AbsoluteSize.x
		-- remove focus from the size, and figure out how many other items can fit
		local fittingSize = containerSizeX - getFocusSize().X.Offset
		if fittingSize <= 0 then
			return 0
		end

		local itemSize = getNonFocusSize().X.Offset + padding
		local count = math.floor(fittingSize/itemSize) + 1
		return count
	end

	function this:InsertCollectionFront(collection)
		for i = #collection, 1, -1 do
			local item = collection[i]
			table.insert(items, 1, item)
			item.Parent = container
		end
		recalcLayout()
	end

	function this:InsertCollectionBack(collection)
		for i = 1, #collection do
			local item = collection[i]
			table.insert(items, item)
			item.Parent = container
		end
		recalcLayout()
	end

	function this:RemoveAmountFromFront(amount)
		for i = 1, amount do
			local item = table.remove(items, 1)
			item.Parent = nil
		end
		recalcLayout()
	end

	function this:RemoveAmountFromBack(amount)
		for i = 1, amount do
			local item = table.remove(items)
			item.Parent = nil
		end
		recalcLayout()
	end

	function this:InsertFront(newItem)
		table.insert(items, 1, newItem)
		newItem.Parent = container
		recalcLayout()
	end

	function this:InsertBack(newItem)
		table.insert(items, newItem)
		newItem.Parent = container
		recalcLayout()
	end

	function this:RemoveFront()
		local item = table.remove(items, 1)
		item.Parent = nil
		recalcLayout()
	end

	function this:RemoveBack()
		local item = table.remove(items, #items)
		item.Parent = nil
		recalcLayout()
	end

	function this:RemoveItem(item)
		for i = 1, #items do
			if items[i] == item then
				local removedItem = table.remove(items, i)
				removedItem.Parent = nil
				break
			end
		end
		recalcLayout()
	end

	function this:RemoveAllItems()
		for i = #items, 1, -1 do
			local item = table.remove(items, #items)
			item.Parent = nil
		end
	end

	function this:ContainsItem(item)
		if not item then
			return false
		end
		return item.Parent == container
	end

	return this
end

return createCarouselView
