/**
 * The Draw Component is a surface in which sprites can be rendered. The Draw Component
 * manages and holds a `Surface` instance: an interface that has
 * an SVG or VML implementation depending on the browser capabilities and where
 * Sprites can be appended.
 * One way to create a draw component is:
 *
 *     var drawComponent = new Ext.draw.Component({
 *         items: [{
 *             type: 'circle',
 *             fill: '#79BB3F',
 *             radius: 100,
 *             x: 100,
 *             y: 100
 *         }]
 *     });
 *
 *     new Ext.Panel({
 *         fullscreen: true,
 *         items: [drawComponent]
 *     });
 *
 * In this case we created a draw component and added a sprite to it.
 * The *type* of the sprite is *circle* so if you run this code you'll see a yellow-ish
 * circle in a Window. When setting `viewBox` to `false` we are responsible for setting the object's position and
 * dimensions accordingly.
 *
 * You can also add sprites by using the surface's add method:
 *
 *     drawComponent.getSurface('main').add({
 *         type: 'circle',
 *         fill: '#79BB3F',
 *         radius: 100,
 *         x: 100,
 *         y: 100
 *     });
 *
 * For more information on Sprites, the core elements added to a draw component's surface,
 * refer to the {@link Ext.draw.sprite.Sprite} documentation.
 */
