import 'CoreLibs/timer'
import 'CoreLibs/easing'

playdate.ui = playdate.ui or {}

playdate.ui.gridview = {}
playdate.ui.gridview.__index = playdate.ui.gridview

-- used to avoid having timer callback closures retain gridView instances, so that the grid view's __gc metamethod gets called
local gridViewForTimer = {}
setmetatable(gridViewForTimer, {__mode = "v"})

local gfx = playdate.graphics
local easingFunctions = playdate.easingFunctions



playdate.ui.gridview.__index = function(table, key)
	if key == "isScrolling" then
		return table._isScrolling
	elseif key == "scrollEasingFunction" then
		if table.timerX ~= nil then
			return table.timerX.originalValues.easingFunction
		else
			return easingFunctions.outCubic
		end
	elseif key == "easingAmplitude" then
		if table.timerX ~= nil then
			return table.timerX.easingAmplitude
		end
	elseif key == "easingPeriod" then
		if table.timerX ~= nil then
			return table.timerX.easingPeriod
		end
	else
		return rawget(playdate.ui.gridview, key)
	end
end


playdate.ui.gridview.__newindex = function(table, key, value)
	if key == "isScrolling" then	-- read-only variables
		print("ERROR: playdate.gridview."..key.." is read-only.")
	elseif key == "scrollEasingFunction" then
		-- update the values that will be set on the timer when it is reset before our animations
		if table.timerX ~= nil and table.timerY ~= nil then
			table.timerX.originalValues.easingFunction = value
			table.timerY.originalValues.easingFunction = value
		end
	elseif key == "easingAmplitude" then
		if table.timerX ~= nil and table.timerY ~= nil then
			table.timerX.easingAmplitude = value
			table.timerY.easingAmplitude = value
		end
	elseif key == "easingPeriod" then
		if table.timerX ~= nil and table.timerY ~= nil then
			table.timerX.easingPeriod = value
			table.timerY.easingPeriod = value
		end
	else
		rawset(table, key, value)
	end
end


playdate.ui.gridview.__gc = function(self)
	self.timerX:remove()
	self.timerY:remove()
end


function playdate.ui.gridview.new(cellWidth, cellHeight)
	
	local o = {}
	setmetatable(o, playdate.ui.gridview)
	
	o.cellWidth = cellWidth or 0		-- cellWidth = 0 means it takes up the entire table width
	o.cellHeight = cellHeight or 0

	o.numSections = 1
	o.numRows = {}
	o.numRows[1] = 0
	o.numColumns = 1
	
	o.selectedSection = 1
	o.selectedRow = 1
	o.selectedColumn = 1
	
	o.contentInsetTop = 0
	o.contentInsetBottom = 0
	o.contentInsetLeft = 0
	o.contentInsetRight = 0
	
	o.cellPaddingTop = 0
	o.cellPaddingBottom = 0
	o.cellPaddingLeft = 0
	o.cellPaddingRight = 0
	
	o.sectionHeaderPaddingTop = 0
	o.sectionHeaderPaddingBottom = 0
	o.sectionHeaderPaddingLeft = 0
	o.sectionHeaderPaddingRight = 0
	
	o.horizontalDividers = {}
	o.horizontalDividerHeight = cellHeight or 0
	o.horizontalDividerHeight = o.horizontalDividerHeight / 2
	
	o.backgroundImage = nil		-- can be either a regular image that gets tiled or a nineslice
	
	o.sectionHeaderHeight = 0
	
	o._isScrolling = false
	o.scrollPositionX = 0
	o.scrollPositionY = 0
	
	o.changeRowOnColumnWrap = true
	o.scrollCellsToCenter = true
	
	o.needsDisplay = true -- will be true for things like scrolling changes
	
	-- private
	
	-- set up timers
	local timerX = playdate.timer.new(250)
	timerX.discardOnCompletion = false
	timerX.easingFunction = easingFunctions.outCubic
	timerX:pause()
	gridViewForTimer[timerX] = o
	timerX.updateCallback = function(timer)
			local gridView = gridViewForTimer[timer]
			gridView.scrollPositionX = timer.value
			gridView.needsDisplay = true
		end
	timerX.timerEndedCallback = function(timer)
			local gridView = gridViewForTimer[timer]
			gridView.scrollPositionX = timer.endValue
			gridView._isScrolling = false
			gridView.needsDisplay = true
		end
		
	local timerY = playdate.timer.new(250)
	timerY.discardOnCompletion = false
	timerY.easingFunction = easingFunctions.outCubic
	timerY:pause()
	gridViewForTimer[timerY] = o
	timerY.updateCallback = function(timer)
			local gridView = gridViewForTimer[timer]
			gridView.scrollPositionY = timer.value
			gridView.needsDisplay = true
		end
	timerY.timerEndedCallback = function(timer)
			local gridView = gridViewForTimer[timer]
			gridView.scrollPositionY = timer.endValue
			gridView._isScrolling = false
			gridView.needsDisplay = true
		end
		
	o.timerX = timerX
	o.timerY = timerY
	
	o.contentWidth = 0
	o.contentHeight = 0
	o.drawingWidth = 0
	o.drawingHeight = 0
	o.drawingX = 0
	o.drawingY = 0

	return o
