Ext.ns('App');

Ext.onReady(function() {
    Ext.BLANK_IMAGE_URL = 'http://extjs.cachefly.net/ext-3.1.0/resources/images/default/s.gif';
    Ext.QuickTips.init();
    
    App.init();
});

App = {
    
    // Initialize application
    init : function() {
        
        var start = new Date();
        start.clearTime();
        start.setHours(11);
        
        this.scheduler = this.createScheduler();
        
        this.unplannedTaskGrid = this.createTaskGrid();
        this.newStaffGrid = this.createStaffGrid();

        var ctPanel = new Ext.Panel({
            layout : 'border',
            border : false,
            height:650,
            width: 1000,
            items : [
                this.scheduler,
                {
                    height : 230,
                    region : 'south',
                    layout : 'border',
                    border : false,
                    items : [
                        this.unplannedTaskGrid,
                        this.newStaffGrid
                    ]
                }
            ]
        });
        
        ctPanel.on('afterrender', function() {
            initializeTaskDragZone(this.unplannedTaskGrid, this.scheduler.id);
            initializeStaffDragDrop(this.newStaffGrid, this.scheduler);
        }, this);
        
        ctPanel.render('ctPanel');
    },
    
    createTaskGrid : function() {
        return new Ext.grid.GridPanel({
            region : 'center',
            cls : 'taskgrid',
            title : 'Unplanned tasks (drag them onto the schedule)',
            store: new Ext.data.JsonStore({
                sortInfo:{field: 'Title', direction: "ASC"},
                fields : [
                    'Id', 
                    'Title',
                    'Duration'
                ],
                data : [
                    {Id : 't1', Title : 'Fix bug', Duration : 4},
                    {Id : 't2', Title : 'Add documentation', Duration : 3},
                    {Id : 't3', Title : 'Talk to customer', Duration : 3},
                    {Id : 't4', Title : 'Phone call', Duration : 0.5},
                    {Id : 't5', Title : 'Eat burger', Duration : 1},
                    {Id : 't6', Title : 'Enjoy cold beer', Duration : 2},
                    {Id : 't7', Title : 'Have fun', Duration : 5}
                ]
            }),
            columns : [
               {header : 'Tasks', sortable:true, width:150, dataIndex : 'Title'},
               {header : 'Duration (h)', sortable:true, width:150, dataIndex : 'Duration'}
            ]
        });
    },
    
    createStaffGrid : function() {
        return new Ext.grid.GridPanel({
            region : 'east',
            cls : 'staffgrid',
            width:450,
            title : 'New staff (drag them onto a staff header in the scheduling view)',
            store: new Ext.data.JsonStore({
                sortInfo:{field: 'Name', direction: "ASC"},
                fields : [
                    'Id', 
                    'Name',
                     // Resource availability hours
                    'StartAvailability',
                    'EndAvailability'
                ],
                data : [
                    {Id : 'r11', Name : 'Mike', StartAvailability : 7, EndAvailability : 18},
                    {Id : 'r12', Name : 'Stewart', StartAvailability : 5, EndAvailability : 10},
                    {Id : 'r13', Name : 'Ben', StartAvailability : 9, EndAvailability : 19},
                    {Id : 'r14', Name : 'Dion', StartAvailability : 8, EndAvailability : 16},
                    {Id : 'r15', Name : 'Jack', StartAvailability : 7, EndAvailability : 18}
                ]
            }),
            columns : [
               {header : 'Staff', sortable:true, dataIndex : 'Name', renderer : function(v,m) { m.css = 'user'; return v;}},
               {header : 'Available from', sortable:true, dataIndex : 'StartAvailability'},
               {header : 'Available until', sortable:true, dataIndex : 'EndAvailability'}
            ]
        });
    },
    
    // This function adds some background styling to the scheduling cells
    timeCellRenderer : function (events, m, resourceRecord, row, col, ds, cellStartTime, cellEndTime, grid) {
        
        var cellStartHours = cellStartTime.getHours();
            cellEndHours = cellEndTime.getHours();
        
        if (cellStartHours < resourceRecord.get('StartAvailability') || cellStartHours > resourceRecord.get('EndAvailability'))  {
            m.css = 'unavailable';
        }
    },
    
    // Default renderer, supplies data to be applied to the event template
    renderer : function (event, r, row, col, ds) {
        // Return data to be applied to the event template
        return { 
            text : event.get('Title')
        };
    },
    
    createScheduler : function() {
        
        // Store holding all the resources
        var resourceStore = new Ext.data.GroupingStore({
            sortInfo:{field: 'Name', direction: "ASC"},
            groupField : 'Group',
            idProperty : 'Id',
            data : [
                {Id : 'r1', Group : 'Sales', Name : 'Mike Anderson', StartAvailability : 7, EndAvailability : 18},
                {Id : 'r2', Group : 'Sales', Name : 'Kevin Larson', StartAvailability : 8, EndAvailability : 17},
                {Id : 'r3', Group : 'Sales', Name : 'Doug Bacon', StartAvailability : 9, EndAvailability : 16},
                {Id : 'r5', Group : 'Management', Name : 'Lars Bacon', StartAvailability : 9, EndAvailability : 16},
                {Id : 'r6', Group : 'Management', Name : 'Tomas Taylow', StartAvailability : 9, EndAvailability : 16},
                {Id : 'r73', Group : 'Management', Name : 'Brett Hornbach', StartAvailability : 9, EndAvailability : 16},
                {Id : 'r17', Group : 'Marketing', Name : 'David Mantorp', StartAvailability : 6, EndAvailability : 19},
                {Id : 'r18', Group : 'Marketing', Name : 'Ann Withersby', StartAvailability : 5, EndAvailability : 15},
                {Id : 'r20', Group : 'Marketing', Name : 'John Dough', StartAvailability : 5, EndAvailability : 15}
            ],
            reader : new Ext.data.JsonReader({
                idProperty : 'Id'
            }, [
                'Id', 
                'Group',
                'Name',
                
                // Resource availability hours
                'StartAvailability',
                'EndAvailability'
                ]
            )
        });
        
        // Store holding all the tasks
        var eventStore = new Ext.data.JsonStore({
            sortInfo:{field: 'ResourceId', direction: "ASC"},
            idProperty : 'Id',
            fields : [
                {name: 'Id', type:'string'},
                {name: 'ResourceId'},
                {name: 'StartDate', type : 'date'},
                {name: 'EndDate', type : 'date'},
                
                {name: 'Title'}
            ]
        });
        
        var start = new Date();
        start.clearTime();
        start.setHours(5);
        
        var end = start.clone();
        end.setHours(21);
        
        // Date line functionality
        var datelineStore = new Ext.data.JsonStore({
            autoDestroy: true,
            fields: [
                'Text', 
                {name:'Date', type: 'date'}
            ],
            data : [{
                    Text : 'Today',
                    Date : new Date()
                }
            ]
        });
        
        var task = {
            run: function() {
                datelineStore.getAt(0).set('Date', new Date());
            },
            interval: 10000 // Update line store every 10 seconds
        };
        var runner = new Ext.util.TaskRunner();
        runner.start(task);
                    
        var g = new Sch.SchedulerPanel({
            resizeHandles : 'none',
            enableDragCreation : false,
            region : 'center',
            title : 'Task scheduling',
            border : false,
            enabledHdMenu : false,
            
            // Setup your static columns
            columns : [
               {header : 'Staff', sortable:true, width:150, dataIndex : 'Name'},
               {header : 'Type', sortable:true, width:150, dataIndex : 'Group'}
            ],
            
            view: new Sch.SchedulerGroupingView({
                forceFit : true,
                showGroupName : false
            }),
            
            eventRenderer : this.renderer,
            timeCellRenderer : this.timeCellRenderer,
            
            store : resourceStore,
            eventStore : eventStore,
            border : true,
            
            // Timline view configuration
            viewModel : {
                start : start, 
                end : end, 
                columnType : 'hourAndDay',
                viewBehaviour : Sch.ViewBehaviour.HourView
            },
            tbar : [
                {
                    iconCls : 'icon-prev',
                    handler : function() {
                        g.setView(g.getStart().add(Date.DAY, -1), g.getEnd().add(Date.DAY, -1));
                    }
                },
                '->',
                {
                    iconCls : 'icon-next',
                    handler : function() {
                        g.setView(g.getStart().add(Date.DAY, 1), g.getEnd().add(Date.DAY, 1));
                    }
                }
            ],
            
            dndValidatorFn : function(dragRecords, targetResourceRecord, date, duration, e) {
                var eventEnd = date.add(Date.MINUTE, duration),
                    endAvailabilityDate = date.clone();
                endAvailabilityDate.clearTime().setHours(targetResourceRecord.get('EndAvailability') + 1);
                    
                return (date.getHours() >= targetResourceRecord.get('StartAvailability') && (endAvailabilityDate - eventEnd >= 0));
            },
                    
            listeners : {
                drop : function(p, records, copy, valid) {
                    var record = records[0];
                    
                    if (g.eventStore.indexOf(record) < 0) {
                        g.eventStore.add([record.copy()]);
                        record.store.remove(record);
                    }
                }
            },
                    
            plugins : [
                new Sch.plugins.Lines({
                    store : datelineStore
                })
            ],
            
            trackMouseOver : false
        });
        
        return g;
    }
};

