local Rx = require 'Rx'

local AbstractObserver = Rx.AbstractObserver
local CompositeDisposable = Rx.CompositeDisposable
local ObservableBase = Rx.ObservableBase
local Observer = Rx.Observer

local observerCreate = Observer.create

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

local TapObserver = Rx.Class 'Rx.internals.TapObserver' {

    AbstractObserver,
        -- superclass

    _observer = false,
        -- (Observer)

    _tapObserver = false,
        -- (Observer)

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

    __init = function(self, observer, tapObservable)

        self._observer = observer

        local observerOrOnNext = tapObservable._observerOrOnNext

        local createObserver = not observerOrOnNext or type(observerOrOnNext) == 'function'

        if createObserver then

            self._tapObserver = observerCreate(observerOrOnNext,
                tapObservable._onError,
                tapObservable._onCompleted)

        else

            self._tapObserver = observerOrOnNext

        end

        return function()

            if createObserver then
                self._tapObserver:dispose()
            end

        end

    end,

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

    relayToObserver = function(self, success, ...)

        if not success then
            self._observer:onError(...)
        end

        return success

    end,

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

    next = function(self, ...)

        local tapObserver = self._tapObserver

        if self:relayToObserver(pcall(tapObserver.onNext, tapObserver, ...)) then
            self._observer:onNext(...)
        end

    end,

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

    error = function(self, ...)

        local tapObserver = self._tapObserver

        if self:relayToObserver(pcall(tapObserver.onError, tapObserver, ...)) then
            self._observer:onError(...)
        end

    end,

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

    onCompleted = function(self)

        local tapObserver = self._tapObserver

        if self:relayToObserver(pcall(tapObserver.onCompleted, tapObserver)) then
            self._observer:onCompleted()
        end

    end,

}

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

local TapObservable = Rx.Class 'Rx.internals.TapObservable' {

    ObservableBase,
        -- superclass

    _source = false,
        -- (Observable)

    _observerOrOnNext = false,
        -- (Observer or function)

    _onError = false,
        -- (function)

    _onCompleted = false,
        -- (function)

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

    __init = function(self, source, observerOrOnNext, onError, onCompleted)

        self._source = source
        self._observerOrOnNext = observerOrOnNext
        self._onError = onError
        self._onCompleted = onCompleted

    end,

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

    subscribeCore = function(self, observer)

        local tapObserver = TapObserver(observer, self)

        return CompositeDisposable(self._source:subscribe(tapObserver), tapObserver)

    end

}

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

return function(self, observerOrOnNext, onError, onCompleted)

    if Observer:isClassOf(observerOrOnNext) then

        if onError ~= nil then
            error("Rx.Observable:tap: onError must be nil if providing an Observer", 2)
        end

        if onCompleted ~= nil then
            error("Rx.Observable:tap: onCompleted must be nil if providing an Observer", 2)
        end

    else

        if observerOrOnNext ~= nil and type(observerOrOnNext) ~= 'function' then
            error("Rx.Observable:tap: observerOrOnNext must be an Observer or a function", 2)
        end

        if onError ~= nil and type(onError) ~= 'function' then
            error("Rx.Observable:tap: onError must be a function or nil", 2)
        end

        if onCompleted ~= nil and type(onCompleted) ~= 'function' then
            error("Rx.Observable:tap: onCompleted must be a function or nil", 2)
        end

    end

    return TapObservable(self, observerOrOnNext, onError, onCompleted)

end
