/**
 * This store contains the {@link Ext.calendar.model.EventBase events} for a particular
 * {@link Ext.calendar.model.CalendarBase} calendar.
 *
 * This store has an active range that is typically set via a calendar view.  This store
 * prefetches events outside of the current range (governed by
 * {@link #cfg-prefetchMode}), to facilitate a smoother user experience when navigating
 * views. Once events fall out of the prefetched range, they are pruned from the store.
 *
 * For more information on the the data the Events store may consume refer to
 * {@link Ext.calendar.model.Event}
 */
Ext.define('Ext.calendar.store.Events', {
    extend: 'Ext.data.Store',
    alias: 'store.calendar-events',

    model: 'Ext.calendar.model.Event',

    requires: [
        'Ext.calendar.model.Event',
        'Ext.calendar.date.Range'
    ],

    config: {
        /**
         * @cfg {Ext.calendar.model.Calendar} calendar
         * The calendar for the events.
         */
        calendar: null,

        /**
         * @cfg {String} calendarParam
         * The parameter name for the calendar to be sent to the server.
         */
        calendarParam: 'calendar',

        /**
         * @cfg {String} dateFormat
         * The date format to send to the server.
         */
        dateFormat: 'C',

        /**
         * @cfg {String} endParam
         * The parameter name for the end date to be sent to the server.
         */
        endParam: 'endDate',

        /**
         * @cfg {String} prefetchMode
         * The prefetch mode for pre-loading records on either side of the active range.
         * Possible values are:
         * - `month`
         * - `week`
         * - `day`
         *
         * If this store will be used amongst multiple views, it is recommended to use the largest
         * unit.
         */
        prefetchMode: 'month',

        /**
         * @cfg {String} startParam
         * The parameter name for the start date to be sent to the server.
         */
        startParam: 'startDate'
    },

    remoteSort: false,
    pageSize: 0,

    sorters: [{
        direction: 'ASC',
        sorterFn: function(a, b) {
            return Ext.calendar.model.Event.sort(a, b);
        }
    }],

    prefetchSettings: {
        month: {
            unit: Ext.Date.MONTH,
            amount: 2
        },
        week: {
            unit: Ext.Date.WEEK,
            amount: 2
        },
        day: {
            unit: Ext.Date.DAY,
            amount: 4
        }
    },

    constructor: function(config) {
        this.requests = {};
        this.callParent([config]);
    },

    /**
     * Gets a list of events that occurs in the specified range.
     * @param {Date} start The start of the range.
     * @param {Date} end The end of the range.
     * @return {Ext.calendar.model.EventBase[]} The events.
     */
    getInRange: function(start, end) {
        var records = this.data.items,
            len = records.length,
            ret = [],
            i, rec;

        for (i = 0; i < len; ++i) {
            rec = records[i];

            if (rec.occursInRange(start, end)) {
                ret.push(rec);
            }
        }

        return ret;
    },

    /**
     * Checks whether a particular date range is cached in this store.
     * @param {Ext.calendar.date.Range} range The range.
     * @return {Boolean} `true` if the range is cached.
     */
    hasRangeCached: function(range) {
        var current = this.range,
            ret = false;

        if (current) {
            ret = current.full.containsRange(range);
        }

        return ret;
    },

    /**
     * Sets the range for the current store. This may trigger the
     * store to load, or to prefetch events.
     * @param {Ext.calendar.date.Range} range The range.
     */
    setRange: function(range) {
        var me = this,
            D = Ext.Date,
            R = Ext.calendar.date.Range,
            current = me.range,
            prefetchSettings = me.getPrefetchSetting(),
            fetchCount = 0,
            isLeading = false,
            fetchStart, fetchEnd, fullStart, fullEnd, newRange;

        fullStart = D.subtract(range.start, prefetchSettings.unit, prefetchSettings.amount, true);
        fullEnd = D.add(range.end, prefetchSettings.unit, prefetchSettings.amount, true);

        newRange = {
            actual: range.clone(),
            full: new R(fullStart, fullEnd)
        };

        if (me.compareRange(current, newRange)) {
            return;
        }

        if (current && current.full.containsRange(newRange.full)) {
            return;
        }

        if (me.hasRangeCached(range)) {
            if (!me.hasRangeCached(newRange.full)) {
                if (current.full.start > fullStart) {
                    fetchStart = fullStart;
                    fetchEnd = current.full.start;
                    ++fetchCount;
                    isLeading = true;
                }

                if (current.full.end < fullEnd) {
                    fetchStart = current.full.end;
                    fetchEnd = fullEnd;
                    ++fetchCount;
                }

                if (fetchCount === 1) {
                    me.prefetchRange(fetchStart, fetchEnd, isLeading, newRange);
                }
                else if (fetchCount === 2) {
                    me.loadRange(fullStart, fullEnd, newRange);
                }
                //<debug>
                else {
                    Ext.raise('Should never be here.');
                }
                //</debug>
            }
        }
        else {
            me.loadRange(newRange.full.start, newRange.full.end, newRange);
        }

        me.requested = newRange;
    },

    // Overrides
    onProxyLoad: function(operation) {
        var me = this;

        if (operation.wasSuccessful()) {
            me.range = me.requested;
            me.requested = null;
        }

        me.setCalendarFromLoad = true;
        me.callParent([operation]);
        me.setCalendarFromLoad = false;
    },

    onCollectionAdd: function(collection, info) {
        var me = this;

        me.setRecordCalendar(me.getCalendar(), info.items, !me.setCalendarFromLoad);
        me.callParent([collection, info]);
    },

    onCollectionRemove: function(collection, info) {
        this.callParent([collection, info]);

        if (!this.isMoving) {
            this.setRecordCalendar(null, info.items, true);
        }
    },

    privates: {
        isMoving: 0,

        abortAll: function() {
            var requests = this.requests,
                request, id;

            for (id in requests) {
                request = requests[id];

                if (request && !request.destroyed) {
                    request.abort();
                }
            }

            this.requests = {};
        },

        compareRange: function(a, b) {
            var ret = false;

            if (!a || !b) {
                ret = (a || null) === (b || null);
            }
            else {
                ret = a.full.equals(b.full) && a.actual.equals(b.actual);
            }

            return ret;
        },

        getPrefetchSetting: function() {
            return this.prefetchSettings[this.getPrefetchMode()];
        },

        loadRange: function(start, end, newRequested) {
            var me = this,
                requested = me.requested,
                range = new Ext.calendar.date.Range(start, end);

            // We don't have the range cached, are we requesting it?
            if (!(requested && requested.full.equals(range))) {
                me.abortAll();

                me.load({
                    params: me.setupParams(start, end),
                    requested: newRequested
                });
            }
        },

        onBeforeLoad: function(operation) {
            this.requests[operation._internalId] = operation;
        },

        onPrefetch: function(operation) {
            var me = this,
                records = operation.getRecords() || [],
                toPrune = [],
                toAdd = [],
                map = Ext.Array.toMap(records, 'id'),
                range = me.getDataSource().getRange(),
                len = range.length,
                start = me.range.full.start,
                end = me.range.full.end,
                i, rec;

            if (operation.wasSuccessful()) {
                me.range = me.requested;
                me.requested = null;
            }

            delete me.requests[operation._internalId];

            me.suspendEvents();

            for (i = 0; i < len; ++i) {
                rec = range[i];

                if (!(map[rec.id] || rec.occursInRange(start, end))) {
                    toPrune.push(rec);
                }
            }

            for (i = 0, len = records.length; i < len; ++i) {
                rec = records[i];

                if (!me.getById(rec.id)) {
                    toAdd.push(rec);
                }
            }

            me.ignoreCollectionRemove = me.setCalendarFromLoad = true;
            me.getData().splice(0, toPrune, toAdd);
            me.ignoreCollectionRemove = me.setCalendarFromLoad = false;
            me.resumeEvents();

            me.fireEvent('prefetch', me, toAdd, toPrune);
        },

        prefetch: function(options) {
            var me = this,
                operation;

            options = Ext.apply({
                internalScope: me,
                internalCallback: me.onPrefetch
            }, options);

            me.setLoadOptions(options);

            operation = me.createOperation('read', options);
            me.requests[operation._internalId] = operation;
            operation.execute();
        },

        prefetchRange: function(start, end, isLeading, newRequested) {
            this.prefetch({
                params: this.setupParams(start, end),
                isLeading: isLeading,
                newRequested: newRequested
            });
        },

        setRecordCalendar: function(calendar, records, dirty) {
            var len = records.length,
                i, record;

            for (i = 0; i < len; ++i) {
                record = records[i];
                record.$moving = true;
                record.setCalendar(calendar, dirty);
                delete record.$moving;
            }
        },

        setupParams: function(start, end) {
            var me = this,
                D = Ext.Date,
                format = me.getDateFormat(),
                params = {};

            params[me.getCalendarParam()] = me.getCalendar().id;
            params[me.getStartParam()] = D.format(start, format);
            params[me.getEndParam()] = D.format(end, format);

            return params;
        }
    }
});