function initializeTaskDragZone(g, ddGroup) {
    g.dragZone = new Ext.dd.DragZone(g.getEl(), {
        ddGroup : ddGroup,
        
//      On receipt of a mousedown event, see if it is within a draggable element.
//      Return a drag data object if so. The data object can contain arbitrary application
//      data, but it should also contain a DOM element in the ddel property to provide
//      a proxy to drag.
        getDragData: function(e) {
            var sourceEl = e.getTarget(),
                rec = g.store.getAt(g.view.findRowIndex(sourceEl));
            
            if (sourceEl && rec) {
                
                var d = sourceEl.cloneNode(true);
                d.id = Ext.id();
                
                var wrap = Ext.get(Ext.DomHelper.createDom({
                    tag: 'div',
                    style : {
                        width : '100px'
                    },
                    children: [
                        {
                            tag: 'span', 
                            cls: 'sch-dd-newtime',
                            html : '&nbsp;'
                        }
                    ]
                }));
                
                wrap.appendChild(d);
                Ext.fly(d).addClass('sch-event');
                Ext.fly(d).update(rec.get('Title'));
                
                return {
                    sourceEl: sourceEl,
                    repairXY: Ext.fly(sourceEl).getXY(),
                    duration : rec.get('Duration') * 60,
                    ddel: wrap.dom,
                    sourceEventRecord : rec,
                    records : [rec]
                };
            }
        },

//      Provide coordinates for the proxy to slide back to on failed drag.
//      This is the original XY coordinates of the draggable element.
        getRepairXY: function() {
            return this.dragData.repairXY;
        }
    });
}

