module(..., package.seeall);
--[[
    This module implements the data structure returned by query functions,
    for easy access to the programmer the results, such as:

    result = resultset:new()
    while true do
        result = search.query (... current_handle, resultset.lastHandle (result)})

        if result:length() == 0 then
            break
        end

        while not result:atEnd() do
            do_something(result:getCurrentValue()
            result:moveToNext()
        end
    end

    The abstract data type is implemented as two arrays: one with handles and the other with values.

    Remember that a handle is the composition of the sortingField value and the ID of the document.
--]]

local function moveToFirst(resultSet)
	resultSet.pos = 1
end

local function getCurrentValue(resultSet)
	return resultSet.values[resultSet.pos]
end

local function getCurrentHandle(resultSet)
	return resultSet.handles[resultSet.pos]
end

local function getLastHandle(resultSet)
	return resultSet.handles[#resultSet.handles]
end

local function moveToNext(resultSet)
	resultSet.pos = resultSet.pos + 1
end

local function length(resultSet)
	return #resultSet.values
end

local function atEnd(resultSet)
	return resultSet.pos > #resultSet.values
end

local function isEmpty(resultSet)
	return #resultSet.handles == 0
end

local function append(resultSet, handle, value)
	table.insert(resultSet.handles, handle)
	table.insert(resultSet.values, value)
end

local function truncate(resultSet, maxRows)
	while resultSet:length() > maxRows do
		table.remove(resultSet.handles)
		table.remove(resultSet.values)
	end
end

local function union(resultsetOne, resultsetTwo, comparator, maxRows)
	if (resultsetOne:isEmpty() and resultsetTwo:isEmpty())  then
		return resultsetOne
	end

	if (resultsetOne:isEmpty()) then
		truncate(resultsetTwo, maxRows)
		return resultsetTwo
	end

	if (resultsetTwo:isEmpty()) then
		truncate(resultsetOne, maxRows)
		return resultsetOne
	end

	resultsetOne:moveToFirst()
	resultsetTwo:moveToFirst()
	local result = new()

	while (result:length() < maxRows) and (not resultsetOne:atEnd()) and (not resultsetTwo:atEnd())  do
		local comparisionRet = comparator(resultsetOne:getCurrentHandle(), resultsetTwo:getCurrentHandle())
		if comparisionRet == 1 then
			result:append(resultsetOne:getCurrentHandle(), resultsetOne:getCurrentValue())
			moveToNext(resultsetOne)
		elseif comparisionRet == -1 then
			result:append(resultsetTwo:getCurrentHandle(), resultsetTwo:getCurrentValue())
			moveToNext(resultsetTwo)
		else
			result:append(resultsetOne:getCurrentHandle(), resultsetOne:getCurrentValue())
			resultsetOne:moveToNext()
			resultsetTwo:moveToNext()
	   end
	end
	if (result:length() < maxRows) then
		if not resultsetOne:atEnd() then
			while result:length() < maxRows and not resultsetOne:atEnd() do
				result:append(resultsetOne:getCurrentHandle(), resultsetOne:getCurrentValue())
				resultsetOne:moveToNext()
			end
		else
			while result:length() < maxRows and not resultsetTwo:atEnd() do
				result:append(resultsetTwo:getCurrentHandle(), resultsetTwo:getCurrentValue())
				resultsetTwo:moveToNext()
			end
		end
	end

	return result
end

function new()
	return {
	    values  = {},
	    handles = {},
	    pos     = 1,
	    --functions
	    moveToFirst      = moveToFirst,
	    getCurrentValue  = getCurrentValue,
	    getCurrentHandle = getCurrentHandle,
	    getLastHandle    = getLastHandle,
	    moveToNext       = moveToNext,
	    length           = length,
	    atEnd            = atEnd,
	    isEmpty          = isEmpty,
	    append           = append,
	    union            = union,
	}
end