Ext.define('Ext.draw.Component', {

    extend: 'Ext.Container',
    xtype: 'draw',
    defaultType: 'surface',

    requires: [
        'Ext.draw.Surface',
        'Ext.draw.engine.Svg',
        'Ext.draw.engine.Canvas',
        'Ext.draw.gradient.GradientDefinition'
    ],
    engine: 'Ext.draw.engine.Canvas',
    statics: {
        WATERMARK: 'Powered by <span style="color:#22E962; font-weight: 900">Sencha Touch</span> <span style="color:#75cdff; font-weight: 900">GPLv3</span>'
    },
    config: {
        cls: 'x-draw-component',

        /**
         * @deprecated 2.2.0 Please implement custom resize event handler.
         * Resize the draw component by the content size of the main surface.
         *
         * __Note:__ It is applied only when there is only one surface.
         */
        autoSize: false,

        /**
         * @deprecated 2.2.0 Please implement custom resize event handler.
         * Pan/Zoom the content in main surface to fit the component size.
         *
         * __Note:__ It is applied only when there is only one surface.
         */
        viewBox: false,

        /**
         * @deprecated 2.2.0 Please implement custom resize event handler.
         * Fit the main surface to the size of component.
         *
         * __Note:__ It is applied only when there is only one surface.
         */
        fitSurface: true,

        /**
         * @cfg {Function} [resizeHandler] The resize function that can be configured to have a behavior.
         *
         * __Note:__ since resize events trigger {@link #renderFrame} calls automatically,
         * return `false` from the resize function, if it also calls `renderFrame`, to prevent double rendering.
         */
        resizeHandler: null,

        background: null,

        sprites: null,

        /**
         * @cfg {Object[]} gradients
         * Defines a set of gradients that can be used as color properties
         * (fillStyle and strokeStyle, but not shadowColor) in sprites.
         * The gradients array is an array of objects with the following properties:
         * - **id** - string - The unique name of the gradient.
         * - **type** - string, optional - The type of the gradient. Available types are: 'linear', 'radial'. Defaults to 'linear'.
         * - **angle** - number, optional - The angle of the gradient in degrees.
         * - **stops** - array - An array of objects with 'color' and 'offset' properties, where 'offset' is a real number from 0 to 1.
         *
         * For example:
         *
         *     gradients: [{
         *         id: 'gradientId1',
         *         type: 'linear',
         *         angle: 45,
         *         stops: [{
         *             offset: 0,
         *             color: 'red'
         *         }, {
         *            offset: 1,
         *            color: 'yellow'
         *         }]
         *     }, {
         *        id: 'gradientId2',
         *        type: 'radial',
         *        stops: [{
         *            offset: 0,
         *            color: '#555',
         *        }, {
         *            offset: 1,
         *            color: '#ddd',
         *        }]
         *     }]
         *
         * Then the sprites can use 'gradientId1' and 'gradientId2' by setting the color attributes to those ids, for example:
         *
         *     sprite.setAttributes({
         *         fillStyle: 'url(#gradientId1)',
         *         strokeStyle: 'url(#gradientId2)'
         *     });
         */
        gradients: []
    },

    constructor: function (config) {
        config = config || {};
        this.callSuper(arguments);
        this.frameCallbackId = Ext.draw.Animator.addFrameCallback('renderFrame', this);
    },

    applyGradients: function (gradients) {
        var result = [],
            i, n, gradient, offset;
        if (!Ext.isArray(gradients)) {
            return result;
        }
        for (i = 0, n = gradients.length; i < n; i++) {
            gradient = gradients[i];
            if (!Ext.isObject(gradient)) {
                continue;
            }
            // ExtJS only supported linear gradients, so we didn't have to specify their type
            if (typeof gradient.type !== 'string') {
                gradient.type = 'linear';
            }
            if (gradient.angle) {
                gradient.degrees = gradient.angle;
                delete gradient.angle;
            }
            // Convert ExtJS stops object to Touch stops array
            if (Ext.isObject(gradient.stops)) {
                gradient.stops = (function (stops) {
                    var result = [], stop;
                    for (offset in stops) {
                        stop = stops[offset];
                        stop.offset = offset / 100;
                        result.push(stop);
                    }
                    return result;
                })(gradient.stops);
            }
            result.push(gradient);
        }
        Ext.draw.gradient.GradientDefinition.add(result);
        return result;
    },

    initialize: function () {
        var me = this;
        me.callSuper();
        me.element.on('resize', 'onResize', this);
    },

    applySprites: function (sprites) {
        // Never update
        if (!sprites) {
            return;
        }

        sprites = Ext.Array.from(sprites);

        var ln = sprites.length,
            i, surface;

        for (i = 0; i < ln; i++) {
            if (sprites[i].surface instanceof Ext.draw.Surface) {
                surface = sprites[i].surface;
            } else if (Ext.isString(sprites[i].surface)) {
                surface = this.getSurface(sprites[i].surface);
            } else {
                surface = this.getSurface('main');
            }
            surface.add(sprites[i]);
        }
    },

    getElementConfig: function () {
        return {
            reference: 'element',
            className: 'x-container',
            children: [
                {
                    reference: 'innerElement',
                    className: 'x-inner',
                    children: [
                        {
                            reference: 'watermarkElement',
                            cls: 'x-chart-watermark',
                            html: Ext.draw.Component.WATERMARK,
                            style: Ext.draw.Component.WATERMARK ? '': 'display: none'
                        }
                    ]
                }
            ]
        };
    },

    updateBackground: function (background) {
        this.element.setStyle({
            background: background
        });
    },

    /**
     * @protected
     * Place water mark after resize.
     */
    onPlaceWatermark: function () {
        // Do nothing
    },

    onResize: function () {
        var me = this,
            size = me.element.getSize(),
            resizeHandler = me.getResizeHandler() || me.resizeHandler,
            result;
        me.fireEvent('resize', me, size);
        result = resizeHandler.call(me, size);
        if (result !== false) {
            me.renderFrame();
            me.onPlaceWatermark();
        }
    },

    resizeHandler: function (size) {
        var me = this;

        //<deprecated product=touch since=2.2>
        var surfaces = me.getItems(),
            surface, bbox, mat, zoomX, zoomY, zoom;

        if (surfaces.length === 1) {
            surface = surfaces.get(0);
            if (me.getAutoSize()) {
                bbox = surface.getItems().getBBox();
                mat = new Ext.draw.Matrix();
                mat.prepend(1, 0, 0, 1, -bbox.x, -bbox.y);
                surface.matrix = mat;
                surface.inverseMatrix = mat.inverse();
                surface.setRegion([0, 0, bbox.width, bbox.height]);
            } else if (me.getViewBox()) {
                bbox = surface.getItems().getBBox();
                zoomX = size.width / bbox.width;
                zoomY = size.height / bbox.height;
                zoom = Math.min(zoomX, zoomY);
                mat = new Ext.draw.Matrix();
                mat.prepend(
                    zoom, 0, 0, zoom,
                    size.width * 0.5 + (-bbox.x - bbox.width * 0.5) * zoom,
                    size.height * 0.5 + (-bbox.y - bbox.height * 0.5) * zoom);
                surface.matrix = mat;
                surface.inverseMatrix = mat.inverse();
                surface.setRegion([0, 0, size.width, size.height]);
            } else if (me.getFitSurface()) {
                surface.setRegion([0, 0, size.width, size.height]);
            }
        } else if (!me.getFitSurface()) {
            return;
        }
        //</deprecated>

        me.getItems().each(function (surface) {
            surface.setRegion([0, 0, size.width, size.height]);
        });
    },

    /**
     * Get a surface by the given id or create one if it doesn't exist.
     * @param {String} [id="main"]
     * @return {Ext.draw.Surface}
     */
    getSurface: function (id) {
        id = this.getId() + '-' + (id || 'main');
        var me = this,
            surfaces = me.getItems(),
            surface = surfaces.get(id),
            size;

        if (!surface) {
            surface = me.add({xclass: me.engine, id: id});
            if (me.getFitSurface()) {
                size = me.element.getSize();
                surface.setRegion([0, 0, size.width, size.height]);
            }
            surface.renderFrame();
        }
        return surface;
    },

    /**
     * Render all the surfaces in the component.
     */
    renderFrame: function () {
        var me = this,
            i, ln, bbox,
            surfaces = me.getItems();

        for (i = 0, ln = surfaces.length; i < ln; i++) {
            surfaces.items[i].renderFrame();
        }
        //<deprecated product=touch since=2.2>
        // TODO: Throw a deprecation message
        if (surfaces.length === 1 && me.getAutoSize()) {
            bbox = me.getSurface().getItems().getBBox();
            me.setSize(Math.ceil(bbox.width) + 1, Math.ceil(bbox.height) + 1);
        }
        //</deprecated>
    },

    destroy: function () {
        Ext.draw.Animator.removeFrameCallback(this.frameCallbackId);
        this.callSuper();
    }

}, function () {
    if (location.search.match('svg')) {
        Ext.draw.Component.prototype.engine = 'Ext.draw.engine.Svg';
    } else if ((Ext.os.is.BlackBerry && Ext.os.version.getMajor() === 10) || (Ext.browser.is.AndroidStock4 && (Ext.os.version.getMinor() === 1 || Ext.os.version.getMinor() === 2 || Ext.os.version.getMinor() === 3))) {
        // http://code.google.com/p/android/issues/detail?id=37529
        Ext.draw.Component.prototype.engine = 'Ext.draw.engine.Svg';
    }
});