/**********************************************************************************************
 * JAFFA - Java Application Framework For All - Copyright (C) 2008 JAFFA Development Group
 *
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License (version 2.1 any later).
 *
 * See http://jaffa.sourceforge.net/site/legal.html for more details.
 *********************************************************************************************/

/** Based on Original Work found at http://extjs.com/forum/showthread.php?p=203828#post203828
 *
 * @author chander, PaulE
 */
Ext.namespace("Ext.ux.grid");

Ext.ux.grid.MultiGroupingStore = Ext.extend(Ext.data.GroupingStore, {

    constructor: function(config){
        Ext.ux.grid.MultiGroupingStore.superclass.constructor.apply(this, arguments);
    }

   /* ----------------------------------------------------------------------
      Below is needed for Multi Grouping
      ---------------------------------------------------------------------- */
    /**
    * @cfg {Object} sortInfo A config object in the format: [{field: "fieldName", direction: "ASC|DESC"}].  The direction
    * property is case-sensitive.
    *     
    * This Override Ext.data.Store as this is now an array    
    */
   ,sortInfo: []
    
    /**
     * @param field If a single field is passed in it is appended to the grouped 
     *              fields (it is ignored if its already in the list).
     *              If an array is passed, we replace the current list of group
     *              fields with this new list.
     *              If field is empty/null it has the same effect as clearGrouping().
     * @param forceRegroup If true it forces the regroup logic even if there were
     *                     no changes to the groupField list.
     *
     * Overrides Ext.data.GroupingStore                               
     */           
   ,groupBy: function(field, forceRegroup){
      //alert("groupBy   " + field + "   " + forceRegroup);
      if (!forceRegroup && this.groupField == field) {
          return; // already grouped by this field
      }
      
      //if field passed in is an array, assume this is a complete replacement for the 'groupField'
      if(Ext.isArray(field)) {
        if(field.length==0)
          // @todo: field passed in is empty/null, assume this means group by nothing, ie remove all groups
          this.groupField=false;
        else
          this.groupField=field;
      } else {
        // Add the field passed as as an additional group
        if (this.groupField) {
          // If there is already some grouping, make sure this field is not already in here
          if(this.groupField.indexOf(field)==-1)
            this.groupField.push(field);
          else
            return; // Already grouped by this field  
        } else 
          // If there is no grouping already use this field
          this.groupField = [field];
      }
      if (this.remoteGroup) {
          if (!this.baseParams) {
              this.baseParams = {};
          }
          this.baseParams['groupBy'] = this.groupField;
      }
      //console.debug("store.groupBy: data=",this.lastOptions);
      if(this.lastOptions!=null) { // do nothing if the store has never been loaded
        if (this.groupOnSort) {
            this.sort(field);
            return;
        }
        if (this.remoteGroup) {
            this.reload();
        }
        else {
            var si = this.sortInfo || [];
            if (si.field != field) {
                this.applySort();
            }
            else {
              //  alert(field);
                this.sortData(field);
            }
            this.fireEvent('datachanged', this);
        }
      }
    }

    /** private - Overrides Ext.data.GroupingStore
     * Initially sort based on sortInfo (if set and not remote)
     * Then resort based on groupFields (if set and not remote)
     */      
   ,applySort: function(){
        var si = this.sortInfo;
        if (si && si.length > 0 && !this.remoteSort) {
            this.sortData(si, si[0].direction);
        }
        if (!this.groupOnSort && !this.remoteGroup) {
            var gs = this.getGroupState();
            if (gs && gs != this.sortInfo) {
                this.sortData(this.groupField);
            }
        }
    }
    
    /** private - Overrides Ext.data.Store
     * @param flist is an array of fields to sort by
     */     
   ,sortData: function(flist, direction) {
      //console.debug('Store.sortData: ',flist, direction);
      direction = direction || 'ASC';
      var st = [];
      var o;
      for (var i = 0, len = flist.length; i < len; ++i) {
        o = flist[i];
        st.push(this.fields.get(o.field ? o.field : o).sortType);
      }
      var fn = function(r1, r2){
        var v1 = [];
        var v2 = [];
        var len = flist.length;
        var o;
        var name;
        
        for (var i = 0; i < len; ++i) {
          o = flist[i];
          name = o.field ? o.field : o;
          v1.push(st[i](r1.data[name]));
          v2.push(st[i](r2.data[name]));
        }
        
        var result;
        for (var i = 0; i < len; ++i) {
          result = v1[i] > v2[i] ? 1 : (v1[i] < v2[i] ? -1 : 0);
          if (result != 0) 
            return result;
        }
        
        return result; //if it gets here, that means all fields are equal
      };
      
      this.data.sort(direction, fn);
      if (this.snapshot && this.snapshot != this.data) {
          this.snapshot.sort(direction, fn);
      }
    }

    /**
     * Sort the Records. Overrides Ext.data.store
     * If remote sorting is used, the sort is performed on the server, and the cache is
     * reloaded. If local sorting is used, the cache is sorted internally.
     * @param {String} field This is either a single field (String) or an array of fields [<String>] to sort by
     * @param {String} dir (optional) The sort order, "ASC" or "DESC" (case-sensitive, defaults to "ASC")
     */
   ,sort: function(field, dir){
      //console.debug('Store.sort: ',field,dir);
      var f = [];
      if (Ext.isArray(field)) {
          for (var i = 0, len = field.length; i < len; ++i) {
              f.push(this.fields.get(field[i]));
          }
      } else {
          f.push(this.fields.get(field));
      }
      
      if (f.length < 1) {
          return false;
      }
      
      if (!dir) {
          if (this.sortInfo && this.sortInfo.length > 0 && this.sortInfo[0].field == f[0].name) { // toggle sort dir
              dir = (this.sortToggle[f[0].name] || "ASC").toggle("ASC", "DESC");
          } else {
              dir = f[0].sortDir;
          }
      }
      
      var st = (this.sortToggle) ? this.sortToggle[f[0].name] : null;
      var si = (this.sortInfo) ? this.sortInfo : null;
      
      this.sortToggle[f[0].name] = dir;
      this.sortInfo = [];
      for (var i = 0, len = f.length; i < len; ++i) {
          this.sortInfo.push({
              field: f[i].name,
              direction: dir
          });
      }

      //console.debug("store.sort: data=",this.lastOptions);
      if(this.lastOptions!=null) { // do nothing if the store has never been loaded
        if (!this.remoteSort) {
            this.applySort();
            this.fireEvent("datachanged", this);
        } else {
            this.nextKey = null;
            if (!this.reload()) {
                if (st) {
                    this.sortToggle[f[0].name] = st;
                }
                if (si) {
                    this.sortInfo = si;
                }
            }
        }
      }        
    }
    
    /**
     * Returns an object describing the current sort state of this Store.
     * @return {Object} The sort state of the Store. An object with two properties:<ul>
     * <li><b>field : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
     * <li><b>direction : String<p class="sub-desc">The sort order, "ASC" or "DESC" (case-sensitive).</p></li>
     * </ul>
     */
    ,getSortState : function(){
        return this.sortInfo && this.sortInfo.length>0 ?
               {field:this.sortInfo[0].field, direction:this.sortInfo[0].direction} :
               {};
     }
    
    /**
     * Sets the default sort column and order to be used by the next load operation.
     * Overrides Ext.data.Store     
     * @param {String} field The name of the field to sort by, or an array of fields
     * @param {String} dir (optional) The sort order, "ASC" or "DESC" (case-sensitive, defaults to "ASC")
     */    
   ,setDefaultSort: function(field, dir){
        // alert('setDefaultSort '+ field);
        dir = dir ? dir.toUpperCase() : "ASC";
        this.sortInfo = [];
        
        if (!Ext.isArray(field)) 
            this.sortInfo.push({
                field: field,
                direction: dir
            });
        else {
            for (var i = 0, len = field.length; i < len; ++i) {
                this.sortInfo.push({
                    field: field[i].field,
                    direction: dir
                });
                this.sortToggle[field[i]] = dir;
            }
        }
    }
    
    
   ,removeGroupField: function(fld) {
    // @todo
      if(this.groupField) {
        var i=this.groupField.length;
        this.groupField.remove(fld);
        // See if anything was really removed?
        if(this.groupField.length < i) {
           if(this.groupField.length==0)
             this.groupField=false;
           // Fire event so grid can be re-drawn  
           this.fireEvent('datachanged', this);
        }
      }  
    }
});




