var

Iterable,
Map;

(function($module) {

"use strict";

var
    Iterator_ = function(collection) {
        var
            iteration = -1,
            primeFactor = 2,
            remains = collection.clone(),
            primeFactors = [],
            quotients = [],
            decomposition = new Map(),

            hasSomeMultipleOf = function(remains, factor) {
                var
                    i = 0;

                while(i < remains.length) {
                    if (remains[i++].multipleOf(factor)) {
                        return true;
                    }
                }

                return false;
            },

            resolveFactor = function(remains) {
                if (remains.amount(1) < remains.length) {
                    while(!hasSomeMultipleOf(remains, primeFactor)) {
                        primeFactor = primeFactor.nextPrimeFactor();
                    }
                } else {
                    primeFactor = 1;
                }

                return primeFactor;
            },

            resolveDecomposition = function(reference) {
                if (reference.hasNext()) {
                    reference.decompose();
                }
            };

        this.primeFactor = function() {
            return primeFactor;
        };

        this.next = function() {
            var
                i,
                clone,
                iterator = Iterable.Proxy.new(remains),

                callback = function(n) {
                    i = iterator.index();
                    if (n.multipleOf(primeFactor)) {
                        remains[i] /= primeFactor;
                    }
                };

            if (this.hasNext()) {
                iteration += 1;
                if(iteration === 0) {
                    return remains;
                }

                primeFactors.push(primeFactor);

                iterator.each(callback);

                resolveFactor(remains);

                clone = remains.clone();
                decomposition.set(clone, primeFactor);
                quotients.push(clone);

                return remains;
            }

            return undefined;

        };

        this.hasNext = function() {
            return primeFactor > 1;
        };

        this.primeFactors = function() {
            resolveDecomposition(this);

            return primeFactors;
        };

        this.reset = function() {
            remains = collection.clone();
            iteration = -1;
            primeFactor = 2;
        };

        this.finalize = function() {
            primeFactor = 1;
        };

        this.decompose = function(callback) {
            if (typeof callback != "function") {
                callback = function(){};
            }

            while (this.hasNext(remains)) {
                callback.call(collection, this.next());
            }

            return decomposition;
        };

        this.decomposition = function() {
            resolveDecomposition(this);

            return decomposition;
        };

        this.quotients = function() {
            resolveDecomposition(this);

            return quotients;
        };

        resolveFactor(remains);
        decomposition.set(remains.clone(), primeFactor);
    };

try {
    Object.defineProperties($module, {
        PrimeFactorsDecomposition: {
            value: {}
        }
    });
} catch(e) {}

try {
    Object.defineProperties($module.PrimeFactorsDecomposition, {
        Proxy: {
            value: {}
        }
    });
} catch(e) {}

try {
    Object.defineProperties($module.PrimeFactorsDecomposition.Proxy, {
        new: {
            value: function(numbers, callback) {
                var
                    collection,
                    _iterator,
                    self = $module.PrimeFactorsDecomposition.Proxy,
                    argumentsAsArray = Array.from(arguments);

                if (!(this instanceof self.new)) {
                    return new self.new(numbers, callback);
                }

                if (typeof argumentsAsArray[argumentsAsArray.length - 1] ==
                    "function") {
                    collection = argumentsAsArray
                        .slice(0, argumentsAsArray.length - 1);
                } else {
                    collection = argumentsAsArray.flatten().compact();
                }

                _iterator = new Iterator_(collection);

                this.primeFactor = function() {
                    return _iterator.primeFactor();
                };

                this.primeFactors = function() {
                    return _iterator.primeFactors();
                };

                this.next = function() {
                    return _iterator.next();
                };

                this.reset = function() {
                    _iterator.reset();
                };

                this.finalize = function() {
                    _iterator.finalize();
                };

                this.hasNext = function() {
                    return _iterator.hasNext();
                };

                this.decompose = function(callback) {
                    return _iterator.decompose(callback);
                };

                this.decomposition = function() {
                    return _iterator.decomposition();
                };

                this.quotients = function() {
                    return _iterator.quotients();
                };

                return this;
            }
        }
    });
} catch(e) {}

})(Math);