/** @module editorElements.Transform */

/**
 * Helper class used by {@link Transform}.
 *
 * Represents one single property of the transform argument, for example `translate(360 150)`
 * that may be a part of longer transform argument like `transform="translate(360 150) rotate(90 30 20)"`
 */
class Property {
    /**
     * Initialize the Property object
     * @param {string} [string] string in the property format `propertyname(list of space separated values)`
     */
    constructor(string) {
        if (string !== undefined) {
            this.name = string.replace(/^[ ]*([^(]+).*/, '$1');
            this.args = string.replace(/^[^(]+\((.*)\)/, '$1').split(' ');
        }
    }

    /**
     * set or replace the name of this property
     * @param {string} name new name for this property
     */
    setName(name) {
        this.name = name;
    }

    /**
     * set arguments of this property
     * @param {array} args array of arguments
     */
    setArguments(args) {
        this.args = args;
    }

    /**
     * get string representation of the property
     * @return {string} property in the property format `name(arg1 arg2)`
     */
    get() {
        return this.name + '(' + this.args.join(' ') + ')';
    }
}

/**
 * API for manipulating the transform argument used in SVG
 */
export default class Transform {
    /**
     * Initialize the Transform object
     * @param {string} [string] string in the format of the `transform` argument in SVG, for example `translate(360 150) rotate(90 30 20)`
     */
    constructor(string) {
        /**
         * array of {@link Property} instances
         * @type {Array}
         */
        this.items = [];

        if (string !== undefined) {
            for (const item of string.split(')')) {
                if (item) {
                    // if not empty
                    this.items.push(new Property(item + ')'));
                }
            }
        }
    }

    /**
     * convert distances from SVG pixels to grid pixels
     * @param {App} appInstance instance of [App](./module-App.html)
     */
    toGridPixels(appInstance) {
        this.pixelConversion(val => appInstance.SVGToGrid(val));
    }

    /**
     * convert distances from grid pixels to SVG pixels
     * @param {App} appInstance instance of [App](./module-App.html)
     */
    toSVGPixels(appInstance) {
        this.pixelConversion(val => appInstance.gridToSVG(val));
    }

    /**
     * Convert distances using a specified convertor. Used by toGridPixels and toSVGPixels
     * @param  {Function} convertor function that converts int to int
     */
    pixelConversion(convertor) {
        const propertyMap = {
            translate: item => {
                item.args = item.args.map(arg => convertor(arg));
                return item;
            },
            rotate: item => {
                item.args = [item.args[0], convertor(item.args[1]), convertor(item.args[2])];
                return item;
            }
        };

        this.items = this.items.map(item => {
            return propertyMap[item.name] ? propertyMap[item.name](item) : item;
        });
    }

    /**
     * find a transform property by name and get its index in the [items](#items) array
     * @param  {string} name name of the property
     * @return {number}      index of the property in the array of properties or `-1` if not found
     */
    getIndex(name) {
        for (let i = 0; i < this.items.length; i++) {
            if (name === this.items[i].name) {
                return i;
            }
        }

        return -1;
    }

    /**
     * get the translate property
     * @return {Object} object containing parameters of the translate attribute
     */
    getTranslate() {
        let args = this.getArguments(this.getIndex('translate'));

        return {
            x: Number(args[0]),
            y: Number(args[1])
        };
    }

    /**
     * get the rotate property
     * @return {Object} object containing parameters of the rotate attribute
     */
    getRotate() {
        let args = this.getArguments(this.getIndex('rotate'));

        return {
            deg: Number(args[0]),
            centerX: Number(args[1]),
            centerY: Number(args[2])
        };
    }

    /**
     * set translate to the specified values
     * @param {number} x horizontal translation
     * @param {number} y vertical translation
     */
    setTranslate(x, y) {
        this.setParameter('translate', [x, y]);
    }

    /**
     * set rotate to the specified values
     * @param {number} deg     angle of the rotation in degrees
     * @param {number} centerX horizontal position of the center of the rotation
     * @param {number} centerY vertical position of the center of the rotation
     */
    setRotate(deg, centerX, centerY) {
        this.setParameter('rotate', [deg, centerX, centerY]);
    }

    /**
     * rotate by 90 degrees to the right or left, depending on the parameter `right`
     * @param {number} centerX horizontal position of the center of the rotation
     * @param {number} centerY vertical position of the center of the rotation
     * @param {boolean} right rotate to the right if `true`, to the left if `false`
     */
    rotateRightAngle(centerX, centerY, right) {
        const amount = right ? 90 : 270;

        if (this.getIndex('rotate') === -1) {
            this.setRotate(amount, centerX, centerY);
        } else {
            let newRotation = (parseInt(this.getRotate().deg) + amount) % 360;

            if (newRotation === 180) {
                // swap center coordinates
                // because rotate(c, x, y) is defined like transform(-x, -y) rotate(c) transform(x, y)
                let a = centerX;
                centerX = centerY;
                centerY = a;
            }

            this.setRotate(newRotation, centerX, centerY);
        }
    }

    /**
     * rotate by 90 degrees to the right
     * @param  {number} centerX horizontal position of the center of the rotation
     * @param  {number} centerY vertical position of the center of the rotation
     */
    rotateRight(centerX, centerY) {
        this.rotateRightAngle(centerX, centerY, true);
    }

    /**
     * rotate by 90 degrees to the left
     * @param  {number} centerX horizontal position of the center of the rotation
     * @param  {number} centerY vertical position of the center of the rotation
     */
    rotateLeft(centerX, centerY) {
        this.rotateRightAngle(centerX, centerY, false);
    }

    /**
     * get the transform values in a string
     * @return {string} string that can be used as a value for the transform property of a SVG element
     */
    get() {
        let retVal;
        for (const item of this.items) {
            if (retVal) {
                retVal += ' ' + item.get();
            } else {
                retVal = item.get();
            }
        }
        return retVal;
    }

    /**
     * get arguments of a property specified by index
     * @param  {number} index index of the property
     * @return {array}       array of arguments of the specified property
     */
    getArguments(index) {
        return this.items[index].args;
    }

    /**
     * set argumets of a property specified by name
     * @param {string} name name of the property
     * @param {array} args array of arguments of the specified property
     */
    setParameter(name, args) {
        // determine index of the parameter (if set), else index == -1
        let index = this.getIndex(name);

        // if the property has been already set, change it (rewrite the array in the right index)
        // else create a new one (set index to the length of an array --> ad an item to the end)
        if (index === -1) {
            index = this.items.length;
            this.items[index] = new Property();
            this.items[index].setName(name);
        }

        // save args under the right index
        this.items[index].setArguments(args);
    }
}
