--[[
Copyright 2016, Crank Software Inc.
All Rights Reserved.
For more information email info@cranksoftware.com
** FOR DEMO PURPOSES ONLY **
]]--

--- This is inspired by the JS version:
-- https://github.com/roeierez/infinite-list
--
-- In place of a DOM element, we pass the callbacks two integers:
-- * Item index: This is the index of the entry we are looking at directly
-- * Table index: This is the table index we are showing at
-- numItems = Number of items of virtual data
-- numCells = Number of cells in the table (numCells <= numItems)
-- numVisible = Number of table cells visible in the table (numVisible <= numCells)
--
-- firstItem = Index of the first item (Table Cell 1)
-- lastItem = Index of the last item (Table Cell numCells)
--
--visibleUICells < settableUICells < virtualDataCells < realDataCells

InfiniteList = {}

function InfiniteList:new(tableName, renderCB, numItems, numRows, numCols)
  local newList = {}
  setmetatable(newList, self)
  self.__index = self
  
  if(numCols == nil) then
    numCols = 1
  end
  
  local numCells = numRows * numCols
  self:dbg("new infinite table, numItems: %d numCells:%d", numItems, numCells)
  
  newList.tableName = tableName
  newList.renderCB = renderCB
  
  local tInfo = gre.get_table_attrs(newList.tableName, "width", "height")
  newList.tableHeight = tInfo.height
  newList.tableWidth = tInfo.width  
  newList.numCols = numCols
  newList.numRows = numRows
  
  local cInfo = gre.get_table_cell_attrs(newList.tableName, 1, 1, "width", "height")
  newList.cellHeight = cInfo.height
  newList.cellWidth = cInfo.width

  --newList.numVisible = (newList.tableHeight + newList.cellHeight) / newList.cellHeight
  newList.numVisibleYCells = math.ceil(newList.tableHeight / newList.cellHeight)
  if(newList.numRows < newList.numVisibleYCells) then
    newList.numVisibleYCells = newList.numRows
  end
  
  newList.numVisibleXCells = math.ceil(newList.tableWidth / newList.cellWidth)
  if(newList.numCols < newList.numVisibleXCells) then
    newList.numVisibleXCells = newList.numCols
  end
  
  newList.numVisible = newList.numVisibleYCells * newList.numVisibleXCells
  if(numItems == nil) then
    numItems = 10000
  end
  newList.numItems = numItems
  
--  if(numCells == nil) then
--    numCells = newList.numVisibleYCells * 4
--  end
  
  if(numCells > numItems) then
    numRows = math.ceil(numItems / numCols)
  end
  
  numCells = numRows * numCols
  
  newList.numCells = numCells
  newList.numRows = numRows
 
  -- Initialize the list with content at the top
  newList.firstItem = 1   
  newList.lastItem = newList.numCells --1 + newList.numCells
  
  -- Seed the list with the current content
  newList:SyncCellsToData()
  
  -- Resize the table control
  gre.set_table_attrs(newList.tableName, { rows = newList.numRows, yoffset = 0 })
  
  newList.sliderSize = newList.numItems
  newList.sliderIndex = 0

  return newList
end

function InfiniteList:dbg(fmt, ...)
  --local msg = string.format(fmt, unpack(arg))
  --print(msg)
end

-- Convert a 1 based table cell index to a 1 based data index
function InfiniteList:GetDataIndexFromCell(ci)
  return self.firstItem + (ci - 1)  
end

-- Convert a 1 based data index to a 1 based table cell index
function InfiniteList:GetCellIndexFromData(di)
  return di - (self.firstItem - 1)
end

function InfiniteList:RefreshCell(cellIndex)
  self:SyncCellsToData(cellIndex, cellIndex)
end

--Synchronize a set of table cells to the backing store data
function InfiniteList:SyncCellsToData(cellStartIndex, cellEndIndex)
  if(cellStartIndex == nil) then
    cellStartIndex = 1
    cellEndIndex = self.numCells
  end
  
  local di = self:GetDataIndexFromCell(cellStartIndex)
  
  local row = cellStartIndex - 1
  local data = {}
  for ci=cellStartIndex,cellEndIndex,self.numCols do
    local columnData = self:ItemRenderer(di, ci)
    row = row + 1
    --TODO Support a short hand for a single column of data
    for c=1,#columnData do
      local entry = columnData[c]
      for k,v in pairs(entry) do
        local nk = string.format("%s.%s.%d.%d", self.tableName, k, row, c)
        self:dbg("Setting %s, %s", nk, v)
        data[nk] = v
      end
    end
    di = di + self.numCols
    --TODO: Measure some sort of insertion index here that makes sense ..
  end
  
  gre.set_data(data)
end

-- Return a table with a set of local table variables for each column 
function InfiniteList:ItemRenderer(dataIndex, cellIndex)
    self:dbg("itemRenderer, %d dataIndex %d cellIndex", dataIndex, cellIndex)
    return self:renderCB(dataIndex, cellIndex)
