-- Given index intervals and input function to compare items at indexes,
-- return by output function the number of adjacent items and starting indexes
-- of each common subsequence. Divide and conquer with only linear space.
--
-- The index intervals are half open [start, end) like array slice method.
-- DO NOT CALL if start === end, because interval cannot contain common items
-- and because divide function will throw the “no overlap” error.

local function findSubsequences(
	nChange: number,
	aStart: number,
	aEnd: number,
	bStart: number,
	bEnd: number,
	transposed: boolean,
	callbacks,
	aIndexesF: Indexes, -- temporary memory, not input nor output
	aIndexesR: Indexes, -- temporary memory, not input nor output
	division: Division -- temporary memory, not input nor output
): ()
	if bEnd - bStart < aEnd - aStart then
		-- Transpose graph so it has portrait instead of landscape orientation.
		-- Always compare shorter to longer sequence for consistency and optimization.
		transposed = not transposed
		if transposed and #callbacks == 1 then
			-- Lazily wrap callback functions to swap args if graph is transposed.
			local foundSubsequence, isCommon = unpack(callbacks[1])
			callbacks[2] = {
				function(nCommon, bCommon, aCommon)
					return foundSubsequence(nCommon, aCommon, bCommon)
				end,
				function(bIndex, aIndex)
					return isCommon(aIndex, bIndex)
				end,
			}
		end

		local tStart = aStart
		local tEnd = aEnd
		aStart = bStart
		aEnd = bEnd
		bStart = tStart
		bEnd = tEnd
	end
	local foundSubsequence, isCommon = unpack(callbacks[transposed and 2 or 1])

	-- Divide the index intervals at the middle change.
	divide(nChange, aStart, aEnd, bStart, bEnd, isCommon, aIndexesF, aIndexesR, division)
	local nChangePreceding = division.nChangePreceding
	local aEndPreceding = division.aEndPreceding
	local bEndPreceding = division.bEndPreceding
	local nCommonPreceding = division.nCommonPreceding
	local aCommonPreceding = division.aCommonPreceding
	local bCommonPreceding = division.bCommonPreceding
	local nCommonFollowing = division.nCommonFollowing
	local aCommonFollowing = division.aCommonFollowing
	local bCommonFollowing = division.bCommonFollowing
	local nChangeFollowing = division.nChangeFollowing
	local aStartFollowing = division.aStartFollowing
	local bStartFollowing = division.bStartFollowing

	-- Unless either index interval is empty, they might contain common items.
	if aStart < aEndPreceding and bStart < bEndPreceding then
		-- Recursely find and return common subsequences preceding the division.
		findSubsequences(
			nChangePreceding,
			aStart,
			aEndPreceding,
			bStart,
			bEndPreceding,
			transposed,
			callbacks,
			aIndexesF,
			aIndexesR,
			division
		)
	end

	-- Return common subsequences that are adjacent to the middle change.
	if nCommonPreceding ~= 0 then
		foundSubsequence(nCommonPreceding, aCommonPreceding, bCommonPreceding)
	end
	if nCommonFollowing ~= 0 then
		foundSubsequence(nCommonFollowing, aCommonFollowing, bCommonFollowing)
	end

	-- Unless either index interval is empty, they might contain common items.
	if aStartFollowing < aEnd and bStartFollowing < bEnd then
		-- Recursely find and return common subsequences following the division.
		findSubsequences(
			nChangeFollowing,
			aStartFollowing,
			aEnd,
			bStartFollowing,
			bEnd,
			transposed,
			callbacks,
			aIndexesF,
			aIndexesR,
			division
		)
	end
end

local function validateLength(name: string, arg: any): ()
	if typeof(arg) ~= "number" then
		error(string.format("%s: %s type %s is not a number", pkg, name, type(arg)))
	end
	if not Number.isSafeInteger(arg) then
		error(string.format("%s: %s type %s is not a safe integer", pkg, name, type(arg)))
	end
	if arg < 0 then
		error(string.format("%s: %s type %s is a negative integer", pkg, name, type(arg)))
	end
end

local function validateCallback(name: string, arg: any): ()
	if typeof(arg) ~= "function" then
		error(string.format("%s: %s type %s is not a function", pkg, name, type(arg)))
	end
end
