/**
 *  Chart captions can be used to place titles, subtitles, credits and other captions
 *  inside a chart. Please see the chart's {@link Ext.chart.AbstractChart#captions}
 *  config documentation for the general description of the way captions work, and
 *  refer to the documentation of this class' configs for details.
 */
Ext.define('Ext.chart.Caption', {
    mixins: [
        'Ext.mixin.Observable',
        'Ext.mixin.Bindable'
    ],
 
    isCaption: true,
 
    config: {
        /**
         * The weight controls the order in which the captions are created.
         * Captions with lower weights are created first.
         * This affects chart's layout. For example, if two captions are docked
         * to the 'top', the one with the lower weight will end up on top
         * of the other.
         */
        weight: 0,
 
        /**
         * @cfg {String} text
         * The text displayed by the caption.
         * Multi-line captions are allowed, e.g.:
         *
         *     captions: {
         *         title: {
         *             text: 'India\'s tiger population\n'
         *                 + 'from 1970 to 2015'
         *         }
         *     }
         *
         */
        text: '',
 
        /**
         * @cfg {'left'/'center'/'right'} [align='center']
         * Determines the horizontal alignment of the caption's text.
         */
        align: 'center',
 
        /**
         * @cfg {'series'/'chart'} [alignTo='series']
         * Whether to align the caption to the 'series' (default) or the 'chart'.
         */
        alignTo: 'series',
 
        /**
         * @cfg {Number} padding
         * The uniform padding applied to both top and bottom of the caption's text.
         */
        padding: 0,
 
        /**
         * @cfg {Boolean} [hidden=false]
         * Controls the visibility of the caption.
         */
        hidden: false,
 
        /**
         * @cfg {'top'/'bottom'} [docked='top']
         * The position of the caption in a chart.
         */
        docked: 'top',
 
        /**
         * @cfg {Object} style
         * Style attributes for the caption's text.
         * All attributes that are valid {@link Ext.draw.sprite.Text text sprite} attributes
         * are valid here. However, only font attributes (such as `fontSize`, `fontFamily`, ...),
         * color attributes (such as `fillStyle`) and `textAlign` attribute are guaranteed to
         * produce correct behavior. For example, transform attributes are not officially supported.
         */
        style: {
            fontSize: '14px',
            fontWeight: 'bold',
            fontFamily: 'Verdana, Aria, sans-serif'
        },
 
        /**
         * @private
         * @cfg {Ext.chart.AbstractChart} chart
         * The chart the label belongs to.
         */
        chart: null,
 
        /**
         * @private
         * The text sprite used to render caption's text.
         */
        sprite: {
            type: 'text',
            preciseMeasurement: true,
            zIndex: 10
        },
 
        //<debug>
        /**
         * @private
         * @cfg {Boolean} debug
         * Whether to show the bounding boxes or not.
         */
        debug: false,
        //</debug>
 
        /**
         * @private
         * The logical rect of the caption in the `surfaceName` surface.
         */
        rect: null
    },
 
    surfaceName: 'caption',
 
    constructor: function(config) {
        var me = this,
            id;
 
        if ('id' in config) {
            id = config.id;
        }
        else if ('id' in me.config) {
            id = me.config.id;
        }
        else {
            id = me.getId();
        }
 
        me.setId(id);
 
        me.mixins.observable.constructor.call(me, config);
        me.initBindable();
    },
 
    updateChart: function() {
        if (!this.isConfiguring) {
            // Re-create caption's sprite in another chart.
            this.setSprite({
                type: 'text'
            });
        }
    },
 
    applySprite: function(sprite) {
        var me = this,
            chart = me.getChart(),
            surface = me.surface = chart.getSurface(me.surfaceName);
 
        //<debug>
        me.rectSprite = surface.add({
            type: 'rect',
            fillStyle: 'yellow',
            strokeStyle: 'red'
        });
 
        //</debug>
        return sprite && surface.add(sprite);
    },
 
    updateSprite: function(sprite, oldSprite) {
        if (oldSprite) {
            oldSprite.destroy();
        }
    },
 
    updateText: function(text) {
        this.getSprite().setAttributes({
            text: text
        });
    },
 
    updateStyle: function(style) {
        this.getSprite().setAttributes(style);
    },
 
    //<debug>
    updateDebug: function(debug) {
        var me = this,
            sprite = me.getSprite();
 
        if (debug && !me.rectSprite) {
            me.rectSprite = me.surface.add({
                type: 'rect',
                fillStyle: 'yellow',
                strokeStyle: 'red'
            });
        }
 
        if (sprite) {
            sprite.setAttributes({
                debug: debug ? { bbox: true } : null
            });
        }
 
        if (me.rectSprite) {
            me.rectSprite.setAttributes({
                hidden: !debug
            });
        }
 
        if (!me.isConfiguring) {
            me.surface.renderFrame();
        }
    },
    //</debug>
 
    updateRect: function(rect) {
        if (this.rectSprite) {
            this.rectSprite.setAttributes({
                x: rect[0],
                y: rect[1],
                width: rect[2],
                height: rect[3]
            });
        }
    },
 
    updateDocked: function() {
        var chart = this.getChart();
 
        if (chart && !this.isConfiguring) {
            chart.scheduleLayout();
        }
    },
 
    /**
     * @private
     * Computes and sets the caption's rect.
     * Shrinks the given chart rect to accomodate the caption.
     * The chart rect is [top, left, width, height] in chart's
     * body element coordinates.
     * The shrink rect is {left, top, right, bottom} in `caption`
     * surface coordinates.
     */
    computeRect: function(chartRect, shrinkRect) {
        if (this.getHidden()) {
            return null;
        }
 
        // eslint-disable-next-line vars-on-top
        var rect = [0, 0, chartRect[2], 0],
            docked = this.getDocked(),
            padding = this.getPadding(),
            textSize = this.getSprite().getBBox(),
            height = textSize.height + padding * 2;
 
        switch (docked) {
            case 'top':
                rect[1] = shrinkRect.top;
                rect[3] = height;
 
                chartRect[1] += height;
                chartRect[3] -= height;
 
                shrinkRect.top += height;
                break;
 
            case 'bottom':
                chartRect[3] -= height;
                shrinkRect.bottom -= height;
 
                rect[1] = shrinkRect.bottom;
                rect[3] = height;
                break;
        }
 
        this.setRect(rect);
    },
 
    alignRect: function(seriesRect) {
        var surfaceRect = this.surface.getRect(),
            rect = this.getRect();
 
        rect[0] = seriesRect[0] - surfaceRect[0];
        rect[2] = seriesRect[2];
 
        // Slice to trigger the applier/updater.
        this.setRect(rect.slice());
    },
 
    performLayout: function() {
        var me = this,
            rect = me.getRect(),
            x = rect[0],
            y = rect[1],
            width = rect[2],
            height = rect[3],
            sprite = me.getSprite(),
            tx = sprite.attr.translationX,
            ty = sprite.attr.translationY,
            bbox = sprite.getBBox(),
            align = me.getAlign(),
            dx, dy;
 
        switch (align) {
            case 'left':
                dx = x - bbox.x;
                break;
 
            case 'right':
                dx = (+ width) - (bbox.x + bbox.width);
                break;
 
            case 'center':
                dx = x + (width - bbox.width) / 2 - bbox.x;
                break;
        }
 
        dy = y + (height - bbox.height) / 2 - bbox.y;
 
        sprite.setAttributes({
            translationX: tx + dx,
            translationY: ty + dy
        });
    },
 
    destroy: function() {
        var me = this;
 
        //<debug>
        if (me.rectSprite) {
            me.rectSprite.destroy();
        }
 
        //</debug>
        me.getSprite().destroy();
 
        me.callParent();
    }
 
});