end

-- When we hit a scroll threshold where we are 80% through the data going in a 
-- direction then we need to kick off a cycle to shift the variables around.
-- This gives us a range:
-- [0 - 20%] (20% - 80%) [80% - 100%]
-- When we drift into the outer ranges, then we shift the content by 20% to
-- the 40%/60% window in either direction.
function InfiniteList:AutoSync()
  local tInfo = gre.get_table_attrs(self.tableName, "yoffset")
  if(tInfo.yoffset >= 0) then
    return
  end
  
  -- Add N (1) to represent the percent we are interested in
  local offscreenCount = (math.floor((-1 * tInfo.yoffset) / self.cellHeight)) * self.numCols;
  local thresholdCount = math.ceil(self.numRows * .20) * self.numCols
  local topThreshold = thresholdCount
  local bottomThreshold = self.numCells - self.numVisible - thresholdCount
  
  self:dbg("Content Check %d offscreen %d top %d bottom", offscreenCount, topThreshold, bottomThreshold)

  local newFirstItem = self.firstItem
  if(offscreenCount <= topThreshold) then
    newFirstItem = self.firstItem - thresholdCount
  elseif(offscreenCount > bottomThreshold) then
    newFirstItem = self.firstItem + thresholdCount
    if(newFirstItem + self.numCells - 1 > self.numItems) then
      newFirstItem = self.numItems - self.numCells + 1
    end
  end

  if(newFirstItem < 1) then
    newFirstItem = 1
  end
  
  self.sliderSize = (self.numItems - self.numVisible + 1)
  self.sliderIndex = self.firstItem + offscreenCount
    
  if(newFirstItem == self.firstItem) then
    return
  end

  self:dbg("Change top virtual index from %d to %d, %d", self.firstItem, newFirstItem, thresholdCount)
  -- ie Old = 1, New = 9 -> Add to the yoffset value by cellHeight * difference 
  local rowDiff =  math.floor(newFirstItem/self.numCols) - math.floor(self.firstItem/self.numCols)   -- 9 - 1 = 8 
  local yPixDiff = rowDiff * self.cellHeight  -- 8 * height 
  
  self.firstItem = newFirstItem
  self.lastItem = self.numCells + self.firstItem
  
  self:SyncCellsToData()

  local newYOffset = tInfo.yoffset + yPixDiff
  gre.set_table_attrs(self.tableName, { ["yoffset"] = newYOffset })

end

--- @brief Used to set a new position in the list.
--- @param position - relative position to set the list to (float range 0.0 to 1.0)
function InfiniteList:SetIndex(position)
  if self.numItems < self.numVisible then
    return
  end
  if self.numItems < self.numCells then
    self.firstItem = 1
    self.lastItem = self.numCells + self.firstItem
  else
    local index = math.floor(position * (self.numItems - 1)) + 1
    self.firstItem = index - math.floor(self.numCells / 2)
    if self.firstItem < 1 then
      self.firstItem = 1
    end
    self.lastItem = self.numCells + self.firstItem
    if self.lastItem > self.numItems + 1 then
      self.firstItem = self.numItems - self.numCells + 1
      self.lastItem = self.numItems + 1
    end
    
    position = (index - self.firstItem) / (self.numCells - 1)
    if position < 0 then position = 0 elseif position > 1 then position = 1 end
  end
  
  local newYOffset = math.floor(position * (self.numRows - self.numVisibleYCells + 1)  * self.cellHeight + 0.5)
  gre.set_table_attrs(self.tableName, { ["yoffset"] = -newYOffset })

  self:SyncCellsToData()
end

--- @brief Centres the specified index on the screen 
--- @param index - item to centre.
function InfiniteList:CentreItem(index)
  if self.numItems < self.numVisible then
    return
  end
  if self.numItems < self.numCells then
    self.firstItem = 1
    self.lastItem = self.numCells + self.firstItem
  else
    self.firstItem = index - math.floor(self.numCells / 2)
    if self.firstItem < 1 then
      self.firstItem = 1
    end
    self.lastItem = self.numCells + self.firstItem
    if self.lastItem > self.numItems + 1 then
      self.firstItem = self.numItems - self.numCells + 1
      self.lastItem = self.numItems + 1
    end
  end
  
  local yoffset = (self.tableHeight - self.cellHeight) / 2 - (index - self.firstItem) * self.cellHeight

  if yoffset > 0 then
    yoffset = 0
  elseif yoffset < self.tableHeight - (self.numRows * self.cellHeight) then
    yoffset = self.tableHeight - (self.numRows * self.cellHeight) 
  end

  gre.set_table_attrs(self.tableName, { yoffset = yoffset })

  self:SyncCellsToData()
end

function InfiniteList:ScrollableList()
  return self.numCells >= self.numVisibleYCells
end