end


local function animateScroll(self, newX, newY)

	if self.timerX.duration > 0 or self.timerY.duration > 0 then
		self._isScrolling = true
	end

	if self.timerX.duration > 0 then
		self.timerX:reset()
		self.timerX.startValue = self.scrollPositionX
		self.timerX.endValue = newX
		self.timerX:start()
	else
		self.scrollPositionX = newX
	end
	
	if self.timerY.duration > 0 then
		self.timerY:reset()
		self.timerY.startValue = self.scrollPositionY
		self.timerY.endValue = newY
		self.timerY:start()
	else
		self.scrollPositionY = newY
	end
end


-- override to draw the contents of the cells
function playdate.ui.gridview:drawCell(section, row, column, selected, x, y, width, height)
	gfx.setColor(gfx.kColorBlack)
	gfx.drawRect(x, y, width, height)
	if selected then
		gfx.fillRect(x + 2, y + 2, width - 4, height - 4)
	end
end

-- override to draw the contents of the section headers
function playdate.ui.gridview:drawSectionHeader(section, x, y, width, height)
	gfx.setColor(gfx.kColorBlack)
	gfx.fillRect(x, y, width, height)
end

-- override to draw the contents of horizontal dividers
function playdate.ui.gridview:drawHorizontalDivider(x, y, width, height)
	gfx.setColor(gfx.kColorBlack)
	local lineHeight = 2
	gfx.fillRect(x + 2, y + ((height-lineHeight)/2), width - 4, lineHeight)
end


function playdate.ui.gridview:setHorizontalDividerHeight(newHeight)
	assert(newHeight ~= nil, "nil value passed to playdate.ui.gridview.setHorizontalDividerHeight()")
	assert(newHeight >= 0, "negative value passed to playdate.ui.gridview.setHorizontalDividerHeight()")
	self.horizontalDividerHeight = newHeight
	self.needsDisplay = true
end

function playdate.ui.gridview:getHorizontalDividerHeight()
	return self.horizontalDividerHeight
end


function playdate.ui.gridview:removeHorizontalDividers()
	self.horizontalDividers = {}
	self.needsDisplay = true
end


function playdate.ui.gridview:addHorizontalDividerAbove(section, row)

	if self.horizontalDividers[section] == nil then
		self.horizontalDividers[section] = {}
		self.horizontalDividers[section].count = 0
	end

	self.horizontalDividers[section][row] = true
	self.horizontalDividers[section].count += 1
	
	self.needsDisplay = true
end


local function horizontalDividerSpaceForSection(self, section)
	if self.horizontalDividers[section] == nil then
		return 0
	end
	
	return self.horizontalDividers[section].count * self.horizontalDividerHeight
end


function horizontalDividerSpaceAboveRow(self, section, row, includePreviousSections)
	local space = 0
	
	if includePreviousSections == true then
		for aboveSection = 1, section-1 do
			if self.horizontalDividers[aboveSection] ~= nil then
				space += (self.horizontalDividers[aboveSection].count * self.horizontalDividerHeight)
			end
		end
	end
	
	if self.horizontalDividers[section] ~= nil then
		for k, _ in pairs(self.horizontalDividers[section]) do
			if type(k) == "number" and k <= row then	-- type check is to filter out the "count" entry
				space += self.horizontalDividerHeight
			end
		end
	end

	return space
