-- Copyright 2008-2020 Dan Dumont
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.

-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.

-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <https://www.gnu.org/licenses/>.

local MAJOR, MINOR = "ScrollingTable", tonumber("@project-timestamp@") or 40300;
local lib, oldminor = LibStub:NewLibrary(MAJOR, MINOR);
if not lib then
	return; -- Already loaded and no upgrade necessary.
end

do
	lib.SORT_ASC = 1;
	lib.SORT_DSC = 2;

	local defaultcolor = { ["r"] = 1.0, ["g"] = 1.0, ["b"] = 1.0, ["a"] = 1.0 };
	local defaulthighlight = { ["r"] = 1.0, ["g"] = 0.9, ["b"] = 0.0, ["a"] = 0.5 };
	local defaulthighlightblank = { ["r"] = 0.0, ["g"] = 0.0, ["b"] = 0.0, ["a"] = 0.0 };
	local lrpadding = 2.5;

	local ScrollPaneBackdrop = {
		bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
		edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
		tile = true, tileSize = 16, edgeSize = 16,
		insets = { left = 3, right = 3, top = 5, bottom = 3 }
	};

	local SetHeight = function(self)
		self.frame:SetHeight( (self.displayRows * self.rowHeight) + 10);
		self:Refresh();
	end

	local SetWidth = function(self)
		local width = 13;
		for num, col in pairs(self.cols) do
			width = width + col.width;
		end
		self.frame:SetWidth(width+20);
		self:Refresh();
	end

	--- API for a ScrollingTable table
	-- @name SetHighLightColor
	-- @description Set the row highlight color of a frame ( cell or row )
	-- @usage st:SetHighLightColor(rowFrame, color)
	-- @see http://www.wowace.com/addons/lib-st/pages/colors/
	local function SetHighLightColor (self, frame, color)
		if not frame.highlight then
			frame.highlight = frame:CreateTexture(nil, "OVERLAY");
			frame.highlight:SetAllPoints(frame);
		end
		frame.highlight:SetColorTexture(color.r, color.g, color.b, color.a);
	end

	local FireUserEvent = function (self, frame, event, handler, ...)
		if not handler( ...) then
			if self.DefaultEvents[event] then
				self.DefaultEvents[event]( ...);
			end
		end
	end

	--- API for a ScrollingTable table
	-- @name RegisterEvents
	-- @description Set the event handlers for various ui events for each cell.
	-- @usage st:RegisterEvents(events, true)
	-- @see http://www.wowace.com/addons/lib-st/pages/ui-events/
	local function RegisterEvents (self, events, fRemoveOldEvents)
		local table = self; -- save for closure later

		for i, row in ipairs(self.rows) do
			for j, col in ipairs(row.cols) do
				-- unregister old events.
				if fRemoveOldEvents and self.events then
					for event, handler in pairs(self.events) do
						col:SetScript(event, nil);
					end
				end

				-- register new ones.
				for event, handler in pairs(events) do
					col:SetScript(event, function(cellFrame, ...)
						local realindex = table.filtered[i+table.offset];
						table:FireUserEvent(col, event, handler, row, cellFrame, table.data, table.cols, i, realindex, j, table, ... );
					end);
				end
			end
		end

		for j, col in ipairs(self.head.cols) do
			-- unregister old events.
			if fRemoveOldEvents and self.events then
				for event, handler in pairs(self.events) do
					col:SetScript(event, nil);
				end
			end

			-- register new ones.
			for event, handler in pairs(events) do
				col:SetScript(event, function(cellFrame, ...)
					table:FireUserEvent(col, event, handler, self.head, cellFrame, table.data, table.cols, nil, nil, j, table, ...);
				end);
			end
		end
		self.events = events;
	end

	--- API for a ScrollingTable table
	-- @name SetDisplayRows
	-- @description Set the number and height of displayed rows
	-- @usage st:SetDisplayRows(10, 15)
	local function SetDisplayRows (self, num, rowHeight)
		local table = self; -- reference saved for closure
		-- should always set columns first
		self.displayRows = num;
		self.rowHeight = rowHeight;
		if not self.rows then
			self.rows = {};
		end
		for i = 1, num do
			local row = self.rows[i];
			if not row then
				row = CreateFrame("Button", self.frame:GetName().."Row"..i, self.frame);
				self.rows[i] = row;
				if i > 1 then
					row:SetPoint("TOPLEFT", self.rows[i-1], "BOTTOMLEFT", 0, 0);
					row:SetPoint("TOPRIGHT", self.rows[i-1], "BOTTOMRIGHT", 0, 0);
				else
					row:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 4, -5);
					row:SetPoint("TOPRIGHT", self.frame, "TOPRIGHT", -4, -5);
				end
				row:SetHeight(rowHeight);
			end

			if not row.cols then
				row.cols = {};
			end
			for j = 1, #self.cols do
				local col = row.cols[j];
				if not col then
					col = CreateFrame("Button", row:GetName().."col"..j, row);
					col.text = row:CreateFontString(col:GetName().."text", "OVERLAY", "GameFontHighlightSmall");
					row.cols[j] = col;
					local align = self.cols[j].align or "LEFT";
					col.text:SetJustifyH(align);
					col:EnableMouse(true);
					col:RegisterForClicks("AnyUp");

					if self.events then
						for event, handler in pairs(self.events) do
							col:SetScript(event, function(cellFrame, ...)
								if table.offset then
									local realindex = table.filtered[i+table.offset];
									table:FireUserEvent(col, event, handler, row, cellFrame, table.data, table.cols, i, realindex, j, table, ... );
								end
							end);
						end
					end
				end

				if j > 1 then
					col:SetPoint("LEFT", row.cols[j-1], "RIGHT", 0, 0);
				else
					col:SetPoint("LEFT", row, "LEFT", 2, 0);
				end
				col:SetHeight(rowHeight);
				col:SetWidth(self.cols[j].width);
				col.text:SetPoint("TOP", col, "TOP", 0, 0);
				col.text:SetPoint("BOTTOM", col, "BOTTOM", 0, 0);
				col.text:SetWidth(self.cols[j].width - 2*lrpadding);
			end
			j = #self.cols + 1;
			col = row.cols[j];
			while col do
				col:Hide();
				j = j + 1;
				col = row.cols[j];
			end
		end

		for i = num + 1, #self.rows do
			self.rows[i]:Hide();
		end

		self:SetHeight();
	end

	--- API for a ScrollingTable table
	-- @name SetDisplayCols
	-- @description Set the column info for the scrolling table
	-- @usage st:SetDisplayCols(cols)
	-- @see http://www.wowace.com/addons/lib-st/pages/create-st/#w-cols
	local function SetDisplayCols (self, cols)
		local table = self; -- reference saved for closure
		self.cols = cols;

		local row = self.head
		if not row then
			row = CreateFrame("Frame", self.frame:GetName().."Head", self.frame);
			row:SetPoint("BOTTOMLEFT", self.frame, "TOPLEFT", 4, 0);
			row:SetPoint("BOTTOMRIGHT", self.frame, "TOPRIGHT", -4, 0);
			row:SetHeight(self.rowHeight);
			row.cols = {};
			self.head = row;
		end
		for i = 1, #cols do
			-- Backwards compat: Check all provided columns for sort-related strings and convert them to numbers.
			-- The past string-system used incorrect names for the directions. This conversion preserves that behavior.
			if type(cols[i].defaultsort) == "string" then
				if cols[i].defaultsort:lower() == "asc" then cols[i].defaultsort = lib.SORT_DSC;
				elseif cols[i].defaultsort:lower() == "dsc" then cols[i].defaultsort = lib.SORT_ASC;
				else cols[i].defaultsort = nil; end
			end
			if type(cols[i].sort) == "string" then
				if cols[i].sort:lower() == "asc" then cols[i].sort = lib.SORT_DSC;
				elseif cols[i].sort:lower() == "dsc" then cols[i].sort = lib.SORT_ASC;
				else cols[i].sort = nil; end
			end

			-- Now proceed to set up the columns.
			local colFrameName = row:GetName().."Col"..i;
			local col = getglobal(colFrameName);
			if not col then
				col = CreateFrame("Button", colFrameName, row);
				col:RegisterForClicks("AnyUp");	 -- LS: right clicking on header

				if self.events then
					for event, handler in pairs(self.events) do
						col:SetScript(event, function(cellFrame, ...)
							table:FireUserEvent(col, event, handler, row, cellFrame, table.data, table.cols, nil, nil, i, table, ...);
						end);
					end
				end
			end
			row.cols[i] = col;

			local fs = col:GetFontString() or col:CreateFontString(col:GetName().."fs", "OVERLAY", "GameFontHighlightSmall");
			fs:SetAllPoints(col);
			fs:SetPoint("LEFT", col, "LEFT", lrpadding, 0);
			fs:SetPoint("RIGHT", col, "RIGHT", -lrpadding, 0);
			local align = cols[i].align or "LEFT";
			fs:SetJustifyH(align);

			col:SetFontString(fs);
			fs:SetText(cols[i].name);
			fs:SetTextColor(1.0, 1.0, 1.0, 1.0);
			col:SetPushedTextOffset(0,0);

			if i > 1 then
				col:SetPoint("LEFT", row.cols[i-1], "RIGHT", 0, 0);
			else
				col:SetPoint("LEFT", row, "LEFT", 2, 0);
			end
			col:SetHeight(self.rowHeight);
			col:SetWidth(cols[i].width);

			local color = cols[i].bgcolor;
			if (color) then
				local colibg = "col"..i.."bg";
				local bg = self.frame[colibg];
				if not bg then
					bg = self.frame:CreateTexture(nil, "OVERLAY");
					self.frame[colibg] = bg;
				end
				bg:SetPoint("BOTTOM", self.frame, "BOTTOM", 0, 4);
				bg:SetPoint("TOPLEFT", col, "BOTTOMLEFT", 0, -4);
				bg:SetPoint("TOPRIGHT", col, "BOTTOMRIGHT", 0, -4);
				bg:SetColorTexture(color.r, color.g, color.b, color.a);
			end
		end

		self:SetDisplayRows(self.displayRows, self.rowHeight);
		self:SetWidth();
	end

	--- API for a ScrollingTable table
	-- @name Show
	-- @description Used to show the scrolling table when hidden.
	-- @usage st:Show()
	local function Show (self)
		self.frame:Show();
		self.scrollframe:Show();
		self.showing = true;
	end

	--- API for a ScrollingTable table
	-- @name Hide
	-- @description Used to hide the scrolling table when shown.
	-- @usage st:Hide()
	local function Hide (self)
		self.frame:Hide();
		self.showing = false;
	end

	--- API for a ScrollingTable table
	-- @name SortData
	-- @description Resorts the table using the rules specified in the table column info.
	-- @usage st:SortData()
	-- @see http://www.wowace.com/addons/lib-st/pages/create-st/#w-defaultsort
	local function SortData (self)
		-- sanity check
		if not(self.sorttable) or (#self.sorttable ~= #self.data)then
			self.sorttable = {};
		end
		if #self.sorttable ~= #self.data then
			for i = 1, #self.data do
				self.sorttable[i] = i;
			end
		end

		-- go on sorting
		local i, sortby = 1, nil;
		while i <= #self.cols and not sortby do
			if self.cols[i].sort then
				sortby = i;
			end
			i = i + 1;
		end
		if sortby then
			table.sort(self.sorttable, function(rowa, rowb)
				local column = self.cols[sortby];
				if column.comparesort then
					return column.comparesort(self, rowa, rowb, sortby);
				else
					return self:CompareSort(rowa, rowb, sortby);
				end
			end);
		end
		self.filtered = self:DoFilter();
		self:Refresh();
	end

	local StringToNumber = function(str)
		if str == "" then
			return 0;
		else
			return tonumber(str)
		end
	end

	--- API for a ScrollingTable table
	-- @name CompareSort
	-- @description CompareSort function used to determine how to sort column values.  Can be overridden in column data or table data.
	-- @usage used internally.
	-- @see Core.lua
	local function CompareSort (self, rowa, rowb, sortbycol)
		local cella, cellb = self:GetCell(rowa, sortbycol), self:GetCell(rowb, sortbycol);
		local a1, b1 = cella, cellb;
		if type(a1) == 'table' then
			a1 = a1.value;
		end
		if type(b1) == 'table' then
			b1 = b1.value;
		end
		local column = self.cols[sortbycol];

		if type(a1) == "function" then
			if (cella.args) then
				a1 = a1(unpack(cella.args))
			else
				a1 = a1(self.data, self.cols, rowa, sortbycol, self);
			end
		end
		if type(b1) == "function" then
			if (cellb.args) then
				b1 = b1(unpack(cellb.args))
			else
				b1 = b1(self.data, self.cols, rowb, sortbycol, self);
			end
		end

		if type(a1) ~= type(b1) then
			local typea, typeb = type(a1), type(b1);
			if typea == "number" and typeb == "string" then
				if tonumber(b1) then -- is it a number in a string?
					b1 = StringToNumber(b1); -- "" = 0
				else
					a1 = tostring(a1);
				end
			elseif typea == "string" and typeb == "number" then
				if tonumber(a1) then -- is it a number in a string?
					a1 = StringToNumber(a1); -- "" = 0
				else
					b1 = tostring(b1);
				end
			end
		end

		if a1 == b1 then
			if column.sortnext then
				local nextcol = self.cols[column.sortnext];
				if not(nextcol.sort) then
					if nextcol.comparesort then
						return nextcol.comparesort(self, rowa, rowb, column.sortnext);
					else
						return self:CompareSort(rowa, rowb, column.sortnext);
					end
				else
					return false;
				end
			else
				return false;
			end
		else
			local direction = column.sort or column.defaultsort or lib.SORT_DSC;
			if direction == lib.SORT_ASC then
				return a1 < b1;
			else
				return a1 > b1;
			end
		end
	end

	local Filter = function(self, rowdata)
		return true;
	end

	--- API for a ScrollingTable table
	-- @name SetFilter
	-- @description Set a display filter for the table.
	-- @usage st:SetFilter( function (self, ...) return true end )
	-- @see http://www.wowace.com/addons/lib-st/pages/filtering-the-scrolling-table/
	local function SetFilter (self, Filter)
		self.Filter = Filter;
		self:SortData();
	end

	local DoFilter = function(self)
		local result = {};
		for row = 1, #self.data do
			local realrow = self.sorttable[row];
			local rowData = self:GetRow(realrow);
			if self:Filter(rowData) then
				table.insert(result, realrow);
			end
		end
		return result;
	end

	function GetDefaultHighlightBlank(self)
		return self.defaulthighlightblank;
	end

	function SetDefaultHighlightBlank(self, red, green, blue, alpha)
		if not self.defaulthighlightblank then
			self.defaulthighlightblank = defaulthighlightblank;
		end

		if red then self.defaulthighlightblank["r"] = red; end
		if green then self.defaulthighlightblank["g"] = green; end
		if blue then self.defaulthighlightblank["b"] = blue; end
		if alpha then self.defaulthighlightblank["a"] = alpha; end
	end

	function GetDefaultHighlight(self)
		return self.defaulthighlight;
	end

	function SetDefaultHighlight(self, red, green, blue, alpha)
		if not self.defaulthighlight then
			self.defaulthighlight = defaulthighlight;
		end

		if red then self.defaulthighlight["r"] = red; end
		if green then self.defaulthighlight["g"] = green; end
		if blue then self.defaulthighlight["b"] = blue; end
		if alpha then self.defaulthighlight["a"] = alpha; end
	end

	--- API for a ScrollingTable table
	-- @name EnableSelection
	-- @description Turn on or off selection on a table according to flag.  Will not refresh the table display.
	-- @usage st:EnableSelection(true)
	local function EnableSelection(self, flag)
		self.fSelect = flag;
	end

	--- API for a ScrollingTable table
	-- @name ClearSelection
	-- @description Clear the currently selected row.  You should not need to refresh the table.
	-- @usage st:ClearSelection()
	local function ClearSelection(self)
		self:SetSelection(nil);
	end

	--- API for a ScrollingTable table
	-- @name SetSelection
	-- @description Sets the currently selected row to 'realrow'.  Realrow is the unaltered index of the data row in your table. You should not need to refresh the table.
	-- @usage st:SetSelection(12)
	local function SetSelection(self, realrow)
    if self.multiselection then
      if realrow then
        local selected = self:GetRow(realrow)
        if selected and selected.cols then -- Handle column titles click
          self.selected:Set(realrow);
        end
      else
        self.selected:Clear();
      end
    else
		  self.selected = realrow;
    end
		self:Refresh();
	end

	--- API for a ScrollingTable table
	-- @name GetSelection
	-- @description Gets the currently selected to row.  Return will be the unaltered index of the data row that is selected.
	-- @usage st:GetSelection()
	local function GetSelection(self)
		if self.multiselection then
			return self.selected:Get()
		else
			return self.selected;
		end
	end

	--- API for a ScrollingTable table
	-- @name DoCellUpdate
	-- @description Cell update function used to paint each cell.  Can be overridden in column data or table data.
	-- @usage used internally.
	-- @see http://www.wowace.com/addons/lib-st/pages/docell-update/
	local function DoCellUpdate (rowFrame, cellFrame, data, cols, row, realrow, column, fShow, table, ...)
		if fShow then
			local rowdata = table:GetRow(realrow);
			local celldata = table:GetCell(rowdata, column);

			local cellvalue = celldata;
			if type(celldata) == "table" then
				cellvalue = celldata.value;
			end
			if type(cellvalue) == "function" then
				if celldata.args then
					cellFrame.text:SetText(cellvalue(unpack(celldata.args)));
				else
					cellFrame.text:SetText(cellvalue(data, cols, realrow, column, table));
				end
			else
				cellFrame.text:SetText(cellvalue);
			end

			local color = nil;
			if type(celldata) == "table" then
				color = celldata.color;
			end

			local colorargs = nil;
			if not color then
			 	color = cols[column].color;
			 	if not color then
			 		color = rowdata.color;
			 		if not color then
			 			color = defaultcolor;
			 		else
			 			colorargs = rowdata.colorargs;
			 		end
			 	else
			 		colorargs = cols[column].colorargs;
			 	end
			else
				colorargs = celldata.colorargs;
			end
			if type(color) == "function" then
				if colorargs then
					color = color(unpack(colorargs));
				else
					color = color(data, cols, realrow, column, table);
				end
			end
			cellFrame.text:SetTextColor(color.r, color.g, color.b, color.a);

			local highlight = nil;
			if type(celldata) == "table" then
				highlight = celldata.highlight;
			end

			if table.fSelect then
        local selected = false
        if table.multiselection then
          selected = (table.selected and table.selected:IsSelected(realrow))
        else
				  selected = (table.selected == realrow)
        end
        if selected then
					table:SetHighLightColor(rowFrame, highlight or cols[column].highlight or rowdata.highlight or table:GetDefaultHighlight());
				else
					table:SetHighLightColor(rowFrame, table:GetDefaultHighlightBlank());
				end
			end
		else
			cellFrame.text:SetText("");
		end
	end

	--- API for a ScrollingTable table
	-- @name SetData
	-- @description Sets the data for the scrolling table
	-- @usage st:SetData(datatable)
	-- @see http://www.wowace.com/addons/lib-st/pages/set-data/
	local function SetData (self, data, isMinimalDataformat)
		self.isMinimalDataformat = isMinimalDataformat;
		self.data = data;
		self:SortData();
	end

	--- API for a ScrollingTable table
	-- @name GetRow
	-- @description Returns the data row of the table from the given data row index
	-- @usage used internally.
	local function GetRow(self, realrow)
		return self.data[realrow];
	end

	--- API for a ScrollingTable table
	-- @name GetCell
	-- @description Returns the cell data of the given row from the given row and column index
	-- @usage used internally.
	local function GetCell(self, row, col)
		local rowdata = row;
		if type(row) == "number" then
			rowdata = self:GetRow(row);
		end

		if self.isMinimalDataformat then
			return rowdata[col];
		else
			return rowdata.cols[col];
		end
	end

	--- API for a ScrollingTable table
	-- @name IsRowVisible
	-- @description Checks if a row is currently being shown
	-- @usage st:IsRowVisible(realrow)
	local function IsRowVisible(self, realrow)
		-- Scan through all on-screen rows, checking their real data row numbers for a match.
		local firstVisibleIdx = 1 + (self.offset or 0);
		local lastVisibleIdx = (firstVisibleIdx + self.displayRows) - 1;
		for i=firstVisibleIdx,lastVisibleIdx do
			if self.filtered[i] == realrow and realrow ~= nil then
				return true;
			end
		end
		return false;
	end

	function lib:CreateST(cols, numRows, rowHeight, highlight, parent, multiselection)
		local st = {};
		self.framecount = self.framecount or 1;
		local f = CreateFrame("Frame", "ScrollTable" .. self.framecount, parent or UIParent, BackdropTemplateMixin and "BackdropTemplate");
		self.framecount = self.framecount + 1;
		st.showing = true;
		st.frame = f;
    st.multiselection = multiselection or false
		st.Show = Show;
		st.Hide = Hide;
		st.SetDisplayRows = SetDisplayRows;
		st.SetRowHeight = SetRowHeight;
		st.SetHeight = SetHeight;
		st.SetWidth = SetWidth;
		st.SetDisplayCols = SetDisplayCols;
		st.SetData = SetData;
		st.SortData = SortData;
		st.CompareSort = CompareSort;
		st.RegisterEvents = RegisterEvents;
		st.FireUserEvent = FireUserEvent;
		st.SetDefaultHighlightBlank = SetDefaultHighlightBlank;
		st.SetDefaultHighlight = SetDefaultHighlight;
		st.GetDefaultHighlightBlank = GetDefaultHighlightBlank;
		st.GetDefaultHighlight = GetDefaultHighlight;
		st.EnableSelection = EnableSelection;
		st.SetHighLightColor = SetHighLightColor;
		st.ClearSelection = ClearSelection;
		st.SetSelection = SetSelection;
		st.GetSelection = GetSelection;
		st.GetCell = GetCell;
		st.GetRow = GetRow;
		st.DoCellUpdate = DoCellUpdate;
		st.IsRowVisible = IsRowVisible;
		st.RowIsVisible = IsRowVisible; -- Old name for backwards compatibility.

		st.SetFilter = SetFilter;
		st.DoFilter = DoFilter;

		highlight = highlight or {};
		st:SetDefaultHighlight(highlight["r"], highlight["g"], highlight["b"], highlight["a"]); -- highlight color
		st:SetDefaultHighlightBlank(); -- non highlight color

		st.displayRows = numRows or 12;
		st.rowHeight = rowHeight or 15;
		st.cols = cols;
		st.DefaultEvents = {
			["OnEnter"] = function (rowFrame, cellFrame, data, cols, row, realrow, column, table, ...)
				if row and realrow then
					local rowdata = table:GetRow(realrow);
					local celldata = table:GetCell(rowdata, column);
					local highlight = nil;
					if type(celldata) == "table" then
						highlight = celldata.highlight;
					end
					table:SetHighLightColor(rowFrame, highlight or cols[column].highlight or rowdata.highlight or table:GetDefaultHighlight());
				end
				return true;
			end,
			["OnLeave"] = function(rowFrame, cellFrame, data, cols, row, realrow, column, table, ...)
				if row and realrow then
					local rowdata = table:GetRow(realrow);
					local celldata = table:GetCell(rowdata, column);
          local emptySelection
          if table.multiselection then
            emptySelection = (not table.selected or not table.selected:IsSelected(realrow) or not table.fSelect)
          else
            emptySelection = (realrow ~= table.selected or not table.fSelect)
          end
					if emptySelection then
						table:SetHighLightColor(rowFrame, table:GetDefaultHighlightBlank());
					end
				end
				return true;
			end,
			["OnClick"] = function(rowFrame, cellFrame, data, cols, row, realrow, column, table, button, ...)		-- LS: added "button" argument
				if button == "LeftButton" then	-- LS: only handle on LeftButton click (right passes thru)
					if not (row or realrow) then
						for i, col in ipairs(st.cols) do
							if i ~= column then -- clear out all other sort marks
								cols[i].sort = nil;
							end
						end
						local sortorder = lib.SORT_DSC;
						if not cols[column].sort and cols[column].defaultsort then
							sortorder = cols[column].defaultsort; -- sort by columns default sort first;
						elseif cols[column].sort and cols[column].sort == lib.SORT_DSC then
							sortorder = lib.SORT_ASC;
						end
						cols[column].sort = sortorder;
						table:SortData();

					else
						local selected
						if table.multiselection then
							selected = (table.selected and table.selected:IsSelected(realrow))
						else
							selected = (table:GetSelection() == realrow)
						end
						if selected then
							table:ClearSelection(realrow);
						else
							table:SetSelection(realrow);
						end
					end
					return true;
				end
			end,
		};
		st.data = {};

		f:SetBackdrop(ScrollPaneBackdrop);
		f:SetBackdropColor(0.1,0.1,0.1);
		f:SetPoint("CENTER",parent or UIParent,"CENTER",0,0);
    if multiselection then
      local multiselector = { _storage =  {} }
      multiselector.Set = (function(self, realrow)
          self._storage[realrow] = true
      end)
      multiselector.Get = (function(self)
        local keyList = {}
        local n = 0

        for k,_ in pairs(self._storage) do
          n = n + 1
          keyList[n] = k
        end
        return keyList
      end)
      multiselector.Clear = (function(self)
        self._storage = {}
      end)
      multiselector.IsSelected = (function(self, realrow)
        return self._storage[realrow]
      end)

      st.selected = multiselector
    end
		-- build scroll frame
		local scrollframe = CreateFrame("ScrollFrame", f:GetName().."ScrollFrame", f, "FauxScrollFrameTemplate");
		st.scrollframe = scrollframe;
		scrollframe:Show();
		scrollframe:SetScript("OnHide", function(self, ...)
			self:Show();
		end);

		scrollframe:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -4);
		scrollframe:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -26, 3);

		local scrolltrough = CreateFrame("Frame", f:GetName().."ScrollTrough", scrollframe);
		scrolltrough:SetWidth(17);
		scrolltrough:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -3);
		scrolltrough:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -4, 4);
		scrolltrough.background = scrolltrough:CreateTexture(nil, "BACKGROUND");
		scrolltrough.background:SetAllPoints(scrolltrough);
		scrolltrough.background:SetColorTexture(0.05, 0.05, 0.05, 1.0);
		local scrolltroughborder = CreateFrame("Frame", f:GetName().."ScrollTroughBorder", scrollframe);
		scrolltroughborder:SetWidth(1);
		scrolltroughborder:SetPoint("TOPRIGHT", scrolltrough, "TOPLEFT");
		scrolltroughborder:SetPoint("BOTTOMRIGHT", scrolltrough, "BOTTOMLEFT");
		scrolltroughborder.background = scrolltrough:CreateTexture(nil, "BACKGROUND");
		scrolltroughborder.background:SetAllPoints(scrolltroughborder);
		scrolltroughborder.background:SetColorTexture(0.5, 0.5, 0.5, 1.0);

		st.Refresh = function(self)
			FauxScrollFrame_Update(scrollframe, #st.filtered, st.displayRows, st.rowHeight);
			local o = FauxScrollFrame_GetOffset(scrollframe);
			st.offset = o;

			for i = 1, st.displayRows do
				local row = i + o;
				if st.rows then
					local rowFrame = st.rows[i];
					local realrow = st.filtered[row];
					local rowData = st:GetRow(realrow);
					local fShow = true;
					for col = 1, #st.cols do
						local cellFrame = rowFrame.cols[col];
						local fnDoCellUpdate = st.DoCellUpdate;
						if rowData then
							st.rows[i]:Show();
							local cellData = st:GetCell(rowData, col);
							if type(cellData) == "table" and cellData.DoCellUpdate then
								fnDoCellUpdate = cellData.DoCellUpdate;
							elseif st.cols[col].DoCellUpdate then
								fnDoCellUpdate = st.cols[col].DoCellUpdate;
							elseif rowData.DoCellUpdate then
								fnDoCellUpdate = rowData.DoCellUpdate;
							end
						else
							st.rows[i]:Hide();
							fShow = false;
						end
						fnDoCellUpdate(rowFrame, cellFrame, st.data, st.cols, row, st.filtered[row], col, fShow, st);
					end
				end
			end
		end

		scrollframe:SetScript("OnVerticalScroll", function(self, offset)
			FauxScrollFrame_OnVerticalScroll(self, offset, st.rowHeight, function() st:Refresh() end);					-- LS: putting st:Refresh() in a function call passes the st as the 1st arg which lets you reference the st if you decide to hook the refresh
		end);

		st:SetFilter(Filter);
		st:SetDisplayCols(st.cols);
		st:RegisterEvents(st.DefaultEvents);

		return st;
	end
end
