//
//  MoleculeViewProtocol.swift
//  MVMCoreUI
//
//  Created by Suresh, Kamlesh on 10/24/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

import UIKit
import MVMCore.MVMCoreViewProtocol


public protocol MoleculeViewProtocol: UIView, ModelHandlerProtocol {
    
    /// Initializes the view with the model
    init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?)
    
    /// Sets the view with the model.
    func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?)
    
    /// Resets to default state before set with json is called again.
    func reset()
    
    /// Allows the molecule to set its name for reuse. Default could be moleculeName. Mainly used for list or collections.
    static func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String?
    
    /// For the molecule list to load more efficiently.  Mainly used for list or collections.
    static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat?
    
    /// Can return the required modules
    static func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]?
}

extension MoleculeViewProtocol {
    
    /// Calls set with model
    public init(model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        self.init(frame: .zero)
        set(with: model, delegateObject, additionalData)
    }
    
    /// Uses moleculeName
    public static func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
        model.moleculeName
    }
    
    // Do nothing, optionals.
    public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { }
    
    public func reset() { }
    
    public static func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { nil }
    
    public static func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? { nil }
    
    /// Temporary convenience function for helping bridge the gap for legacy code.
    public func setOptional<T: MoleculeModelProtocol>(with model: T?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
        if let model = model {
            set(with: model, delegateObject, additionalData)
        }
    }
}

// Convenience Functions
public extension ModelRegistry {
    /// Returns the type of molecule view for the given model
    static func getMoleculeClass(_ model: MoleculeModelProtocol) -> MoleculeViewProtocol.Type? {
        do {
            let type = try ModelRegistry.getHandler(model) as! MoleculeViewProtocol.Type
            return type
        } catch {
            if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) {
                MVMCoreLoggingHandler.addError(toLog: errorObject)
            }
            return nil
        }
    }
    
    /// Creates a molecule with the given model.
    static func createMolecule(_ model: MoleculeModelProtocol, delegateObject: MVMCoreUIDelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) -> MoleculeViewProtocol? {
        guard let type = getMoleculeClass(model) else { return nil }
        return type.init(model: model, delegateObject, additionalData)
    }
    
    // TODO: move below to KyleTraverse (tm)
    /// Convenience function to get required modules for a give model
    static func getRequiredModules(for model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>?) -> [String]? {
        guard let model = model else { return nil }
        return ModelRegistry.getMoleculeClass(model)?.requiredModules(with: model, delegateObject, error: error)
    }

    /// Convenience function to add require modules for the  given model to the passed in array.
    static func addRequiredModules(for model: MoleculeModelProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, moduleList: inout [String]?, errorList: inout [MVMCoreErrorObject]?) {
        guard let model = model else { return }
        let error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>? = nil
        if let modules = getRequiredModules(for: model, delegateObject, error: error) {
            moduleList?.append(contentsOf: modules)
        }
        if let error = error?.pointee {
            errorList?.append(error)
        }
    }
}
