Ext.ns('ec2');

ec2.scripts = [];
ec2.loadScript = function(src) {
  if (ec2.scripts[src]) return;
  var script = document.createElement('script');
  script.type = 'text/javascript';
  script.src = src;
  document.getElementsByTagName('head')[0].appendChild(script);  
  ec2.scripts[src] = true;
}


ec2.editDec = function (v, prec) {
  if (typeof v == 'undefined') v = 0;
  var dp =(1/2).toLocaleString().charAt(1);
  var thSep = dp  == '.' ? ',' : '.';
  var p = 1;
  var pc = "";
  for (i=0;i < prec; i++){ 
    p *= 10;
    pc = pc + "0";
  }

  v = (Math.round((v-0)*p))/p;
  if (prec == 0) return (String(v));
  v = (v == Math.floor(v)) ? v + '.' + pc : ((v*10 == Math.floor(v*10)) ? v + "0" : v);
  v = String(v);
  var ps = v.split('.');
  var whole = ps[0];
  var sub = ps[1] ? dp + ps[1] : dp + pc;
  var r = /(\d+)(\d{3})/;
  while (r.test(whole)) {
    whole = whole.replace(r, '$1' + thSep  + '$2');
  }
  return whole + sub ;
}

ec2.obj2url = function(obj) {
  var p='', sep= '?';
  for (var o in obj) {
	if (obj.hasOwnProperty(o)) {
		var v = obj[o];
		if (typeof(v) != "undefined" && v != "undefined"
		&& o > 'A' && o < 'Z' ) { // Todo not only uppercase but parms as seperate object 
		  p = p + sep + o + '=' + v;  
		  sep= '&';
		}
	   }
	}
  return p;
}

ec2.unique = function() {
  ec2.next = ec2.next ? (ec2.next + 1) : 1;
  return (ec2.next);
}

ec2.cloneObject = function(o) {
  if(typeof(o) != 'object') return o;
  if(o == null) return o;
  var newObj = new Object();
  for(var i in o) newObj[i] = ec2.cloneObject(o[i]);
  return newObj;
}

ec2.JsonDateConvert = function(val , rec) {
  if (Ext.isEmpty(val)) {
    v = null;
  } else {
    v = new Date(Date.parseDate(val,'Y-m-d\TH:i:s'));
  }
  return v;
}
ec2.jsonConcat = function(o1, o2) {
  for (var key in o2) {
    if (typeof (o2[key]) != 'undefined' && o2[key] != null) {
      o1[key] = o2[key];
    }
  }
  return o1;
}

ec2.loadRefObject = function (ref, r, o, obj) {
  if (typeof(o) == "object") {
    for (var key in o) {
      ec2.loadRefObject(ref, key , o[key], o);
    }
  } else if (r == '$id' && obj ) {
    ref[obj.$id] = obj;
  }
}

ec2.extServiceUrl = function (service) {
  if (ec2.offline)
	return '../examples/' + service + '.aspx';
  else
	return "../ExtService/" + service;
};

ec2.guid = function() {
  var chars = '0123456789abcdef'.split('');
  var uuid = [], rnd = Math.random, r;
  uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
  uuid[14] = '4'; // version 4

  for (var i = 0; i < 36; i++) {
    if (!uuid[i]) {
      r = 0 | rnd()*16;
      uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r & 0xf];
    }
  }
  return uuid.join('');
}

ec2.exists = function(v) {
  try {
    if (v) return true;
  } catch(e) {
  }
  return false;
}

ec2.Base64 = {

  // private property
  _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

  // public method for encoding
  encode : function (input) {
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;

    while (i < input.length) {
      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);

      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;

      if (isNaN(chr2)) {
        enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
        enc4 = 64;
      }

      output = output +
        this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
        this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

    }

    return output;
  },

  // public method for decoding
  decode : function (input) {
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

    while (i < input.length) {
      enc1 = this._keyStr.indexOf(input.charAt(i++));
      enc2 = this._keyStr.indexOf(input.charAt(i++));
      enc3 = this._keyStr.indexOf(input.charAt(i++));
      enc4 = this._keyStr.indexOf(input.charAt(i++));

      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;

      output = output + String.fromCharCode(chr1);

      if (enc3 != 64) {
        output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
        output = output + String.fromCharCode(chr3);
      }
    }

    return output;
  }
}

ec2.profileValue = {
  set: function(key, value) {
    Ext.Ajax.request({
      url: ec2.extServiceUrl('setProfileValue'),
      params: { 
        "key": key ,
        "value" : value
      },
      method: 'POST'      
    });
  },
  get: function(key, dft) {
    var value = ec2.sajax.request({
      url:ec2.extServiceUrl('getProfileValue') + '?key=' + key,
      method: 'GET',
      asJson: false
    });
    value  = Ext.util.JSON.decode (value);
    return (value == "") ? dft:value; 
  }
}