end


-- returns left, top, right, bottom for the cell in question. Includes padding
local function paddedEdgesForCell(self, section, row, column)
	
	if section < 1 or row < 1 or column < 1 or section > self.numSections or column > self.numColumns then
		return 0, 0, 0, 0
	end
	
	local numRows = 0
	local numSections = 0
	
	for s = 1, section-1 do
		numRows = numRows + self.numRows[s]
	end
	numRows = numRows + row
	
	local cellAndPaddingWidth = (self.cellPaddingLeft + self.cellWidth + self.cellPaddingRight)
	local cellAndPaddingHeight = (self.cellPaddingTop + self.cellHeight + self.cellPaddingBottom)
	
	local dividersSpace = horizontalDividerSpaceAboveRow(self, section, row, true)
	
	local right = self.contentInsetLeft + (column * cellAndPaddingWidth)
	local bottom = self.contentInsetTop + (section * (self.sectionHeaderPaddingTop + self.sectionHeaderHeight + self.sectionHeaderPaddingBottom)) + (numRows * cellAndPaddingHeight) + dividersSpace
	
	return right - cellAndPaddingWidth, bottom - cellAndPaddingHeight, right, bottom
end


-- returns the tuple (x, y, width, height) representing the frame of the cell, not including padding, relative to the top-right corner of the grid view
function playdate.ui.gridview:getCellBounds(section, row, column, gridWidth)

	assert(self.cellWidth ~= 0 or gridWidth ~= nil, "playdate.ui.gridview:getCellBounds() requires `gridWidth` argument when cell width is zero")

	local l, t, r, b = paddedEdgesForCell(self, section, row, column)
	local width = r-l
	if width == 0 and gridWidth ~= nil then
		width = gridWidth - self.cellPaddingLeft + self.cellPaddingRight - self.contentInsetLeft - self.contentInsetRight
	end

	return l - self.scrollPositionX + self.cellPaddingLeft, t - self.scrollPositionY + self.cellPaddingTop, width - self.cellPaddingLeft - self.cellPaddingRight, b - t - self.cellPaddingTop - self.cellPaddingBottom

end


-- when a scroll method is called before the grid view has been drawn we don't know the size yet, so scrolling must be deferred

local function doDeferredScrollIfNecessary(self)
	if self.deferredScrollFunction ~= nil then
		self.deferredScrollFunction(self, self.deferredArg1, self.deferredArg2, self.deferredArg3, self.deferredArg4)
		self.deferredScrollFunction = nil
		self.deferredArg1 = nil
		self.deferredArg2 = nil
		self.deferredArg3 = nil
		self.deferredArg4 = nil
	end
end


local function deferScroll(self, funcArg, arg1, arg2, arg3, arg4)
	
	if self.drawingHeight <= 0 then	-- view has not drawn yet, defer the scroll
		self.deferredScrollFunction = funcArg
		self.deferredArg1 = arg1
		self.deferredArg2 = arg2
		self.deferredArg3 = arg3
		self.deferredArg4 = arg4
		return true
	end
	return false
end



-- utility function used by drawInRect()
local function calculateContentSizeAndClampScrollPosition(self, drawingWidth, drawingHeight)

	if self.needsDisplay == false then
		return
	end

	local contentWidth, contentHeight

	-- width
	if self.cellWidth > 0 then
		self.contentWidth = self.contentInsetLeft + self.contentInsetRight + (self.numColumns * (self.cellPaddingLeft + self.cellWidth + self.cellPaddingRight))
	else
		self.contentWidth = drawingWidth
	end
	
	-- height
	if self.numSections > 0 and self.numRows[1] > 0 and self.numColumns > 0 then
		local totalRows = 0
		local dividerSpace = 0
		for section = 1, self.numSections do
			totalRows = totalRows + self.numRows[section]
			dividerSpace += horizontalDividerSpaceForSection(self, section)
		end
		
		self.contentHeight = self.contentInsetTop + self.contentInsetBottom + (self.numSections * (self.sectionHeaderHeight + self.sectionHeaderPaddingTop + self.sectionHeaderPaddingBottom)) + (totalRows * (self.cellHeight + self.cellPaddingTop + self.cellPaddingBottom)) + dividerSpace

	else
		self.contentHeight = self.contentInsetTop + self.contentInsetBottom
	end
	
	-- make sure we're not scrolled beyond what we're allowed to be scrolled to
	-- since gridview doesn't have an intrinsic size, this is really the only place this can be done
	if self.scrollPositionX < 0 then
		self.scrollPositionX = 0
	elseif self.scrollPositionX  > self.contentWidth - drawingWidth then
		self.scrollPositionX = math.max(0, self.contentWidth - drawingWidth)
	end
	
	if self.scrollPositionY < 0 then
		self.scrollPositionY = 0
	elseif self.scrollPositionY  > self.contentHeight - drawingHeight then
		self.scrollPositionY = math.max(0, self.contentHeight - drawingHeight)
	end
