//
//  ThreeLayerCollectionViewController.swift
//  MVMCoreUI
//
//  Created by Scott Pfeil on 4/6/20.
//  Copyright © 2020 Verizon Wireless. All rights reserved.
//

import Foundation

/// A view controller that has three main layers, a header, collection rows, and a footer. The header is added as a supplement header to the first section, and the footer is added as a supplement footer to the last section. This view controller allows for flexible space between the three layers to fit the screeen.
@objc open class ThreeLayerCollectionViewController: ProgrammaticCollectionViewController, UICollectionViewDelegateFlowLayout {
    
    private var topView: UIView?
    private var bottomView: UIView?
    private var headerView: ContainerCollectionReusableView?
    private var footerView: ContainerCollectionReusableView?
    private let headerID = "header"
    private let footerID = "footer"
    public var bottomViewOutsideOfScrollArea: Bool = false
    public var topViewOutsideOfScrollArea: Bool = false
    
    open override func updateViewConstraints() {
        // Update the spacing on constraint update
        updateFlexibleSpace()
        super.updateViewConstraints()
    }
    
    /// Updates the padding for flexible space (header or footer)
    private func updateFlexibleSpace() {
        guard let collectionView = collectionView else { return }

        let minimumSpace: CGFloat = minimumFillSpace()
        var currentSpace: CGFloat = 0
        var totalMinimumSpace: CGFloat = 0

        var fillTop = false
        if spaceBelowTopView() == nil, headerView != nil {
            fillTop = true
            currentSpace += headerView?.bottomConstraint?.constant ?? 0
            totalMinimumSpace += minimumSpace
        }

        var fillBottom = false
        if spaceAboveBottomView() == nil, footerView != nil {
            fillBottom = true
            currentSpace += footerView?.topConstraint?.constant ?? 0
            totalMinimumSpace += minimumSpace
        }

        guard fillTop || fillBottom else { return }
        let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: collectionView, minimumHeight: totalMinimumSpace)
        if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpace, 1) {
            if fillTop && fillBottom {
                // space both
                let half = newSpace / 2
                headerView?.bottomConstraint?.constant = half
                footerView?.topConstraint?.constant = half
                collectionView.collectionViewLayout.invalidateLayout()
            } else if fillTop {
                // Only top is spaced.
                headerView?.bottomConstraint?.constant = newSpace
                collectionView.collectionViewLayout.invalidateLayout()
            } else if fillBottom {
                // Only bottom is spaced.
                footerView?.topConstraint?.constant = newSpace
                collectionView.collectionViewLayout.invalidateLayout()
            }
        }
    }
    
    func addTopViewOutside() {
        guard let collectionView = collectionView, let topView = topView else { return }
        view.addSubview(topView)
        topView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        topView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
        view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true
        collectionView.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true
    }
     
    func addBottomViewOutside() {
        guard let collectionView = collectionView, let bottomView = bottomView else { return }
        view.addSubview(bottomView)
        bottomView.topAnchor.constraint(equalTo: collectionView.bottomAnchor).isActive = true
        bottomView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
        view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true
        view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true
    }
        
    //MARK: - ViewController
    open override func updateViews() {
        super.updateViews()
        // Needed due to dispatch in reloadCollectionData.
        DispatchQueue.main.async {
            let width = self.view.bounds.width
            if let topView = self.topView as? MVMCoreViewProtocol {
                topView.updateView(width)
            }
            if let bottomView = self.bottomView as? MVMCoreViewProtocol {
                bottomView.updateView(width)
            }
            if let cells = self.collectionView?.visibleCells {
                for cell in cells {
                    self.update(cell: cell, size: width)
                }
            }
            self.invalidateCollectionLayout()
        }
    }

    open override func handleNewData() {
        super.handleNewData()
        topView?.removeFromSuperview()
        bottomView?.removeFromSuperview()
        topView = viewForTop()
        bottomView = viewForBottom()
        if topViewOutsideOfScrollArea {
            topConstraint?.isActive = false
            addTopViewOutside()
        } else {
            topConstraint?.isActive = true
        }
        if bottomViewOutsideOfScrollArea {
            bottomConstraint?.isActive = false
            addBottomViewOutside()
        } else {
            bottomConstraint?.isActive = true
        }
        reloadCollectionData()
    }
    
    override open func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        collectionView?.frameChangeAction = { [weak self] in
            self?.invalidateCollectionLayout()
        }
    }
    
    //MARK: - Spacing
    // If both are subclassed to return a value, then the buttons will not be pinned towards the bottom because neither spacing would try to fill the screen.
    /// Space between the top view and the collection rows, nil to fill. 0 default
    open func spaceBelowTopView() -> CGFloat? {
        return 0
    }
    
    /// Space between the bottom view and the collection rows, nil to fill. nil default
    open func spaceAboveBottomView() -> CGFloat? {
        return nil
    }
    
    /// can override to return a minimum fill space. 0 default
    open func minimumFillSpace() -> CGFloat {
        return 0
    }

    //MARK: - Functions to subclass
    /// Subclass for a top view.
    open func viewForTop() -> UIView? {
        return nil
    }
        
    /// Subclass for a bottom view.
    open func viewForBottom() -> UIView? {
        return nil
    }
    
    //MARK: - Collection
    
    /// Should be used to refresh the layout of the collection view. Updates flexible padding.
    open func invalidateCollectionLayout() {
        self.collectionView?.collectionViewLayout.invalidateLayout()
        // TODO: Improve this workaround (autolayout for cells happens async so contentSize isn't accurate immediately)
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
            self.updateFlexibleSpace()
        })
    }
    
    /// Should be used to reload the data of the collection view. Updates flexible padding.
    open func reloadCollectionData() {
        collectionView?.reloadData()
        // TODO: Improve this workaround (autolayout for cells happens async so contentSize isn't accurate immediately)
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
            self.updateFlexibleSpace()
        })
    }
    
    /// Called in updateView, updates the cell.
    open func update(cell: UICollectionViewCell, size: CGFloat) {
        (cell as? MVMCoreViewProtocol)?.updateView(size)
    }
    
    open override func registerCells() {
        super.registerCells()
        collectionView?.register(ContainerCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID)
        collectionView?.register(ContainerCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: footerID)
    }
    
    open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 0
    }
    
    open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        guard section == 0 else { return .zero }
        // Calculate the height of the header since apple doesn't support autolayout. Width is fixed, height is tall as content.
        let header = headerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionHeader, at: IndexPath(row: 0, section: section))
        return header.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    }
    
    open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        guard section == numberOfSections(in: collectionView) - 1 else { return .zero }
        // Calculate the height of the footr since apple doesn't support autolayout. Width is fixed, height is tall as content.
        let footer = footerView ?? self.collectionView(collectionView, viewForSupplementaryElementOfKind: UICollectionView.elementKindSectionFooter, at: IndexPath(row: 0, section: section))
        return footer.systemLayoutSizeFitting(CGSize(width: collectionView.frame.width, height: UIView.layoutFittingExpandedSize.height), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
    }
    
    open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
        if kind == UICollectionView.elementKindSectionFooter,
            let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: footerID, for: indexPath) as? ContainerCollectionReusableView {
            let bottomView = (bottomViewOutsideOfScrollArea ?  nil : self.bottomView) ?? MVMCoreUICommonViewsUtility.getView(with: 0.5)
            footerView.addAndContain(view: bottomView)
            footerView.topConstraint?.constant = spaceAboveBottomView() ?? 0
            self.footerView = footerView
            return footerView
        } else if kind == UICollectionView.elementKindSectionHeader,
            let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? ContainerCollectionReusableView {
            let topView = (topViewOutsideOfScrollArea ?  nil : self.topView) ?? MVMCoreUICommonViewsUtility.getView(with: 0.5)
            headerView.addAndContain(view: topView)
            headerView.bottomConstraint?.constant = spaceBelowTopView() ?? 0
            self.headerView = headerView
            return headerView
        }
        fatalError()
    }
    
    open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        (collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObjectIVar, additionalData: nil)
    }
}
