var NeumondForm = Class.create({
  initialize:function(elm, rootName, defaultValues, useAjax, stateMethods){
    this.form = $(elm);
    this.rootName = rootName;
    if (defaultValues){
      this.defaultValues = defaultValues;
    } else {
      this.defaultValues = {};
    }
    this.useAjax = !!useAjax;
    this.stateMethods = (stateMethods || {});
    if (!('start' in this.stateMethods)){ this.stateMethods.start = this.emptyFunc; }
    if (!('stop' in this.stateMethods)){ this.stateMethods.stop = this.emptyFunc; }
    if (!('success' in this.stateMethods)){ this.stateMethods.success = this.emptyFunc; }
    if (!('fail' in this.stateMethods)){ this.stateMethods.fail = this.emptyFunc; }
    this.reset();
    this.processing = false;
    this.additionalPostData = {};
  },
  emptyFunc:function(){},
  reset:function(){
    this.values({});
  },
  values:function(data){
    var v,n;
    var els=this.form.getElements();
    var r = new RegExp('^'+this.rootName+'\\[([^\\[\\]]*)\\](?:\\[\\])?$');
    for(var i=0;i<els.length;i++){
      n = r.exec(els[i].name);
      if (!n || !n[1]){continue;}
      n = n[1];
      //TODO: make array values possible to use
      if(n in data){
        v=data[n];
      }else if(n in this.defaultValues){
        v=this.defaultValues[n];
      }else{
        v='';
      }
      if(els[i].match('input[type="checkbox"]')){
        els[i].checked=Boolean(parseInt(v));
      }else if(els[i].match('select')){
        if (els[i].attributes.getNamedItem('multiple')){
          if (!v){v = [];}
          for(var j=0;j<els[i].options.length;j++){
            els[i].options[j].selected = !!(v.indexOf(els[i].options[j].value)>=0);
          }
        } else {
          for(var j=0;j<els[i].options.length;j++){
            if (els[i].options[j].value==v){
              els[i].selectedIndex=j;break;
            }
          }
        }
      }else{
        els[i].value=v;
      }
    }
  },
  title:function(text){
    var e=this.form.down('.xform-title h3');
    if (e){e.innerHTML=text;}
  },
  clearAdvices:function(){
    this.form.select('.advice').each(function(e){
      e.remove();
    });
    this.form.getElements().each(function(e){
      e.removeClassName('invalid');
    });
  },
  validate:function(){
    this.clearAdvices();
    //TODO: make js validation
    return true;
  },
  showAdvice:function(fname, text){
    fname=this.rootName+'['+fname+']';
    var e=false;
    if (this.form[fname]){
      e=$(this.form[fname]);
    } else {
      fname+='[]';
      if (this.form[fname]){ e=$(this.form[fname]); }
    }
    if (e){
      e.addClassName('invalid');
      var a=new Element('div', {'class':'advice'}).update(text);
      e.up().appendChild(a);
    }
  },
  submit:function(){
    if (this.validate()){
      if (this.useAjax){
        this.ajaxSubmit();
      } else {
        this.nativeSubmit();
      }
    }
  },
  nativeSubmit:function(){
    this.form.submit();
  },
  ajaxSubmit:function(){
    if (this.processing){return;}
    this.processing = true;
    var params = this.form.serialize({hash:true});
    Object.extend(params, this.additionalPostData);
    this.form.disable();
    this.stateMethods.start();
    new Ajax.Request(this.form.action, {
      method:this.form.method,
      parameters:params,
      onSuccess:function(t){
        if (!t.responseText){this.stateMethods.fail();return;}
        try {var res=eval('('+t.responseText+')');}
        catch (e){this.stateMethods.fail();return;}
        if (!('error' in res)){this.stateMethods.fail();return;}
        switch(res.error){
          case 0:
            this.stateMethods.success(res);
            return;
          case 1:
            alert(res.error_msg);
            break;
          case 2:
            for(var j in res.validation){
              this.showAdvice(j, res.validation[j]);
            }
            break;
        }
        this.stateMethods.fail();
      }.bind(this),
      onFailure:function(){
        this.stateMethods.fail();
      }.bind(this),
      onComplete:function(){
        this.form.enable();
        this.processing = false;
        this.stateMethods.stop();
      }.bind(this)
    });
  },
  focusAtFirst:function(){
    var els = this.form.getElements();
    if (els.length){
      els[0].focus();
    }
  }
});