// Implementation of "synchronous ajax"
ec2.sajax = {
  request: function(parms) {
    if ( parms.method != 'POST') parms.method = "GET";
    if ( typeof(parms.payload) == "undefined" ) parms.payload = null;
    var xmlhttp = this.getXMLHTTP();
    try {
      xmlhttp.open(parms.method, parms.url, false);
      xmlhttp.setRequestHeader("CharSet", "windows-1252");
      xmlhttp.send(parms.payload);
    } catch(ex){
      return ex.message;
    }
    if (parms.asJson) {
      try {
        eval ('json = ' + xmlhttp.responseText );
        return ( json);
      } catch(ex){
        return '';
      }
    } else {
      return xmlhttp.responseText;
    }
  },

  getXMLHTTP:  function () {
    var xmlhttp;
    try {
      xmlhttp = new XMLHttpRequest( );
    } catch(ex) {
      try {
        xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
      } catch(ex) {
        try {
          xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        } catch(ex) {
          xmlhttp = false;
        }
      }
    }
    return xmlhttp;
  }
}

// Implementation of "State provider" using synchronous ajax 
ec2.stateProvider = Ext.extend(Ext.state.Provider, {

  constructor : function(config){
    Ext.apply(this, config);
    //this.state = sessvars;
  },

  get : function(name, defaultValue) {
    if (name.substring(0,6) != 'state-') return defaultValue; // Only stated components

    var json  = ec2.sajax.request({
      url:ec2.extServiceUrl('getProfileValue') + '?key=' + name,
      method: 'GET',
      asJson: false
    });
    
    if (json && json != 'null' && json != '\"\"' && json != '\"\"\"\"') {
      // double decode since the object is double encoded  
      json = Ext.util.JSON.decode (json);
      json = Ext.util.JSON.decode (json);
      return json;
    } else  {
      return defaultValue;
    }
  },

  set : function(name, value){
    if (name.substring(0,6) != 'state-') return; // Only stated components
    // console.log("set" + name + ':' + value);
    json = Ext.util.JSON.encode (value);
    Ext.Ajax.request({
      url: ec2.extServiceUrl('setProfileValue'),
      params: { 
        "key": name ,
        "value" : json
      },
      method: 'POST'      
    });
    return value;
  },

  clearAll : function(){
    // console.log("clear");
  }
});


ec2.columnRenderer = function(value, metaData, record, rowIndex, colIndex, store, field) {
  var v = '';
  field = field || this.scope;
  if (!value && record.json && record.json.$ref) {
    var i = parseInt(record.json.$ref, 10);
    var obj = field.Entities[i];
    var name = field.dataIndex;
    var value = obj[name];
    if (field.type == 'date') {
      if (!value ) return('');
      value = new Date(Date.parseDate(value ,'Y-m-d\TH:i:s'));
    }
    v = ec2.renderer(null, value, name, field);
  } else {
	if (field.ec2Type == 'love' ) {
	  field.currentValue = record.json[field.key];
	}	
    v = ec2.renderer(null, value, field.dataIndex, field);
  }
  if (metaData != null && field.editor) {
    var errors = field.editor.getErrors(v);
    if (typeof (errors[0]) != 'undefined') {
      metaData.css = 'ec2-cell-invalid';
      metaData.attr = 'ext:qtip="'+Ext.util.Format.htmlEncode(errors[0]) +'"';
    }
  }
  return v;
};

ec2.fieldRenderer = function(data, value, name, field) {
  return(ec2.renderer(data, value, name, field, null));
};
ec2.renderer = function(data, value, name, field) {
  // Fitst time, we resolve the references into an array of objects
  // Placed here bacause "onload" is after rendering the cells
  if ( ! field.refobj.loaded) {
    ec2.loadRefObject (field.refobj.items, 'root', field.refobj.storeReader.jsonData, null);
    field.refobj.loaded = true;
  }
  if (field.ec2Type == 'love' 
  && ( typeof(value) == "undefined" || value == "")){ // TODO !! only from form
  // if (field.ec2Type == 'love' &&  ! Ext.isObject(value) ) { // TODO !! only from form
	if (data) field.currentValue = data[field.key];
	if (!field.currentValue) return '';
	var et = field.entityType ? field.entityType : field.editor.entityType;
    var rsp = ec2.sajax.request({
      url:ec2.extServiceUrl('getLoveById') + '?entityType=' + et + '&entityId=' + field.currentValue,
      method: 'GET',
      asJson: true
    });
	var v = rsp.rows[0].value; 
	if (data) data[field.name] = v;
    return v;	
  } else if (field.xtype == 'ec2.FileAttachment') { // TODO !! only from form
    field.currentValue = data[field.key];
    if (value && value.$ref)  {
      try {
        var i =  parseInt(value.$ref, 10);
        field.obj = field.refobj.items[i];
        field.currentValue = field.refobj.items[i].Id;
        return field.obj.Title; 
      } catch(e) {
      }
    } 
    field.data = data;
    return value;
  } else if (field.editor && field.editor.enumData) {
      var i = field.editor.store.find('key' , value);
      var r = field.editor.store.getAt(i);
      if (r) {
        field.editor.keyValue = value;
        return(r.data.value);
      }
      return(value);
  } else if (!Ext.isObject(value) && Ext.isEmpty(value)) { // Below is functions where "undefined" is not an option
    return '';
  } else if (field.type == 'date') {
    if (typeof (value) == 'string') {
      try { 
        value = new Date(Date.parseDate(value ,'Y-m-d\TH:i:s'));
      } catch (e) {
        return value;
      }
    }
    return value.dateFormat(field.format);
  } else if (value.$ref)  {
    try {
      var i =  parseInt(value.$ref, 10);
      field.currentValue = field.refobj.items[i].Id;
      field.obj = field.refobj.items[i];
      if (field.editor) field.editor.obj = field.obj;
      return field.obj.Title; ;
    } catch (e) {
      return 'N/A';
    }
  } else if (typeof (value) == 'object')  {
    return(value.Title);
  } else if (field.editor && field.editor.xtype == 'checkbox') {
    if (value) {
      return('<center><div style="background-image:url(\'ext3/resources/images/default/form/checkbox.gif\');background-position:0 -13px;height:13px;width:13px;">&nbsp;</div></center>');
    } else {
      return('<center><div style="background-image:url(\'ext3/resources/images/default/form/checkbox.gif\');background-position:0 0;height:13px;width:13px;">&nbsp;</div></center>');
    }
  } else {
    var editor = (field.editor) ? field.editor : {};
    return (editor.lastSelectionText) ? editor.lastSelectionText : value;
  }
};

