/**
 * This class is used to generate the rendering parameters for an event
 * in a {@link Ext.calendar.view.Weeks}. The purpose of this class is
 * to provide the rendering logic insulated from the DOM.
 * 
 * @private
 */
Ext.define('Ext.calendar.view.WeeksRenderer', {
    /**
     * @cfg {Number} days
     * The number of days.
     */
    days: null,
 
    /**
     * @cfg {Number} index
     * The index of this week.
     */
    index: null,
 
    /**
     * @cfg {Number} maxEvents
     * The maximum number of events per day before overflow.
     * `null` to disable this.
     */
    maxEvents: null,
 
    /**
     * @cfg {Boolean} overflow
     * `true` to calculate sizes as if overflow could occur.
     */
    overflow: true,
 
    /**
     * @cfg {Date} start
     * The start of the week.
     */
    start: null,
 
    /**
     * {@link Ext.calendar.view.Base} view
     * The view.
     */
    view: null,
 
    constructor: function(config) {
        var me = this,
            D = Ext.Date,
            start, end;
 
        Ext.apply(me, config);
 
        start = me.start;
        me.end = end = D.add(start, D.DAY, me.days);
        me.utcStart = this.view.utcTimezoneOffset(start);
        me.utcEnd = D.add(me.utcStart, D.DAY, me.days);
 
        me.rows = [];
        me.events = [];
        me.seen = {};
        me.overflows = [];
    },
 
    /**
     * Add an event if it occurs within the range for this week.
     * @param {Ext.calendar.model.EventBase} event The event.
     */
    addIf: function(event) {
        var me = this,
            start, end;
 
        if (event.getAllDay()) {
            // Treat all day events as UTC
            start = me.utcStart;
            end = me.utcEnd;
        } else {
            start = me.start;
            end = me.end;
        }
 
        if (event.occursInRange(start, end)) {
            me.events.push(event);
        }
    },
 
    /**
     * Indicates that all events are added and the positions can be calculated.
     */
    calculate: function() {
        var me = this,
            D = Ext.Date,
            view = me.view,
            seen = me.seen,
            events = me.events,
            len = events.length,
            days = me.days,
            rangeEnd = me.end,
            utcRangeEnd = me.utcEnd,
            start = D.clone(me.start),
            utcStart = D.clone(me.utcStart),
            maxEvents = me.maxEvents,
            i, j, dayEvents, event, eLen,
            utcEnd, end, id, eventEnd, span,
            offsetStart, offsetEnd, offsetRangeEnd, 
            allDay, item, offset, isSpan;
 
        for (= 0; i < days; ++i) {
            end = D.add(start, D.DAY, 1);
            utcEnd = D.add(utcStart, D.DAY, 1);
            dayEvents = [];
 
            for (= 0; j < len; ++j) {
                event = events[j];
                id = event.id;
 
                allDay = event.getAllDay();
                if (allDay) {
                    offsetStart = utcStart;
                    offsetEnd = utcEnd;
                    offsetRangeEnd = utcRangeEnd;
                } else {
                    offsetStart = start;
                    offsetEnd = end;
                    offsetRangeEnd = rangeEnd;
                }
 
                if (event.occursInRange(offsetStart, offsetEnd)) {
                    isSpan = event.isSpan();
                    if (!seen[id]) {
                        span = 1;
                        // If the event only spans 1 day, don't bother calculating
                        if (isSpan) {
                            eventEnd = event.getEndDate();
                            if (eventEnd > offsetRangeEnd) {
                                // If the event finishes after our range, then just span it to the end
                                span = days - i;
                            } else {
                                // Otherwise, calculate the number of days used by this event
                                span = view.getDaysSpanned(offsetStart, eventEnd, allDay);
                            }
                        }
                        seen[id] = span;
                        dayEvents.push({
                            event: event
                        });
                    } else if (isSpan) {
                        // Seen this span already, but it needs to go in the day events so
                        // overflows get generated correctly
                        dayEvents.push({
                            isPlaceholder: true,
                            event: event
                        });
                    }
                }
            }
 
            eLen = dayEvents.length;
            if (eLen) {
                // Now we have all of the events for this day, sort them based on "priority",
                // then add them to our internal structure
                dayEvents.sort(me.sortEvents);
 
                if (maxEvents !== null && eLen > maxEvents) {
                    // -1 here because maxEvents is the total we can show, without the "show more" item.
                    // Assuming that "show more" is roughly the same size as an event, which we'll 
                    // also need to show, we have to lop off another event.
                    offset = me.overflow ? 1 : 0;
                    offset = Math.max(0, maxEvents - offset);
                    me.overflows[i] = Ext.Array.map(Ext.Array.splice(dayEvents, offset), function(item) {
                        return item.event;
                    });
                    eLen = dayEvents.length;
                }
 
                for (= 0; j < eLen; ++j) {
                    item = dayEvents[j];
                    if (!item.isPlaceholder) {
                        event = item.event;
                        me.addToRow(event, i, seen[event.id]);
                    }
                }
            }
 
            start = end;
            utcStart = utcEnd;
        }
    },
 
    /**
     * Compress existing rows into consumable pieces for the view.
     * @param {Number} rowIdx The row index to compress.
     * @return {Object[]} A compressed set of config objects for the row.
     */
    compress: function(rowIdx) {
        var row = this.rows[rowIdx],
            ret = [],
            days = this.days,
            count = 0,
            i = 0,
            inc, item;
 
        while (< days) {
            inc = 1;
            item = row[i];
            if (item.event) {
                if (count > 0) {
                    ret.push({
                        isEmpty: true,
                        len: count
                    });
                    count = 0;
                }
                ret.push(item);
                i += item.len;
            } else {
                ++count;
                ++i;
            }
        }
 
        if (count > 0) {
            ret.push({
                isEmpty: true,
                len: count
            });
        }
 
        return ret;
    },
 
    /**
     * Checks if this renderer has any events.
     * @return {Boolean} `true` if there are events.
     */
    hasEvents: function() {
        return this.events.length > 0;
    },
 
    privates: {
        /**
         * Add an event to an existing row. Creates a new row
         * if one cannout be found.
         * @param {Ext.calendar.model.EventBase} event The event.
         * @param {Number} dayIdx The start day for the event.
         * @param {Number} days The number of days to span
         *
         * @private
         */
        addToRow: function(event, dayIdx, days) {
            var me = this,
                rows = me.rows,
                len = rows.length,
                end = days + dayIdx,
                found, i, j, row, idx;
 
            for (= 0; i < len; ++i) {
                row = rows[i];
                for (= dayIdx; j < end; ++j) {
                    if (row[j]) {
                        // Something occupying the space
                        break;
                    }
                }
 
                // If we got to the end of the loop above, we're ok to use this row
                if (=== end) {
                    found = row;
                    idx = i;
                    break;
                }
            }
 
            if (!found) {
                found = me.makeRow();
                rows.push(found);
                idx = rows.length - 1;
            }
            me.occupy(event, found, idx, dayIdx, end - 1);
        },
 
        /**
         * Construct a new row.
         * @return {Object[]} The new row.
         *
         * @private
         */
        makeRow: function() {
            var row = [],
                days = this.days,
                i;
 
            for (= 0; i < days; ++i) {
                row[i] = 0;
            }
            return row;
        },
 
        /**
         * Have an event occupy space in a row.
         * @param {Ext.calendar.model.EventBase} event The event.
         * @param {Object[]} row  The row.
         * @param {Number} rowIdx The local index of the row.
         * @param {Number} fromIdx The start index to occupy.
         * @param {Number} toIdx The end index to occupy.
         *
         * @private
         */
        occupy: function(event, row, rowIdx, fromIdx, toIdx) {
            var len = toIdx - fromIdx + 1,
                i;
 
            for (= fromIdx; i <= toIdx; ++i) {
                row[i] = i === fromIdx ? {
                    event: event,
                    len: len,
                    start: fromIdx,
                    weekIdx: this.index,
                    localIdx: rowIdx
                } : true;
            }
        },
 
        /**
         * A sort comparator function for processing events.
         * @param {Object} event1 The first event.
         * @param {Object} event2 The second event,
         * @return {Number} A standard sort comparator.
         * @private
         */
        sortEvents: function(event1, event2) {
            event1 = event1.event;
            event2 = event2.event;
            return +event2.isSpan() - +event1.isSpan() ||
                   Ext.calendar.model.Event.sort(event1, event2);
        }
    }
});