import extend from './extend';
import Image from '../image/image';

let computedPropertyDescriptor = {
    configurable: true,
    enumerable: false,
    get: undefined
};

/**
 * Class representing stack of images
 * @class Stack
 */
export default class Stack extends Array {
    constructor(images) {
        if (Array.isArray(images)) {
            super(images.length);
            for (let i = 0; i < images.length; i++) {
                this[i] = images[i];
            }
        } else if (typeof images === 'number') {
            super(images);
        } else {
            super();
        }
        this.computed = null;
    }

    static load(urls) {
        return Promise.all(urls.map(Image.load)).then(images => new Stack(images));
    }

    static extendMethod(name, method, {inPlace = false, returnThis = true, partialArgs = []} = {}) {
        if (inPlace) {
            Stack.prototype[name] = function (...args) {
                // remove computed properties
                this.computed = null;
                let result = method.apply(this, [...partialArgs, ...args]);
                if (returnThis)
                    return this;
                return result;
            };
        } else {
            Stack.prototype[name] = function (...args) {
                return method.apply(this, [...partialArgs, ...args]);
            };
        }
        return Stack;
    }

    static extendProperty(name, method, {partialArgs = []} = {}) {
        computedPropertyDescriptor.get = function () {
            if (this.computed === null) {
                this.computed = {};
            } else if (this.computed.hasOwnProperty(name)) {
                return this.computed[name];
            }
            let result = method.apply(this, partialArgs);
            this.computed[name] = result;
            return result;
        };
        Object.defineProperty(Stack.prototype, name, computedPropertyDescriptor);
        return Stack;
    }

    /**
     * Check if a process can be applied on the stack
     * @param processName
     * @param options
     * @private
     */
    checkProcessable(processName, options = {}) {
        if (typeof processName !== 'string') {
            throw new TypeError('checkProcessable requires as first parameter the processName (a string)');
        }
        if (this.size === 0) {
            throw new TypeError('The process: ' + processName + ' can not be applied on an empty stack');
        }
        this[0].checkProcessable(processName, options);
        for (let i = 1; i < this.length; i++) {
            if ((options.sameSize === undefined || options.sameSize) && this[0].width !== this[i].width) {
                throw new TypeError('The process: ' + processName + ' can not be applied if width is not identical in all images');
            }
            if ((options.sameSize === undefined || options.sameSize) && this[0].height !== this[i].height) {
                throw new TypeError('The process: ' + processName + ' can not be applied if height is not identical in all images');
            }
            if ((options.sameAlpha === undefined || options.sameAlpha) && this[0].alpha !== this[i].alpha) {
                throw new TypeError('The process: ' + processName + ' can not be applied if alpha is not identical in all images');
            }
            if ((options.sameBitDepth === undefined || options.sameBitDepth) && this[0].bitDepth !== this[i].bitDepth) {
                throw new TypeError('The process: ' + processName + ' can not be applied if bitDepth is not identical in all images');
            }
            if ((options.sameColorModel === undefined || options.sameColorModel) && this[0].colorModel !== this[i].colorModel) {
                throw new TypeError('The process: ' + processName + ' can not be applied if colorModel is not identical in all images');
            }
            if ((options.sameNumberChannels === undefined || options.sameNumberChannels) && this[0].channels !== this[i].channels) {
                throw new TypeError('The process: ' + processName + ' can not be applied if channels is not identical in all images');
            }
        }
    }
}

if (!Symbol.species) { // support old engines
    Stack.prototype.map = function (cb, thisArg) {
        if (typeof cb !== 'function') {
            throw new TypeError(cb + ' is not a function');
        }
        let newStack = new Stack(this.length);
        for (let i = 0; i < this.length; i++) {
            newStack[i] = cb.call(thisArg, this[i], i, this);
        }
        return newStack;
    };
}

extend(Stack);

@@ -4187,16 +4187,22 @@
 %7D%0A%0Aif (!
+Array%5B
 Symbol.s
@@ -4207,16 +4207,17 @@
 .species
+%5D
 ) %7B // s

fix check of Symbol.species