/**
 * The base class of every Canvas D3 Component that can also be used standalone.
 * For example:
 *
 *     @example
 *     Ext.create({
 *         renderTo: document.body,
 *
 *         width: 300,
 *         height: 300,
 *
 *         xtype: 'd3-canvas',
 *
 *         listeners: {
 *             sceneresize: function (component, canvas, size) {
 *                 var barCount = 10,
 *                     barWidth = size.width / barCount,
 *                     barHeight = size.height,
 *                     context = canvas.getContext('2d'),
 *                     colors = d3.scaleOrdinal(d3.schemeCategory20),
 *                     i = 0;
 *
 *                 for (; i < barCount; i++) {
 *                     context.fillStyle = colors(i);
 *                     context.fillRect(i * barWidth, 0, barWidth, barHeight);
 *                 }
 *             }
 *         }
 *     });
 */
Ext.define('Ext.d3.canvas.Canvas', {
    extend: 'Ext.d3.Component',
    xtype: 'd3-canvas',

    isCanvas: true,

    config: {
        /**
         * @cfg {Boolean} [hdpi=true]
         * If `true`, will automatically override Canvas context ('2d') methods
         * when running on HDPI displays. Setting this to 'false' will greatly
         * improve performance on such devices at the cost of image quality.
         * It can also be useful when this class is used in conjunction with
         * another Canvas library that provides HDPI support as well.
         * Once set cannot be changed.
         */
        hdpi: true
    },

    requires: [
        'Ext.d3.canvas.HiDPI'
    ],

    template: [{
        tag: 'canvas',
        reference: 'canvasElement',
        style: {
            position: 'absolute'
        }
    }],

    canvas: null,
    context2D: null,

    /**
     * This method will be called by the {@link #onPanZoom} method each time
     * the canvas context is transformed via {@link Ext.d3.interaction.PanZoom}
     * interaction.
     * @method renderScene
     * @param {CanvasRenderingContext2D} ctx
     */
    renderScene: null,

    /**
     * Returns the Canvas element to draw on.
     * Overrides for resolution independent drawing are automatically applied
     * to the '2d' rendering context of the canvas the first time the method is called.
     * @return {HTMLCanvasElement}
     */
    getCanvas: function() {
        var me = this,
            canvas = me.canvas;

        if (!canvas) {
            canvas = me.canvasElement.dom;

            if (me.getHdpi()) {
                canvas = Ext.d3.canvas.HiDPI.applyOverrides(canvas);
            }

            me.canvas = canvas;
            me.context2D = canvas.getContext('2d');
        }

        return canvas;
    },

    /**
     * @private
     * Calculates and sets scene size and position based on the given `size` object.
     * @param {Object} size
     * @param {Number} size.width
     * @param {Number} size.height
     */
    resizeHandler: function(size) {
        var me = this,
            canvas = me.canvas || me.getCanvas(),
            rect = me.sceneRect || (me.sceneRect = {});

        rect.x = 0;
        rect.y = 0;

        rect.width = size.width;
        rect.height = size.height;

        Ext.d3.canvas.HiDPI.setSize(canvas, rect.width, rect.height);

        me.onSceneResize(canvas, rect);
        me.fireEvent('sceneresize', me, canvas, rect);
    },

    /**
     * Whether or not the component got its first size.
     * Can be used in the `canvasresize` event handler to do user-defined setup on first
     * resize, for example:
     *
     *     listeners: {
     *         canvasresize: function (component, canvas, rect) {
     *             if (!component.size) {
     *                 // set things up
     *             } else {
     *                 // handle resize
     *             }
     *         }
     *     }
     *
     * @cfg {Object} size
     * @accessor
     */

    /**
     * @event sceneresize
     * Fires after scene size has changed.
     * Note that resizing the Canvas will reset its context, e.g.
     * `lineWidth` will be reset to `1`, `fillStyle` to `#000000`, and so on,
     * including transformations.
     * @param {Ext.d3.canvas.Canvas} component
     * @param {HTMLCanvasElement} canvas
     * @param {Object} size An object with `width` and `height` properties.
     */

    /**
     * @method onSceneResize
     * @protected
     * This method is called after the scene gets its position and size.
     * It's a good place to recalculate layout(s) and re-render the scene.
     * @param {HTMLCanvasElement} canvas
     * @param {Object} rect
     * @param {Number} rect.x
     * @param {Number} rect.y
     * @param {Number} rect.width
     * @param {Number} rect.height
     */
    onSceneResize: Ext.emptyFn,

    onPanZoom: function(interaction, scaling, translation) {
        var me = this,
            canvas = me.canvas,
            ctx = me.context2D;

        if (ctx && me.renderScene) {
            ctx.save();
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.setTransform(scaling[0], 0, 0, scaling[1], translation[0], translation[1]);
            me.renderScene(ctx);
            ctx.restore();
        }
    },

    /**
     * @private
     */
    getSceneRect: function() {
        return this.sceneRect;
    }
});
