//
//  ActivityIndicator.swift
//  RxExample
//
//  Created by Krunoslav Zaher on 10/18/15.
//  Copyright © 2015 Krunoslav Zaher. All rights reserved.
//

import RxSwift

private struct ActivityToken<Element> : ObservableConvertibleType, Disposable {
    private let _source: Observable<Element>
    private let _dispose: Cancelable

    init(source: Observable<Element>, disposeAction: @escaping () -> Void) {
        _source = source
        _dispose = Disposables.create(with: disposeAction)
    }

    func dispose() {
        _dispose.dispose()
    }

    func asObservable() -> Observable<Element> {
        return _source
    }
}

/**
Enables monitoring of sequence computation.

If there is at least one sequence computation in progress, `true` will be sent.
When all activities complete `false` will be sent.
*/
public class ActivityIndicator : ObservableConvertibleType {
    public typealias Element = Bool
    
    private let _lock = NSRecursiveLock()
    private let _subject = BehaviorSubject(value: 0)
    private let _loading: Observable<Bool>

    public init() {
        _loading = _subject.observeOnMainScheduler().shareOnce()
            .map { $0 > 0 }
            .distinctUntilChanged()
    }

    fileprivate func trackActivityOfObservable<O: ObservableConvertibleType>(_ source: O) -> Observable<O.Element> {
        return Observable.using({ () -> ActivityToken<O.Element> in
            self.increment()
            return ActivityToken(source: source.asObservable(), disposeAction: self.decrement)
        }) { t in
            return t.asObservable()
        }
    }

    private func increment() {
        _lock.lock()
        if let value = try? _subject.value() {
            _subject.onNext(value + 1)
        }
        _lock.unlock()
    }

    private func decrement() {
        _lock.lock()
        if let value = try? _subject.value() {
            _subject.onNext(value - 1)
        }
        _lock.unlock()
    }

    public func asObservable() -> Observable<Bool> {
        return _loading
    }
}

extension ObservableConvertibleType {
    public func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable<Element> {
        return activityIndicator.trackActivityOfObservable(self)
    }
}
