/**
 * Mixin that provides the functionality to place markers.
 */
Ext.define('Ext.chart.MarkerHolder', {
    extend: 'Ext.Mixin',

    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],
            id = this.getId();

        if (marker) {
            marker.putMarkerFor(id, attr, index, bypassNormalization, keepRevision);
        }
    },

    getMarkerBBox: function (name, index, isWithoutTransform) {
        var marker = this.boundMarkers[name],
            id = this.getId();

        if (marker) {
            return marker.getMarkerBBoxFor(id, index, isWithoutTransform);
        }
    },

    destroy: function () {
        var boundMarkers = this.boundMarkers,
            name, marker;

        for (name in boundMarkers) {
            marker = boundMarkers[name];
            marker.destroy();
        }
    }

});
