/**
 * @file Roam controller manager.
 */
define(function(require) {

    // Only create one roam controller for each coordinate system.
    // one roam controller might be refered by two inside data zoom
    // components (for example, one for x and one for y). When user
    // pan or zoom, only dispatch one action for those data zoom
    // components.

    var zrUtil = require('zrender/core/util');
    var RoamController = require('../../component/helper/RoamController');
    var throttle = require('../../util/throttle');
    var curry = zrUtil.curry;

    var ATTR = '\0_ec_dataZoom_roams';

    var roams = {

        /**
         * @public
         * @param {module:echarts/ExtensionAPI} api
         * @param {Object} dataZoomInfo
         * @param {string} dataZoomInfo.coordId
         * @param {Object} dataZoomInfo.coordinateSystem
         * @param {Array.<string>} dataZoomInfo.allCoordIds
         * @param {string} dataZoomInfo.dataZoomId
         * @param {number} dataZoomInfo.throttleRate
         * @param {Function} dataZoomInfo.panGetRange
         * @param {Function} dataZoomInfo.zoomGetRange
         */
        register: function (api, dataZoomInfo) {
            var store = giveStore(api);
            var theDataZoomId = dataZoomInfo.dataZoomId;
            var theCoordId = dataZoomInfo.coordId;

            // Do clean when a dataZoom changes its target coordnate system.
            zrUtil.each(store, function (record, coordId) {
                var dataZoomInfos = record.dataZoomInfos;
                if (dataZoomInfos[theDataZoomId]
                    && zrUtil.indexOf(dataZoomInfo.allCoordIds, theCoordId) < 0
                ) {
                    delete dataZoomInfos[theDataZoomId];
                    record.count--;
                }
            });

            cleanStore(store);

            var record = store[theCoordId];

            // Create if needed.
            if (!record) {
                record = store[theCoordId] = {
                    coordId: theCoordId,
                    dataZoomInfos: {},
                    count: 0
                };
                record.controller = createController(api, dataZoomInfo, record);
                record.dispatchAction = zrUtil.curry(dispatchAction, api);
            }

            // Consider resize, area should be always updated.
            record.controller.rect = dataZoomInfo.coordinateSystem.getRect().clone();

            // Update throttle.
            throttle.createOrUpdate(
                record,
                'dispatchAction',
                dataZoomInfo.throttleRate,
                'fixRate'
            );

            // Update reference of dataZoom.
            !(record.dataZoomInfos[theDataZoomId]) && record.count++;
            record.dataZoomInfos[theDataZoomId] = dataZoomInfo;
        },

        /**
         * @public
         * @param {module:echarts/ExtensionAPI} api
         * @param {string} dataZoomId
         */
        unregister: function (api, dataZoomId) {
            var store = giveStore(api);

            zrUtil.each(store, function (record) {
                var dataZoomInfos = record.dataZoomInfos;
                if (dataZoomInfos[dataZoomId]) {
                    delete dataZoomInfos[dataZoomId];
                    record.count--;
                }
            });

            cleanStore(store);
        },

        /**
         * @public
         */
        shouldRecordRange: function (payload, dataZoomId) {
            if (payload && payload.type === 'dataZoom' && payload.batch) {
                for (var i = 0, len = payload.batch.length; i < len; i++) {
                    if (payload.batch[i].dataZoomId === dataZoomId) {
                        return false;
                    }
                }
            }
            return true;
        },

        /**
         * @public
         */
        generateCoordId: function (coordModel) {
            return coordModel.type + '\0_' + coordModel.id;
        }
    };

    /**
     * Key: coordId, value: {dataZoomInfos: [], count, controller}
     * @type {Array.<Object>}
     */
    function giveStore(api) {
        // Mount store on zrender instance, so that we do not
        // need to worry about dispose.
        var zr = api.getZr();
        return zr[ATTR] || (zr[ATTR] = {});
    }

    function createController(api, dataZoomInfo, newRecord) {
        var controller = new RoamController(api.getZr());
        controller.enable();
        controller.on('pan', curry(onPan, newRecord));
        controller.on('zoom', curry(onZoom, newRecord));

        return controller;
    }

    function cleanStore(store) {
        zrUtil.each(store, function (record, coordId) {
            if (!record.count) {
                record.controller.off('pan').off('zoom');
                delete store[coordId];
            }
        });
    }

    function onPan(record, dx, dy) {
        wrapAndDispatch(record, function (info) {
            return info.panGetRange(record.controller, dx, dy);
        });
    }

    function onZoom(record, scale, mouseX, mouseY) {
        wrapAndDispatch(record, function (info) {
            return info.zoomGetRange(record.controller, scale, mouseX, mouseY);
        });
    }

    function wrapAndDispatch(record, getRange) {
        var batch = [];

        zrUtil.each(record.dataZoomInfos, function (info) {
            var range = getRange(info);
            range && batch.push({
                dataZoomId: info.dataZoomId,
                start: range[0],
                end: range[1]
            });
        });

        record.dispatchAction(batch);
    }

    /**
     * This action will be throttled.
     */
    function dispatchAction(api, batch) {
        api.dispatchAction({
            type: 'dataZoom',
            batch: batch
        });
    }

    return roams;

});