ec2.addAjaxMessage = function(msg) {
  alert(msg);
}

// generic connection (ajax) request error handler
Ext.data.Connection.prototype._handleFailure = Ext.data.Connection.prototype.handleFailure;
Ext.data.Connection.prototype.handleFailure = function(response, e) {
  ec2.addAjaxMessage(response);
  Ext.data.Connection.prototype._handleFailure.call(this, response, e);
}

Ext.override(Ext.form.DateField, {
  format:'Y-m-d',
  altFormats:'Ymd|Y/m/d|Y-m-d'
});

Ext.override(Ext.form.Field, {
  anchor:'95%',
  // Why does this not fire??
  onChange: function(n,o) {
    ec2.activeObject.changePending = true;
    this.fireEvent('change', this, n, o);
  },
  // Onchange does not work - make our own
  onBlur: function() {
    if (this.getValue() != this.originalValue)  ec2.activeObject.changePending = true;
    this.fireEvent('blur', this);
  },
  onFocus : function() {
    this.preFocus();
    if(this.focusClass) {
      this.el.addClass(this.focusClass);
    }
    if(!this.hasFocus) {
      this.hasFocus = true;

      this.startValue = this.getValue();
      this.fireEvent('focus', this);
    }

//    if (this.help) {
//      ec2.help.fireEvent('updateHelp',this.help);
//   } else if (this.ownerCt) {
//     ec2.help.fireEvent('updateHelp',this.ownerCt.help);
//   }
  }
});


Ext.override(Ext.data.Store,{
  addField: function(field){
    field = new Ext.data.Field(field);
    this.recordType.prototype.fields.replace(field);
    if(typeof field.defaultValue != 'undefined'){
      this.each(function(r){
        if(typeof r.data[field.name] == 'undefined'){
          r.data[field.name] = field.defaultValue;
        }
      });
    }
  },
  removeField: function(name){
    this.recordType.prototype.fields.removeKey(name);
    this.each(function(r){
      delete r.data[name];
      if(r.modified){
        delete r.modified[name];
      }
    });
  }
});
Ext.override(Ext.grid.ColumnModel,{
  addColumn: function(column, colIndex){
    if(typeof column == 'string'){
      column = {header: column, dataIndex: column};
    }
    if(Ext.isArray(this.config)){
      var config = this.config;
    } else {
      var config = [];
    }
    this.config = [];
    if(typeof column.renderer == 'undefined'){
      column.renderer = ec2.columnRenderer;
    }
    if(typeof colIndex == 'number'){
      config.splice(colIndex, 0, column);
    }else{
      colIndex = config.push(column);
    }
    this.setConfig(config);

    return colIndex;
  },
  removeColumn: function(colIndex){
    var config = this.config;
    this.config = [config[colIndex]];
    config.splice(colIndex, 1);
    this.setConfig(config);
  }
});
Ext.override(Ext.grid.EditorGridPanel,{
  addColumn: function(field, column, colIndex){
    if(!column){
      if(field.dataIndex){
        column = field;
        field = field.dataIndex;
      } else{
        column = field.name || field;
      }
    }
    this.store.addField(field);
    return this.colModel.addColumn(column, colIndex);
  },
  removeColumn: function(name, colIndex){
    this.store.removeField(name);
    if(typeof colIndex != 'number'){
      colIndex = this.colModel.findColumnIndex(name);
    }
    if(colIndex >= 0){
      this.colModel.removeColumn(colIndex);
    }
  }
});

