/**
 * Describes a gauge needle as a shape defined in SVG path syntax.
 *
 * Note: this class and its subclasses are not supposed to be instantiated directly
 * - an object should be passed the gauge's {@link Ext.ux.gauge.Gauge#needle}
 * config instead. Needle instances are also not supposed to be moved
 * between gauges.
 */
Ext.define('Ext.ux.gauge.needle.Abstract', {
    mixins: [
        'Ext.mixin.Factoryable'
    ],

    alias: 'gauge.needle.abstract',

    isNeedle: true,

    config: {
        /**
         * The generator function for the needle's shape.
         * Because the gauge component is resizable, and it is generally
         * desirable to resize the needle along with the gauge, the needle's
         * shape should have an ability to grow, typically non-uniformly,
         * which necessitates a generator function that will update the needle's
         * path, so that its proportions are appropriate for the current gauge size.
         *
         * The generator function is given two parameters: the inner and outer
         * radius of the needle. For example, for a straight arrow, the path
         * definition is expected to have the base of the needle at the origin
         * - (0, 0) coordinates - and point downwards. The needle will be automatically
         * translated to the center of the gauge and rotated to represent the current
         * gauge {@link Ext.ux.gauge.Gauge#value value}.
         *
         * @param {Function} path The path generator function.
         * @param {Number} path.innerRadius The function's first parameter.
         * @param {Number} path.outerRadius The function's second parameter.
         * @return {String} path.return The shape of the needle in the SVG path syntax returned by
         * the generator function.
         */
        path: null,

        /**
         * The inner radius of the needle. This works just like the `innerRadius`
         * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
         * The default value is `25` to make sure the needle doesn't overlap with
         * the value of the gauge shown at its center by default.
         *
         * @param {Number/String} [innerRadius=25]
         */
        innerRadius: 25,

        /**
         * The outer radius of the needle. This works just like the `outerRadius`
         * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
         *
         * @param {Number/String} [outerRadius='100% - 20']
         */
        outerRadius: '100% - 20',

        /**
         * The shape generated by the {@link #path} function is used as the value
         * for the `d` attribute of the SVG `<path>` element. This element
         * has the default class name of `.x-gauge-needle`, so that CSS can be used
         * to give all gauge needles some common styling. To style a particular needle,
         * one can use this config to add styles to the needle's `<path>` element directly,
         * or use a custom {@link Ext.ux.gauge.Gauge#cls class} for the needle's gauge
         * and style the needle from there.
         *
         * This config is not supposed to be updated manually, the styles should
         * always be updated by the means of the `setStyle` call. For example,
         * this is not allowed:
         *
         *     gauge.getStyle().fill = 'red';      // WRONG!
         *     gauge.setStyle({ 'fill': 'red' });  // correct
         *
         * Subsequent calls to the `setStyle` will add to the styles set previously
         * or overwrite their values, but won't remove them. If you'd like to style
         * from a clean slate, setting the style to `null` first will remove the styles
         * previously set:
         *
         *     gauge.getNeedle().setStyle(null);
         *
         * If an SVG shape was produced by a designer rather than programmatically,
         * in other words, the {@link #path} function returns the same shape regardless
         * of the parameters it was given, the uniform scaling of said shape is the only
         * option, if one wants to use gauges of different sizes. In this case,
         * it's possible to specify the desired scale by using the `transform` style,
         * for example:
         *
         *     transform: 'scale(0.35)'
         *
         * @param {Object} style
         */
        style: null,

        /**
         * @private
         * @param {Number} radius
         */
        radius: 0,

        /**
         * @private
         * Expected in the initial config, required during construction.
         * @param {Ext.ux.gauge.Gauge} gauge
         */
        gauge: null
    },

    constructor: function(config) {
        this.initConfig(config);
    },

    applyInnerRadius: function(innerRadius) {
        return this.getGauge().getRadiusFn(innerRadius);
    },

    applyOuterRadius: function(outerRadius) {
        return this.getGauge().getRadiusFn(outerRadius);
    },

    updateRadius: function() {
        this.regeneratePath();
    },

    setTransform: function(centerX, centerY, rotation) {
        var needleGroup = this.getNeedleGroup();

        needleGroup.setStyle(
            'transform',
            'translate(' + centerX + 'px,' + centerY + 'px) ' + 'rotate(' + rotation + 'deg)'
        );
    },

    applyPath: function(path) {
        return Ext.isFunction(path) ? path : null;
    },

    updatePath: function(path) {
        this.regeneratePath(path);
    },

    regeneratePath: function(path) {
        path = path || this.getPath();

        // eslint-disable-next-line vars-on-top
        var me = this,
            radius = me.getRadius(),
            inner = me.getInnerRadius()(radius),
            outer = me.getOuterRadius()(radius),
            d = outer > inner ? path(inner, outer) : '';

        me.getNeedlePath().dom.setAttribute('d', d);
    },

    getNeedleGroup: function() {
        var gauge = this.getGauge(),
            group = this.needleGroup;

        // The gauge positions the needle by calling its `setTransform` method,
        // which applies a transformation to the needle's group, that contains
        // the actual path element. This is done because we need the ability to
        // transform the path independently from it's position in the gauge.
        // For example, if the needle has to be made bigger, is shouldn't be
        // part of the transform that centers it in the gauge and rotates it
        // to point at the current value.
        if (!group) {
            group = this.needleGroup = Ext.get(document.createElementNS(gauge.svgNS, 'g'));
            gauge.getSvg().appendChild(group);
        }

        return group;
    },

    getNeedlePath: function() {
        var me = this,
            pathElement = me.pathElement;

        if (!pathElement) {
            pathElement = me.pathElement =
                Ext.get(document.createElementNS(me.getGauge().svgNS, 'path'));
            pathElement.dom.setAttribute('class', Ext.baseCSSPrefix + 'gauge-needle');
            me.getNeedleGroup().appendChild(pathElement);
        }

        return pathElement;
    },

    updateStyle: function(style) {
        var pathElement = this.getNeedlePath();

        // Note that we are setting the `style` attribute, e.g `style="fill: red"`,
        // instead of path attributes individually, e.g. `fill="red"` because
        // the attribute styles defined in CSS classes will override the values
        // of attributes set on the elements individually.
        if (Ext.isObject(style)) {
            pathElement.setStyle(style);
        }
        else {
            pathElement.dom.removeAttribute('style');
        }
    },

    destroy: function() {
        var me = this;

        me.pathElement = Ext.destroy(me.pathElement);
        me.needleGroup = Ext.destroy(me.needleGroup);
        me.setGauge(null);
    }
});
