//
//  UIPickerView+Rx.swift
//  RxCocoa
//
//  Created by Segii Shulga on 5/12/16.
//  Copyright © 2016 Krunoslav Zaher. All rights reserved.
//

#if os(iOS)
    
#if !RX_NO_MODULE
    import RxSwift
#endif
    import UIKit

    extension UIPickerView {

        /// Factory method that enables subclasses to implement their own `delegate`.
        ///
        /// - returns: Instance of delegate proxy that wraps `delegate`.
        public func createRxDelegateProxy() -> RxPickerViewDelegateProxy {
            return RxPickerViewDelegateProxy(parentObject: self)
        }
        
        /**
         Factory method that enables subclasses to implement their own `rx.dataSource`.
         
         - returns: Instance of delegate proxy that wraps `dataSource`.
         */
        public func createRxDataSourceProxy() -> RxPickerViewDataSourceProxy {
            return RxPickerViewDataSourceProxy(parentObject: self)
        }

    }
    
    extension Reactive where Base: UIPickerView {

        /// Reactive wrapper for `delegate`.
        /// For more information take a look at `DelegateProxyType` protocol documentation.
        public var delegate: DelegateProxy {
            return RxPickerViewDelegateProxy.proxyForObject(base)
        }
        
        /// Installs delegate as forwarding delegate on `delegate`.
        /// Delegate won't be retained.
        ///
        /// It enables using normal delegate mechanism with reactive delegate mechanism.
        ///
        /// - parameter delegate: Delegate object.
        /// - returns: Disposable object that can be used to unbind the delegate.
        public func setDelegate(_ delegate: UIPickerViewDelegate)
            -> Disposable {
                return RxPickerViewDelegateProxy.installForwardDelegate(delegate, retainDelegate: false, onProxyForObject: self.base)
        }
        
        /**
         Reactive wrapper for `dataSource`.
         
         For more information take a look at `DelegateProxyType` protocol documentation.
         */
        public var dataSource: DelegateProxy {
            return RxPickerViewDataSourceProxy.proxyForObject(base)
        }
        
        /**
         Reactive wrapper for `delegate` message `pickerView:didSelectRow:inComponent:`.
         */
        public var itemSelected: ControlEvent<(row: Int, component: Int)> {
            let source = delegate
                .methodInvoked(#selector(UIPickerViewDelegate.pickerView(_:didSelectRow:inComponent:)))
                .map {
                    return (row: try castOrThrow(Int.self, $0[1]), component: try castOrThrow(Int.self, $0[2]))
                }
            return ControlEvent(events: source)
        }
        
        /**
         Reactive wrapper for `delegate` message `pickerView:didSelectRow:inComponent:`.
         
         It can be only used when one of the `rx.itemTitles, rx.itemAttributedTitles, items(_ source: O)` methods is used to bind observable sequence,
         or any other data source conforming to a `ViewDataSourceType` protocol.
         
         ```
         pickerView.rx.modelSelected(MyModel.self)
         .map { ...
         ```
         - parameter modelType: Type of a Model which bound to the dataSource
         */
        public func modelSelected<T>(_ modelType: T.Type) -> ControlEvent<[T]> {
            let source = itemSelected.flatMap { [weak view = self.base as UIPickerView] (_, component) -> Observable<[T]> in
                guard let view = view else {
                    return Observable.empty()
                }

                let model: [T] = try (0 ..< view.numberOfComponents).map { component in
                    let row = view.selectedRow(inComponent: component)
                    return try view.rx.model(at: IndexPath(row: row, section: component))
                }

                return Observable.just(model)
            }
            
            return ControlEvent(events: source)
        }
        
        /**
         Binds sequences of elements to picker view rows.
         
         - parameter source: Observable sequence of items.
         - parameter titleForRow: Transform between sequence elements and row titles.
         - returns: Disposable object that can be used to unbind.
         
         Example:
         
            let items = Observable.just([
                    "First Item",
                    "Second Item",
                    "Third Item"
                ])
         
            items
                .bind(to: pickerView.rx.itemTitles) { (row, element) in
                    return element.title
                }
                .disposed(by: disposeBag)
         
         */
        
        public func itemTitles<S: Sequence, O: ObservableType>
            (_ source: O)
            -> (_ titleForRow: @escaping (Int, S.Iterator.Element) -> String?)
            -> Disposable where O.E == S  {
                return { titleForRow in
                    let adapter = RxStringPickerViewAdapter<S>(titleForRow: titleForRow)
                    return self.items(adapter: adapter)(source)
                }
        }
        
        /**
         Binds sequences of elements to picker view rows.
         
         - parameter source: Observable sequence of items.
         - parameter attributedTitleForRow: Transform between sequence elements and row attributed titles.
         - returns: Disposable object that can be used to unbind.
         
         Example:
         
         let items = Observable.just([
                "First Item",
                "Second Item",
                "Third Item"
            ])
         
         items
            .bind(to: pickerView.rx.itemAttributedTitles) { (row, element) in
                return NSAttributedString(string: element.title)
            }
            .disposed(by: disposeBag)
        
         */

        public func itemAttributedTitles<S: Sequence, O: ObservableType>
            (_ source: O)
            -> (_ attributedTitleForRow: @escaping (Int, S.Iterator.Element) -> NSAttributedString?)
            -> Disposable where O.E == S  {
                return { attributedTitleForRow in
                    let adapter = RxAttributedStringPickerViewAdapter<S>(attributedTitleForRow: attributedTitleForRow)
                    return self.items(adapter: adapter)(source)
                }
        }
        
        /**
         Binds sequences of elements to picker view rows.
         
         - parameter source: Observable sequence of items.
         - parameter viewForRow: Transform between sequence elements and row views.
         - returns: Disposable object that can be used to unbind.
         
         Example:
         
         let items = Observable.just([
                "First Item",
                "Second Item",
                "Third Item"
            ])
         
         items
            .bind(to: pickerView.rx.items) { (row, element, view) in
                guard let myView = view as? MyView else {
                    let view = MyView()
                    view.configure(with: element)
                    return view
                }
                myView.configure(with: element)
                return myView
            }
            .disposed(by: disposeBag)
         
         */

        public func items<S: Sequence, O: ObservableType>
            (_ source: O)
            -> (_ viewForRow: @escaping (Int, S.Iterator.Element, UIView?) -> UIView)
            -> Disposable where O.E == S  {
                return { viewForRow in
                    let adapter = RxPickerViewAdapter<S>(viewForRow: viewForRow)
                    return self.items(adapter: adapter)(source)
                }
        }
        
        /**
         Binds sequences of elements to picker view rows using a custom reactive adapter used to perform the transformation.
         This method will retain the adapter for as long as the subscription isn't disposed (result `Disposable`
         being disposed).
         In case `source` observable sequence terminates successfully, the adapter will present latest element
         until the subscription isn't disposed.
         
         - parameter adapter: Adapter used to transform elements to picker components.
         - parameter source: Observable sequence of items.
         - returns: Disposable object that can be used to unbind.
         */
        public func items<O: ObservableType,
                          Adapter: RxPickerViewDataSourceType & UIPickerViewDataSource & UIPickerViewDelegate>(adapter: Adapter)
            -> (_ source: O)
            -> Disposable where O.E == Adapter.Element {
                return { source in
                    let delegateSubscription = self.setDelegate(adapter)
                    let dataSourceSubscription = source.subscribeProxyDataSource(ofObject: self.base, dataSource: adapter, retainDataSource: true, binding: { [weak pickerView = self.base] (_: RxPickerViewDataSourceProxy, event) in
                        guard let pickerView = pickerView else { return }
                        adapter.pickerView(pickerView, observedEvent: event)
                    })
                    return Disposables.create(delegateSubscription, dataSourceSubscription)
                }
        }
        
        /**
         Synchronous helper method for retrieving a model at indexPath through a reactive data source.
         */
        public func model<T>(at indexPath: IndexPath) throws -> T {
            let dataSource: SectionedViewDataSourceType = castOrFatalError(self.dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx.itemTitles, rx.itemAttributedTitles, items(_ source: O)` methods was used.")
            
            return castOrFatalError(try dataSource.model(at: indexPath))
        }
    }

#endif