Ext.override(Ext.form.HtmlEditor, {
  markInvalid: function(msg){
      if(!this.rendered || this.preventMark){
          return;
      }
      msg = msg || this.invalidText;
      switch(this.msgTarget){
          case 'qtip':
              this.iframe.qtip = msg;
              this.iframe.qclass = 'x-form-invalid-tip';
              Ext.get(this.iframe).addClass(this.invalidClass);
              
              break;
      }
      return Ext.form.TextArea.superclass.markInvalid.call(this, [msg]);
  },
  clearInvalid: function(){
      if(!this.rendered || this.preventMark){
          return;
      }
      switch(this.msgTarget){
          case 'qtip':
              this.iframe.qtip = '';
              Ext.get(this.iframe).removeClass(this.invalidClass);
              break;
      }
      return Ext.form.TextArea.superclass.clearInvalid.call(this);
  },
  validateValue:function(){
    if(this.allowBlank==false){
      var value =this.getRawValue();
      value = value.replace(/&nbsp;/gi,"");
      value = value.replace(/<br>/gi,"");
      value = value.trim();
      if(value != ''){
        this.clearInvalid();
        return true;
      } else {
        this.markInvalid("This field is required.")
        return false;
      }
    }else{
      this.clearInvalid();
      return true;
    }
  },

  // private
  fixKeys : function(){ // load time branching for fastest keydown performance
    if(Ext.isIE){
      return function(e){
        var k = e.getKey(), r;
        if(k == e.TAB){
          e.stopEvent();
          r = this.doc.selection.createRange();
          if(r){
            r.collapse(true);
            r.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
            this.deferFocus();
          }
        }/*else if(k == e.ENTER){
          r = this.doc.selection.createRange();
          if(r){
            var target = r.parentElement();
            if(!target || target.tagName.toLowerCase() != 'li'){
              e.stopEvent();
              r.pasteHTML('<br />');
              r.collapse(false);
              r.select();
            }
          }
        }*/
      };
    }else if(Ext.isOpera){
      return function(e){
        var k = e.getKey();
        if(k == e.TAB){
          e.stopEvent();
          this.win.focus();
          this.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
          this.deferFocus();
        }
      };
    }else if(Ext.isSafari){
      return function(e){
        var k = e.getKey();
        if(k == e.TAB){
          e.stopEvent();
          this.execCmd('InsertText','\t');
          this.deferFocus();
        }
      };
    }
  }(),
    getDocMarkup : function(){
    return '<html><head><style type="text/css">body{border:0;margin:0;padding:3px;height:98%;cursor:text;}p{margin:0;padding:0;}</style></head><body></body></html>';
  }
});

Ext.form.HtmlEditor.prototype.listeners = { 
  sync: function(t,html) {
    t.validateValue();
  }
};


ec2.EnumComboBox = Ext.extend(Ext.form.ComboBox, {
  ec2Type:'enum',
  width: 250,
  anchor:null,
  disableArrowsUpDown:true,
  initComponent:function() {
    Ext.apply(this, {
      store: new Ext.data.SimpleStore ({
        fields: ['key', 'value'],
        data: this.enumData
      }),
      valueField: 'key',
      displayField:'value',
      hiddenName:'key',
      mode: 'local',
      editable: false,
      typeAhead: true,
      forceSelection: true,
      triggerAction: 'all',
      selectOnFocus:true,
      listeners: {
        select: function(combo, record, index) {
          this.currentValue = this.getValue();
        }
      }
      // hiddenName: this.name
    });
    ec2.EnumComboBox.superclass.initComponent.call(this);
  }
});

ec2.LoveComboBox = Ext.extend(Ext.form.ComboBox, {
  ec2Type:'love',
  width: 250,
  anchor:null,
  isValid:function () {
	return true;
  },
  disableArrowsUpDown:true,
  initComponent:function() {
    Ext.apply(this, {
      store:new Ext.data.Store({
        url: ec2.extServiceUrl('GetLove')
        ,entityType:this.entityType
        ,reader: new Ext.data.JsonReader({
          root:'rows',
          fields:['key','value']
        })
      }),
      isValid:function () {
     	return true;
      }
      ,valueField:'key'
      ,displayField:'value'
      ,hiddenName:'key'
      ,typeAhead: false
      ,loadingText:'Searching...'
      ,hideTrigger:false
      ,triggerAction:'all'
      ,listWidth:this.width
      ,autoSelect:false
      ,listeners: {
        keydown: function(combo, e){
          if (searchMode && e.getKey() == e.UP){
            e.stopEvent();
          }
        },
        focus: function(combo, e){
          searchMode = false;
        },
        blur: function(combo, e){
          searchMode = false;
        },
        beforequery: function(combo, record, index){
          searchMode = true;
          this.store.baseParams = {entityType:this.entityType};
        },
        select: function(combo, record, index) {
          searchMode = false;
          var value = this.getValue();
          if (value == -1) {
            var node = {};
            node.attributes = {};
            node.attributes.EntityType = this.entityType;
            node.attributes.EntityId  = 0;
            node.attributes.ViewType   = 'Detail';
            node.attributes.ParentComponent = this; //NL
            ec2.addTab(node);
          } else {
            this.currentValue = value;
			this.setValue(value);
          }
        }
      }
    });
    ec2.LoveComboBox.superclass.initComponent.call(this);
	this.isValid = function() {
	  return true;
	}
  }
});

ec2.collapsedPanelTitlePlugin = function () {
    this.init = function(p) {
        if (p.collapsible) {
            var r = p.region; if ((r == 'north') || (r == 'south')) {
                p.on ('render',
                function() {
                    var ct = p.ownerCt; ct.on ('afterlayout',
                    function() {
                        if (ct.layout[
                            r
                        ].collapsedEl) {
                            p.collapsedTitleEl = ct.layout[
                                r
                            ].collapsedEl.createChild ({
                                tag: 'span',
                                cls: 'x-panel-collapsed-text',
                                html: p.title
                            }); p.setTitle = Ext.Panel.prototype.setTitle.createSequence (function(t) {
                                p.collapsedTitleEl.dom.innerHTML = t;
                            });
                        }
                    },
                    false,
                    {
                        single: true
                    }); p.on ('collapse',
                    function() {
                        if (ct.layout[
                            r
                        ].collapsedEl && !p.collapsedTitleEl) {
                            p.collapsedTitleEl = ct.layout[
                                r
                            ].collapsedEl.createChild ({
                                tag: 'span',
                                cls: 'x-panel-collapsed-text',
                                html: p.title
                            }); p.setTitle = Ext.Panel.prototype.setTitle.createSequence (function(t) {
                                p.collapsedTitleEl.dom.innerHTML = t;
                            });
                        }
                    },
                    false,
                    {
                        single: true
                    });
                });
            }
        }
    };
}

