﻿/*!
 * Ext Scheduler v1.5 beta
 * Copyright(c) 2009-2010 Mats Bryntse Consulting
 * mats@ext-scheduler.com
 * http://www.ext-scheduler.com/license.html
 *
 */

Ext.ns('Sch');

/**
 * @class Sch.ColumnFactory
 * Static factory which creates column arrays consumed by the {@link Sch.SchedulerPanel}.
 * @static
 */
Sch.ColumnFactory = {
    
    /**
     * Default column properties which are applied to all column objects regardless of column type
     */
    defaults : {
        align : 'center',
        menuDisabled : true,
        hideable : false,
        resizable : false,
        sortable : false,
        headerCls : 'sch-timeheader '
    },
    
    // private
    createColumns : function (startDate, endDate, type, defaults) {
        if (startDate > endDate || !this.columnConstructors[type]) {
            throw 'Invalid parameters passed to createColumns';
        }
        
        return this.columnConstructors[type].call(this, startDate, endDate, defaults);
    },
    
    /**
     * Methods used to populated the column headers. Override these to provide your own header formatting
     */
    headerRenderers : {
        minute : function(date, cfg) {
            cfg.header = date.format('i');
        },
        
        quarterMinute : function(date, cfg) {
            cfg.headerCls += 'sch-quarterminuteheader';
            cfg.header = '<table class="quarterMinuteIndicator" cellpadding="0" cellspacing="0"><tr><td>00</td><td>15</td><td>30</td><td>45</td></tr></table>';
        },
        
        hourMinute : function(date, cfg) {
            cfg.header = date.format('H:i');
        },
        
        day : function(date, cfg) {
            cfg.header = String.format('{0} {1}/{2}', Date.getShortDayName([date.getDay()]), date.getDate(), date.getMonth() + 1);
        },
        
        dayLetter : function(date, cfg) {
            cfg.headerCls += 'sch-dayheadercell-' + date.getDay();
            cfg.header = Date.dayNames[date.getDay()].substring(0,1);
        },
        
        dayNumber : function(date, cfg) {
            cfg.headerCls += 'sch-dayheadercell-' + date.getDay();
            cfg.header = date.getDate();
        },
        
        dayHours : function(date, cfg) {
            cfg.headerCls += 'sch-dayheader';
            cfg.header = '<table class="hourIndicator" cellpadding="0" cellspacing="0"><tr><td></td><td>3</td><td>6</td><td>9</td><td>12</td><td>15</td><td>18</td><td>21</td></tr></table>';
        },
        
        week : function(date, cfg) { 
            cfg.headerCls += 'sch-weekheader';
            var w = date.getWeekOfYear(),
                y = (w === 1 && date.getMonth() > 0) ? (date.getFullYear() + 1) : date.getFullYear();
            cfg.header = ((w < 10) ? ('0' + w) : w);
        },
        
        weekMonthYear : function(date, cfg) { 
            cfg.headerCls += 'sch-weekheader';
            var w = date.getWeekOfYear();
            cfg.header = 'w.' + ((w < 10) ? '0' : '') + w + ' ' + Date.getShortMonthName(date.getMonth()) + ' ' + date.getFullYear();
        },
        
        weekDays : function() {
            var dayTable = '<table class="sch-dayIndicator" cellpadding="0" cellspacing="0"><tr class="days"><td class="sch-dayheadercell-{7}">{0}</td><td class="sch-dayheadercell-{8}">{1}</td><td class="sch-dayheadercell-{9}">{2}</td><td class="sch-dayheadercell-{10}">{3}</td><td class="sch-dayheadercell-{11}">{4}</td><td class="sch-dayheadercell-{12}">{5}</td><td class="sch-dayheadercell-{13}">{6}</td></tr></table>';
            
            return function(date, cfg) {
                cfg.headerCls += 'sch-daytableheader';
                var w = date.getWeekOfYear(),
                    y = (w === 1 && date.getMonth() > 0) ? (date.getFullYear() + 1) : date.getFullYear();
                    
                cfg.header = String.format(dayTable, 
                                     date.getDate(),
                                     date.add(Date.DAY, 1).getDate(),
                                     date.add(Date.DAY, 2).getDate(),
                                     date.add(Date.DAY, 3).getDate(),
                                     date.add(Date.DAY, 4).getDate(),
                                     date.add(Date.DAY, 5).getDate(),
                                     date.add(Date.DAY, 6).getDate(),
                                     date.getDay(),
                                     (date.getDay() + 1) % 7,
                                     (date.getDay() + 2) % 7,
                                     (date.getDay() + 3) % 7,
                                     (date.getDay() + 4) % 7,
                                     (date.getDay() + 5) % 7,
                                     (date.getDay() + 6) % 7
                                     );
            };
        }(),
        
        month : function(date, cfg) {
            cfg.headerCls += 'sch-monthheader';
            cfg.header = String.format('{0} {1}', Date.getShortMonthName(date.getMonth()), date.getFullYear());
        },
        
        quarter : function(date, cfg) {
            cfg.headerCls += 'sch-quarterheader';
            cfg.header = String.format('Q{0} {1}', Math.floor(date.getMonth() / 3) + 1, date.getFullYear());
        },
        
        year : function(date, cfg) {
            cfg.headerCls += 'sch-yearheader';
            cfg.header = date.getFullYear();
        }
    },
    
    // private, interval is either a string constant such as Date.DAY or a number representing seconds
    createColumnsInternal : function(start, end, interval, headerRenderer, skipFn, defaults) {
        var cols = [],
            cursor = start.clone(),
            intervalEnd,
            colCfg;
        
        if (typeof skipFn !== "function") {
            defaults = skipFn;
            skipFn = null;
        }
            
        while (cursor < end) {
            if (typeof interval == "number") {
                intervalEnd = cursor.add(Date.SECOND, interval);
            } else {
                intervalEnd = cursor.add(interval, 1);
            }
            
            if (!skipFn || skipFn.call(this, cursor, intervalEnd) !== true) {
                colCfg = Ext.apply({
                    start : cursor,
                    end : intervalEnd
                }, defaults, this.defaults);
                
                headerRenderer.call(this, cursor, colCfg);
                cols.push(colCfg);
            }
            cursor = intervalEnd;
        }
        return cols;
    },
    
    /**
     * @ignore
     * Object containing column constructors used to create columns.
     * Each method returns an object containing at minimum an array of basic Ext.grid.Column columns, and
     * optionally a 'rows' property which is also an array of extra columns to render above the standard ones.
     */
    columnConstructors : {
        quarterMinutes : function(startDate, endDate, defaults) {
            var cols = this.createColumnsInternal(startDate, endDate, Date.HOUR, this.headerRenderers.quarterMinute, defaults);
            
            return {
                columns : cols,
                rows : [
                    Sch.ColumnFactory.rowConstructors.day.call(this, startDate, endDate),
                    Sch.ColumnFactory.rowConstructors.hour.call(this, startDate, endDate)
                ]
            };
        },
        
        hour : function(startDate, endDate, defaults) {
            var cols = this.createColumnsInternal(startDate, endDate, Date.HOUR, this.headerRenderers.hourMinute, defaults);
            
            return {
                columns : cols
            };
        },
        
        hourAndDay : function(startDate, endDate, defaults) {
            var cols = this.createColumnsInternal(startDate, endDate, Date.HOUR, this.headerRenderers.hourMinute, defaults);
            
            return {
                columns : cols,
                rows : [
                    Sch.ColumnFactory.rowConstructors.day.call(this, startDate, endDate)
                ]
            };
        },
        
        dayAndHours : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.DAY, this.headerRenderers.dayHours, defaults);
            
            return {
                columns : cols,
                rows : [
                    Sch.ColumnFactory.rowConstructors.day.call(this, startDate, endDate)
                ]
            };
        },
        
        dayNoWeekends : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.DAY, this.headerRenderers.day, function(start, end) {
                var d = start.getDay();
                return d === 0 || d === 6;
            }, defaults);
            
            return {
                columns : cols
            };
        },
        
        day : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.DAY, this.headerRenderers.day, defaults);
            
            return {
                columns : cols
            };
        },
        
        dayAndWeeks : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.DAY, this.headerRenderers.dayNumber, defaults);
            
            return {
                columns : cols,
                rows : [
                    Sch.ColumnFactory.rowConstructors.week.call(this, startDate, endDate, this.headerRenderers.week)
                ]
            };
        },
       
        dayAndMonths : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.DAY, this.headerRenderers.dayNumber, defaults);
            
            return {
                columns : cols,
                rows : [
                    Sch.ColumnFactory.rowConstructors.month.call(this, startDate, endDate, this.headerRenderers.month)
                ]
            };
        },
        
        dayWeekAndMonths : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            // Make sure we are dealing with an even number of weeks
            endDate = startDate.add(Date.WEEK, Math.round(Date.getDurationInDays(startDate, endDate)/7));
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.WEEK, this.headerRenderers.weekDays, defaults);
            
            return {
                columns : cols,
                rows : [
                    Sch.ColumnFactory.rowConstructors.month.call(this, startDate, endDate, this.headerRenderers.month),
                    Sch.ColumnFactory.rowConstructors.week.call(this, startDate, endDate, this.headerRenderers.week)
                ]
            };
        },
        
        week : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            // Make sure we are dealing with an even number of weeks
            endDate = startDate.add(Date.WEEK, Math.round(Date.getDurationInDays(startDate, endDate)/7));
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.WEEK, this.headerRenderers.week, defaults);
            
            return {
                columns : cols
            };
        },
        
        weekAndMonths : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            // Make sure we are dealing with an even number of weeks
            endDate = startDate.add(Date.WEEK, Math.round(Date.getDurationInDays(startDate, endDate)/7));
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.WEEK, this.headerRenderers.week, defaults);
            
            return {
                columns : cols,
                rows : [
                    Sch.ColumnFactory.rowConstructors.month.call(this, startDate, endDate)
                ]
            };
        },
        
        weekAndDays : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            // Make sure we are dealing with an even number of weeks
            endDate = startDate.add(Date.WEEK, Math.round(Date.getDurationInDays(startDate, endDate)/7));
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.WEEK, this.headerRenderers.weekDays, defaults);
                
            return {
                columns : cols,
                rows : [
                    Sch.ColumnFactory.rowConstructors.week.call(this, startDate, endDate, this.headerRenderers.weekMonthYear)
                ]
            };
        },
        
        month : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            startDate.setDate(1);
            
            // Make sure we deal with a whole number of months
            if (endDate.getDate() !== 1) {
                endDate.setDate(1);
                endDate.add(Date.MONTH, 1);
            }
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.MONTH, this.headerRenderers.month, defaults);
            
            return {
                columns : cols
            };
        },
        
        monthAndQuarters : function(startDate, endDate, defaults) {
            startDate.clearTime();
            endDate.clearTime();
            
            startDate.setDate(1);
            
            // Make sure we deal with a whole number of months
            if (endDate.getDate() !== 1) {
                endDate.setDate(1);
                endDate.add(Date.MONTH, 1);
            }
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.MONTH, this.headerRenderers.month, defaults);
            
            return {
                columns : cols,
                rows : [
                    Sch.ColumnFactory.rowConstructors.quarter.call(this, startDate, endDate)
                ]
            };
        },
        
        year : function(startDate, endDate, defaults) {
            startDate.clearTime();
            startDate.setDate(1);
            
            var cols = this.createColumnsInternal(startDate, endDate, Date.YEAR, this.headerRenderers.year, defaults);
            
            return {
                columns : cols
            };
        }
    },
    
    /**
     * @ignore
     * Object containing column constructors used to create extra layers of rows on top of the standard time columns.
     * These methods are called internally by the constructors in columnConstructors 
     */
    rowConstructors : {
        hour : function(startDate, endDate, headerRenderer) {
            var spanHours = Date.getDurationInHours(startDate, endDate),
                cols = [],
                dt,
                i,
                cfg;
            
            for (i = 0; i < spanHours; i++) {
                dt = startDate.add(Date.HOUR, i);
                
                cfg = Ext.applyIf({
                    width : 1 / spanHours,
                    align : 'center',
                    start : dt,
                    end : dt.add(Date.HOUR, 1)
                }, Sch.ColumnFactory.defaults);
                
                (headerRenderer || this.headerRenderers.hourMinute).call(this, dt, cfg);
                
                cols.push(cfg);
            }
            
            return cols;
        },
        
        day : function(startDate, endDate, headerRenderer) {
            var spanDays = Math.max(Math.round(Date.getDurationInDays(startDate, endDate)), 1),
                cols = [],
                dt,
                i,
                cfg;
            
            for (i = 0; i < spanDays; i++) {
                dt = startDate.add(Date.DAY, i);
                
                cfg = Ext.applyIf({
                    width : 1 / spanDays,
                    align : 'center',
                    start : dt,
                    end : dt.add(Date.DAY, 1)
                }, Sch.ColumnFactory.defaults);
                
                (headerRenderer || this.headerRenderers.day).call(this, dt, cfg);
                
                cols.push(cfg);
            }
            
            return cols;
        },
        
        // Works only with a whole number of weeks
        week : function(startDate, endDate, headerRenderer) {
            var cols = [],
                i = 0,
                spanDays = Math.round(Date.getDurationInDays(startDate, endDate)),
                width,
                cursor = startDate.clone(),
                cfg;
            
            while (cursor < endDate) {
                if (i === 0) {
                    width = Math.round(Date.getDurationInDays(startDate, startDate.add(Date.WEEK, 1))) / spanDays;
                } else {
                    width = Math.min(7, Math.round(Date.getDurationInDays(cursor, endDate))) / spanDays;
                }
                
                cfg = Ext.applyIf({
                    width : width,
                    align : 'center',
                    start : cursor,
                    end : cursor.add(Date.WEEK, 1)
                }, Sch.ColumnFactory.defaults);
                
                (headerRenderer || this.headerRenderers.week).call(this, cursor, cfg);
                
                cols.push(cfg);
                
                i++;
                cursor = cursor.add(Date.WEEK, 1);
            }
            
            return cols;
        },
        
        
        /* EXPERIMENTAL */
        weekNoWeekends : function(startDate, endDate, headerRenderer) {
            var cols = [],
                i = 0,
                spanDays = Math.round(Date.getDurationInBusinessDays(startDate, endDate)),
                width,
                cursor = startDate.clone(),
                cfg,
                nbrDays;
            
            while (cursor < endDate) {
                if (i === 0) {
                    nbrDays = 6 - cursor.getDay();
                } else {
                    nbrDays = Date.getDurationInBusinessDays(cursor, Date.min(endDate, cursor.add(Date.DAY, 5))); 
                }
                
                cfg = Ext.applyIf({
                    width : nbrDays / spanDays,
                    align : 'center',
                    start : cursor,
                    end : cursor.add(Date.WEEK, 1)
                }, Sch.ColumnFactory.defaults);
                
                (headerRenderer || this.headerRenderers.week).call(this, cursor, cfg);
                
                cols.push(cfg);
                
                i++;
                cursor = cursor.add(Date.DAY, nbrDays + 2);
            }
            
            return cols;
        },
        
        month : function(startDate, endDate, headerRenderer) {
            var spanMonths = Date.getDurationInMonths(startDate, endDate) + 1,
                spanDays = Date.getDurationInDays(startDate, endDate),
                firstDateOfStartMonth = new Date(startDate.getFullYear(), startDate.getMonth(), 1),
                cols = [],
                dt,
                i,
                width,
                totalWidth = 0,
                cfg;
            
            for (i = 0; i < spanMonths; i++) {
                dt = startDate.add(Date.MONTH, i);
                dt.setDate(1);
                
                if (i === 0) {
                    width = Date.getDurationInDays(startDate, firstDateOfStartMonth.add(Date.MONTH, 1)) / spanDays;
                    totalWidth += width;
                } else if (i === spanMonths - 1) {
                    // Assign whatever is left to last column
                    width = 1 - totalWidth;
                } else {
                    width = dt.getDaysInMonth() / spanDays;
                    totalWidth += width;
                }
                
                cfg = Ext.applyIf({
                    width : width,
                    align : 'center',
                    start : dt,
                    end : dt.add(Date.MONTH, 1)
                }, Sch.ColumnFactory.defaults);
                
                (headerRenderer || this.headerRenderers.month).call(this, dt, cfg);
                cols.push(cfg);
            }
            
            return cols;
        },
        
        // For use with the month column type, and only for a whole number of months
        quarter : function(startDate, endDate, headerRenderer) {
            var startQuarter = Math.floor(startDate.getMonth() / 3) + 1,
                endQuarter = Math.floor(endDate.getMonth() / 3) + 1,
                spanQuarters = 4 * (endDate.getYear() - startDate.getYear()) + (endQuarter - startQuarter),
                spanMonths = Date.getDurationInMonths(startDate, endDate),
                startMonth = startDate.getMonth(),
                endMonth = endDate.getMonth(),
                firstDateOfStartQuarter = new Date(startDate.getFullYear(), Math.floor(startMonth / 3) * 3, 1),
                cols = [],
                dt = startDate.clone(),
                i,
                width,
                cfg;
            
            // TODO clean up
            if (startQuarter === endQuarter && startDate.getYear() === endDate.getYear()) {
                spanQuarters = 1;
            }
            else {
                if (endDate.getMonth() % 3 === 0 && startDate.getMonth() % 3 > 0) {
                    spanQuarters--;
                }
                
                if ((startDate.getMonth() % 3 > 0 || endDate.getMonth() % 3 > 0))
                {
                    spanQuarters++;
                }
            }
            
            dt = firstDateOfStartQuarter;
            
            for (i = 0; i < spanQuarters; i++) {
                
                if (i === 0) {
                    width = Date.getDurationInMonths(startDate, Date.min(firstDateOfStartQuarter.add(Date.MONTH, 3), endDate)) / spanMonths;
                } else if (i === spanQuarters - 1) {
                    width = Date.getDurationInMonths(dt, endDate) / spanMonths;
                } else {
                    width = Date.getDurationInMonths(dt, dt.add(Date.MONTH, 3)) / spanMonths;
                }
                
                cfg = Ext.applyIf({
                    width : width,
                    align : 'center',
                    start : dt,
                    end : dt.add(Date.MONTH, 3)
                }, Sch.ColumnFactory.defaults);
                
               (headerRenderer || this.headerRenderers.quarter).call(this, dt, cfg);
                cols.push(cfg);
                
                dt = dt.add(Date.MONTH, 3);
            }
            
            return cols;
        }
    }
};