end


local floor = math.floor

function playdate.ui.gridview:drawInRect(x, y, width, height)

	if width == 0 or height == 0 then
		return
	end
	
	self.drawingX = x
	self.drawingY = y
	self.drawingWidth = width
	self.drawingHeight = height

	local insetX = x + self.contentInsetLeft
	local insetY = y + self.contentInsetTop
	local insetWidth = width - self.contentInsetLeft - self.contentInsetRight
	local insetHeight = height - self.contentInsetTop - self.contentInsetBottom

	local currentImageDrawMode = gfx.getImageDrawMode()
	gfx.setImageDrawMode(gfx.kDrawModeCopy)

	-- draw background image
	if self.backgroundImage ~= nil then
		if self.backgroundImage.slices ~= nil then -- it's a nineslice
			self.backgroundImage:drawInRect(x, y, width, height)
		else -- it's a regular image
			self.backgroundImage:drawTiled(x, y, width, height)
		end
	end

	-- calculate drawing positions
	doDeferredScrollIfNecessary(self)
	calculateContentSizeAndClampScrollPosition(self, width, height)
	
	-- draw cell content and section headers
	
	local clipX, clipY, clipW, clipH = gfx.getClipRect()	-- save the current clipRect
	local tempX, tempY, tempW, tempH = playdate.geometry.rect.fast_intersection(clipX, clipY, clipW, clipH, insetX, insetY, insetWidth, insetHeight)
	gfx.setClipRect(tempX, tempY, tempW, tempH) -- set temporary clip rect
	
	local headerWidth = width - (self.contentInsetLeft + self.contentInsetRight) -- always draw headers full-width
	local cellHeight = self.cellHeight
	local cellWidth = self.cellWidth
	if cellWidth == 0 then
		-- if cellWidth is set to 0, we want to stretch the cells horizontally to fit the table, and section headers always do this
		cellWidth = headerWidth - self.cellPaddingLeft - self.cellPaddingRight
	end

	local drawX = -self.scrollPositionX + self.contentInsetLeft
	local drawY = -self.scrollPositionY + self.contentInsetTop

	-- SECTIONS --
	if self.numSections > 0 and self.numRows[1] > 0 and self.numColumns > 0 then
		
		for section = 1, self.numSections do
			
			-- draw section header
			
			drawY = drawY + self.sectionHeaderPaddingTop	-- add section header top padding
			
			
			if self.sectionHeaderHeight > 0 and drawY + self.sectionHeaderHeight > 0 and drawY < height then
				-- draw the section header, inset by left and right paddings, and by the x passed in by the caller
				self:drawSectionHeader(section, floor(x + drawX + self.sectionHeaderPaddingLeft + self.scrollPositionX), floor(y + drawY), headerWidth - self.sectionHeaderPaddingLeft - self.sectionHeaderPaddingRight, self.sectionHeaderHeight)
			end
			
			drawY = drawY + self.sectionHeaderHeight + self.sectionHeaderPaddingBottom -- add section header height and bottom padding
			
			
			-- ROWS --
			for row = 1, self.numRows[section] do
				
				drawY += self.cellPaddingTop	-- add top cell padding
				
				if self.horizontalDividers[section] ~= nil and self.horizontalDividers[section][row] == true and self.horizontalDividerHeight > 0 then
					self:drawHorizontalDivider(floor(x + drawX), floor(y + drawY), self.contentWidth - (self.contentInsetRight + self.contentInsetLeft), self.horizontalDividerHeight)
					drawY += self.horizontalDividerHeight
				end
				
				-- COLUMNS --
				for col = 1, self.numColumns do
					
					drawX = drawX + self.cellPaddingLeft	-- add left cell padding
					
					if drawY + self.cellHeight > 0 and drawY < height and drawX + cellWidth > 0 and drawX < width then
						-- cell is visible, draw it, offset by caller's x, y
						local selected = (section == self.selectedSection) and (row == self.selectedRow) and (col == self.selectedColumn)
						self:drawCell(section, row, col, selected, floor(x + drawX), floor(y + drawY), cellWidth, self.cellHeight)
					end
					
					drawX = drawX + cellWidth + self.cellPaddingRight	-- add cell width and right padding
					
					if drawY > height then
						break -- don't need to draw anymore
					end
					
				end -- columns
				
				drawX = -self.scrollPositionX + self.contentInsetLeft	--reset for next row
				drawY = drawY + self.cellHeight + self.cellPaddingBottom -- add cell height and bottom padding
				
				if drawY > height then
					break  -- don't need to draw anymore
				end
				
			end -- rows
			
		end -- sections
		
	end
	
	gfx.setClipRect(clipX, clipY, clipW, clipH) -- reset to the original clipRect
	gfx.setImageDrawMode(currentImageDrawMode)
	
	self.needsDisplay = false
