local Rx = require 'Rx'

local config = Rx.config

local Disposable -- filled in later

local ObjectDisposedError = Rx.internals.Errors.ObjectDisposedError

--config.debugDisposables = true
--config.traceDisposables = true
    -- These config flags can be used to find objects that aren't getting disposed of.

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

local disposablePrototype = {

    _isDisposed = false,
        -- (boolean) true if :dispose() has already been called

    _action = false,
        -- (function) function to call when :dispose() is called

    class = {
        _activeDisposables = false,
        _numCreated = 0,
        _numDisposed = 0,
    },
        -- class methods (filled in later)

}

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

local function nop()
end

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

function disposablePrototype:__init(action)

    if action ~= nil and type(action) ~= 'function' then
        error("Rx.Disposable: action must be a function, if provided", 3)
    end

    self._action = action or nop

    if config.debugDisposables then

        local numCreated = disposablePrototype.class._numCreated + 1
        disposablePrototype.class._numCreated = numCreated

        disposablePrototype.class._activeDisposables =
            disposablePrototype.class._activeDisposables or {}

        disposablePrototype.class._activeDisposables[numCreated] = {
            kind = tostring(self),
            traceback = debug.traceback()
        }

        rawset(self, '_id', numCreated)

        if config.traceDisposables then

            local debugPrint = type(config.traceDisposables) == 'function'
                and config.traceDisposables or print

            local numActive = numCreated - disposablePrototype.class._numDisposed

            debugPrint("Disposable:init id" .. tostring(numCreated)
                .. " active: " .. tostring(numActive)
                .. " type: " .. tostring(self) )

        end

    end

end

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

function disposablePrototype:isDisposed()

    return self._isDisposed

end

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

function disposablePrototype:dispose()

    if not self._isDisposed then

        if config.debugDisposables then

            local activeDisposables = disposablePrototype.class._activeDisposables or {}

            disposablePrototype.class._activeDisposables = activeDisposables

            local numDisposed = disposablePrototype.class._numDisposed

            if activeDisposables[self._id] then

                numDisposed = numDisposed + 1

                disposablePrototype.class._numDisposed = numDisposed

                activeDisposables[self._id] = nil

            end

            if config.traceDisposables then

                local debugPrint = type(config.traceDisposables) == 'function'
                    and config.traceDisposables or print

                local numActive = disposablePrototype.class._numCreated - numDisposed

                debugPrint("Disposable:dispose id" .. tostring(self._id)
                    .. " active: " .. tostring(numActive))

            end

        end

        self._action()
        self._action = nil
            -- Release any references that might be held in that closure.

        self._isDisposed = true

    end

end

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

function disposablePrototype:disposeWith(disposable)

    if not Disposable:isClassOf(disposable) then
        error("Rx.Disposable:disposeWith argument must be a Disposable", 2)
    end

    if disposable._isDisposed then

        self:dispose()

    else

        local oldAction = disposable._action

        disposable._action = function()
            self:dispose()
            oldAction()
        end

    end

end

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

function disposablePrototype.class.create(_, action)

    if action and type(action) ~= 'function' then
        error("Rx.disposable.create: action must be nil (no-op) or a function", 2)
    end

    if not action then
        action = function() end
    end

    return Disposable(action)

end

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

function disposablePrototype.class.empty()

    return Disposable(function() end)

end

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

local disposedDisposable

function disposablePrototype.class._fixup(_, disposable)

    local result = disposable or disposedDisposable

    if result == nil then
        disposedDisposable = Disposable.empty()
        disposedDisposable:dispose()
        result = disposedDisposable
    end

    return result

end

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

function disposablePrototype.class.checkDisposed(_, disposable)

    if disposable._isDisposed then
        error(ObjectDisposedError, 2)
    end

end

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

function disposablePrototype.class.dump(_, title, verbose, printFn)

    local debugPrint = printFn or print

    local numCreated = disposablePrototype.class._numCreated
    local numDisposed = disposablePrototype.class._numDisposed

    local numActive = numCreated - numDisposed

    if numActive > 0 then

        local message = string.format("Error: %d disposables active", numActive)

        if title then
            message = message .. " in " .. title
        end

        debugPrint(message)

        if verbose then

            for k, v in pairs(disposablePrototype.class._activeDisposables) do
                debugPrint(k .. " " .. v.kind)
                debugPrint(v.traceback)
            end

        end

    end

end

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

function disposablePrototype.class.resetDebuggingStats()

    disposablePrototype.class._activeDisposables = {}
    disposablePrototype.class._numCreated = 0
    disposablePrototype.class._numDisposed = 0

end

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

function disposablePrototype.class.numCreated()

    return disposablePrototype.class._numCreated

end

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

function disposablePrototype.class.numDisposed()

    return disposablePrototype.class._numDisposed

end

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

Disposable = Rx.Class('Rx.Disposable', disposablePrototype)

return Disposable
