/**
 * Mixin that provides the functionality to place markers.
 */
Ext.define('Ext.chart.MarkerHolder', {
    extend: 'Ext.Mixin',
 
    requires: [
        'Ext.chart.Markers'
    ],
 
    mixinConfig: {
        id: 'markerHolder',
        after: {
            constructor: 'constructor',
            preRender: 'preRender'
        },
        before: {
            destroy: 'destroy'
        }
    },
 
    isMarkerHolder: true,
 
    // The combined transformation applied to the sprite by its parents. 
    // Does not include the transformation matrix of the sprite itself. 
    surfaceMatrix: null,
    // The inverse of the above transformation to go back to the original state. 
    inverseSurfaceMatrix: null,
 
    deprecated: {
        6: {
            methods: {
                /**
                 * Returns the markers bound to the given name.
                 * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
                 * @return {Ext.chart.Markers[]}
                 * @method getBoundMarker
                 * @deprecated 6.0 Use {@link #getMarker} instead.
                 */
                getBoundMarker: {
                    message: "Please use the 'getMarker' method instead.",
                    fn: function (name) {
                        var marker = this.boundMarkers[name];
                        return marker ? [marker] : marker;
                    }
                }
            }
        }
    },
 
    constructor: function () {
        this.boundMarkers = {};
        this.cleanRedraw = false;
    },
 
    /**
     * Registers the given marker with the marker holder under the specified name.
     * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
     * @param {Ext.chart.Markers} marker
     */
    bindMarker: function (name, marker) {
        var me = this,
            markers = me.boundMarkers;
 
        if (marker && marker.isMarkers) {
            //<debug> 
            if (markers[name] && markers[name] === marker) {
                Ext.log.warn(me.getId(), " (MarkerHolder): the Markers instance '", marker.getId(), "' is already bound under the name '", name, "'.");
            }
            //</debug> 
            me.releaseMarker(name);
            markers[name] = marker;
            marker.on('destroy', me.onMarkerDestroy, me);
        }
    },
 
    onMarkerDestroy: function (marker) {
        this.releaseMarker(marker);
    },
 
    /**
     * Unregisters the given marker or a marker with the given name.
     * Providing a name of the marker is more efficient as it avoids lookup.
     * @param marker {String/Ext.chart.Markers}
     * @return {Ext.chart.Markers} Released marker or null.
     */
    releaseMarker: function (marker) {
        var markers = this.boundMarkers,
            name;
 
        if (marker && marker.isMarkers) {
            for (name in markers) {
                if (markers[name] === marker) {
                    delete markers[name];
                    break;
                }
            }
        } else {
            name = marker;
            marker = markers[name];
            delete markers[name];
        }
 
        return marker || null;
    },
 
    /**
     * Returns the marker bound to the given name (or null). See {@link #bindMarker}.
     * @param {String} name The name of the marker (e.g., "items", "labels", etc.).
     * @return {Ext.chart.Markers}
     */
    getMarker: function (name) {
        return this.boundMarkers[name] || null;
    },
 
    preRender: function (surface, ctx, rect) {
        var me = this,
            id = me.getId(),
            boundMarkers = me.boundMarkers,
            parent = me.getParent(),
            name, marker,
            matrix;
 
        if (me.surfaceMatrix) {
            matrix = me.surfaceMatrix.set(1, 0, 0, 1, 0, 0);
        } else {
            matrix = me.surfaceMatrix = new Ext.draw.Matrix();
        }
 
        me.cleanRedraw = !me.attr.dirty;
        if (!me.cleanRedraw) {
            for (name in boundMarkers) {
                marker = boundMarkers[name];
                if (marker) {
                    marker.clear(id);
                }
            }
        }
 
        // Parent can be either a sprite (like a composite or instancing) 
        // or a surface. First, climb up and apply transformations of the 
        // parent sprites. 
        while (parent && parent.attr && parent.attr.matrix) {
            matrix.prependMatrix(parent.attr.matrix);
            parent = parent.getParent();
        }
        // Finally, apply the transformation used by the surface. 
        matrix.prependMatrix(parent.matrix);
        me.surfaceMatrix = matrix;
        me.inverseSurfaceMatrix = matrix.inverse(me.inverseSurfaceMatrix);
    },
 
    putMarker: function (name, attr, index, bypassNormalization, keepRevision) {
        var marker = this.boundMarkers[name];
 
        if (marker) {
            marker.putMarkerFor(this.getId(), attr, index, bypassNormalization, keepRevision);
        }
    },
 
    getMarkerBBox: function (name, index, isWithoutTransform) {
        var marker = this.boundMarkers[name];
 
        if (marker) {
            return marker.getMarkerBBoxFor(this.getId(), index, isWithoutTransform);
        }
    },
 
    destroy: function () {
        var boundMarkers = this.boundMarkers,
            name, marker;
 
        for (name in boundMarkers) {
            marker = boundMarkers[name];
            marker.destroy();
        }
    }
 
});