/**
 * This is a modifier to place labels and callouts by additional attributes.
 */
Ext.define('Ext.chart.modifier.Callout', {
    extend: 'Ext.draw.modifier.Modifier',
    alternateClassName: 'Ext.chart.label.Callout',
 
    prepareAttributes: function(attr) {
        if (!attr.hasOwnProperty('calloutOriginal')) {
            attr.calloutOriginal = Ext.Object.chain(attr);
            // No __proto__, nor getPrototypeOf in IE8,
            // so manually saving a reference to 'attr' after chaining.
            attr.calloutOriginal.prototype = attr;
        }
 
        if (this._lower) {
            this._lower.prepareAttributes(attr.calloutOriginal);
        }
    },
 
    setAttrs: function(attr, changes) {
        var callout = attr.callout,
            origin = attr.calloutOriginal,
            bbox = attr.bbox.plain,
            width = (bbox.width || 0) + attr.labelOverflowPadding,
            height = (bbox.height || 0) + attr.labelOverflowPadding,
            dx, dy, rotationRads, x, y, calloutPlaceX, calloutPlaceY,
            calloutVertical, temp;
 
        if ('callout' in changes) {
            callout = changes.callout;
        }
 
        if ('callout' in changes || 'calloutPlaceX' in changes || 'calloutPlaceY' in changes ||
            'x' in changes || 'y' in changes) {
            rotationRads = 'rotationRads' in changes
                ? origin.rotationRads = changes.rotationRads
                : origin.rotationRads;
 
            x = 'x' in changes ? (origin.x = changes.x) : origin.x;
            y = 'y' in changes ? (origin.y = changes.y) : origin.y;
            calloutPlaceX = 'calloutPlaceX' in changes ? changes.calloutPlaceX : attr.calloutPlaceX;
            calloutPlaceY = 'calloutPlaceY' in changes ? changes.calloutPlaceY : attr.calloutPlaceY;
            calloutVertical =
                'calloutVertical' in changes ? changes.calloutVertical : attr.calloutVertical;
 
            // Normalize Rotations
            rotationRads %= Math.PI * 2;
 
            if (Math.cos(rotationRads) < 0) {
                rotationRads = (rotationRads + Math.PI) % (Math.PI * 2);
            }
 
            if (rotationRads > Math.PI) {
                rotationRads -= Math.PI * 2;
            }
 
            if (calloutVertical) {
                rotationRads = rotationRads * (1 - callout) - Math.PI / 2 * callout;
                temp = width;
                width = height;
                height = temp;
            }
            else {
                rotationRads = rotationRads * (1 - callout);
            }
 
            changes.rotationRads = rotationRads;
 
            // Placing a label in the middle of a pie slice (x/y)
            // if callout doesn't exists (callout=0),
            // or outside the pie slice (calloutPlaceX/Y) if it does (callout=1).
            changes.x = x * (1 - callout) + calloutPlaceX * callout;
            changes.y = y * (1 - callout) + calloutPlaceY * callout;
 
            dx = calloutPlaceX - x;
            dy = calloutPlaceY - y;
 
            // Finding where the callout line intersects the bbox of the label
            // if it were to go to the center of the label,
            // and make that intersection point the end of the callout line.
            // Effectively, the end of the callout line traces label's bbox when chart is rotated.
            if (Math.abs(dy * width) > Math.abs(dx * height)) {
                // on top/bottom
                if (dy > 0) {
                    changes.calloutEndX = changes.x - (height / 2) * (dx / dy) * callout;
                    changes.calloutEndY = changes.y - (height / 2) * callout;
                }
                else {
                    changes.calloutEndX = changes.x + (height / 2) * (dx / dy) * callout;
                    changes.calloutEndY = changes.y + (height / 2) * callout;
                }
            }
            else {
                // on left/right
                if (dx > 0) {
                    changes.calloutEndX = changes.x - width / 2;
                    changes.calloutEndY = changes.y - (width / 2) * (dy / dx) * callout;
                }
                else {
                    changes.calloutEndX = changes.x + width / 2;
                    changes.calloutEndY = changes.y + (width / 2) * (dy / dx) * callout;
                }
            }
 
            // Since the length of the callout line is adjusted depending on the label's position
            // and dimensions, we hide the callout line if the length becomes negative.
            if (changes.calloutStartX && changes.calloutStartY) {
                changes.calloutHasLine =
                    (dx > 0 && changes.calloutStartX < changes.calloutEndX) ||
                    (dx <= 0 && changes.calloutStartX > changes.calloutEndX) ||
                    (dy > 0 && changes.calloutStartY < changes.calloutEndY) ||
                    (dy <= 0 && changes.calloutStartY > changes.calloutEndY);
            }
            else {
                changes.calloutHasLine = true;
            }
        }
 
        return changes;
    },
 
    pushDown: function(attr, changes) {
        changes = this.callParent([attr.calloutOriginal, changes]);
 
        return this.setAttrs(attr, changes);
    },
 
    popUp: function(attr, changes) {
        attr = attr.prototype;
        changes = this.setAttrs(attr, changes);
 
        if (this._upper) {
            return this._upper.popUp(attr, changes);
        }
        else {
            return Ext.apply(attr, changes);
        }
    }
});