/* Require: ave.js */

AVE.Table = Class.create({
	
	initialize: function(el, options) {
		this.options = Object.extend(Object.clone(AVE.Table.defaults), options || {});
		this.onRowClick = this.options.onRowClick;
		
		
		this.table   = $(el);
		this.wrapper = this.prepTable();
		
		if(this.onRowClick !== Prototype.emptyFunction) 
			this.table.select('tr').invoke('setStyle', {cursor: 'pointer'});
		
		if(this.wrapper.down("thead")) {
			this.wrapper.insertBefore(this.createHeader(),this.wrapper.firstChild);
			this.removeOriginalHeader();
	
			if(this.options.roundHeader)
				this.wrapper.down('.table-header').round('top');
		}
			
		if(this.wrapper.down('tfoot')) {
			this.wrapper.appendChild(this.createFooter());
			this.removeOriginalFooter();
			if(this.options.roundFooter)
				this.wrapper.down('.table-footer').round('bottom');
		}
		    
		if(this.table.readAttribute('scrollable'))
		    this.makeScrollable(this.table.readAttribute('scrollable'));
		
		
		this.hideLinks();
		this.stripe();
    
    
        // TODO: change these to new delegate method
		var objRef = this;
		this.table.delegate('mouseover mouseout', {
	        'tr' : function(e,el){ 
				objRef.toggleHover(el);
				e.stop();
	        }
	    });
	
		this.table.delegate('click', {
			'tr' : function(e,el){
				objRef.onRowClick(el);
				//e.stop();
			}
		});
	
		// if the table has an id we save a reference to locate it later if need be.
		if(el.id) AVE.DataTables.set(el.id, this);
		
		// sortable table requires data-table class
		if(this.table.hasClassName('sortable')){
		    this.table.removeClassName('sortable');
		    this.wrapper.addClassName('sortable');
            this.sortIndex = -1;
            this.sortOrder = 'asc';
            this.initDOMReferences();
            this.initEventHandlers();
            this.sortIndex = 0;
		}
	},
	
	toggleHover: function(el) {
		if(el.hasClassName('hover')) el.removeClassName('hover');
		else el.addClassName('hover');
	},
	
	prepTable: function() {
	    this.table.removeClassName('data-table').addClassName('wrapped-data-table');
		return this.table.wrap('div').addClassName('scroll-wrapper').wrap('div').addClassName('outer-scroll-wrapper').setStyle({'position':'relative'}).wrap('div').addClassName('table-wrapper');
	},
	
	createHeader: function() {
		var wrapper = new Element('table').addClassName('table-header').writeAttribute('width', '100%');
		
		wrapper.insert(new Element('thead'));
		wrapper.down('thead').insert(new Element('tr'));
        
		var headers = this.table.down('thead tr').childElements().pluck('innerHTML').collect(function(h, i){
		        var style = this.table.down('col', i).readAttribute('style').split(':')[1].strip().replace(/;/,"");
				var th = new Element('th');
				th.setStyle({'width' : style});
				
				var div = new Element('div');
				div.setStyle({'width' : style});
				
				var text = document.createTextNode(h);
				var span = new Element('span').update(h);
				
				div.insert(span);
				th.insert(div);
				
				return th;
			}.bind(this));
			
		headers.each(function(h){
			wrapper.down('tr').insert(h);
		});
		return wrapper;
	},
	
	createFooter: function() {
		var wrapper = new Element('div').addClassName('table-footer border');
		var table = new Element('table', {'width': '100%'});
		table.insert(new Element('tfoot'));
		table.down('tfoot').insert(new Element('tr'));

        var currentCol = 0;
        var width, span;
        
		var cells = this.table.down('tfoot tr').childElements().collect(function(tc){
		    width = 0;
		    // Make sure we can use colspans within the tfoot.
		    if(tc.readAttribute("colspan")){
		        span = tc.readAttribute("colspan") * 1; // ensure it's a number
		        width = $R(currentCol, (currentCol + span), true).inject(0, function(acc, n) {
		            var w = acc + (this.table.down('col', currentCol).readAttribute('style').split(':')[1].strip().replace(/[^0-9\.]/g,"") * 1); // ensure it's a numbers
		            currentCol++; // save what columns we've already counted
		            return w;
		        }.bind(this));
		    } else {
		        width = (this.table.down('col', currentCol).readAttribute('style').split(':')[1].strip().replace(/[^0-9\.]/g,"") * 1); // ensure it's a numbers
		        currentCol++;
		    }
		    
		    var td = new Element('td');
				td.setStyle({'width' : width + "em"});
				var div = new Element('div').addClassName('footer-pad').update(tc.innerHTML);
				td.insert(div);
				return td;
		}.bind(this));
		        
		cells.each(function(h){
			table.down('tr').insert(h);
		});
		
		wrapper.insert(table);
		return wrapper;
	},
	
	removeOriginalHeader: function() {
		this.table.down('thead').remove();
	},
	
	makeScrollable: function(visible) {
	
	    visible = (this.table.select('tr').size() > visible) ? visible - 1 : this.table.select('tr').size() - 1;
	    var visibleHeight = $R(0,visible).inject(0, function(acc, n) { return acc + this.table.select('tr')[n].getHeight(); }.bind(this));
	    var totalHeight = this.table.select('tr').inject(0, function(acc, n) { return acc + n.getHeight(); });
	    
	    this.scrollWrapper = this.wrapper.down('.scroll-wrapper');
	    this.scrollWrapper.setStyle({height: visibleHeight + 'px', overflow: 'hidden'});
	    
	    this.scrollBarWrapper = new Element('div');
	   // need to add 1px to the width of the scrollbar, or IE won't scroll the content.
	    this.scrollBarWrapper.setStyle({width: AVE.WindowProperties.getScrollbarWidth() + 1 + 'px', height: visibleHeight + 'px', position: 'absolute', right: '0', top: '0', overflow: 'auto' });
	        
	    var scrollBar = new Element('div');
	        scrollBar.setStyle({width: '1px', height: totalHeight + 'px'});
	    
	    this.scrollBarWrapper.insert(scrollBar);
	    this.wrapper.down('.outer-scroll-wrapper').insert(this.scrollBarWrapper);
	    
	    if(Prototype.Browser.IE)
	        scrollBar.redraw(); // Make scrollbar show up in IE7
	        
	    if(/MSIE 6/i.test(navigator.userAgent))
	        this.scrollBarWrapper.setStyle({right: AVE.WindowProperties.getScrollbarWidth() + 'px'}); // Position scrollbar in IE6, Browser detection painfully necessary.
	    
	    this.scrollBarWrapper.scrollTop = this.scrollWrapper.scrollTop = 0;
	    this.scrollBarWrapper.observe('scroll', this._monitorScroll.bind(this));
	    Event.observe(this.wrapper.down('.outer-scroll-wrapper'), "mousewheel", this._monitorScrollWheel.bind(this), false);
        Event.observe(this.wrapper.down('.outer-scroll-wrapper'), "DOMMouseScroll", this._monitorScrollWheel.bind(this), false); // Firefox
	},
	
	_monitorScroll: function() {
	    this.scrollWrapper.scrollTop = this.scrollBarWrapper.scrollTop;
	},
	
	_monitorScrollWheel: function(e) {
	    	this.scrollWrapper.scrollTop = this.scrollBarWrapper.scrollTop = (Event.wheel(e) < 0 ) 
	        ? this.scrollWrapper.scrollTop + Math.exp(Event.wheel(e) * -1)
	        : this.scrollWrapper.scrollTop - Math.exp(Event.wheel(e));
	    e.stop();
	},
	
	removeOriginalFooter: function() {
		this.table.down('tfoot').remove();
	},
	
	stripe: function() {
		if(this.table.down('tbody')) {
			this.table.select('tbody')
			    .select(function(t) { return t.visible(); })
			    .collect(function(t) { return t.select('tr'); })
			    .flatten()
			    .each(function(tr,c){
				    if(c % 2 == 0) tr.addClassName('odd');
			    });
		}
	},
	
	restripe: function() {
		if(this.table.down('tbody')) {
			this.table.select('tbody')
			    .select(function(t) { return t.visible(); })
			    .collect(function(t) { return t.select('tr'); })
			    .flatten()
			    .invoke('removeClassName', 'odd');
			this.stripe();
		}
	},
	
	hideLinks: function() {
		this.table.select('.table-link').each(function(link) {
			var atts = {};
			atts.avebehavior = link.readAttribute('avebehavior') || 'link';
			atts.aveoptions  = link.readAttribute('aveoptions');
			atts.avedialogclass = link.readAttribute('avedialogclass');
			atts.href		 = link.readAttribute('href');
			
			var tr = link.up('tr');
			tr.writeAttribute(atts); 
			
			tr.setStyle({cursor: 'pointer'});			
			link.hide();
		});
	},
	
	initDOMReferences: function() {
        var head = this.wrapper.down('thead');
        var body = this.wrapper.down('tbody');
		if (!head || !body) throw 'Table must have a head and a body to be sortable.';
        this.headers = head.down('tr').childElements(); 
        this.headers.each(function(e, i) { 
            e._colIndex = i;
        });
		this.body = body;
		$$('.sortable th').invoke('setStyle', 'cursor', 'pointer');
		head.down('th').addClassName('sort-asc');
    }, 
    
    initEventHandlers: function() {
        this.handler = this.handleHeaderClick.bind(this); 
        this.wrapper.observe('click', this.handler);
    },
    
    handleHeaderClick: function(e) {
        var element = e.element();
        if (!('_colIndex' in element)) {
            element = element.ancestors().find(function(elt) { 
                return '_colIndex' in elt;
            });
            if (!((element) && '_colIndex' in element))
                return;
        }
        this.sort(element._colIndex);
    },
    
    adjustSortMarkers: function(index) {
        if (this.sortIndex != -1)
            this.headers[this.sortIndex].removeClassName('sort-' + this.sortOrder);
        if (this.sortIndex != index) {
            this.sortOrder = 'asc';
            this.sortIndex = index;
        } else
            this.sortOrder = ('asc' == this.sortOrder ? 'desc' : 'asc');
		    this.headers[index].addClassName('sort-' + this.sortOrder);
		},
		
        sort: function(index) {
            this.adjustSortMarkers(index);
		    var rows = this.body.childElements();
		    rows = rows.sortBy(function(row) {
		        node = row.childElements()[this.sortIndex].collectTextNodes();
		        if( node.toLowerCase().startsWith('n/a') ){
		            node = node.gsub('N/A', '100000.00'); //to bring all 'N/A' cells to bottom
		        }
		        if( node.startsWith('$') ){
		            node = eval(node.sub(/^\$/,""));
		        }
		        
		        if(node){
                    return node; 
                }
		    }.bind(this));
		    if ('desc' == this.sortOrder)
		        rows.reverse();
		    rows.reverse().each(function(row, index) {
		        if (index > 0) this.body.insertBefore(row, rows[index - 1]);
		    }.bind(this));
		    rows.reverse().each(function(row, index) {
		        row[(1 != index % 2 ? 'add' : 'remove') + 'ClassName']('odd'); 
		    });
		}
});

/*======================================================*/
/*= Table subclass for striping based on row visbility =*/
/*======================================================*/
AVE.TableStripeVisible = Class.create(AVE.Table, {
    initialize: function($super, el, options) {
        $super(el, options);
        if(el) AVE.DataTables.set(el, this);
    },
    stripe: function() {
        if(this.table.down('tbody')) {
	        this.table.select('tbody')
	            .select(function(t) { return t.visible(); })
	            .collect(function(t) {
	                var trs = t.select('tr');
	                trs = trs.select(function(tr){
	                    if(tr.visible()) return tr;
	                });
	                return trs; 
	             })
	            .flatten()
	            .each(function(tr,c){
		            if(c % 2 == 0) tr.addClassName('odd');
	            });
        }
    }
});
AVE.Table.defaults = {
	onRowClick: Prototype.emptyFunction,
	roundHeader: true,
	roundFooter: true
};

AVE.DataTables = new Hash();

document.observe('dom:loaded', function() {
	$$('.data-table').each(function(table){
		new AVE.Table(table);
	});
	$$('.table-preface').invoke("round", "top");
});