var NeumondCurtain = {
  initialize:function(){
    this.celm = new Element('div', {'class':'xcurtain','style':'display:none;'});
    this.loader = new Element('div', {'class':'xcurtain-loader','style':'display:none;'});
    this.loaderCapt = new Element('span');
    document.body.appendChild(this.celm);
    document.body.appendChild(this.loader);
    this.loader.appendChild(this.loaderCapt);
    this.loader.style.zIndex = (this.celm.style.zIndex ? this.celm.style.zIndex: 1000) + 1000;
    Event.observe(window, 'resize', this.wresize.bindAsEventListener(this));
    this.active = {curtain:false, elm:false, loader:false};
    this.wresize();
  },
  realign:function(){
    this.element.style.left = 
      Math.round((document.viewport.getWidth() - this.element.getWidth()) / 2) + 'px';
  },
  realignLoader:function(){
    this.loader.style.left = Math.round((document.viewport.getWidth()-this.loader.getWidth())/2)+'px';
    this.loader.style.top = Math.round((document.viewport.getHeight()-this.loader.getHeight())/2)+'px';
  },
  wresize:function(){
    if (this.active.elm){
      this.realign();
    }
    this.realignLoader();
  },
  check:function(){
    if (!(this.active.loader || this.active.elm)){
      this.celm.hide();
      this.active.curtain = false;
    }
  },
  showCurtain:function(){
    this.celm.show();
    this.active.curtain = true;
  },
  hideLoader:function(){
    this.active.loader = false;
    this.loader.hide();
    this.check();
  },
  hideElement:function(){
    this.active.elm = false;
    this.element.hide();
    this.check();
  },
  showLoader:function(caption){
    if (!this.active.curtain){this.showCurtain();}
    if (caption){this.loaderCapt.innerHTML = caption;}
    this.loader.show();
    this.active.loader = true;
  },
  show:function(element){
    if (!this.active.curtain){this.showCurtain();}
    //set these css before using element there
    //position:absolute;top:[value];background:[value];width:[value];display:none(in style of element);
    this.element = $(element);
    this.realign();
    this.element.style.zIndex = (this.celm.style.zIndex ? this.celm.style.zIndex: 1000) + 1;
    this.element.show();
    this.active.elm = true;
  },
  hide:function(){
    if (this.active.loader){this.hideLoader();}
    if (this.active.elm){this.hideElement();}
    this.celm.hide();
    this.active.curtain = false;
  }
}

NeumondDragController = {
  initialize:function(){
    this.activeDragObject = false;
  },
  eMouseMove:function(e){
    if (false===this.activeDragObject){ return; }
    this.activeDragObject.eMouseMove(e);
  },
  eMouseUp:function(e){
    if (false===this.activeDragObject){ return; }
    this.activeDragObject.eMouseUp(e);
    this.activeDragObject = false;
    Event.stopObserving(document, 'mousemove');
    Event.stopObserving(document, 'mouseup');
  },
  trackObject:function(o,e){
    if (!this.activeDragObject && o.eMouseMove && o.eMouseUp){
      if(e.preventDefault){
        e.preventDefault();
      }
      this.activeDragObject = o;
      document.observe('mousemove', this.eMouseMove.bindAsEventListener(this));
      document.observe('mouseup', this.eMouseUp.bindAsEventListener(this));
    }
  }
}
NeumondDragController.initialize();

var NeumondObservable = Class.create({
  initialize:function(eventList){//comma-delimited list of events
    this.observers = {};
    var x = eventList.split(',');
    
    for(var i=0;i<x.length;i++){
      this.observers[x[i]] = [];
    }
  },
  observe:function(evt, handler){
    if(evt in this.observers){
      this.observers[evt].push(handler);
    }
  },
  stopObserving:function(evt){
    if(evt in this.observers){
      this.observers[evt].length = 0;
    }
  },
  fireEvent:function(name, data){
    if(name in this.observers){
      for(var i=0;i<this.observers[name].length;i++){
        this.observers[name][i](data);
      }
    }
  }
});

