local Rx = require 'Rx'

local AnonymousObservable = Rx.AnonymousObservable

local unpack = table.unpack or unpack

--------------------------------------------------------------------------------

local function pcallPack(success, ...)

    return success, { length = select('#', ...), ... }

end

--------------------------------------------------------------------------------

return function(self, accumulatorFn, ...)

    if type(accumulatorFn) ~= 'function' then
        error("Rx.Observable:scan: accumulator must be a function", 2)
    end

    local seeds = { length = select('#', ...), ... }

    return AnonymousObservable(function(observer)

        local accumulation

        return self:subscribe(

            function(...)

                local accumParams

                if accumulation ~= nil then

                    accumParams = accumulation

                else

                    local numSeeds = seeds.length
                    if numSeeds == 0 then
                        accumParams = { length = 1, nil }
                    else
                        accumParams = { length = numSeeds }
                        for i = 1, numSeeds do
                            accumParams[i] = seeds[i]
                        end
                    end

                end

                local values = { ... }
                local numValues = select('#', ...)

                local numAccumParams = accumParams.length
                for i = 1, numValues do
                    accumParams[numAccumParams + i] = values[i]
                end
                numAccumParams = numAccumParams + numValues

                local success
                success, accumulation = pcallPack(pcall(accumulatorFn,
                    unpack(accumParams, 1, numAccumParams)))

                if success then

                    if accumulation.length > 1 and seeds.length == 0 then
                        observer:onError("Rx.Observable:scan: must specify seed values when returning multiple values from accumulator function")
                    else

                        if accumulation.length == 0 or seeds.length > 0
                            and accumulation ~= seeds.length then

                            accumulation.length = math.max(seeds.length, 1)

                        end

                        observer:onNext(unpack(accumulation, 1,
                            accumulation.length))
                    end

                else

                    local err = accumulation[1]
                    observer:onError(err)

                end

            end,
            function(...)

                return observer:onError(...)

            end,
            function()

                if accumulation == nil and seeds.length > 0 then
                    observer:onNext(unpack(seeds, 1, seeds.length))
                end

                observer:onCompleted()

            end)

    end, self)

end
