local Rx = require 'Rx'

local CompositeDisposable = Rx.CompositeDisposable

local notSupported = Rx.internals.Errors.notSupported

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

local schedulerPrototype = {

    class = {},
        -- class methods to be filled in later

}

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

local Scheduler -- filled in at end of file

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

-- TODO: Provide a way to configure this. Built-in implementation from Lua is
-- pretty poor (1-second granularity on MacOS).

local function defaultNow()
    return os.time() * 1000
end

schedulerPrototype.class.now = defaultNow

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

function schedulerPrototype:__init(...)

    if select('#', ...) > 0 then
        error("Rx.Scheduler: takes no parameters", 3)
    end

end

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

--function schedulerPrototype:schedule(state, action)
--end
    -- When subclassing Scheduler you must implement the schedule method.

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

function schedulerPrototype:_scheduleFuture(state, dueTime, action)

    notSupported()
        -- When subclassing Scheduler, if you want to schedule items to occur
        -- in the future you should implement the _scheduleFuture() method.

end

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

function schedulerPrototype:scheduleFuture(state, dueTime, action)

    if action == nil then
        action = dueTime
        dueTime = state
        state = nil
    end

    dueTime = Scheduler.normalize(dueTime)
    if dueTime == 0 then
        return self:schedule(state, action)
    end

    return self:_scheduleFuture(state, dueTime, action)

end

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

schedulerPrototype.now = defaultNow

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

local function invokeRecImmediate(scheduler, pair)

    -- The net effect of this function is to provide recursive-ish behavior
    -- without actually using stack recursion.

    local userActionFn = pair[1]
    local invokeUserActionFn = pair[2]

    local group = CompositeDisposable()

    local function reschedule(userActionFn2)

        local isAdded = false
        local isDone = false

        local d

        local function scheduleWork(_, userActionFn3)

            if isAdded then
                group:remove(d)
            else
                isDone = true
            end

            invokeUserActionFn(userActionFn3, reschedule)

        end

        d = scheduler:schedule(userActionFn2, scheduleWork)

        if not isDone then
            group:add(d)
            isAdded = true
        end

    end

    local success, errorMessage = pcall(invokeUserActionFn, userActionFn, reschedule)

    if not success then
        group:dispose()
        error(errorMessage)
    end

    return group

end

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

function schedulerPrototype:scheduleRecursive(state, action)

    if action == nil then
        action = state
        state = nil
    end

    return self:schedule({ state, action }, invokeRecImmediate)

end

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

local function invokeRecDate(scheduler, pair)

    local userActionFn = pair[1]
    local invokeUserActionFn = pair[2]

    local group = CompositeDisposable()

    local function reschedule(userActionFn2, dueTime1)

        local isAdded = false
        local isDone = false

        local d

        local function scheduleWork(_, userActionFn3)

            if isAdded then
                group:remove(d)
            else
                isDone = true
            end

            invokeUserActionFn(userActionFn3, reschedule)

        end

        d = scheduler:scheduleFuture(userActionFn2, dueTime1, scheduleWork)

        if not isDone then
            group:add(d)
            isAdded = true
        end

    end

    local success, errorMessage = pcall(invokeUserActionFn, userActionFn, reschedule)

    if not success then
        group:dispose()
        error(errorMessage)
    end

    return group

end

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

function schedulerPrototype:scheduleRecursiveFuture(state, dueTime, action)

    if action == nil then
        action = dueTime
        dueTime = state
        state = nil
    end

    return self:scheduleFuture({ state, action }, dueTime, invokeRecDate)

end

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

local function normalize(_, timeSpan)

    if timeSpan < 0 then
        return 0
    else
        return timeSpan
    end

end

schedulerPrototype.class.normalize = normalize

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

function schedulerPrototype:schedulePeriodic(state, period, action)

    notSupported()

end

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

function schedulerPrototype.class.immediate()

    return require 'Rx.ImmediateScheduler'

end

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

function schedulerPrototype.class.currentThread()

    return require 'Rx.CurrentThreadScheduler'

end

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

Scheduler = Rx.Class('Rx.Scheduler', schedulerPrototype)

return Scheduler
