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


@objc open class CollectionTemplate: ThreeLayerCollectionViewController, TemplateProtocol {
    //--------------------------------------------------
    // MARK: - Properties
    //--------------------------------------------------
    
    public typealias TemplateModel = CollectionTemplateModel
    public var templateModel: CollectionTemplateModel?
    
    public var moleculesInfo: [(identifier: String, class: AnyClass, molecule: (CollectionItemModelProtocol & MoleculeModelProtocol))]?
    
    var observer: NSKeyValueObservation?
    
    //--------------------------------------------------
    // MARK: - Computed Properties
    //--------------------------------------------------
    open override func parsePageJSON() throws {
        try parseTemplate(json: loadObject?.pageJSON)
        try super.parsePageJSON()
    }
    
    open override var loadObject: MVMCoreLoadObject? {
        didSet {
            guard loadObject != oldValue else { return }
            
            updateRequiredModules()
            observer?.invalidate()
            if let newObject = loadObject {
                observer = newObject.observe(\MVMCoreLoadObject.pageJSON, options: [.old, .new]) { [weak self] object, change in
                    self?.updateRequiredModules()
                }
            }
        }
    }
    
    //--------------------------------------------------
    // MARK: - Methods
    //--------------------------------------------------
    
    open override func viewForTop() -> UIView? {
        guard let headerModel = templateModel?.header,
            let molecule = ModelRegistry.createMolecule(headerModel, delegateObject: delegateObjectIVar)
            else { return super.viewForTop() }
        
        // Temporary, Default the horizontal padding
        if var container = templateModel?.header as? ContainerModelProtocol, container.useHorizontalMargins == nil {
            container.useHorizontalMargins = true
        }
        
        return molecule
    }
    
    override open func viewForBottom() -> UIView? {
        guard let footerModel = templateModel?.footer,
            let molecule = ModelRegistry.createMolecule(footerModel, delegateObject: delegateObjectIVar)
            else { return super.viewForBottom() }
        
        return molecule
    }
    
    open override func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>) -> Bool {
        guard super.shouldFinishProcessingLoad(loadObject, error: error) else { return false }
        
        // This template requires atleast one of the three layers.
        if templateModel?.header == nil,
           templateModel?.molecules?.count ?? 0 == 0,
           templateModel?.footer == nil,
           let errorObject = MVMCoreErrorObject(title: nil, message: MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess), messageToLog: "Collection template requires atleast one of the following: header, footer, molecules", code: CoreUIErrorCode.ErrorCodeListMolecule.rawValue, domain: ErrorDomainNative, location: String(describing: self)) {
            error.pointee = errorObject
            return false
        }
        return true
    }
    
    
    open override func handleNewData() {
        topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false
        bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false
        setup()
        registerCells()        
        super.handleNewData()
    }
    
    //--------------------------------------------------
    // MARK: - Collection
    //--------------------------------------------------
    
    open override func update(cell: UICollectionViewCell, size: CGFloat) {
        super.update(cell: cell, size: size)
        
        // Update the width for columns.
        if let collectionView = collectionView,
           let columns = templateModel?.columns, columns > 0,
           let cell = cell as? CollectionTemplateItemProtocol {
            let width = (size - collectionView.adjustedContentInset.left - collectionView.adjustedContentInset.right) / CGFloat(columns)
            cell.set(width: width)
        }
    }
    
    open override func registerCells() {
        super.registerCells()
        guard let moleculesInfo = moleculesInfo else { return }
        
        for moleculeInfo in moleculesInfo {
            collectionView?.register(moleculeInfo.class, forCellWithReuseIdentifier: moleculeInfo.identifier)
        }
    }
    
    open override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        moleculesInfo?.count ?? 0
    }
    
    open override func numberOfSections(in collectionView: UICollectionView) -> Int { 1 }
    
    open override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let moleculeInfo = moleculesInfo?[indexPath.row] else { return UICollectionViewCell() }
        
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: moleculeInfo.identifier, for: indexPath)
        (cell as? MoleculeViewProtocol)?.reset()
        (cell as? MoleculeViewProtocol)?.set(with: moleculeInfo.molecule, delegateObjectIVar, nil)
        update(cell: cell, size: view.frame.width)
        // Neded to fix an apple defect where the cell is not the correct size on certain devices for certain cells
        cell.layoutIfNeeded()
        return cell
    }
    
    public override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        (collectionView.cellForItem(at: indexPath) as? CollectionTemplateItemProtocol)?.didSelectCell(at: indexPath, delegateObject: delegateObjectIVar, additionalData: nil)
    }
    
    public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        (cell as? CollectionTemplateItemProtocol)?.willDisplay()
    }
    
    //--------------------------------------------------
    // MARK: - Convenience
    //--------------------------------------------------
    
    /// Returns the (identifier, class) of the molecule for the given map.
    open func getMoleculeInfo(with item: (CollectionItemModelProtocol & MoleculeModelProtocol)?) -> (identifier: String, class: AnyClass, molecule: CollectionItemModelProtocol & MoleculeModelProtocol)? {
        guard let item = item,
            let moleculeClass = ModelRegistry.getMoleculeClass(item) else { return nil }
        let moleculeName = moleculeClass.nameForReuse(with: item, delegateObjectIVar) ?? item.moleculeName
        return (moleculeName, moleculeClass, item)
    }
    
    /// Sets up the molecule list and ensures no errors loading all content.
    open func getMoleculeInfoList() -> [(identifier: String, class: AnyClass, molecule: (CollectionItemModelProtocol & MoleculeModelProtocol))]? {
        
        var moleculeList: [(identifier: String, class: AnyClass, molecule: CollectionItemModelProtocol & MoleculeModelProtocol)] = []
        
        if let molecules = templateModel?.molecules {
            for molecule in molecules {
                if let info = getMoleculeInfo(with: molecule) {
                    moleculeList.append(info)
                }
            }
        }
        
        return moleculeList.count > 0 ? moleculeList : nil
    }
    
    /// Sets up the header, footer, molecule list and ensures no errors loading all content.
    open func setup() {
        moleculesInfo = getMoleculeInfoList()
    }
    
    /// Adds modules from requiredModules() to the MVMCoreViewControllerMapping.requiredModules map.
    open func updateRequiredModules() {
        if let requiredModules = requiredModules(), let pageType = pageType {
            MVMCoreViewControllerMappingObject.shared()?.addRequiredModules(toMapping: requiredModules, forPageType: pageType)
        }
    }
    
    /// Gets modules required by the loadObject.pageJSON.
    open func requiredModules() -> [Any]? {
        var modules: [String]? = []
        var errors: [MVMCoreErrorObject]? = nil
        ModelRegistry.addRequiredModules(for: templateModel?.header, delegateObjectIVar, moduleList: &modules, errorList: &errors)
        ModelRegistry.addRequiredModules(for: templateModel?.footer, delegateObjectIVar, moduleList: &modules, errorList: &errors)
        
        if let molecules = templateModel?.molecules {
            for molecule in molecules {
                ModelRegistry.addRequiredModules(for: molecule, delegateObjectIVar, moduleList: &modules, errorList: &errors)
            }
        }
        
        return modules
    }
}