var NeumondSlider = Class.create(NeumondObservable, {
  initialize:function($super, elm, config){
    $super('change,startChange,finishChange');
    this.elm = $(elm);
    this.createElements();
    if (!config) {config = {};};
    this.holdF = false;
    this.dragPt = 0;
    this.pos = 0;
    this.value = 0;
    this.minValue = 'minValue' in config ? config.minValue : 0;
    this.maxValue = 'maxValue' in config ? config.maxValue : 100;
    this.maxPos = this.ctnrElm.getWidth()-this.thumbElm.getWidth();
    this.setValue('value' in config ? config.value : 0);
    this.createEvents();
    this.enabled = true;
  },
  observe:NeumondObservable.prototype.observe.wrap(function(origF, evt, handler){
    origF(evt, handler);
    if(evt=='change'){
      this.fireEvent(evt, {value:this.value});
    }
    if(evt=='startChange' && this.holdF){
      this.fireEvent(evt, {value:this.value});
    }
  }),
  createElements:function(){
    this.ctnrElm=new Element('div', {'class':'n-slider'});
    this.elm.appendChild(this.ctnrElm);
    this.thumbElm=new Element('div', {'class':'n-slider-thumb'});
    this.ctnrElm.appendChild(this.thumbElm);
    this.focusElm=new Element('a', {'class':'n-slider-focus','href':'javascript:;'});
    this.thumbElm.appendChild(this.focusElm);
  },
  createEvents:function(){
    this.focusElm.observe('mousedown', this.eMouseDown.bindAsEventListener(this));
  },
  eMouseDown:function(e){
    if (!this.enabled) {return;}
    this.dragPt = e.pointerX();
    this.holdF = true;
    NeumondDragController.trackObject(this,e);
    this.fireEvent('startChange', {'value':this.value});
  },
  eMouseUp:function(e){
    this.holdF = false;
    this.fireEvent('finishChange', {'value':this.value});
  },
  eMouseMove:function(e){
    if (this.holdF && (e.pointerX()!=this.dragPt)){
      var oldPos = this.pos;
      this.trySetPos(this.pos + e.pointerX() - this.dragPt);
      this.dragPt = this.dragPt + this.pos - oldPos;
    }
  },
  setConstraints:function(min,max){
    this.minValue=min;
    this.maxValue=max;
    this.setValue(this.value);
  },
  setValue:function(x){
    var hf=this.holdF;
    this.holdF = false;
    this.doValueChange(x);
    this.rePaintByValue();
    this.holdF = hf;
  },
  doValueChange:function(nv){
    if (nv<this.minValue){nv=this.minValue;}
    if (nv>this.maxValue){nv=this.maxValue;}
    if (nv!=this.value){
      this.fireEvent('change', {value:nv});
      this.value = nv;
      this.rePaintByValue();
      return true;
    }
    return false;
  },
  trySetPos:function(newPos){
    if (newPos<0){newPos=0;}
    if (newPos>this.maxPos){newPos=this.maxPos;}
    var v = Math.round(newPos / this.maxPos * (this.maxValue - this.minValue) + this.minValue);
    if (this.doValueChange(v)){
      this.rePaintByValue();
    }
  },
  rePaintByValue:function(){
    this.pos = Math.round((this.value - this.minValue) / (this.maxValue - this.minValue) * this.maxPos);
    this.thumbElm.style.left = this.pos + 'px';
  },
  enable:function(){
    this.enabled = true;
    this.elm.removeClassName('disabled');
  },
  disable:function(){
    this.enabled = false;
    this.elm.removeClassName('enabled');
  }
});

