// LiveGridMetaData -----------------------------------------------------
//version 1.1 -- 2005-07-27
LiveGridMetaData = Class.create();

LiveGridMetaData.prototype = {

   initialize: function( pageSize, totalRows, options ) {
      this.pageSize  = pageSize;
      this.totalRows = totalRows;
      this.setOptions(options);
      this.scrollArrowHeight = 16;
   },

   setOptions: function(options) {
      this.options = {
         largeBufferSize    : 7.0,   // 7 pages
         smallBufferSize    : 1.0,   // 1 page
         nearLimitFactor    : 0.2    // 20% of buffer
      }
      Object.extend(this.options, options || {});
   },

   getPageSize: function() {
      return this.pageSize;
   },

   getTotalRows: function() {
      return this.totalRows;
   },

   setTotalRows: function(n) {
      this.totalRows = n;
   },

   getLargeBufferSize: function() {
      return parseInt(this.options.largeBufferSize * this.pageSize);
   },

   getSmallBufferSize: function() {
      return parseInt(this.options.smallBufferSize * this.pageSize);
   },

   getLimitTolerance: function() {
      return parseInt(this.getLargeBufferSize() * this.options.nearLimitFactor);
   },

	getBufferSize: function(isFull) {
		return isFull ? this.getLargeBufferSize() : this.getSmallBufferSize();
	}
};

// LiveGridScroller -----------------------------------------------------

LiveGridScroller = Class.create();

