--[[
	sequence - functional + oo wrapper for ordered tables

	mainly beneficial when used for method chaining
	to save on typing and data plumbing
]]

local path = (...):gsub("sequence", "")
local table = require(path .. "tablex") --shadow global table module
local functional = require(path .. "functional")
local stable_sort = require(path .. "sort").stable_sort

--(not a class, because we want to be able to upgrade tables that are passed in without a copy)
local sequence = {}
sequence.__index = sequence
setmetatable(sequence, {
	__index = table,
	__call = function(self, ...)
		return sequence:new(...)
	end,
})

--iterators as method calls
--(no pairs, sequences are ordered)
--todo: pico8 like `all`
sequence.ipairs = ipairs
sequence.iterate = ipairs

--upgrade a table into a sequence, or create a new sequence
function sequence:new(t)
	return setmetatable(t or {}, sequence)
end

--sorting default to stable
sequence.sort = stable_sort

--patch various interfaces in a type-preserving way, for method chaining

--import copying tablex
--(common case where something returns another sequence for chaining)
for _, v in ipairs({
	"keys",
	"values",
	"dedupe",
	"collapse",
	"append",
	"shallow_overlay",
	"deep_overlay",
	"copy",
	"shallow_copy",
	"deep_copy",
}) do
	local table_f = table[v]
	sequence[v] = function(self, ...)
		return sequence(table_f(self, ...))
	end
end

--aliases
for _, v in ipairs({
	{"flatten", "collapse"},
}) do
	sequence[v[1]] = sequence[v[2]]
end

--import functional interface in method form

--(common case where something returns another sequence for chaining)
for _, v in ipairs({
	"map",
	"map_field",
	"map_call",
	"filter",
	"remove_if",
	"zip",
	"combine",
	"stitch",
	"map_stitch",
	"cycle",
	"map_cycle",
	"chain",
	"map_chain",
	"map_pairs",
}) do
	local functional_f = functional[v]
	sequence[v] = function(self, ...)
		return sequence(functional_f(self, ...))
	end
end

--(cases where we don't want to construct a new sequence)
for _, v in ipairs({
	"map_inplace",
	"filter_inplace",
	"foreach",
	"reduce",
	"any",
	"none",
	"all",
	"count",
	"contains",
	"sum",
	"mean",
	"minmax",
	"max",
	"min",
	"find_min",
	"find_max",
	"find_nearest",
	"find_match",
}) do
	sequence[v] = functional[v]
end


--aliases
for _, v in ipairs({
	{"remap", "map_inplace"},
	{"map_stitch", "stitch"},
	{"map_cycle", "cycle"},
	{"find_best", "find_max"},
}) do
	sequence[v[1]] = sequence[v[2]]
end

--(anything that needs bespoke wrapping)
function sequence:partition(f)
	local a, b = functional.partition(self, f)
	return sequence(a), sequence(b)
end

function sequence:unzip(f)
	local a, b = functional.unzip(self, f)
	return sequence(a), sequence(b)
end

return sequence