var NeumondPager = Class.create(NeumondObservable, {
  initialize:function($super, elm, config){
    $super('page');
    if (!config){config = {};}
    this.elm = $(elm);
    this.visibleCount = 'visibleCount' in config ? config.visibleCount : 10;
    this.currentPage = false;
    this.createElements();
    this.setPageCount('pageCount' in config ? config.pageCount : 200);
    this.enabled = true;
  },
  createElements:function(){
    this.ctnrElm = new Element('div', {'class':'n-paging'});
    this.elm.appendChild(this.ctnrElm);
    this.pagesElm = new Element('div', {'class':'n-paging-items'});
    this.ctnrElm.appendChild(this.pagesElm);
    this.listElm = new Element('ul');
    this.pagesElm.appendChild(this.listElm);
    this.itemElms = [];
    var ielm, aelm;
    for(var i=0;i<this.visibleCount;i++){
      ielm = new Element('li');
      aelm = new Element('a', {'href':'javascript:;'});
      ielm.appendChild(aelm);
      this.listElm.appendChild(ielm);
      this.itemElms.push(aelm);
      aelm.observe('click', this.selectPage.bindAsEventListener(this));
    }
    this.sliderElm = new Element('div', {'class':'n-paging-slider'});
    this.ctnrElm.appendChild(this.sliderElm);
    this.slider = new NeumondSlider(this.sliderElm);
    this.slider.observe('change', function(e){
      if(this.enabled){
        this.scroll(e);
      }
    }.bind(this));
  },
  selectPage:function(evt){
    if (this.enabled){
      this.setCurrentPage(evt.element().nId);
    }
  },
  setPageCount:function(x){
    this.pageCount = x;
    if (x<=this.visibleCount){
      for(var i=0;i<this.visibleCount;i++){
        if(i<x){
          this.itemElms[i].up().show();
        }else{
          this.itemElms[i].up().hide();
        }
      }
      this.slider.setValue(0);
      this.sliderElm.hide();
    } else {
      for(var i=0;i<this.visibleCount;i++){
        this.itemElms[i].up().show();
      }
      this.sliderElm.show();
      this.slider.setConstraints(0, x-this.visibleCount);
    }
  },
  scroll:function(e){
    for(var i=0;i<this.visibleCount;i++){
      this.itemElms[i].innerHTML = (e.value+i+1);
      this.itemElms[i].nId = e.value+i+1;
      if (e.value+i+1==this.currentPage){
        this.itemElms[i].addClassName('selected');
      } else {
        this.itemElms[i].removeClassName('selected');
      }
    }
  },
  setCurrentPage:function(i,preventFiring){
    if (i!=this.currentPage){
      this.currentPage = i;
      this.scroll({value:this.slider.value});
      if (!preventFiring){ this.fireEvent('page', {id:i}); }
    }
  },
  enable:function(){
    this.enabled = true;
    this.elm.removeClassName('disabled');
  },
  disable:function(){
    this.enabled = false;
    this.elm.removeClassName('enabled');
  }
});

