/**
 * @class Ext.draw.sprite.Text
 * @extends Ext.draw.sprite.Sprite
 *
 * A sprite that represents text.
 *
 *     @example
 *     Ext.create({
 *        xtype: 'draw', 
 *        renderTo: document.body,
 *        width: 600,
 *        height: 400,
 *        sprites: [{
 *            type: 'text',
 *            x: 50,
 *            y: 50,
 *            text: 'Sencha',
 *            fontSize: 30,
 *            fillStyle: '#1F6D91'
 *        }]
 *     });
 */
Ext.define('Ext.draw.sprite.Text', {
    extend: 'Ext.draw.sprite.Sprite',
    requires: [
        'Ext.draw.TextMeasurer',
        'Ext.draw.Color'
    ],
    alias: 'sprite.text',
    type: 'text',
    lineBreakRe: /\n/g,
    //<debug> 
    statics: {
        /**
         * Debug rendering options:
         *
         * debug: {
         *     bbox: true // renders the bounding box of the text sprite
         * }
         *
         */
        debug: false
    },
    //</debug> 
    inheritableStatics: {
        shortHand1Re: /'(.*)'/g,
        shortHand2Re: / /g,
        shortHand3Re: /\s*,\s*/g,
        shortHand4Re: /\$\$\$\$/g,
        def: {
            animationProcessors: {
                text: 'text'
            },
            processors: {
                /**
                 * @cfg {Number} [x=0]
                 * The position of the sprite on the x-axis.
                 */
                x: 'number',
 
                /**
                 * @cfg {Number} [y=0]
                 * The position of the sprite on the y-axis.
                 */
                y: 'number',
 
                /**
                 * @cfg {String} [text='']
                 * The text represented in the sprite.
                 */
                text: 'string',
 
                /**
                 * @cfg {String/Number} [fontSize='10px']
                 * The size of the font displayed.
                 */
                fontSize: function (n) {
                    if (!isNaN(n)) {
                        return ++ 'px';
                    } else if (n.match(Ext.dom.Element.unitRe)) {
                        return n;
                    }
                },
 
                /**
                 * @cfg {String} [fontStyle='']
                 * The style of the font displayed. {normal, italic, oblique}
                 */
                fontStyle: 'enums(,italic,oblique)',
 
                /**
                 * @cfg {String} [fontVariant='']
                 * The variant of the font displayed. {normal, small-caps}
                 */
                fontVariant: 'enums(,small-caps)',
 
                /**
                 * @cfg {String} [fontWeight='']
                 * The weight of the font displayed. {normal, bold, bolder, lighter}
                 */
                fontWeight: (function (fontWeights) {
                    return function (n) {
                        if (!n) {
                            return '';
                        } else if (=== 'normal') {
                            return '';
                        } else if (!isNaN(n)) {
                            n = +n;
                            if (100 <= n && n <= 900) {
                                return n;
                            }
                        } else if (in fontWeights) {
                            return n;
                        }
                    };
                })({
                    normal: true,
                    bold: true,
                    bolder: true,
                    lighter: true
                }),
 
                /**
                 * @cfg {String} [fontFamily='sans-serif']
                 * The family of the font displayed.
                 */
                fontFamily: 'string',
 
                /**
                 * @cfg {String} [textAlign='start']
                 * The alignment of the text displayed. {left, right, center, start, end}
                 */
                textAlign: (function (textAligns) {
                    return function (n) {
                        return textAligns[n] || 'center';
                    };
                })({
                    start: 'start',
                    left: 'start',
                    center: 'center',
                    middle: 'center',
                    end: 'end',
                    right: 'end'
                }),
 
                /**
                 * @cfg {String} [textBaseline="alphabetic"]
                 * The baseline of the text displayed. {top, hanging, middle, alphabetic, ideographic, bottom}
                 */
                textBaseline: (function (textBaselines) {
                    return function (n) {
                        return textBaselines[n] || 'alphabetic';
                    };
                })({
                    top: 'top',
                    hanging: 'hanging',
                    middle: 'middle',
                    center: 'middle',
                    alphabetic: 'alphabetic',
                    ideographic: 'ideographic',
                    bottom: 'bottom'
                }),
 
                /**
                 * @cfg {String} [font='10px sans-serif']
                 * The font displayed.
                 */
                font: "string"
                //<debug> 
                ,debug: 'default'
                //</debug> 
            },
            aliases: {
                'font-size': 'fontSize',
                'font-family': 'fontFamily',
                'font-weight': 'fontWeight',
                'font-variant': 'fontVariant',
                'text-anchor': 'textAlign'
            },
            defaults: {
                fontStyle: '',
                fontVariant: '',
                fontWeight: '',
                fontSize: '10px',
                fontFamily: 'sans-serif',
                font: '10px sans-serif',
                textBaseline: 'alphabetic',
                textAlign: 'start',
                strokeStyle: 'rgba(0, 0, 0, 0)',
                fillStyle: '#000',
                x: 0,
                y: 0,
                text: ''
            },
            triggers: {
                fontStyle: 'font,bbox',
                fontVariant: 'font,bbox',
                fontWeight: 'font,bbox',
                fontSize: 'font,bbox',
                fontFamily: 'font,bbox',
                font: 'font-short-hand,bbox,canvas',
                textBaseline: 'bbox',
                textAlign: 'bbox',
                x: 'bbox',
                y: 'bbox',
                text: 'bbox'
            },
            updaters: {
                'font-short-hand': (function (dispatcher) {
                    return function (attrs) {
                        // TODO: Do this according to http://www.w3.org/TR/CSS21/fonts.html#font-shorthand 
                        var value = attrs.font,
                            parts, part, i, ln, dispKey;
                        value = value.replace(Ext.draw.sprite.Text.shortHand1Re, function (a, arg1) {
                            return arg1.replace(Ext.draw.sprite.Text.shortHand2Re, '$$$$');
                        });
                        value = value.replace(Ext.draw.sprite.Text.shortHand3Re, ',');
                        parts = value.split(' ');
 
                        attrs = {};
                        for (= 0, ln = parts.length; i < ln; i++) {
                            part = parts[i];
                            dispKey = dispatcher[part];
                            if (dispKey) {
                                attrs[dispKey] = part;
                            } else if (part.match(Ext.dom.Element.unitRe)) {
                                attrs.fontSize = part;
                            } else {
                                attrs.fontFamily = part.replace(Ext.draw.sprite.Text.shortHand4Re, ' ');
                            }
                        }
                        this.setAttributes(attrs, true);
                    };
                })({
                    'italic': 'fontStyle',
                    'oblique': 'fontStyle',
                    'bold': 'fontWeight',
                    'bolder': 'fontWeight',
                    'lighter': 'fontWeight',
                    '100': 'fontWeight',
                    '200': 'fontWeight',
                    '300': 'fontWeight',
                    '400': 'fontWeight',
                    '500': 'fontWeight',
                    '600': 'fontWeight',
                    '700': 'fontWeight',
                    '800': 'fontWeight',
                    '900': 'fontWeight',
                    'small-caps': 'fontVariant'
                }),
                font: function (attrs) {
                    var font = '';
                    if (attrs.fontWeight) {
                        font += attrs.fontWeight + ' ';
                    }
                    if (attrs.fontStyle) {
                        font += attrs.fontStyle + ' ';
                    }
                    if (attrs.fontVariant) {
                        font += attrs.fontVariant + ' ';
                    }
                    if (attrs.fontSize) {
                        font += attrs.fontSize + ' ';
                    }
                    if (attrs.fontFamily) {
                        font += attrs.fontFamily;
                    }
                    this.setAttributes({
                        font: font
                    }, true);
                }
            }
        }
    },
 
    constructor: function (config) {
        if (config && config.font) {
            config = Ext.clone(config);
            for (var key in config) {
                if (key !== 'font' && key.indexOf('font') === 0) {
                    delete config[key];
                }
            }
        }
        Ext.draw.sprite.Sprite.prototype.constructor.call(this, config);
    },
 
    // Overriding the getBBox method of the abstract sprite here to always 
    // recalculate the bounding box of the text in flipped RTL mode 
    // because in that case the position of the sprite depends not just on 
    // the value of its 'x' attribute, but also on the width of the surface 
    // the sprite belongs to. 
    getBBox: function (isWithoutTransform) {
        var me = this,
            plain = me.attr.bbox.plain,
            surface = me.getSurface();
        //<debug> 
        if (!surface) {
            Ext.Error.raise("The sprite does not belong to a surface.");
        }
        //</debug> 
        if (plain.dirty) {
            me.updatePlainBBox(plain);
            plain.dirty = false;
        } if (surface.getInherited().rtl && surface.getFlipRtlText()) {
            // Since sprite's attributes haven't actually changed at this point, 
            // and we just want to update the position of its bbox 
            // based on surface's width, there's no reason to perform 
            // expensive text measurement operation here, 
            // so we can use the result of the last measurement instead. 
            me.updatePlainBBox(plain, true);
        }
        return me.callParent([isWithoutTransform]);
    },
 
    rtlAlignments: {
        start: 'end',
        center: 'center',
        end: 'start'
    },
 
    updatePlainBBox: function (plain, useOldSize) {
        var me = this,
            attr = me.attr,
            x = attr.x,
            y = attr.y,
            dx = [],
            font = attr.font,
            text = attr.text,
            baseline = attr.textBaseline,
            alignment = attr.textAlign,
            size = (useOldSize && me.oldSize) ? me.oldSize : (me.oldSize = Ext.draw.TextMeasurer.measureText(text, font)),
            surface = me.getSurface(),
            isRtl = surface.getInherited().rtl,
            flipRtlText = isRtl && surface.getFlipRtlText(),
            rect = surface.getRect(),
            sizes = size.sizes,
            blockHeight = size.height,
            blockWidth = size.width,
            ln = sizes ? sizes.length : 0,
            lineWidth,
            i = 0;
 
        // To get consistent results in all browsers we don't apply textAlign and textBaseline 
        // attributes of the sprite to context, so text is always left aligned and has an alphabetic baseline. 
        // Instead we have to calculate the horizontal offset of each line based on sprite's textAlign, 
        // and the vertical offset of the bounding box based on sprite's textBaseline. 
        // These offsets are then used by the sprite's 'render' method to position text properly. 
 
        switch (baseline) {
            case 'hanging' :
            case 'top':
                break;
            case 'ideographic' :
            case 'bottom' :
                y -= blockHeight;
                break;
            case 'alphabetic' :
                y -= blockHeight * 0.8;
                break;
            case 'middle' :
                y -= blockHeight * 0.5;
                break;
        }
        if (flipRtlText) {
            x = rect[2] - rect[0] - x;
            alignment = me.rtlAlignments[alignment];
        }
 
        switch (alignment) {
            case 'start':
                if (isRtl) {
                    for (; i < ln; i++) {
                        lineWidth = sizes[i].width;
                        dx.push(-(blockWidth - lineWidth));
                    }
                }
                break;
            case 'end' :
                x -= blockWidth;
                if (isRtl) {
                    break;
                }
                for (; i < ln; i++) {
                    lineWidth = sizes[i].width;
                    dx.push(blockWidth - lineWidth);
                }
                break;
            case 'center' :
                x -= blockWidth * 0.5;
                for (; i < ln; i++) {
                    lineWidth = sizes[i].width;
                    dx.push((isRtl ? -1 : 1) * (blockWidth - lineWidth) * 0.5);
                }
                break;
        }
 
        attr.textAlignOffsets = dx;
 
        plain.x = x;
        plain.y = y;
        plain.width = blockWidth;
        plain.height = blockHeight;
    },
 
    setText: function (text) {
        this.setAttributes({text: text}, true);
    },
 
    setElementStyles: function (element, styles) {
        var stylesCache = element.stylesCache || (element.stylesCache = {}),
            style = element.dom.style,
            name;
        for (name in styles) {
            if (stylesCache[name] !== styles[name]) {
                stylesCache[name] = style[name] = styles[name];
            }
        }
    },
 
    //<debug> 
    renderBBox: function (surface, ctx) {
        var bbox = this.getBBox(true);
        ctx.beginPath();
        ctx.moveTo(bbox.x, bbox.y);
        ctx.lineTo(bbox.x + bbox.width, bbox.y);
        ctx.lineTo(bbox.x + bbox.width, bbox.y + bbox.height);
        ctx.lineTo(bbox.x, bbox.y + bbox.height);
        ctx.closePath();
        ctx.strokeStyle = 'red';
        ctx.strokeOpacity = 1;
        ctx.lineWidth = 0.5;
        ctx.stroke();
    },
    //</debug> 
 
    render: function (surface, ctx, rect) {
        var me = this,
            attr = me.attr,
            mat = Ext.draw.Matrix.fly(attr.matrix.elements.slice(0)),
            bbox = me.getBBox(true),
            dx = attr.textAlignOffsets,
            none = Ext.draw.Color.RGBA_NONE,
            x, y, i, lines, lineHeight;
        if (attr.text.length === 0) {
            return;
        }
 
        lines = attr.text.split('\n');
        lineHeight = bbox.height / lines.length;
        // Simulate textBaseline and textAlign. 
        x = attr.bbox.plain.x;
        // lineHeight * 0.78 is the approximate distance between the top and the alphabetic baselines 
        y = attr.bbox.plain.y + lineHeight * 0.78;
        mat.toContext(ctx);
        if (surface.getInherited().rtl) {
            // Canvas element in RTL mode automatically flips text alignment. 
            // Here we compensate for that change. 
            // So text is still positioned and aligned as in the LTR mode, 
            // but the direction of the text is RTL. 
            x += attr.bbox.plain.width;
        }
 
        for (= 0; i < lines.length; i++) {
            if (ctx.fillStyle !== none) {
                ctx.fillText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
            }
            if (ctx.strokeStyle !== none) {
                ctx.strokeText(lines[i], x + (dx[i] || 0), y + lineHeight * i);
            }
        }
        //<debug> 
        var debug = me.statics().debug || attr.debug;
        if (debug) {
            debug.bbox && me.renderBBox(surface, ctx);
        }
        //</debug> 
    }
});