/// <reference path="./leaf.ts" />
namespace Leaf {
    export class ViewModel<TVMs, TVMField extends string> extends Model<TVMs, TVMField>{
        private depChain: DependencyChain = new DependencyChain
        constructor(public readonly _name: string, protected _fields: TVMField[] = []) {
            super(_name, _fields)
            this.depChain.events.listenBy(this, "declare", (dep: { src: string, target: string }) => {
                console.log("declare", dep)
                this.events.listenBy(`dep/${dep.src}:${dep.target}`, `change/${dep.src}`, () => {

                    this.events.emit(`depChange/${dep.target}`)
                })
                this.events.listenBy(`dep/${dep.src}:${dep.target}`, `depChange/${dep.src}`, () => {
                    this.events.emit(`depChange/${dep.target}`)
                })
            })
            this.depChain.events.listenBy(this, "revoke", (dep: { src: string, target: string }) => {
                this.events.stopListenBy(`dep/${dep.src}:${dep.target}`)
            })
        }
        public set(key: TVMField, value: any, silent?: boolean): boolean {
            if (typeof value === "function" || typeof this.get(key) === "function") {
                this.depChain.updateFunctionDependency(key, value)
            }
            return super.set(key, value)
        }
    }
    export class DependencyChain {
        events = new Leaf.EventEmitter<"declare" | "revoke">()
        extractFunctionDependency(fn: Function): string[] {
            if (typeof fn !== "function") return []
            let fnString = fn.toString()
            let reg = /VM\.(\w+)\b/g
            let match
            let result: string[] = []
            while (match = reg.exec(fnString)) {
                result.push(match[1])
            }
            return result
        }
        maxDepth: number = 10
        updateFunctionDependency(name: string, fn: Function) {
            let deps: string[] = this.extractFunctionDependency(fn)
            this.validateDependency(name, deps)
            let oldDeps = this.dependencyMap[name]
            let diff = Util.arrayDiff(deps, oldDeps)
            for (let dep of diff.left) {
                this.events.emit("declare", { target: name, src: dep })
            }
            for (let dep of diff.right) {
                this.events.emit("revoke", { target: name, src: dep })
            }
            this.dependencyMap[name] = deps
        }
        validateDependency(target: string, src: string[], depth: number = 0): boolean {
            if (src.indexOf(target) >= 0) {
                throw new Error(`Recursive dependency ${target}`)
            }
            if (depth > this.maxDepth) {
                throw new Error(`Max dependency depth exceed :${this.maxDepth}`)
            }
            let nextCheck: string[] = []
            for (let item of src) {
                if (this.dependencyMap[item]) {
                    let nextDeps = this.dependencyMap[item]
                    for (let nt of nextDeps) {
                        // already checked
                        if (src.indexOf(nt) >= 0) continue
                        if (nextCheck.indexOf(nt) >= 0) continue
                        nextCheck.push(nt)
                    }
                }
            }
            if (nextCheck.length === 0) return true
            return this.validateDependency(target, nextCheck, depth + 1)
        }
        dependencyMap: {
            [index: string]: string[]
        } = {}
    }
}