/**
 * @class Ext.ux.grid.MultiGroupingPagingStore
 * @extends Ext.ux.grid.MultiGroupingStore
 * A specialized {@link Ext.data.Store} that allows data to be appended a page at
 * a time as the user scrolls through. It is based on performing server-side sorting
 * and grouping and should be used in conjunction with a {@link Ext.ux.grid.MultiGroupPagingGrid}  
 * @constructor
 * Create a new MultiGroupingPagingStore
 * @param {Object} config The config object
 * 
 * @author PaulE  
 */
Ext.ux.grid.MultiGroupingPagingStore = Ext.extend(Ext.ux.grid.MultiGroupingStore, {

  /** When creating the store, register an internal callback for post load processing
   */  
    constructor: function(config) {
   	  Ext.ux.grid.MultiGroupingPagingStore.superclass.constructor.apply(this, arguments);
      // When loading has finished, need to see if there are more records
      /*  
      this.on("load", function(store, r, options) {
        return this.loadComplete(r, options);
      }, this);*/
      this.remoteSort=true;
      this.remoteGroup=true;
    }

  /**
   * @cfg {Number} pageSize
   * The number of records to read/display per page (defaults to 20)
   */
   ,pageSize: 20

   /** Private: The Key of the extra record read if there is more that the page size
    */
   ,nextKey: null

    /** Override the load method so it can merge the groupFields and sortField
     * into a single sort criteria (group fields need to be sorted by first!)
     */         
   ,load : function(options){
      //console.debug("Store.load: ", options, this.isLoading);
      options = options || {};
      if(this.fireEvent("beforeload", this, options) !== false){
        this.storeOptions(options);
        if(options.initial==true) {
          delete this.nextKey;
          delete this.totalCount;
        }
        delete this.baseParams.groupBy;
        var p = Ext.apply(options.params || {}, this.baseParams);
        var sort=[];
        var meta=this.recordType.getField;
        var f;
        if(this.groupField && this.remoteGroup){
          if(Ext.isArray(this.groupField))
            for(var i=0;i<this.groupField.length;i++) {
              f=meta(this.groupField[i]);
              sort[sort.length] = (f.sortFieldName||this.groupField[i]) + ' ' + (f.sortDir || '');
            }
          else {
            f=meta(this.groupField);
            sort[sort.length] = (f.sortFieldName||this.groupField) + ' ' + (f.sortDir || '');
          }
        }
        if(this.sortInfo && this.remoteSort){
          if(Ext.isArray(this.sortInfo))
            for(var i=0;i<this.sortInfo.length;i++) {
              f=meta(this.sortInfo[i].field);
              sort[sort.length]=(f.sortFieldName || this.sortInfo[i].field) + " " + this.sortInfo[i].direction;
            }  
          else {
            f=meta(this.sortInfo.field);
            sort[sort.length]=(f.sortFieldName || this.sortInfo.field) + " " + this.sortInfo.direction;
          }
        }
        p[this.paramNames.sort]=sort.join(",");
        //console.debug("Store.load : Query Parameters ",p,sort,this.sortInfo.field,this.sortInfo.direction, this);
        this.proxy.load(p, this.reader, this.loadRecords, this, options);
        return true;
      } else {
        return false;
      }
    }
    
   /** Reload the current set of record, using by default the current options
    * This will reload the same number of records that have currently been loaded, not
    * just the initial page again.       
    * @param options, additional query options that can be provided if needed
    */       
   ,reload : function(options){
        var o = Ext.applyIf(options||{}, this.lastOptions);
        var pn = this.paramNames;
        if(!o.params) o.params=[];
        o.params[pn.start] = 0;
        o.params[pn.limit] = Math.max(this.pageSize,this.data.length) + 1;
        o.add = false;
        o.initial = false;
        //console.debug("Store.reload :",o,this.sortInfo);
        return this.load(o);
    }
   /** Load the next page of records, if there are more available
    * @param initial, set to true if this should be a initial load
    */       
   ,loadMore : function(initial){
        if(!initial && !this.nextKey) {
          //console.debug("Store.loadMore : Reject load, no more records left");
          return;
        } 

        var o = {}, pn = this.paramNames;
        o[pn.start] = initial?0:this.getCount();
        o[pn.limit] = this.pageSize + 1;
        //console.debug("Store.loadMore : Loading based on ",o);
        this.load({params:o,add:!initial,initial:initial});
    }

   /** Private - Override default callback handler once records have been loaded.
    * Looks to see if we are able to find more that just the page size, if so
    * it removes the extra one, but keeps it for consistency checking for when the
    * next page is loaded
    * @param r, array of records read from the server
    * @param options, the options that were used by the load operation to do the query
    */       
   ,loadRecords : function(o, options, success){
      if(o && success && o.records) {
        var r=o.records;
        //console.debug("Store.loadRecords : rows=", r.length, options);
        var nextKey = this.nextKey;
        delete this.nextKey;
        // Need to compare the prior next key, to the first row that was added
        // This could trigger a complete reload
        if(nextKey) {
          var id = this.reader.meta.id; // Get key field name from reader
          //console.debug("Store.loadRecords : Refresh Check...",id,r[0].data[id],nextKey.data[id]);
          if(r[0].data[id] != nextKey.data[id]) {
            //console.debug("Store.loadRecords : Need to refresh all records as they are out of sync");
            var pn = this.paramNames;
            options.params[pn.limit] = options.params[pn.limit]+options.params[pn.start]-1;
            options.params[pn.start] = 0;
            options.add = false;
            options.initial = false;
            delete this.nextKey;
            this.fireEvent("loadexception", this);
            //console.debug("Store.loadRecords : Reload Using ",options);
            //this.load.defer(20, this, [options]);
            this.load(options);
            return;
          }
        }   
        // Need to remove the extra record, and put it in the next key.
        if(r.length>=options.params[this.paramNames.limit]) {
          //console.debug("Store.loadRecords : More records exist, remove extra one");
          this.nextKey = r[r.length-1];
          // remove this last record
          r.remove(this.nextKey);
          //console.debug("Store.loadRecords : Total=",this.data.length,this.getCount());
        } else
          // Set the total count as we now know what it is
          this.totalCount = r.length+(options.add==true?this.getCount():0);
      } 
      Ext.ux.grid.MultiGroupingStore.superclass.loadRecords.call(this, o, options, success);
    }
});  