// override border layout, so clicking on region bar title and expand/collapse icon works same way
Ext.override(Ext.layout.BorderLayout.Region, {
    getCollapsedEl : function(){
        if(!this.collapsedEl){
            if(!this.toolTemplate){
                var tt = new Ext.Template(
                     '<div class="x-tool x-tool-{id}">&#160;</div>'
                );
                tt.disableFormats = true;
                tt.compile();
                Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt;
            }
            this.collapsedEl = this.targetEl.createChild({
                cls: "x-layout-collapsed x-layout-collapsed-"+this.position,
                id: this.panel.id + '-xcollapsed'
            });
            this.collapsedEl.enableDisplayMode('block');

            if(this.collapseMode == 'mini'){
                this.collapsedEl.addClass('x-layout-cmini-'+this.position);
                this.miniCollapsedEl = this.collapsedEl.createChild({
                    cls: "x-layout-mini x-layout-mini-"+this.position, html: "&#160;"
                });
                this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');
                this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
                this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true});
            }else {
                if(this.collapsible !== false && !this.hideCollapseTool) {
                    var t = this.expandToolEl = this.toolTemplate.append(
                            this.collapsedEl.dom,
                            {id:'expand-'+this.position}, true);
                    t.addClassOnOver('x-tool-expand-'+this.position+'-over');
                    t.on('click', this.onExpandClick, this, {stopEvent:true});
                }
                if(this.floatable !== false || this.titleCollapse){
                   this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
                   this.collapsedEl.on("click", this.onExpandClick, this);
                }
            }
        }
        return this.collapsedEl;
    }
});

ec2.CellSelectionModelWithMultiRowSelect = function(config){
  Ext.apply(this, config);
  this.selections = [];
  this.last = false;
  this.lastActive = false;
  this.addEvents(
    "selectionchange",
    "beforecellselect",
    "cellselect",
    "celldeselect"
    );
  ec2.CellSelectionModelWithMultiRowSelect.superclass.constructor.call(this);
};