function initializeStaffDragDrop(staffGrid, scheduler) {
    var dragZone = new Ext.dd.DragZone(staffGrid.getEl(), {
        
        getDragData: function(e) {
            var sourceEl = e.getTarget(),
                rowIndex = staffGrid.view.findRowIndex(sourceEl),
                rec = staffGrid.store.getAt(rowIndex);
            
            if (sourceEl && rec) {
                var d = sourceEl.cloneNode(true);
                d.id = Ext.id();
                
                var wrap = Ext.DomHelper.createDom({
                    tag: 'div',
                    cls : 'user',
                    children: [{
                        style: 'height:16px;width:60px',
                        tag: 'div',
                        html : rec.get('Name')
                    }]
                });
                 
                return {
                    repairXY: Ext.fly(sourceEl).getXY(),
                    ddel: wrap,
                    record : rec
                };
            }
        },

        getRepairXY: function() {
            return this.dragData.repairXY;
        }
    });
    
    var dropZone = new Ext.ux.GroupHeaderDropZone(scheduler.el, scheduler.store);
}

// A DropZone which cooperates with DragZones whose dragData contains
// a "field" property representing a form Field. Fields may be dropped onto
// grid data cells containing a matching data type.
Ext.ux.GroupHeaderDropZone = Ext.extend(Ext.dd.DropZone, {
    constructor : function(el, store) {
        this.store = store;
        Ext.ux.GroupHeaderDropZone.superclass.constructor.apply(this, arguments);
    },

    containerScroll: true,

    getTargetFromEvent: function(e) {
        var t = e.getTarget('.x-grid-group-hd');
        if (t) {
            var groupName = 'asf';
            if (groupName) {
                return {
                    node: t,
                    groupName: Ext.fly(t).child('.x-grid-group-title').dom.innerHTML
                };
            }
        }
    },

//  On Node enter, see if it is valid for us to drop the field on that type of column.
    onNodeEnter: function(target, dd, e, dragData) {
        delete this.dropOK;
        if (!target) {
            return;
        }

        this.dropOK = true;
        Ext.fly(target.node).addClass('x-drop-target-active');
    },

//  Return the class name to add to the drag proxy. This provides a visual indication
//  of drop allowed or not allowed.
    onNodeOver: function(target, dd, e, dragData) {
        return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
    },

//  Unhighlight the target node.
    onNodeOut: function(target, dd, e, dragData) {
        Ext.fly(target.node).removeClass('x-drop-target-active');
    },

//  Process the drop event if we have previously ascertained that a drop is OK.
    onNodeDrop: function(target, dd, e, dragData) {
        if (this.dropOK) {
            var rec = dragData.record,
                copy = new this.store.recordType(rec.data, rec.get('Id'));
            rec.store.remove(rec);    
            copy.set('Group', target.groupName);
            this.store.add(copy);
            
            // Force a re-sort the dataset
            this.store.groupBy(this.store.groupField, true);
            return true;
        }
    }
});