end


function playdate.ui.gridview:setNumberOfColumns(num)
	if num > 0 then
		self.numColumns = num	
	else
		print("ERROR: playdate.ui.gridview must be at least one column wide.")
	end
	
	self.needsDisplay = true
end


function playdate.ui.gridview:getNumberOfColumns()
	return self.numColumns
end


function playdate.ui.gridview:setNumberOfSections(num)

	if num > 0 then
		
		if num < self.numSections then
			-- remove unused row counts
			for i = num+1, self.numSections do
				self.numRows[i] = nil
			end
		elseif num > self.numSections then
			-- seed the numRows array to prevent errors
			for i=self.numSections, num do
				if self.numRows[i] == nil then
					self.numRows[i] = 1
				end
			end
		end
		
		self.numSections = num
	else
		print("ERROR: playdate.ui.gridview must contain at least one section.")
	end
	
	self.needsDisplay = true
end

function playdate.ui.gridview:getNumberOfSections()
	return self.numSections
end


function playdate.ui.gridview:setNumberOfRowsInSection(section, num)
	if num > 0 then
		self.numRows[section] = num
		if self.numSections < section then self:setNumberOfSections(section) end
	else
		print("ERROR: playdate.ui.gridview sections must contain at least one row.")
	end
	
	self.needsDisplay = true
end

function playdate.ui.gridview:setNumberOfRows(...)
	
	local lastSection = select("#",...)
	
	if lastSection > self.numSections then
		self:setNumberOfSections(lastSection)
	end
	
	for section=1, lastSection do
		local num = select(section, ...)
		self:setNumberOfRowsInSection(section, num)
	end
	
	self.needsDisplay = true
end

function playdate.ui.gridview:getNumberOfRowsInSection(section)
	return self.numRows[section]
end


function playdate.ui.gridview:setCellSize(width, height)
	if cellWidth == 0 then
		self.numColumns = 1
	end
	self.cellWidth = width
	self.cellHeight = height
	
	self.needsDisplay = true
end

function playdate.ui.gridview:setSectionHeaderHeight(height)
	if height and height > 0 then
		self.sectionHeaderHeight = height
	else
		self.sectionHeaderHeight = 0
	end
	
	self.needsDisplay = true
end

function playdate.ui.gridview:getSectionHeaderHeight()
	return self.sectionHeaderHeight
end

function playdate.ui.gridview:setContentInset(left, right, top, bottom)
	assert(left ~= nil and right ~= nil and top ~= nil and bottom ~=nil, "nil value passed to playdate.ui.gridview:setContentInset()")
	self.contentInsetTop = top
	self.contentInsetBottom = bottom
	self.contentInsetLeft = left
	self.contentInsetRight = right
	
	self.needsDisplay = true
end


function playdate.ui.gridview:setCellPadding(left, right, top, bottom)
assert(left ~= nil and right ~= nil and top ~= nil and bottom ~=nil, "nil value passed to playdate.ui.gridview:setCellPadding()")
	self.cellPaddingTop = top
	self.cellPaddingBottom = bottom
	self.cellPaddingLeft = left
	self.cellPaddingRight = right
	
	self.needsDisplay = true
