//
//  ParentModelProtocol.swift
//  MVMCoreUI
//
//  Created by Kyle on 3/19/21.
//  Copyright © 2021 Verizon Wireless. All rights reserved.
//

import Foundation

public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol {
    
    /// Returns the direct children of this component. (Does not recurse.)
    var children: [MoleculeModelProtocol] { get }
    
    /// Method for replacing surface level children. (Does not recurse.)
    mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool
}

public extension ParentModelProtocol where Self: AnyObject {
    
    /// Top level test to replace child molecules. Each parent molecule should attempt to replace.
    func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { return false }
    
    /// Helper function for replacing a single molecules with type and optionality checks.
    func replaceChildMolecule<T>(at childMolecule: inout T, with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
        guard let childIdMolecule = childMolecule as? MoleculeModelProtocol else { return false }
        if childIdMolecule.id == replacementMolecule.id {
            guard let replacementMolecule = replacementMolecule as? T else {
                throw MolecularError.error("Molecular replacement '\(replacementMolecule.id)' does not type match \(type(of: T.self)) of \(type(of: self))")
            }
            childMolecule = replacementMolecule
            return true
        }
        return false
    }
    
    /// Helper for replacing a molecule in place within an array. Note the "in".
    func replaceChildMolecule<T>(in molecules: inout [T], with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
        guard let moleculeIdModels = molecules as? [MoleculeModelProtocol], let matchingIndex = moleculeIdModels.firstIndex(where: {
            $0.id == replacementMolecule.id
        }) else { return false }
        guard let replacementMolecule = replacementMolecule as? T else {
            throw MolecularError.error("Molecular replacement '\(replacementMolecule.id)' does not type match \(type(of: T.self)) of \(type(of: self))")
        }
        molecules[matchingIndex] = replacementMolecule
        return true
    }
}

public protocol ParentMoleculeModelProtocol: ParentModelProtocol, MoleculeModelProtocol {
    
}

public extension ParentMoleculeModelProtocol {
    
    func reduceDepthFirstTraverse<Result>(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int) -> Result) -> Result {
        var result = initialResult
        if (options == .parentFirst) {
            result = nextPartialResult(result, self, depth)
        }
        result = children.reduce(result) { (result, molecule) -> Result in
            if let additionalParent = molecule as? ParentMoleculeModelProtocol {
                // Safety net to make sure the ParentMoleculeModelProtocol's method extension is called over the base MoleculeModelProtocol.
                return additionalParent.reduceDepthFirstTraverse(options: options, depth: depth + 1, initialResult: result, nextPartialResult: nextPartialResult)
            }
            return molecule.reduceDepthFirstTraverse(options: options, depth: depth + 1, initialResult: result, nextPartialResult: nextPartialResult)
        }
        if (options == .childFirst) {
            result = nextPartialResult(result, self, depth)
        }
        // if options == .leafOnly don't call on self.
        return result
    }
    
    func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool)->Void) {
        var shouldStop = false
        if (options == .parentFirst) {
            onVisit(depth, self, &shouldStop)
            guard !shouldStop else { return }
        }
        let stopIntercept: (Int, MoleculeModelProtocol, inout Bool)->Void = { depth, model, stop in
            onVisit(depth, model, &shouldStop)
            stop = shouldStop
        }
        for child in children {
            if let additionalParent = child as? ParentMoleculeModelProtocol {
                // Safety net to make sure the ParentMoleculeModelProtocol's method extension is called over the base MoleculeModelProtocol.
                additionalParent.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept)
            } else {
                child.depthFirstTraverse(options: options, depth: depth + 1, onVisit: stopIntercept)
            }
            guard !shouldStop else { return }
        }
        if (options == .childFirst) {
            onVisit(depth, self, &shouldStop)
        }
        // if options == .leafOnly don't call on self.
    }
}
