(function (paw, utils) {
    function IterableUtils() {

    }

    IterableUtils.prototype.iterate = function (iterable, onIteration) {
        if (paw.utils.value.isNumber(iterable)) {
            for (var i = 0; i < iterable; i++) {
                if (onIteration(i) === false) {
                    break;
                }
            }
            return;
        }
        if (paw.utils.value.isArray(iterable)) {
            for (var i = 0; i < iterable.length; i++) {
                if (onIteration(iterable[i], i) === false) {
                    break;
                }
            }
            return;
        }
        if (paw.utils.value.isObject(iterable)) {
            for (var k in iterable) {
                if (onIteration(iterable[k], k) === false) {
                    break;
                }
            }
            return;
        }
        throw "Can not iterate, " + (typeof iterable) + " given. Must be an array or an object.";
    };

    IterableUtils.prototype.filter = function (iterable, condition) {
        if (paw.utils.value.isArray(iterable) || paw.utils.value.isNumber(iterable)) {
            var filtered = [];
            this.iterate(iterable, function (item, i) {
                if (condition(item, i) === true) {
                    filtered.push(item);
                }
            });
            return filtered;
        }
        if (paw.utils.value.isObject(iterable)) {
            var filtered = {};
            this.iterate(iterable, function (item, k) {
                var item = iterable[k];
                if (condition(item, k) === true) {
                    filtered[k] = item;
                }
            })
            return filtered;
        }

    };

    IterableUtils.prototype.get = function (iterable, condition) {
        var filtered = this.filter(iterable, condition);
        return filtered.length === 0 ? null : filtered[0];
    };

    IterableUtils.prototype.exists = function (iterable, condition) {
        return paw.utils.value.isSet(this.get(iterable, condition));
    };

    IterableUtils.prototype.getIndex = function (arr, condition) {
        var index = -1;
        this.iterate(arr, function (item, i) {
            if (condition(item, i) === true) {
                index = i;
                return false;
            }
        });
        return index;
    };

    IterableUtils.prototype.deleteItem = function (iterable, condition) {
        if (paw.utils.value.isArray(iterable)) {
            var index = this.getIndex(iterable, condition);
            if (index > -1) {
                iterable.splice(index, 1);
                return true;
            }
            return false;
        }
        throw "Can not delete item, not an array. " + (typeof iterable) + " given.";
    };

    IterableUtils.prototype.deleteItems = function (iterable, indeces) {
        if (paw.utils.value.isArray(iterable)) {
            this.iterate(indeces, function (index) {
                iterable.splice(index, 1);
            });
            return;
        }
        throw "Can not delete item, not an array. " + (typeof iterable) + " given.";
    };

    IterableUtils.prototype.map = function(iterable, mapper){
        var isArray = paw.utils.value.isArray(iterable);
        var mapped = isArray? [] : {};
        this.iterate(iterable, function(item, i){
            if(isArray){
                mapped.push(mapper(item, i));
                return;
            }
            mapped[i] = mapper(item, i);
        })
        return mapped;
    };

    IterableUtils.prototype.assertProps = function(props, assert){
        if(!utils.value.isSet(props)){
            throw "[Assert properties] Properties not set.";
        }
        if(!utils.value.isObject(props)){
            throw "[Assert properties] Can't assert properties. Given value is not an object."
        }
        var missing = [];
        this.iterate(assert, function(pname){
            if(!props.hasOwnProperty(pname)){
                missing.push(pname);
                return;
            }
        });
        if(missing.length > 0){
            throw "[Assert properties] missing properties: " + missing.join(', ');
        }
        return props;
    };

    paw.utils.iterable = new IterableUtils();
})(paw, paw.utils);