var NeumondList = Class.create({
  initialize:function(objects, elms, funcs, msgs, initialData){
    
    //objects:{form, pager}
    this.objects = objects;
    
    //elms:{formContainer, list}
    this.elms = {};
    for(var i in elms){
      this.elms[i] = $(elms[i]);
    }
    
    this.funcs = funcs;
    //itemUrl:function(o,id)
    //deleteUrl:function(o)
    //updateUrl:function(o)
    //createRow:function(o,registerToggleFunc)
    //updateRow:function(o,row,data)
    //deleteRow:function(o,row)
    //rowSelection:function(o,row,state)
    //rowVisibility:function(o,row,state)
    //selectionCounterHTML:function(o,num)
    //overlayVisibility:function(o,state)
    
    //msgs:{addingTitle, editingTitle, initiateError, initiateLoader, savingError, savingLoader, deleteConfirmation,
    //  deletingError, deletingLoader}
    this.msgs = msgs;
    
    
    this.objects.form.stateMethods = {
      start:this.listeners.form.start.bind(this),
      stop:this.listeners.form.stop.bind(this),
      fail:this.listeners.form.fail.bind(this),
      success:this.listeners.form.success.bind(this)
    }
    
    this.setItemCount(initialData.pageSize);
    this.updateWholeList(initialData);
    this.objects.pager.observe('page', this.listeners.pager.updateList.bind(this));
    this.funcs.overlayVisibility(this, false);
    this.busy = false;
  },
  itemElms:[], //row elements, stored values: nId{false|number}, nSelected{false|true}
  selectionInverse:false,
  selectionIds:{},
  globalItemCount:0,
  setItemCount:function(x){
    var e;
    if(x>this.itemElms.length){
      while(this.itemElms.length<x){
        this.nTempRowId = this.itemElms.length;
        e = this.funcs.createRow(this, function(a,evt,tp){
          if (tp=='select'){
            a.observe(evt, this.listeners.items.toggle.bindAsEventListener(this));
          }
          if (tp=='edit'){
            a.observe(evt, this.listeners.items.edit.bindAsEventListener(this));
          }
          a.nRowId = this.nTempRowId;
        }.bind(this));
        e.nSelected = false;
        e.nId = false;
        this.itemElms.push(e);
        this.funcs.rowVisibility(this, e, false);
      }
      delete this.nTempRowId;
    }else{
      while(this.itemElms.length>x){
        this.funcs.deleteRow(this, this.itemElms.pop());
      }
    }
  },
  updateWholeList:function(data){
    if (!('items' in data)){
      return;
    }
    if (data.items instanceof Array){ data.items = {}; }
    if ('itemCount' in data){
      this.globalItemCount=data.itemCount;
    }
    var j=0;
    for(var i in data.items){
      if (j>=this.globalItemCount || j>=this.itemElms.length){break;}
      this.funcs.updateRow(this, this.itemElms[j], data.items[i]);
      this.itemElms[j].nId = i;
      this.funcs.rowVisibility(this, this.itemElms[j], true);
      
      var slctd = (i in this.selectionIds);
      if (this.selectionInverse) {slctd = !slctd;}
      this.itemElms[j].nSelected = slctd;
      this.funcs.rowSelection(this, this.itemElms[j], slctd);
      
      j++;
    }
    while(j<this.itemElms.length){
      this.itemElms[j].nId = false;
      this.funcs.rowVisibility(this, this.itemElms[j], false);
      j++;
    }
    if (this.itemElms.length){
      this.objects.pager.setPageCount(Math.ceil(data.itemCount/this.itemElms.length));
    } else {
      this.objects.pager.setPageCount = 0;
    }
    if ('currentPage' in data){
      this.objects.pager.setCurrentPage(data.currentPage, true);
    }
    this.updateSelectionCounter();
  },
  updateFormAdditionalData:function(){
    this.objects.form.additionalPostData = {
      'update[limit]':this.itemElms.length,
      'update[page]':this.objects.pager.currentPage
    }
  },
  getSelectedCount:function(){
    var x=0;
    for (var i in this.selectionIds){x++;}
    if (this.selectionInverse){
      x = this.globalItemCount - x;
    }
    return x;
  },
  clearSelection:function(){
    this.selectionIds = {};
    this.selectionInverse = false;
  },
  updateSelectionCounter:function(){
    this.elms.selectCount.innerHTML = this.funcs.selectionCounterHTML(this, this.getSelectedCount());
  },
  //public functions
  selectAll:function(){
    this.selectionInverse = true;
    delete this.selectionIds;
    this.selectionIds = {};
    this.selectVisible();
  },
  deselectAll:function(){
    this.clearSelection();
    this.deselectVisible();
  },
  selectVisible:function(){
    var it;
    for(var i=0;i<this.itemElms.length;i++){
      it=this.itemElms[i];
      if(it.nId===false){continue;}
      it.nSelected = true;
      this.funcs.rowSelection(this, it, true);
      if(!this.selectionInverse){
        this.selectionIds[it.nId] = true;
      }else{
        if (it.nId in this.selectionIds){
          delete this.selectionIds[it.nId];
        }
      }
    }
    this.updateSelectionCounter();
  },
  deselectVisible:function(){
    var it;
    for(var i=0;i<this.itemElms.length;i++){
      it=this.itemElms[i];
      if(it.nId===false){continue;}
      it.nSelected = false;
      this.funcs.rowSelection(this, it, false);
      if(this.selectionInverse){
        this.selectionIds[it.nId] = true;
      }else{
        if (it.nId in this.selectionIds){
          delete this.selectionIds[it.nId];
        }
      }
    }
    this.updateSelectionCounter();
  },
  addItem:function(){
    this.itemId = false;
    this.updateFormAdditionalData();
    this.objects.form.clearAdvices();
    this.objects.form.values({});
    this.objects.form.title(this.msgs.addingTitle);
    NeumondCurtain.show(this.elms.formContainer);
    this.objects.form.focusAtFirst();
  },
  editItem:function(id){
    this.itemId = id;
    NeumondCurtain.showLoader(this.msgs.initiateLoader);
    new Ajax.Request(this.funcs.itemUrl(this, id), {
      onSuccess:function(t){
        var d;
        try{
          d = eval('('+t.responseText+')');
          if (d.error || !d.data){throw "invalid data";};
        }catch(e){this.showInitiateError();return;}
        this.updateFormAdditionalData();
        this.objects.form.clearAdvices();
        this.objects.form.values(d.data);
        this.objects.form.title(this.msgs.editingTitle.replace('%id%', this.itemId));
        NeumondCurtain.show(this.elms.formContainer);
        NeumondCurtain.hideLoader();
        this.objects.form.focusAtFirst();
      }.bind(this),
      onFailure:function(){
        this.showInitiateError();
      }.bind(this),
      onComplete:function(){
      }.bind(this)
    });
  },
  updateList:function(){
    this.funcs.overlayVisibility(this, true);
    this.objects.pager.disable();
    new Ajax.Request(this.funcs.updateUrl(this), {
      parameters:{limit:this.itemElms.length,page:this.objects.pager.currentPage},
      onSuccess:function(t){
        var d;
        try{
          d = eval('('+t.responseText+')');
          if (d.error || !('itemCount' in d) || !('items' in d)){throw "invalid data";};
        }catch(e){this.showUpdateError();return;}
        this.updateWholeList(d);
      }.bind(this),
      onFailure:function(){
        this.showUpdateError();
      }.bind(this),
      onComplete:function(){
        this.objects.pager.enable();
        this.funcs.overlayVisibility(this, false);
      }.bind(this)
    });
  },
  deleteSelected:function(){
    var c=this.getSelectedCount();
    if (c && confirm(this.msgs.deleteConfirmation.replace('%count%', c))){
      NeumondCurtain.showLoader(this.msgs.deletingLoader);
      var p=[];
      for (var i in this.selectionIds){
        p.push(i);
      }
      new Ajax.Request(this.funcs.deleteUrl(this), {
        parameters:{
          'ids[]':p,
          'invert':(this.selectionInverse + 0),
          'update[limit]':this.itemElms.length,
          'update[page]':this.objects.pager.currentPage
        },
        method:'post',
        onSuccess:function(t){
          var d;
          try{
            d = eval('('+t.responseText+')');
            if (d.error){throw d.error_msg;};
          }catch(e){this.showDeletingError();return;}
          this.clearSelection();
          this.updateSelectionCounter();
          this.updateWholeList(d.listUpdate);
        }.bind(this),
        onFailure:function(){
          this.showDeletingError();
        }.bind(this),
        onComplete:function(){
          NeumondCurtain.hide();
        }.bind(this)
      });
    }
  },
  //end of public functions
  processingError:function(){
    this.clearSelection();
    this.updateSelectionCounter();
    this.updateWholeList({itemCount:this.globalItemCount,items:{}});
  },
  showInitiateError:function(){
    NeumondCurtain.hide();
    this.itemId = false;
    alert(this.msgs.initiateError);
  },
  showUpdateError:function(){
    //this.updateWholeList({itemCount:this.globalItemCount,items:{}});
    alert(this.msgs.updateError);
  },
  showDeletingError:function(){
    this.processingError();
    alert(this.msgs.deletingError);
  },
  listeners:{
    form:{
      start:function(){
        NeumondCurtain.showLoader(this.msgs.savingLoader);
      },
      stop:function(){
        NeumondCurtain.hideLoader();
      },
      fail:function(){
      },
      success:function(data){
        this.updateWholeList(data.listUpdate);
        NeumondCurtain.hide();
      }
    },
    items:{
      toggle:function(evt){
        var e=evt.element();
        var it=this.itemElms[e.nRowId];
        it.nSelected = !it.nSelected;
        this.funcs.rowSelection(this, it, it.nSelected);
        if(this.selectionInverse + it.nSelected == 1){
          this.selectionIds[it.nId] = true;
        }else{
          if (it.nId in this.selectionIds){
            delete this.selectionIds[it.nId];
          }
        }
        this.updateSelectionCounter();
      },
      edit:function(evt){
        this.editItem(this.itemElms[evt.element().nRowId].nId);
      }
    },
    pager:{
      updateList:function(evt){
        this.updateList();
      }
    }
  }
});

document.observe('dom:loaded', function(){
  NeumondCurtain.initialize();
});