/*
 * Ext Scheduler v1.0 beta
 * Copyright(c) 2009-2010 Mats Bryntse Consulting
 * mats@ext-scheduler.com
 * http://www.ext-scheduler.com/license.html
 *
 */
 
Ext.ns('Sch');


Sch.Dependency = {
    StartToStart : 0,
    StartToEnd : 1,
    EndToStart : 2,
    EndToEnd : 3
};


/**
 * @class Sch.DependencyManager
 * @extends Ext.util.Observable
 * <p>Internal class handling the dependency related functionality.</p>
 */
Sch.DependencyManager = Ext.extend(Ext.util.Observable, {
    
    /**
     * @cfg {Int} cascadeDelay If you usually have deeply nested dependencies, it might be a good idea to add a small delay
     *            to allow the modified record to be refreshed in the UI right away and then handle the cascading
     */
    cascadeDelay : 10,
    
    /**
     * Highlight the elements representing a particular dependency
     * @param {String} id the id of the record in the dependency store
     */
    highlightDependency : function(id) {
        this.getElementsForDependency(this.store.getById(id)).addClass('sch-dependency-selected');
    },
    
    /**
     * Remove highlight of the elements representing a particular dependency
     * @param {String} id the id of the record in the dependency store
     */
    unhighlightDependency : function(id) {
        this.getElementsForDependency(this.store.getById(id)).removeClass('sch-dependency-selected');
    },
    
    /**
     * Retrieve the elements representing a particular dependency
     * @param {Record} rec the record in the dependency store
     * @return {CompositeElementLite/CompositeElement}
     */
    getElementsForDependency : function(rec) {
        var id = rec instanceof Ext.data.Record ? rec.id : rec;
        return this.containerEl.select('.sch-dep-' + id);
    },
    
    
    // private
    constructor : function (g, cfg) {
        cfg = cfg || {};
        Ext.apply(this, cfg);
        
        this.grid = g;
        this.eventStore = g.store;
        this.eventStore.on('add', this.renderDependencies, this);
        this.eventStore.on('update', this.onEventUpdated, this);
        this.eventStore.on('remove', this.onEventDeleted, this);
        this.eventStore.on('beforewrite', this.onEventStoreBeforeWrite, this);
        this.eventStore.on('write', this.onEventStoreWrite, this);
        
        this.store.on('datachanged', this.renderDependencies, this);
        this.store.on('add', this.onDependencyAdd, this);
        this.store.on('update', this.onDependencyUpdate, this);
        this.store.on('remove', this.onDependencyDelete, this);
        this.store.on('beforewrite', this.onBeforeWrite, this);
        this.store.on('write', this.onWrite, this);
        this.painter = new Sch.DependencyPainter(g, cfg);
        
        if (this.enableDependencyDragDrop !== false) {
            this.dnd = new Sch.DependencyDragDrop(g);
            this.dnd.on('afterdnd', this.onArrowDrop, this);
        }
    },
    
    // private
    depRe : new RegExp('sch-dep-([^\\s]+)'),
    
    // private
    getRecordForDependencyEl : function(t) {
        var m = t.className.match(this.depRe),
            rec = null;
        
        if(m && m[1]) {
            var recordId = m[1];
            
            rec = this.store.getById(recordId);
        }
        
        return rec;
    },
    
    
    renderDependencies : function() {
        this.containerEl.select('.sch-dependency').remove();
        
        if (this.eventStore.getCount() === 0) return;
        
        var dep, 
            l = this.store.getCount();
        
        for (var i = 0; i < l; i++) {
            dep = this.store.getAt(i);
            this.painter.drawSingleDependency(dep);
  	    }
    },
    
    renderEventDependencies : function(id) {
        var dep,
            l = this.store.getCount();
        
        for (var i = 0; i < l; i++) {
            dep = this.store.getAt(i);
            var toEventId = dep.get('To'),
                fromEventId = dep.get('From');
            
            if (id == toEventId || id == fromEventId) {
                this.painter.drawSingleDependency(dep);
            }
  	    }
    },
    
    onEventStoreBeforeWrite : function(s, action, records) {
        // Normalize
        if (!Ext.isArray(records)) {
            records = [records];
        }
        
        if (action === Ext.data.Api.actions.create) {
            Ext.each(records, function(r) {
                // HACK, save the phantom id to be able to replace the task phantom task id's in the dependency store
                r._phantomId = r.id;
            });
        }
    },
    
    onEventStoreWrite : function(s, action, result, t, records) {
        if (!Ext.isArray(records)) {
            records = [records];
        }
        var from, to;
        
        if (action === Ext.data.Api.actions.create) {
            Ext.each(records, function(r) {
                this.store.queryBy(function(dep) {
                    from = dep.get('From');
                    to = dep.get('To');
                    
                    if (from === r._phantomId) {
                        dep.set('From', r.id);
                    } else if (to === r._phantomId){
                        dep.set('To', r.id);
                    }
                });
            }, this);
        }
    },
    
    onDependencyUpdate : function(store, depRecord) {
       if (depRecord._phantomId) {
            // Delete lines drawn for any phantom record.
            this.getElementsForDependency(depRecord._phantomId).remove();
            delete depRecord._phantomId;
        }
        
       // Draw new dependencies for the event
       this.painter.drawSingleDependency(depRecord);
    },
    
    onDependencyAdd : function(store, depRecords) {
        // Draw new dependencies for the event
        this.painter.drawSingleDependency(depRecords[0]);
    },
    
    onDependencyDelete : function(store, depRecord) {
       this.getElementsForDependency(depRecord).fadeOut({ remove : true });
    },
    
    dimEventDependencies : function(eventId) {
        this.containerEl.select(this.depRe + eventId).setOpacity(0.2);
    },
    
    onBeforeWrite : function(s, action, records) {
        // Normalize
        if (!Ext.isArray(records)) {
            records = [records];
        }
        // Delete lines drawn for the phantom dependency record. After the create operation is finished the lines will be redrawn.
        if (action === Ext.data.Api.actions.create) {
            Ext.each(records, function(r) {
                // HACK, save the phantom id to be able to remove the dom elements associated with the phantom record
                r._phantomId = r.id;
            }, this);
        }
    },
    
    onWrite : function(s, action, result, t, records) {
        // Normalize
        if (!Ext.isArray(records)) {
            records = [records];
        }
    },
    
    onEventUpdated : function(store, record, operation, hashPrevious) {
        this.updateDependencies(record);
        
        var id = record.id;
        
        if (this.grid.cascadeChanges && hashPrevious) {
            (function(rec, changes, modified) {
                this.eventStore.un('update', this.onEventUpdated, this);
                this.store.queryBy(function(dep) {
                    if (dep.get('From') == id) {
                        var type = dep.get('Type'),
                            startDate = rec.get('StartDate'),
                            endDate = rec.get('EndDate'),
                            startMin = Date.getDurationInMinutes(hashPrevious.StartDate || startDate, startDate),
                            endMin = Date.getDurationInMinutes(hashPrevious.EndDate || endDate, endDate);
                        
                        if (type === Sch.Dependency.StartToStart || 
                            type === Sch.Dependency.StartToEnd)  {
                            this.performCascade(store.getById(dep.get('To')), startMin, modified);
                        } else {
                            this.performCascade(store.getById(dep.get('To')), endMin, modified);
                        }
                    }
                }, this);
                this.eventStore.on('update', this.onEventUpdated, this);
            }).defer(this.cascadeDelay, this, [record, hashPrevious, {}]);
        }
    },
    
    updateDependencies : function (record) {
        var eventId = record.id;
        
        // First remove old dependency dom elements
        this.store.queryBy(function(r) {
            if (r.get('From') == eventId || r.get('To') == eventId) {
                this.containerEl.select('.sch-dep-' + r.id).remove();
            }
        }, this);
        
        // Draw new dependencies for the event
        this.renderEventDependencies(eventId);
    },
    
    onEventDeleted : function(store, record) {
        var eventId = record.id,
            toRemove = [];
        
        this.store.queryBy(function(r) {
            if (r.data.To == eventId || r.data.From == eventId) {
                toRemove.push(r);
            }
        });
        
        this.store.suspendEvents();
        this.store.remove(toRemove);
        this.store.resumeEvents();
        
        // Redraw all dependencies
        this.renderDependencies();
    },
    
    onArrowDrop : function(plugin, fromId, toId, type) {
        if (fromId === toId) return;
        
        var depRec = new this.store.recordType({
            From : fromId,
            To : toId,
            Type : type
        });
        
        this.store.add(depRec);
    },
    
    getDependenciesForTask : function(record) {
        var taskId = record.id;
        
        return this.store.queryBy(function(r) {
            return r.get('From') == taskId || r.get('To') == taskId;
        });
    },
    
    deleteDependency : function(id) {
        this.store.remove(this.store.getById(id));
    },
    
    performCascade : function(record, minutes, modified) {
        var oldStart = record.get('StartDate'),
            oldEnd = record.get('EndDate'),
            id = record.id;
        
        if (!(id in modified)) {
            modified[id] = null;
            record.set('StartDate', oldStart.add(Date.MINUTE, minutes));
            record.set('EndDate', oldEnd.add(Date.MINUTE, minutes));
            this.updateDependencies(record);
        }
        
        this.store.queryBy(function(dep) {
            if (dep.get('From') == id) {
                this.performCascade(record.store.getById(dep.get('To')), minutes, modified);
            }
        }, this);
    }
});