end


function playdate.ui.gridview:setSectionHeaderPadding(left, right, top, bottom)
assert(left ~= nil and right ~= nil and top ~= nil and bottom ~=nil, "nil value passed to playdate.ui.gridview:setSectionHeaderPadding()")
	self.sectionHeaderPaddingTop = top
	self.sectionHeaderPaddingBottom = bottom
	self.sectionHeaderPaddingLeft = left
	self.sectionHeaderPaddingRight = right
	
	self.needsDisplay = true
end



function playdate.ui.gridview:setScrollPosition(x, y, animated)
	
	if animated == nil then animated = true end
		
	if animated == true then
		animateScroll(self, x, y)
	else
		self.scrollPositionX = x
		self.scrollPositionY = y
	end
	
	self.needsDisplay = true
end


function playdate.ui.gridview:getScrollPosition()
	return self.scrollPositionX, self.scrollPositionY
end


-- attempts to change the scrolling as little as possible, just pushing it to the closest location that will cause the cell (and it's padding) to be visible.
function playdate.ui.gridview:scrollToCell(section, row, column, animated)
	
	if animated == nil then animated = true end
	
	if deferScroll(self, playdate.ui.gridview.scrollToCell, section, row, column, animated) then
		return
	end

	local left, top, right, bottom = paddedEdgesForCell(self, section, row, column)
	
	local scrollX = self.scrollPositionX
	local scrollY = self.scrollPositionY

	-- if the cell is at and edge, scroll all the way to that edge
	if section == 1 and row == 1 then -- top row
		scrollY = 0
	elseif section == self.numSections and row == self.numRows[section] then -- last row
		scrollY = self.contentHeight - self.drawingHeight
	else
		-- not on an edge, need to figure it out for real
		if top < self.scrollPositionY then
			scrollY = top
		elseif bottom > self.scrollPositionY + self.drawingHeight then
			scrollY = bottom - self.drawingHeight
		end
	end
	
	if column == 1 then	-- first column
		scrollX = 0
	elseif column == self.numColumns then -- last column
		scrollX = self.contentWidth - self.drawingWidth
	else -- not an edge, need to figure it out for real
		if left < self.scrollPositionX then
			scrollX = left
		elseif right > self.scrollPositionX + self.drawingWidth then
			scrollX = right - self.drawingWidth
		end
	end

	self:setScrollPosition(scrollX, scrollY, animated)
end


function playdate.ui.gridview:scrollCellToCenter(section, row, column, animated)
	
	if animated == nil then animated = true end

	if deferScroll(self, playdate.ui.gridview.scrollCellToCenter, section, row, column, animated) then
		return
	end

	local left, top, right, bottom = paddedEdgesForCell(self, section, row, column)
	local scrollX = left - (self.drawingWidth + self.contentInsetLeft - self.contentInsetRight)/2 + (right-left)/2
	local scrollY = top - (self.drawingHeight + self.contentInsetTop - self.contentInsetBottom)/2 + (bottom-top)/2
	self:setScrollPosition(scrollX, scrollY, animated)
end


local function scrollToSelectedCell(self, animated)
	if animated == nil then animated = true end
	if self.scrollCellsToCenter then
		self:scrollCellToCenter(self.selectedSection, self.selectedRow, self.selectedColumn, animated)
	else
		self:scrollToCell(self.selectedSection, self.selectedRow, self.selectedColumn, animated)
	end
end


-- tableview convenience function - assumes only one section and one column
function playdate.ui.gridview:scrollToRow(row, animated)
	if animated == nil then animated = true end
	if self.scrollCellsToCenter then
		self:scrollCellToCenter(1, row, 1, animated)
	else
		self:scrollToCell(1, row, 1, animated)
	end
end


function playdate.ui.gridview:scrollToTop(animated)
	if animated == nil then animated = true end
	self:setScrollPosition(0, self.scrollPositionY, animated)
end


function playdate.ui.gridview:setSelection(section, row, column)
	self.selectedSection = section
	self.selectedRow = row
	if column == nil then
		self.selectedColumn = 1
	else
		self.selectedColumn = column
	end
	
	self.needsDisplay = true
end


function playdate.ui.gridview:getSelection()
	return self.selectedSection, self.selectedRow, self.selectedColumn
end

-- tableview convenience function - assumes only one section and one column
function playdate.ui.gridview:setSelectedRow(row)
	self:setSelection(1, row, 1)
end

function playdate.ui.gridview:getSelectedRow()
	return self.selectedRow
end

-- will move the selection to the next row. If wrapSelection is true and the last row in the last section is selected, the first row in the first section will be selected. If it is false the selection will not change. Returns the newly selected section and row
-- scrollToSelection is true by default
function playdate.ui.gridview:selectNextRow(wrapSelection, scrollToSelection, animate)

	if scrollToSelection == nil then scrollToSelection = true end
	if animate == nil then animate = true end

	local lastSection = self.numSections
	local currentSection = self.selectedSection
	local currentRow = self.selectedRow
	
	local nextSelectionSection = currentSection
	local nextSelectionRow = currentRow
	
	if currentSection == lastSection and currentRow == self.numRows[lastSection] then -- wrap from last section/row to first
		if wrapSelection then
			nextSelectionSection = 1
			nextSelectionRow = 1
		end
	elseif currentRow == self.numRows[currentSection]  then -- last row in the section, but not the last section
		nextSelectionSection = currentSection + 1
		nextSelectionRow = 1
	else
		nextSelectionRow = currentRow + 1
	end

	self.selectedSection = nextSelectionSection
	self.selectedRow = nextSelectionRow
	
	if scrollToSelection then
		scrollToSelectedCell(self, animate)
	end
	
	self.needsDisplay = true
	
	return nextSelectionSection, nextSelectionRow
end


function playdate.ui.gridview:selectPreviousRow(wrapSelection, scrollToSelection, animate)

	if scrollToSelection == nil then scrollToSelection = true end
	if animate == nil then animate = true end

	local lastSection = self.numSections
	local currentSection = self.selectedSection
	local currentRow = self.selectedRow
	
	local nextSelectionSection = currentSection
	local nextSelectionRow = currentRow

	if currentSection == 1 and currentRow == 1 then -- wrap from first section/row to last
		if wrapSelection then
			nextSelectionSection = self.numSections
			nextSelectionRow = self.numRows[lastSection]
		end
	elseif currentRow == 1 then -- first row in the section, but not in the top section
		nextSelectionSection = currentSection - 1
		nextSelectionRow = self.numRows[nextSelectionSection]
	else
		nextSelectionRow = currentRow - 1
	end
	
	self.selectedSection = nextSelectionSection
	self.selectedRow = nextSelectionRow
	
	if scrollToSelection then
		scrollToSelectedCell(self, animate)
	end
	
	self.needsDisplay = true
		
	return nextSelectionSection, nextSelectionRow
end


function playdate.ui.gridview:selectNextColumn(wrapSelection, scrollToSelection, animate)

	if scrollToSelection == nil then scrollToSelection = true end
	if animate == nil then animate = true end
	
	local newColumn = self.selectedColumn + 1
	
	if newColumn > self.numColumns then -- move to the next row
		if wrapSelection then
			newColumn = 1
			if self.changeRowOnColumnWrap then
				self:selectNextRow(wrapSelection)
			end
		else
			newColumn = self.selectedColumn
		end
	end
	
	self.selectedColumn = newColumn
	
	if scrollToSelection then
		scrollToSelectedCell(self, animate)
	end
	
	self.needsDisplay = true
	
	return newColumn
end


function playdate.ui.gridview:selectPreviousColumn(wrapSelection, scrollToSelection, animate)

	if scrollToSelection == nil then scrollToSelection = true end
	if animate == nil then animate = true end
	
	local newColumn = self.selectedColumn - 1
	
	if newColumn < 1 then -- move to the next row
		if wrapSelection then
			newColumn = self.numColumns
			if self.changeRowOnColumnWrap then
				self:selectPreviousRow(wrapSelection)
			end
		else
			newColumn = 1
		end
	end
	
	self.selectedColumn = newColumn
	
	if scrollToSelection then
		scrollToSelectedCell(self, animate)
	end
	
	self.needsDisplay = true
	
	return newColumn
end


function playdate.ui.gridview:setScrollDuration(ms)
	self.timerX.duration = ms or 0
	self.timerY.duration = ms or 0
end
	
