//
//  GMListProtocol.swift
//  test
//
//  Created by wangyang on 16/3/15.
//  Copyright © 2016年 北京更美互动信息科技有限公司. All rights reserved.
//

import UIKit
import GM_Swift_Observable
import SnapKit
import GMRefresh
import GMHud

/// 方便GMBaseController能够访问到refreshList这个方法
public protocol Refreshable {
    /**
     刷新UI列表
     */
    func refreshList()
}

/// 任何业务controller都可以挂上这个协议以实现list controller的功能
public protocol GMListProtocol: class, Refreshable {
    associatedtype ViewModelType: ViewModelProtocol // 这里需要一个实现 ViewModelType 的viewModel实例，通过这样一个泛型，可以适应不同的业务需求
    var viewModel: ViewModelType { get set } // 需要controller自己实例化

    /**
     向controller.view中添加一个table，并根据 fetchNow 的情况来判断是否自动请求网络
     - parameter tableStyle: UITableViewStyle
     - parameter now:        true表示立即请求风格
     */
    func addTableView(style tableStyle: UITableViewStyle, fetchNow now: Bool)

    func addCollectionView(_ layout: UICollectionViewLayout, fetchNow now: Bool)

    /**
     主动调用该方法以请求数据
     */
    func fetchData()

    /**
     请求完网络，会执行该方法以更新非table cell、section UI
     */
    func updateOtherUIData()
    
    /**
     上拉刷新时会调用
     */
    func footerRefreshing()
    
    /**
     下拉刷新时会调用
    */
    func headerRefreshing()

    /**
     设置是否需要刷新和加载更多，默认是都需要。
     另外：使用方法而非属性。少些变量，多些方法，这样可以让协议更简单一些

     - parameter need: 是否需要
     */
    func setNeedHeaderRefresh(_ need: Bool)
    func setNeedFooterRefresh(_ need: Bool)
}

private var emptyViewAssociationKey: UInt8 = 0
private var tableViewAssociationKey: UInt8 = 0
private var collectionViewAssociationKey: UInt8 = 0
private var listViewAssociationKey: UInt8 = 0

private extension UIScrollView {
    func reloadList() {
        (self as? UITableView)?.reloadData()
        (self as? UICollectionView)?.reloadData()
    }
}

public extension GMListProtocol where Self: GMBaseController {
    public var tableView: UITableView {
        get {
            return objc_getAssociatedObject(self, &tableViewAssociationKey) as! UITableView
        }
        set(newValue) {
            objc_setAssociatedObject(self, &tableViewAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }

    public var collectionView: UICollectionView {
        get {
            return objc_getAssociatedObject(self, &collectionViewAssociationKey) as! UICollectionView
        }
        set(newValue) {
            objc_setAssociatedObject(self, &collectionViewAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }

    fileprivate var listView: UIScrollView {
        get {
            return objc_getAssociatedObject(self, &listViewAssociationKey) as! UIScrollView
        }
        set(newValue) {
            objc_setAssociatedObject(self, &listViewAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }

    public func addTableView(style tableStyle: UITableViewStyle, fetchNow now: Bool) {
        tableView = UITableView(frame: self.view.bounds, style: tableStyle)
        tableView.backgroundColor = UIColor.background
        tableView.separatorStyle = .none
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 80;
        tableView.sectionHeaderHeight = UITableViewAutomaticDimension
        tableView.sectionFooterHeight = UITableViewAutomaticDimension
        if #available(iOS 11.0, *) {
            tableView.contentInsetAdjustmentBehavior = .never
        }
        manager(tableView, fetchNow: now)
    }

    public func addCollectionView(_ layout: UICollectionViewLayout, fetchNow now: Bool) {
        collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)
        collectionView.backgroundColor = UIColor.separatorLine
        manager(collectionView, fetchNow: now)
    }

    fileprivate func manager(_ list: UIScrollView, fetchNow now: Bool) {
        view.addSubview(list)
        list.snp.makeConstraints { (make) in
            make.left.equalTo(0)
            make.right.equalTo(0)
            make.top.equalTo(GMNavigationBar.barHeight)
            make.bottom.equalTo(0)
        }
        listView = list
        setNeedHeaderRefresh(true)
        setNeedFooterRefresh(true)
        addKVO()
        if now {
            fetchData()
        }
    }

    public func fetchData() {
        if viewModel.isDataArrayEmpty() {
            showLoading(nil)
        }
        viewModel.fetchRemoteData()
    }

    fileprivate func addKVO() {
        _ = viewModel.fetchDataResult.afterChange += { [weak self] in
            self?.observeHandler($1)
        }
    }

    fileprivate func observeHandler(_ newValue: Int) {
        hideLoading()
        if newValue == 0 {
            if viewModel.isDataArrayEmpty() {
                showEmptyView(.empty)
            } else {
                hideEmptyView()
            }
        } else {
            showEmptyView(.exception)
        }

        listView.reloadList()

        // 因为数据源的改变，所以应该先reloadData完UI再endRefreshing
        if let mjHeader = self.listView.mj_header, !mjHeader.isHidden {
            mjHeader.endRefreshing()
        }
        
        if let mjFooter = self.listView.mj_footer, !mjFooter.isHidden {
            mjFooter.endRefreshing()
        }
        updateOtherUIData()
    }

    public func refreshList() {
        viewModel.clearData()
        listView.reloadList()
        fetchData()
    }

    public func updateOtherUIData() {
    }
    
    public func footerRefreshing() {
    }
    
    public func headerRefreshing() {
    }

    public func setNeedHeaderRefresh(_ need: Bool) {
        if need {
            let header = GMRefreshHeader()
            header.refreshingBlock = { [weak self] in
                guard let strongSelf = self else { return }
                strongSelf.viewModel.willRefresh()
                strongSelf.fetchData()
                strongSelf.headerRefreshing()
            }
            listView.mj_header = header
        } else {
            listView.mj_header?.isHidden = true
        }
    }

    public func setNeedFooterRefresh(_ need: Bool) {
        if need {
            let footer = GMRefreshFooter()
            footer.stateLabel?.isHidden = true
            footer.isRefreshingTitleHidden = true
            footer.refreshingBlock = { [weak self] in
                // TODO + _deleteCount
                guard let strongSelf = self else { return }
                strongSelf.viewModel.willLoadMore()
                strongSelf.fetchData()
                strongSelf.footerRefreshing()
            }
            listView.mj_footer = footer
        } else {
            listView.mj_footer?.isHidden = true
        }
    }
}