LiveGridScroller.prototype = {

   initialize: function(liveGrid) {
      this.isIE = navigator.userAgent.toLowerCase().indexOf("msie") >= 0;
      this.liveGrid = liveGrid;
      this.metaData = liveGrid.metaData;
      this.createScrollBar();
      this.scrollTimeout = null;
      //this.sizeIEHeaderHack();
      this.lastScrollPos = 0;
   },

   isUnPlugged: function() {
      return this.scrollerDiv.onscroll == null;
   },

   plugin: function() {
      this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
   },

   unplug: function() {
      this.scrollerDiv.onscroll = null;
   },

   sizeIEHeaderHack: function() {
      if ( !this.isIE ) return;
      var headerTable = $(this.liveGrid.tableId + "_header");
      if ( headerTable )
         headerTable.rows[0].cells[0].style.width =
            (headerTable.rows[0].cells[0].offsetWidth + 1) + "px";
   },

   createScrollBar: function() {
      var table         = this.liveGrid.table;
      var visibleHeight = table.offsetHeight;
      this.lineHeight   =  visibleHeight/this.metaData.getPageSize();
      
      // create the outer div...
      this.scrollerDiv = document.createElement("div");
      var scrollerStyle = this.scrollerDiv.style;
      scrollerStyle.borderRight = "1px solid #ababab"; // hard coded color!!!
      scrollerStyle.position    = "relative";
      scrollerStyle.left        = this.isIE ? "-6px" : "-3px";
      scrollerStyle.width       = "19px";
      scrollerStyle.height      = visibleHeight + "px";
      scrollerStyle.overflow    = "auto";
      if (this.isIE) {
        table.onmousewheel =
            function(evt) {
                if (event.wheelDelta>=0) //wheel-up
                    this.scrollerDiv.scrollTop -= this.lineHeight;
                else
                    this.scrollerDiv.scrollTop += this.lineHeight;
                this.handleScroll(true);
            }.bind(this);
      } else {
        table.addEventListener("DOMMouseScroll",
            function(evt) {
                if (evt.detail < 0) //wheel-up
                    this.scrollerDiv.scrollTop -= this.lineHeight;
                else
                    this.scrollerDiv.scrollTop += this.lineHeight;
                this.handleScroll(true);
            }.bind(this),
            true);
      }

      // create the inner div...
      this.heightDiv = document.createElement("div");
      this.heightDiv.style.width  = "1px";
      this.heightDiv.style.height = parseInt(visibleHeight *
                        this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px" ;

      this.scrollerDiv.appendChild(this.heightDiv);
      this.scrollerDiv.onscroll = this.handleScroll.bindAsEventListener(this);
      table.parentNode.insertBefore( this.scrollerDiv, table.nextSibling );

   },
   
   updateSize: function() {
      var table = this.liveGrid.table;
      var visibleHeight = table.offsetHeight;
      this.heightDiv.style.height = parseInt(visibleHeight *
                                  this.metaData.getTotalRows()/this.metaData.getPageSize()) + "px";
   },

   adjustScrollTop: function() {
      this.unplug();
      var rem = this.scrollerDiv.scrollTop % this.lineHeight;
      if (rem != 0) {
         if (this.lastScrollPos < this.scrollerDiv.scrollTop)
            this.scrollerDiv.scrollTop = this.scrollerDiv.scrollTop + this.lineHeight -rem;
         else
            this.scrollerDiv.scrollTop = this.scrollerDiv.scrollTop - rem;
      }
      this.lastScrollPos = this.scrollerDiv.scrollTop;
      this.plugin();
   },

   moveScroll: function(rowOffset) {
	   var pixelOffset = (rowOffset / this.metaData.getTotalRows()) * this.heightDiv.offsetHeight;
      this.scrollerDiv.scrollTop = pixelOffset;
   },

   handleScroll: function(skiptimeout) {
      if ( this.scrollTimeout )
         clearTimeout( this.scrollTimeout );
      
	   var contentOffset = parseInt(this.scrollerDiv.scrollTop *
                                   this.metaData.getTotalRows() / this.heightDiv.offsetHeight);
      if ( this.metaData.options.onscroll )
         this.metaData.options.onscroll( contentOffset, this.metaData );

      if (skiptimeout == true)
        this.scrollIdle();
      else
        this.scrollTimeout = setTimeout( this.scrollIdle.bind(this), 100 );
   },

   scrollIdle: function() {
      if ( this.scrollTimeout )
         clearTimeout( this.scrollTimeout );

      // this.adjustScrollTop();
	   var contentOffset = parseInt(this.scrollerDiv.scrollTop *
                                   this.metaData.getTotalRows() / this.heightDiv.offsetHeight);
      this.liveGrid.requestContentRefresh(contentOffset);

      if ( this.metaData.options.onscrollidle )
         this.metaData.options.onscrollidle();
   }
};

// LiveGridBuffer -----------------------------------------------------

LiveGridBuffer = Class.create();

LiveGridBuffer.prototype = {

   initialize: function(metaData) {
      this.startPos = 0;
      this.size     = 0;
      this.metaData = metaData;
      this.rows     = new Array();
      this.updateInProgress = false;
   },

   update: function(ajaxResponse,start) {

      this.startPos = parseInt(start);
      this.rows = ajaxResponse.responseText;
      try{ 
        //first try the most reliable way
        this.size = 
            ajaxResponse.responseXML.documentElement ?
            parseInt(ajaxResponse.responseXML.documentElement.getAttribute('rowcount')):
            parseInt(ajaxResponse.responseXML.childNodes[0].getAttribute('rowcount'));
      }
      catch(err){}
      //alternative way
      if (!this.size) {
        if (ajaxResponse.responseXML.childNodes[0].getElementsByTagName)
            this.size = ajaxResponse.responseXML.childNodes[0].getElementsByTagName('*').length;
        else if(ajaxResponse.responseXML.documentElement)
            this.size = ajaxResponse.responseXML.documentElement.childNodes.length;
        else
            this.size = ajaxResponse.responseXML.childNodes[0].childNodes.length;
      }
   },

	isFullP: function() {
	   return this.metaData.pageSize != this.size;
	},

	isClose: function(start) {
		return (start < this.startPos + this.size + (this.size/2)) &&
             (start + this.size + (this.size/2) > this.startPos)
	},

   isInRange: function(start, count) {
      return (start < this.startPos + this.size) && (start + count > this.startPos)
   },

   isFullyInRange: function(position) {
      return (position >= this.startPos) && (position+this.metaData.getPageSize()) <= (this.startPos + this.size)
   },

   isNearingTopLimit: function(position) {
      return position - this.startPos < this.metaData.getLimitTolerance();
   },

   isNearingBottomLimit: function(position) {
      var myEnd     = position + this.metaData.getPageSize();
      var bufferEnd = this.startPos + this.size;
      return bufferEnd - myEnd < this.metaData.getLimitTolerance();
   },

   isAtTop: function() {
      return this.startPos == 0;
   },

   isAtBottom: function() {
      return this.startPos + this.size == this.metaData.getTotalRows();
   },

   isNearingLimit: function(position) {
      return ( !this.isAtTop()    && this.isNearingTopLimit(position)) ||
             ( !this.isAtBottom() && this.isNearingBottomLimit(position) )
   },

   getRows: function(start, count) {
      var begPos = parseInt(start) - this.startPos
      var endPos = begPos + parseInt(count)
      var srch = '(.*?)(<(.*?)rownum="'+begPos+'"((\n|.)*?)';
      // er? need more data...
      if ( endPos >= this.size )
      {
         endPos = this.size;
         srch = srch + ')</ajax-response>'
      }
      else
        srch = srch + ')<(.*?)rownum="'+(endPos)+'"';
      try {return this.rows.match(srch)[2];}
      catch(err){return '';}
   },

   convertSpaces: function(s) {
      return s.split(" ").join("&nbsp;");
   }

};

LiveGridRequest = Class.create();
LiveGridRequest.prototype = {
   initialize: function( requestOffset, options ) {
		this.requestOffset = requestOffset;
   }
};

// LiveGrid -----------------------------------------------------

LiveGrid = Class.create();

LiveGrid.prototype = {

   initialize: function( tableId, visibleRows, totalRows, url, options ) {

      if ( options == null )
         options = {};
         
      this.options = options;
         
      this.tableId     = tableId;
      this.table       = $(tableId);
      this.metaData    = new LiveGridMetaData(visibleRows, totalRows, options);
      this.buffer      = new LiveGridBuffer(this.metaData);

      this.lastDisplayedStartPos = -1;
      this.timeoutHander         = null;
      this.additionalParms       = options.requestParameters || '';

      this.processingRequest = null;
      this.unprocessedRequest = null;

      this.url = url;
      
      if ( options.prefetchBuffer || options.prefetchOffset) {
         var offset = 0;
         if (options.prefetchOffset) {
            this.scroller.moveScroll(options.prefetchOffset);
            offset = options.prefetchOffset;
         }
         this.fetchBuffer(offset, true);
      }
      else
      {
        this.scroller    = new LiveGridScroller(this);
      }
   },

   setRequestParams: function(params) {
      this.additionalParms = params;
   },

   setTotalRows: function( newTotalRows ) {
      this.metaData.setTotalRows(newTotalRows);
      this.scroller.updateSize();
   },

   largeBufferWindowStart: function(offset) {
      val = offset - ( (.5 * this.metaData.getLargeBufferSize()) - (.5 * this.metaData.getPageSize()) )
      return Math.max(parseInt(val), 0);
   },

   handleTimedOut: function() {
      //server did not respond in n seconds... assume that there could have been
      //an error or something, and allow requests to be processed again...
      this.processingRequest = null;
      this.processQueuedRequest();
   },

   fetchBuffer: function(offset, fullBufferp) {
      //@JSD_LOG "fetchBuffer2";
      if (this.processingRequest) {
   	    this.unprocessedRequest = new LiveGridRequest(offset);
        return;
      }
	
      var fetchSize = this.metaData.getBufferSize(fullBufferp);
      bufferStartPos = Math.max(0,fullBufferp ? this.largeBufferWindowStart(offset) : offset);

      this.processingRequest = new LiveGridRequest(offset);
      this.processingRequest.bufferOffset = bufferStartPos;

      var callParms = 'id='        + this.tableId +
                      '&page_size=' + fetchSize + 
                      '&offset='    + bufferStartPos;

      if (this.additionalParms.length)
         callParms = callParms + '&' + this.additionalParms;
      if (!this.ajaxRequest)
      { 
        var options = {parameters: callParms,
            method: 'get'
        };       
        Object.extend(options,this.options);
        options.onComplete = this.ajaxUpdate.bind(this); 
        this.ajaxRequest = new Ajax.Request(this.url, options);
      }
      else 
      {
        Object.extend(this.ajaxRequest.options,{parameters: callParms});
        this.ajaxRequest.request(this.url);
      }
      this.timeoutHandler = setTimeout( this.handleTimedOut.bind(this), 10000 ); //todo: make as option
   },

   requestContentRefresh: function(contentOffset) {
      if ( this.buffer && this.buffer.isFullyInRange(contentOffset) ) {
         this.updateContent(contentOffset);
         if (this.buffer.isNearingLimit(contentOffset))
            this.fetchBuffer(contentOffset, true);
      }
      else if (this.buffer && this.buffer.isClose(contentOffset))
         this.fetchBuffer(contentOffset, true);
      else
	      this.fetchBuffer(contentOffset, false);
   },

   ajaxUpdate: function(ajaxResponse) {
      //try {
         clearTimeout( this.timeoutHandler );
         try
         {
           var totalrows =
            ajaxResponse.responseXML.documentElement ? 
              ajaxResponse.responseXML.documentElement.getAttribute("totalrows"):
              ajaxResponse.responseXML.childNodes[0].getAttribute("totalrows"); //huh?? MSIE thinks <? xml ?> decleration is a node also :S
           if (totalrows)
            this.setTotalRows(totalrows);
         }
         catch(err){}
         this.buffer = new LiveGridBuffer(this.metaData);
         this.buffer.update(ajaxResponse,this.processingRequest.bufferOffset);
         if (this.unprocessedRequest == null) {
            offset = this.processingRequest.requestOffset;
            this.updateContent (offset);
         }
         this.processingRequest = null;
      //}
      //catch(err) {
      //}
      if (!this.scroller)
      {
        this.scroller    = new LiveGridScroller(this);
        if (this.options.onFirstContent)
            this.options.onFirstContent(this);
      }      
      if (this.options.onComplete)
        this.options.onComplete(this);
      this.processQueuedRequest();
   },

   processQueuedRequest: function() {
   	if (this.unprocessedRequest != null) {
   	   this.requestContentRefresh(this.unprocessedRequest.requestOffset);
   	   this.unprocessedRequest = null
   	}	
   },

   updateContent: function( offset ) {
      this.replaceCellContents(this.buffer, offset);
   },

   replaceCellContents: function(buffer, startPos) {
      if (startPos == this.lastDisplayedStartPos){
         return;
      }
      this.lastDisplayedStartPos = startPos
      this.table.innerHTML = buffer.getRows(startPos, this.metaData.getPageSize());
   }
};