Ext.extend(ec2.CellSelectionModelWithMultiRowSelect, Ext.grid.AbstractSelectionModel,  {
  //if you want single cell selection or multiple
  singleSelect : false,

  //if on enter key to move to the next cell down
  moveEditorOnEnter: false,

  //if when a cell is selected with control or shift if the entire row should be selected
  rowSelection: true,

  // private
  initEvents : function(){
    if(!this.grid.enableDragDrop && !this.grid.enableDrag){
      this.grid.on("cellmousedown", this.handleMouseDown, this);
    }else{ // allow click to work like normal
      this.grid.on("cellclick", function(grid, rowIndex, columnIndex, e) {
        if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
          this.selectCell([rowIndex, columnIndex], false);
          grid.view.focusCell(rowIndex, columnIndex);
        }
      }, this);
    }
    this.rowNav = new Ext.KeyNav(this.grid.getGridEl(), {
      "enter": function(e){
        var editor = this.grid.getColumnModel().getCellEditor(this.lastActive[1],this.lastActive[0]);
        if (editor){
          this.grid.startEditing(this.lastActive[0],this.lastActive[1]);
          this.clearSelections();
        }
      },
      "up" : function(e) {
        if (this.last === false) {
          this.selectCell([0, 0]);
        } else if (this.lastActive[0] === 0) {
          return;
        } else {
          var row, col;
          row = this.lastActive[0] - 1;
          col = this.lastActive[1];
          if (!e.shiftKey) {
            this.selectCell([row, col]);
          } else {
            var last = this.last
            this.selectRange(this.last, [row, col]);
            this.last = last;
          }
          this.grid.getView().focusCell(row, col);
          this.lastActive = [row, col];
        }
      },
      "down" : function(e) {
        if (this.last === false) {
          this.selectCell([0, 0]);
        } else if (this.lastActive[0] === this.grid.getStore().getCount() - 1) {
          this.addBottomRow(this);
          return;
        } else {
          var row, col;
          row = this.lastActive[0] + 1;
          col = this.lastActive[1];
          if (!e.shiftKey) {
            this.selectCell([row, col]);
          } else {
            var last = this.last
            this.selectRange(this.last, [row, col]);
            this.last = last;
          }
          this.grid.getView().focusCell(row, col);
          this.lastActive = [row, col];
        }
      },
      "left" : function(e) {
        if (this.last === false) {
          this.selectCell([0, 0]);
        } else if (this.lastActive[1] === 0) {
          return;
        } else {
          var row, col;
          row = this.lastActive[0];
          col = this.lastActive[1] - 1;
          if (!e.shiftKey) {
            this.selectCell([row, col]);
          } else {
            var last = this.last
            this.selectRange(this.last, [row, col]);
            this.last = last;
          }
          this.grid.getView().focusCell(row, col);
          this.lastActive = [row, col];
        }
      },
      "right" : function(e) {
        if (this.last === false) {
          this.selectCell([0, 0]);
        } else if (this.lastActive[1] === this.grid.getColumnModel().getColumnCount() - 1) { //NL
          if (this.lastActive[0] === this.grid.getStore().getCount() - 1) {
            this.addBottomRow(this);
          }
          return;
        } else {
          var row, col;
          row = this.lastActive[0];
          col = this.lastActive[1] + 1;
          if (!e.shiftKey) {
            this.selectCell([row, col]);
          } else {
            var last = this.last
            this.selectRange(this.last, [row, col]);
            this.last = last;
          }
          this.grid.getView().focusCell(row, col);
          this.lastActive = [row, col];
        }
      },
      scope: this
    });
    var view = this.grid.view;
    view.on("refresh", this.onRefresh, this);
    view.on("rowremoved", this.onRemove, this);
  },

  // private
  onRefresh : function(){
    this.clearSelections();
  },

  // private
  onRemove : function(v, index, r){
    this.deselectRange([index, 0], [index, this.grid.getColumnModel().getColumnCount()]);
  },

  //return total number of cells selected
  getCount : function(){
    return this.selections.length;
  },

  //pass
  //  keepExisting
  //return
  //  true if success
  selectRight : function(keepExisting){
    if(this.hasNext()){
      var row, col = this.last[1];
      if (col === this.grid.getColumnModel().getColumnCount() - 1) {
        row = this.last[0] + 1;
        col = 0;
      } else {
        row = this.last[0];
        col += 1;
      }

      this.selectCell([row, col], keepExisting);
      this.grid.getView().focusCell(this.last[0], this.last[1]);
      return true;
    }
    return false;
  },

  //pass
  //  keepExisting
  //return
  //  true if success
  selectDown : function(keepExisting) {
    var r, cols = this.grid.getColumnModel().getColumnCount();
    for (var i = 0; i < cols; i++) {
      r = this.selectRight(keepExisting);
      if (!r) break;
    }
    return r;
  },

  //pass
  //  keepExisting
  //return
  //  true if success
  selectUp : function(keepExisting) {
    var r, cols = this.grid.getColumnModel().getColumnCount();
    for (var i = 0; i < cols; i++) {
      r = this.selectLeft(keepExisting);
      if (!r) break;
    }
    return r;
  },

  //pass
  //  keepExisting
  //return
  //  true if success
  selectLeft : function(keepExisting) {
    if (this.hasPrevious()) {
      var row, col = this.last[1];
      if (col === 0) {
        row = this.last[0] - 1;
        col = this.grid.getColumnModel().getColumnCount() - 1;
      } else {
        row = this.last[0];
        col -= 1;
      }

      this.selectCell([row, col], keepExisting);
      this.grid.getView().focusCell(this.last[0], this.last[1]);
      return true;
    }
    return false;
  },

  //return
  //  true if next cell exists
  hasNext : function() {
    return this.last !== false && ((this.last[0] + 1) < this.grid.store.getCount() || (this.last[1] + 1) < this.grid.getColumnModel().getColumnCount());
  },

  //return
  //  true if previous cell exists
  hasPrevious : function(){
    return this.last !== false && (this.last[0] != 0 || this.last[1] != 0);
  },


  //return
  //  array of cells selected
  getSelections : function() {
    return [].concat(this.selections);
  },

  //return
  //  array of select rows
  getRowsSelected : function() {
    var s = this.selections;
    var results = [];
    for (var i=0; i<s.length; i++){
      var found = false;
      for (var j=0; j<results.length; j++){
        if (s[i][0] === results[j]){
          found = true;
        }
      }
      if (!found){
        results.push(s[i][0]);
      }
    }
    return results.reverse();
  },

  //return
  //  array of selected cols
  getColsSelected : function() {
    var s = this.selections;
    var results = [];
    for (var i=0; i<s.length; i++){
      var found = false;
      for (var j=0; j<results.length; j++){
        if (s[i][1] === results[j]){
          found = true;
        }
      }
      if (!found){
        results.push(s[i][1]);
      }
    }
    return results;
  },


  //return
  //  single selected row
  getSelectedCell : function() {
    return this.selections.length > 0 ? [].concat(this.selections[0]) : null;
  },

  //return
  //  last activated cell
  getLastActiveCell: function(){
    return this.lastActive;
  },

  //pass
  //  fn
  //  scope
  //return
  //  true if successfull
  each : function(fn, scope) {
    var s = this.getSelections();
    for (var i = 0, len = s.length; i < len; i++) {
      if (fn.call(scope || this, s[i], i) === false) {
        return false;
      }
    }
    return true;
  },

  //clears all selected cells
  clearSelections : function(){
    if(this.locked) return;
    for (var i = this.selections.length - 1; i >= 0 ; i--) {
      this.deselectCell(this.selections[i]);
    }
    this.selections = [];
    this.last = false;
  },


  //select all cells in grid
  selectAll : function(){
    if(this.locked) return;
    this.selections = [];
    var rowCount = this.grid.getStore().getCount();
    var colCount = this.grid.getColumnModel().getColumnCount();
    for(var row = 0; row < rowCount; row++){
      for(var col = 0; col < colCount; col++){
        this.selectCell([row, col], true);
      }
    }
  },

  //return
  //  true
  hasSelection : function(){
    return this.selections.length > 0;
  },

  //pass
  //  index - array of [row,col]
  isSelected : function(index){
    var s = this.selections;
    for (var i = 0; i < s.length; i++) {
      if (s[i][0] === index[0] && s[i][1] === index[1]) {
        return true;
      }
    }
    return false;
  },

  // private
  handleKeyDown : function(e) {
    var g = this.grid, s = this.selection;

    // If the cell is not selected and it's selectable then select it.
    // When does this happen?
    if (!s) {
      e.stopEvent();
      var cell = g.walkCells(0, 0, 1, this.isSelectable,  this);
      if (cell) {
          this.select(cell[0], cell[1]);
      }
      return;
    }
    var sm = this;
    var walk = function(row, col, step){
      return g.walkCells(row, col, step, sm.isSelectable,  sm);
    };
    var k = e.getKey(), r = s.cell[0], c = s.cell[1];
    var newCell;

    if (!e.isNavKeyPress() && !e.isSpecialKey()) {
      var keyCode = k;
      /*
          The String.fromCharCode method is return the charactor in upper case so I'm converting
          it back to lower case then later checking if the shiftKey was used and converting to upper case.
      */
      var keyValue = String.fromCharCode(keyCode).toLowerCase();
      var record = g.getStore().getAt(r);
      var field = g.colModel.getDataIndex(c);

      // Shift + charector should be upper case charector.
      if (e.shiftKey) {
          keyValue = keyValue.toUpperCase();
      }

      g.startEditing(r, c);
      record.data[field] = keyValue;
    } else {
      switch(k){
        case e.TAB:
          if(e.shiftKey){
              newCell = walk(r, c-1, -1);
          }else{
              newCell = walk(r, c+1, 1);
          }
        break;
        case e.DOWN:
          newCell = walk(r+1, c, 1);

        break;
        case e.UP:
          newCell = walk(r-1, c, -1);
        break;
        case e.RIGHT:
          newCell = walk(r, c+1, 1);
        break;
        case e.LEFT:
          newCell = walk(r, c-1, -1);
        break;
        case e.ENTER:
          if(g.isEditor && !g.editing){
            g.startEditing(r, c);
            e.stopEvent();
            g.getView().focusCell(r, c);
            return;
          }
        break;
      }
    }
    if (newCell) {
      this.select(newCell[0], newCell[1]);
      e.stopEvent();
    }
  },

  // private
  handleMouseDown : function(g, rowIndex, columnIndex, e){
    if(e.button !== 0 || this.isLocked()){
      return;
    }
    var last = this.last;
    var view = this.grid.getView();
    if (e.ctrlKey && this.last !== false && this.rowSelection){
      this.selectRow([rowIndex, columnIndex], e.ctrlKey);
      this.last = last; // reset the last
      view.focusCell(rowIndex, columnIndex);
    }else if(e.shiftKey && this.last !== false){
      this.selectRange(last, [rowIndex, columnIndex], e.ctrlKey);
      this.last = last; // reset the last
      view.focusCell(rowIndex, columnIndex);
    }else{
      var isSelected = this.isSelected([rowIndex, columnIndex]);
      if(e.ctrlKey && isSelected){
        this.deselectCell([rowIndex, columnIndex]);
      }else if(!isSelected || this.getCount() > 1){
        this.selectCell([rowIndex, columnIndex], e.ctrlKey || e.shiftKey);
        view.focusCell(rowIndex, columnIndex);
      }
    }
  },

  //pass
  //  cell - [row,col]
  selectRow: function(cell){
    var row = cell[0];
    var selectCnt = 0;
    var colCnt = this.grid.getColumnModel().getColumnCount();
    for (var col = 0; col <= colCnt; col++) {
      if (this.isSelected([row, col])){
        selectCnt++;
      }
    }
    if (selectCnt > 1){
      this.deselectRange([row,0], [row,colCnt], false);
    }else{
      this.selectRange([row, 0], [row,colCnt], true);
    }
  },

  //pass
  //  cells - array ([row,col])
  //  keepExisting
  selectCells : function(cells, keepExisting){
    if(!keepExisting){
      this.clearSelections();
    }
    for(var i = 0, len = cells.length; i < len; i++){
      this.selectCell(cells[i], true);
    }
  },

  //pass
  //  startCell
  //  endCell
  //  keepExisting
  selectRange : function(startCell, endCell, keepExisting){
    if(this.locked) return;
    if(!keepExisting){
      this.clearSelections();
    }
    var row;
    var col;
    var startRow = startCell[0];
    var startCol = startCell[1];
    var endRow = endCell[0];
    var endCol = endCell[1];
    if (endRow < startRow) {
      row = endRow;
      endRow = startRow;
      startRow = row;
    }
    if (endCol < startCol) {
      col = endCol;
      endCol = startCol;
      startCol = col;
    }
    if (this.rowSelection){
      for (row = startRow; row <= endRow; row++) {
        for (col = 0; col <= this.grid.getColumnModel().getColumnCount(); col++) {
          this.selectCell([row, col], true);
        }
      }
    }else{
      for (row = startRow; row <= endRow; row++) {
        for (col = startCol; col <= endCol; col++) {
          this.selectCell([row, col], true);
        }
      }
    }
  },

  //pass
  //  startCell
  //  endCell
  //  preventViewNotify
  deselectRange : function(startCell, endCell, preventViewNotify) {
    if(this.locked) return;
    var row;
    var col;
    var startRow = startCell[0];
    var startCol = startCell[1];
    var endRow = endCell[0];
    var endCol = endCell[1];
    if (endRow < startRow) {
      // flip 'em
      row = endRow;
      endRow = startRow;
      startRow = row;
    }
    if (endCol < startCol) {
      // flip 'em
      col = endCol;
      endCol = startCol;
      startCol = col;
    }
    if (this.rowSelection){
      for (row = startRow; row <= endRow; row++) {
        for (col = 0; col <= this.grid.getColumnModel().getColumnCount(); col++) {
          this.deselectCell([row, col], preventViewNotify);
        }
      }
    }else{}
    for (row = startRow; row <= endRow; row++) {
      for (col = startCol; col <= endCol; col++) {

        this.deselectCell([row, col], preventViewNotify);
      }
    }
  },

  //pass
  //  cell
  //  keepExisting
  //  preventViewNotify
  selectCell : function(cell, keepExisting, preventViewNotify){
    if (this.locked) return;
    if (this.isSelected(cell)) return;
    var row = cell[0];
    var col = cell[1];
    if (row < 0 || row >= this.grid.store.getCount()) return;
    if (col < 0 || col >= this.grid.getColumnModel().getColumnCount()) return;

    if (this.fireEvent("beforecellselect", this, cell, keepExisting) !== false) {
      if (!keepExisting || this.singleSelect) {
        this.clearSelections();
      }
      this.selections.push(cell);
      this.last = this.lastActive = cell;
      if(!preventViewNotify) {
        this.grid.getView().onCellSelect(row, col);
      }
      this.fireEvent("cellselect", this, cell);
      this.fireEvent("selectionchange", this, [].concat(this.selections));
    }
  },

  //pass
  //  cell
  //  preventViewNotify
  deselectCell : function(cell, preventViewNotify){
    if (this.locked) return;
    if (this.last[0] === cell[0] && this.last[1] === cell[1]) {
      this.last = false;
    }
    if (this.lastActive[0] === cell[0] && this.lastActive[1] === cell[1]) {
      this.lastActive = false;
    }

    var s = this.selections;
    for (var i = 0; i < s.length; i++) {
      if (s[i][0] === cell[0] && s[i][1] === cell[1]) {
        this.selections.remove(s[i]);
        if (!preventViewNotify) {
          this.grid.getView().onCellDeselect(cell[0], cell[1]);
        }
        this.fireEvent("celldeselect", this, cell);
        this.fireEvent("selectionchange", [].concat(this.selections));
        return;
      }
    }

  },

  // private
  acceptsNav : function(row, col, cm){
    return !cm.isHidden(col) && cm.isCellEditable(col, row);
  },
  // private
  onEditorKey : function(field, e){
    var k = e.getKey(), newCell, g = this.grid, ed = g.uxActiveEditor;
    if (k == e.TAB) {
      if(e.shiftKey){
        newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
      } else {
        newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
        if (this.grid.getStore().getCount() -1  == ed.row
        &&  this.grid.getColumnModel().getColumnCount() - 1 == ed.col) {
          this.addBottomRow(this);
        } else {
        }
      }
      e.stopEvent();
    } else if (k == e.ENTER) {
      ed.completeEdit();
      e.stopEvent();
    } else if (k == e.ESC) {
      e.stopEvent();
      ed.cancelEdit();
      this.selectCell([ed.row, ed.col]);
    } else if (k == e.DOWN && !ed.field.disableArrowsUpDown) {
      ed.completeEdit();
      e.stopEvent();
      newCell = g.walkCells(ed.row+1, ed.col, 1, this.acceptsNav, this);
      ed.field.focus(true);
    } else if (k == e.UP && !ed.field.disableArrowsUpDown) {
      ed.completeEdit();
      e.stopEvent();
      newCell = g.walkCells(ed.row-1, ed.col, -1, this.acceptsNav, this);
      ed.field.focus(true);
    }
    if (newCell) {
      if (this.ec2AddingRow){
        this.ec2AddingRow = false;
        this.clearSelections();
      }else{
        this.clearSelections();
        this.selectCell(newCell);
        g.startEditing(newCell[0], newCell[1]);
      }
    }
  }

});

// Editor deoes not provide much context information... This is a hack
ec2.rowstores = [];
ec2.buildColumns = function (c) {
  for (var i = 0;i < c.length; i++) {
    var r = c[i];
    if (r.editor) {
      if (! r.renderer) {
        if ( r.editor.enumData) {
          ec2.rowstores[r.dataIndex] = r.editor.store;
          r.renderer = function (key) {
            var s = ec2.rowstores[this.name];
            if (typeof(s) !== 'undefined') {
              var i = s.find('key' , key);
              var r = s.getAt(i);
              if (r) {
                return(r.data.value);
              }
            }
            return(key);
          }
        }
      }
    }
  }
  return c;  
}
