/**
 * @class Ext.chart.series.sprite.Box
 * @extends Ext.draw.sprite.Sprite
 *
 * A sprite that represents a 3D bar or column.
 * Used as an item template by the {@link Ext.chart.series.sprite.Bar3D} marker holder.
 *
 */
Ext.define('Ext.chart.series.sprite.Box', {
    extend: 'Ext.draw.sprite.Sprite',
    alias: 'sprite.box',
    type: 'box',
 
    inheritableStatics: {
        def: {
            processors: {
                /**
                 * @cfg {Number} [x=0]
                 * The position of the sprite on the x-axis.
                 * Corresponds to the center of the front face of the box.
                 */
                x: 'number',
                /**
                 * @cfg {Number} [y=0]
                 * The position of the sprite on the y-axis.
                 * Corresponds to the top of the front face of the box.
                 */
                y: 'number',
                /**
                 * @cfg {Number} [width=8] The width of the box.
                 */
                width: 'number',
                /**
                 * @cfg {Number} [height=8] The height of the box.
                 */
                height: 'number',
                /**
                 * @cfg {Number} [depth=8] The depth of the box.
                 */
                depth: 'number',
                /**
                 * @cfg {String} [orientation='vertical'] The orientation of the box.
                 */
                orientation: 'enums(vertical,horizontal)',
                /**
                 * @cfg {Boolean} [showStroke=false]
                 * Whether to render the stroke or not.
                 */
                showStroke: 'bool',
                /**
                 * @cfg {Number} [saturationFactor=1]
                 * The factor applied to the saturation of the box.
                 */
                saturationFactor: 'number',
                /**
                 * @cfg {Number} [brightnessFactor=1]
                 * The factor applied to the brightness of the box.
                 */
                brightnessFactor: 'number',
                /**
                 * @cfg {Number} [colorSpread=1]
                 * An attribute used to control how flat the bar gradient looks.
                 * A value of 0 essentially means no gradient (flat color).
                 */
                colorSpread: 'number'
            },
            triggers: {
                x: 'bbox',
                y: 'bbox',
                width: 'bbox',
                height: 'bbox',
                depth: 'bbox',
                orientation: 'bbox'
            },
            defaults: {
                x: 0,
                y: 0,
                width: 8,
                height: 8,
                depth: 8,
                orientation: 'vertical',
                showStroke: false,
                saturationFactor: 1,
                brightnessFactor: 1,
                colorSpread: 1,
                lineJoin: 'bevel'
            }
        }
    },
 
    constructor: function (config) {
        this.callParent([config]);
 
        this.topGradient = new Ext.draw.gradient.Linear({});
        this.rightGradient = new Ext.draw.gradient.Linear({});
        this.frontGradient = new Ext.draw.gradient.Linear({});
    },
 
    updatePlainBBox: function (plain) {
        var attr = this.attr,
            x = attr.x,
            y = attr.y,
            width = attr.width,
            height = attr.height,
            depth = attr.depth;
 
        plain.x = x - width * 0.5;
        plain.width = width + depth;
 
        if (height > 0) {
            plain.y = y;
            plain.height = height + depth;
        } else {
            plain.y = y + depth;
            plain.height = height - depth;
        }
    },
 
    render: function (surface, ctx) {
        var me = this,
            attr = me.attr,
            center = attr.x,
            top = attr.y,
            bottom = top + attr.height,
            isNegative = top < bottom,
            halfWidth = attr.width * 0.5,
            depth = attr.depth,
            isHorizontal = attr.orientation === 'horizontal',
            isTransparent = attr.globalAlpha < 1,
            fillStyle = attr.fillStyle,
            color = Ext.util.Color.create(
                fillStyle.isGradient ?
                    fillStyle.getStops()[0].color :
                    fillStyle
            ),
            saturationFactor = attr.saturationFactor,
            brightnessFactor = attr.brightnessFactor,
            colorSpread = attr.colorSpread,
            hsv = color.getHSV(),
            bbox = {},
            roundX, roundY,
            temp;
 
        if (!attr.showStroke) {
            ctx.strokeStyle = Ext.util.Color.RGBA_NONE;
        }
 
        if (isNegative) {
            temp = top;
            top = bottom;
            bottom = temp;
        }
 
        // Refresh gradients based on sprite's fillStyle and other attributes.
 
        me.topGradient.setDegrees(isHorizontal ? 0 : 80);
        me.topGradient.setStops([
            {
                offset: 0,
                color: Ext.util.Color.fromHSV(
                    hsv[0],
                    Ext.Number.constrain(hsv[1] * saturationFactor, 0, 1),
                    Ext.Number.constrain((0.5 + colorSpread * 0.10) * brightnessFactor, 0, 1)
                )
            },
            {
                offset: 1,
                color: Ext.util.Color.fromHSV(
                    hsv[0],
                    Ext.Number.constrain(hsv[1] * saturationFactor, 0, 1),
                    Ext.Number.constrain((0.5 - colorSpread * 0.11) * brightnessFactor, 0, 1)
                )
            }
        ]);
 
        me.rightGradient.setDegrees(isHorizontal ? 45 : 90);
        me.rightGradient.setStops([
            {
                offset: 0,
                color: Ext.util.Color.fromHSV(
                    hsv[0],
                    Ext.Number.constrain(hsv[1] * saturationFactor, 0, 1),
                    Ext.Number.constrain((0.5 - colorSpread * 0.14) * brightnessFactor, 0, 1)
                )
            },
            {
                offset: 1,
                color: Ext.util.Color.fromHSV(
                    hsv[0],
                    Ext.Number.constrain(hsv[1] * (1.0 + colorSpread * 0.4) * saturationFactor, 0, 1),
                    Ext.Number.constrain((0.5 - colorSpread * 0.32) * brightnessFactor, 0, 1)
                )
            }
        ]);
 
        if (isHorizontal) {
            me.frontGradient.setDegrees(0); // 0° angle looks like 90° angle because the chart is flipped
        } else {
            me.frontGradient.setRadians(Math.atan2(top - bottom, halfWidth * 2));
        }
 
        me.frontGradient.setStops([
            {
                offset: 0,
                color: Ext.util.Color.fromHSV(
                    hsv[0],
                    Ext.Number.constrain(hsv[1] * (1.0 - colorSpread * 0.1) * saturationFactor, 0, 1),
                    Ext.Number.constrain((0.5 + colorSpread * 0.1) * brightnessFactor, 0, 1)
                )
            },
            {
                offset: 1,
                color: Ext.util.Color.fromHSV(
                    hsv[0],
                    Ext.Number.constrain(hsv[1] * (1.0 + colorSpread * 0.1) * saturationFactor, 0, 1),
                    Ext.Number.constrain((0.5 - colorSpread * 0.23) * brightnessFactor, 0, 1)
                )
            }
        ]);
 
        if (isTransparent || isNegative) {
            // Bottom side.
 
            ctx.beginPath();
            ctx.moveTo(center - halfWidth, bottom);
            ctx.lineTo(center - halfWidth + depth, bottom + depth);
            ctx.lineTo(center + halfWidth + depth, bottom + depth);
            ctx.lineTo(center + halfWidth, bottom);
            ctx.closePath();
 
            bbox.x = center - halfWidth;
            bbox.y = top;
            bbox.width = halfWidth + depth;
            bbox.height = depth;
 
            ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
 
            ctx.fillStroke(attr);
        }
 
        if (isTransparent) {
            // Left side.
 
            ctx.beginPath();
            ctx.moveTo(center - halfWidth, top);
            ctx.lineTo(center - halfWidth + depth, top + depth);
            ctx.lineTo(center - halfWidth + depth, bottom + depth);
            ctx.lineTo(center - halfWidth, bottom);
            ctx.closePath();
 
            bbox.x = center + halfWidth;
            bbox.y = bottom;
            bbox.width = depth;
            bbox.height = top + depth - bottom;
 
            ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
 
            ctx.fillStroke(attr);
        }
 
        // Top side.
 
        roundY = surface.roundPixel(top);
        ctx.beginPath();
        ctx.moveTo(center - halfWidth, roundY);
        ctx.lineTo(center - halfWidth + depth, top + depth);
        ctx.lineTo(center + halfWidth + depth, top + depth);
        ctx.lineTo(center + halfWidth, roundY);
        ctx.closePath();
 
        bbox.x = center - halfWidth;
        bbox.y = top;
        bbox.width = halfWidth + depth;
        bbox.height = depth;
 
        ctx.fillStyle = (isHorizontal ? me.rightGradient : me.topGradient).generateGradient(ctx, bbox);
 
        ctx.fillStroke(attr);
 
        // Right side.
 
        roundX = surface.roundPixel(center + halfWidth);
        ctx.beginPath();
        ctx.moveTo(roundX, surface.roundPixel(top));
        ctx.lineTo(center + halfWidth + depth, top + depth);
        ctx.lineTo(center + halfWidth + depth, bottom + depth);
        ctx.lineTo(roundX, bottom);
        ctx.closePath();
 
        bbox.x = center + halfWidth;
        bbox.y = bottom;
        bbox.width = depth;
        bbox.height = top + depth - bottom;
 
        ctx.fillStyle = (isHorizontal ? me.topGradient : me.rightGradient).generateGradient(ctx, bbox);
 
        ctx.fillStroke(attr);
 
        // Front side.
 
        roundX = surface.roundPixel(center + halfWidth);
        roundY = surface.roundPixel(top);
        ctx.beginPath();
        ctx.moveTo(center - halfWidth, bottom);
        ctx.lineTo(center - halfWidth, roundY);
        ctx.lineTo(roundX, roundY);
        ctx.lineTo(roundX, bottom);
        ctx.closePath();
 
        bbox.x = center - halfWidth;
        bbox.y = bottom;
        bbox.width = halfWidth * 2;
        bbox.height = top - bottom;
 
        ctx.fillStyle = me.frontGradient.generateGradient(ctx, bbox);
 
        ctx.fillStroke(attr);
    }
 
});