/**
 * @class Ext.ux.grid.MultiGroupingPagingDWRStore
 * @extends Ext.ux.grid.MultiGroupingPagingStore
 * @constructor Create a new MultiGroupingPagingStore
 * @param {Object} config The config object
 * 
 * @author PaulE  
 */
Ext.ux.grid.MultiGroupingPagingDWRStore = Ext.extend(Ext.ux.grid.MultiGroupingPagingStore, {

    /** When creating the store, register an internal callback for post load processing
     */  
    constructor: function(config) {
   	  Ext.ux.grid.MultiGroupingPagingDWRStore.superclass.constructor.apply(this, arguments);
      this.paramNames = {
        'start':'objectStart'
       ,'limit':'objectLimit'
       ,'sort':'orderByFields'
       ,'dir' : undefined 
      }
    }
    
    /** Override the load method so it can merge the groupFields and sortField
     * into a single sort criteria (group fields need to be sorted by first!)
     */         
   ,load : function(options){
      //console.debug("Store.load: ", options, this.isLoading);
      options = options || {};
      if(this.fireEvent("beforeload", this, options) !== false){
        this.storeOptions(options);
        if(options.initial==true) {
          delete this.nextKey;
          delete this.totalCount;
        }
        delete this.baseParams.groupBy;
        var p = Ext.apply(options.params || {}, this.baseParams);
        var sort=[];
        var meta=this.recordType.getField;
        var f;
        if(this.groupField && this.remoteGroup){
          if(Ext.isArray(this.groupField))
            for(var i=0;i<this.groupField.length;i++) {
              f=meta(this.groupField[i]);
              sort[sort.length] = {fieldName: f.sortFieldName||this.groupField[i], sortAscending: f.sortDir=='ASC' };
            }
          else {
            f=meta(this.groupField);
            sort[sort.length] = {fieldName: f.sortFieldName||this.groupField, sortAscending: f.sortDir=='ASC' };
          }
        }
        if(this.sortInfo && this.remoteSort){
          if(Ext.isArray(this.sortInfo))
            for(var i=0;i<this.sortInfo.length;i++) {
              f=meta(this.sortInfo[i].field);
              sort[sort.length] = {fieldName: f.sortFieldName || this.sortInfo[i].field, sortAscending: this.sortInfo[i].direction=='ASC' };
            }  
          else {
            f=meta(this.sortInfo.field);
            sort[sort.length] = {fieldName: f.sortFieldName || this.sortInfo.field, sortAscending: this.sortInfo.direction=='ASC' };
          }
        }
        p[this.paramNames.sort]=sort;
        //console.debug("Store.load : Query Parameters ",p,sort,this.sortInfo.field,this.sortInfo.direction, this);
        this.proxy.load(p, this.reader, this.loadRecords, this, options);
        return true;
      } else {
        return false;
      }
    }    
});


