Source: array/IndexedLens.js

"use strict";

var _ = require('lodash'),

    Lens = require('../Lens'),
    utils = require('./utils'),

    IndexedLens,

    get,
    map;

/**
 * Get the element at a specific index of an array
 *
 * @param {int} index The index to get
 * @returns {Function} The get function required to construct a Lens
 * @param {boolean} unsafe If true, throw an error if index is invalid
 */
get = function (index, unsafe) {
    return function (arr) {
        index = utils.normalizeIndex(arr, index);

        if (!(_.isArray(arr))) {
            throw new Error('Argument to indexed lens must be an array');
        }

        // Only allow updates if array element exists
        if (utils.isValidIndex(arr, index + 1)) {
            return arr[index];
        }

        if (unsafe) {
            throw new Error('Attempt to access invalid index ' + index);
        }

        return null;
    };
};

/**
 * Map a function over the value at some index in an array.
 * Index must be in the interval `[0, array.length]` (inclusive); i.e. you may only modify existing elements or
 * add an element to the end.
 *
 * @param {int} index The index to map over
 * @returns {Function} The map function required to construct a Lens
 * @param {boolean} unsafe If true, throw an error if index isn't valid.
 */
map = function (index, unsafe) {
    return function (arr, func) {
        var newArr = _.cloneDeep(arr);

        index = utils.normalizeIndex(arr, index);

        if (!(_.isArray(newArr))) {
            throw new Error('Argument to indexed lens must be an array');
        }

        // Only allow updates if array element exists or is the next element in the array
        if (utils.isValidIndex(arr, index)) {
            newArr[index] = func(newArr[index]);
        } else {
            // Only throw error if unsafe
            if (unsafe) {
                throw new Error('Array index ' + index + ' out of range');
            }
        }

        return newArr;
    };
};

/**
 * An `IndexedLens` is a `Lens` that focuses on some index of an array.
 *
 * @param {int} index The index to view on
 * @param {boolean} unsafe If true, throws errors when index is out of range.
 * @returns {Lens}
 * @constructor
 */
IndexedLens = function (index, unsafe) {
    this.base = Lens;
    this.base(get(index, unsafe), map(index, unsafe), { _index: index });
};

IndexedLens.prototype = new Lens;

/**
 * Construct an Unsafe `IndexedLens` that throws errors when attempting to access
 * elements that are out of bounds.
 *
 * @param {int} index index The index to view on
 * @returns {Lens}
 * @constructor
 */
IndexedLens.Unsafe = function (index) {
    this.base = IndexedLens;
    this.base(index, true);
};

IndexedLens.Unsafe.prototype = new IndexedLens;

/**
 * Derive all indexed lenses for an array and return them in an array
 *
 * @param {Array} arr The array to derive Lenses from
 * @returns {Array} An array of Lenses focusing on each index of arr
 */
IndexedLens.deriveLenses = function (arr) {
    var lenses = [];

    _.forEach(_.range(arr.length), function (index) {
        lenses[index] = new IndexedLens(index);
    });

    return lenses;
};


module.exports = IndexedLens;