/**
 * What I need:
 * 
 * x ObjectOriented framework
 * x Browser detection
 * x ElementPositioning
 * x CrossBrowser events
 * x DOMReady event
 *
 * size - < 20K
 */
 
/**
 * static class with some useful methods
 * "nest" for all other classes
 */
 
(function (){ //to prevent this loading several times
if (typeof(window.rei)!=='undefined') return;

window.rei = function(s){ //
  if (typeof(s)==='string'){
    return rei.id(s);
  }
  else {
    return rei.get(s);
  }
} 
  
rei.rei_base = ''; //domain for images

/**
 * returns Element if element is exists
 */
rei.id = function(id){
     return rei.Element.id(id);
};
   
rei.get = function(el){
     return rei.Element.get(el);
};
   
/**
 * copy all properties except prototype
 */
rei.copy = function(dst,src){
    for(key in src){
        if (key!=='prototype'){
          //if (typeof(dst[key])=='function'){ //pecerve old value as 'base' property of this function
          //  var old = dst[key];
          //  dst[key] = src[key];
          //  dst[key].base = old; 
          //}
          //else {
            dst[key] = src[key];
          //}
        }
    }
};

rei.each = function(obj,fn){
        for(key in obj){
            if (obj.constructor.prototype[key]!==obj[key]){
                if (fn(key,obj[key])===false) break;
            }
        }
};
   
rei.request = function(){
        try {
            return new ActiveXObject('MSXML2.XMLHTTP');
        }
        catch(e){};
        try {
            return new XMLHttpRequest();
        }
        catch(e){}
};
      
rei.loadJS = function(url){
        r = rei.request();
        r.open('get',url,false);
        r.send(null);
        if (window.execScript){
            window.execScript(r.responseText);
        } else {
            var script = document.createElement('script');
            script.setAttribute('type', 'text/javascript');
            script.text = r.responseText;
            var head = document.getElementsByTagName('head')[0];
            head.appendChild(script);
            head.removeChild(script);
        }
}; 
/**
 * experimental
 */    
rei.bind = function(fn,obj){
      return function(){
        return fn.apply(obj,arguments);
      }
}      

 
 
 /**
  * inhetitance in JavaScript
  */
rei.Class = function(){};
rei.base = null;
rei.Class.extend = function(obj){
    var this1 = this;
    var obj1 = function(){
        if (obj.construct!==undefined){
            obj.construct.apply(this,arguments);
        }
        else {
            if (this1.prototype.construct!==undefined){
                this1.prototype.construct.apply(this,arguments);
            }
        }
    }

    rei.copy(obj1.prototype,this.prototype);
    rei.copy(obj1.prototype,obj);
    rei.copy(obj1,this);

    //this is not good - found the better way to implement this
    //should be removed?
    var old_constr = this1.prototype.construct;
    obj1.prototype.base = function(){
        if (this1.prototype.base){
            this.base = this1.prototype.base;
        }
        old_constr.apply(this,arguments);    
    };
    return obj1;
}

rei.Class.implement = function(obj){
    rei.copy(this.prototype,obj);
}   
 
 
/**
 * cross browser manitulations with element properties, events, styles
 */
rei.Element = rei.Class.extend({
    origin:null,
    dom:null, //the same as origin
    events:{},
    
    construct:function(origin){
        origin._z = this;
        this.events = {};
        this.dom = this.origin = origin; //remove origin in the future
        this.shortEvents(['click','mousedown','mouseup','mousemove','mouseover','mouseout','focus','blur','change','keypress','keydown','keyup']);
        this.css = this.style;
        this.p = this.prop;
    },
       
    shortEvents:function(names){
        var i = 0;
        for(i=0;i<names.length;i++){
            this[names[i]] = new Function("fn","remove","this.on('"+[names[i]]+"',fn,remove)");
        }
    },
       
   /**
    * add event listener
    */   
    on:function(eventname,func,remove){
            if (remove) return this.removeEvent(eventname,func);
            if (!this.events[eventname]) {  
                this.events[eventname] = [];
                var t1 = this;
                if (this.origin.addEventListener){
                    this.origin.addEventListener(eventname, function(e){t1.callEvent(eventname,e)},false);
                }
                else {
                    this.origin.attachEvent('on'+eventname, function(e){t1.callEvent(eventname,e)});
                }
            };
            this.events[eventname].push(func);
    },         
   
    callEvent:function(eventname,e){
        var hooks;
        if (e){
            e = rei.Event.get(e);
        }
        else {
            e = rei.Event.get(window.event);
        }
        e.owner = this;
        if (hooks = this.events[eventname]) {
            for(var i=0;i<hooks.length;i++){
                hooks[i].apply(this,[e]);
            }
        }
    },    
    
    fireEvent:function(eventname,e){
        e = new rei.Event();
        if (hooks = this.events[eventname]) {
            for(var i=0;i<hooks.length;i++){
                hooks[i].apply(this,[e]);
            }
        }
    },
   
  /**
   * removes event
   */
    removeEvent:function(eventname,func){
        if (this.events[eventname]){
            var found=false,i,len = this.events[eventname].length;
            for (i=0;i<len;i++){                
                if (this.events[eventname][i]==func){
                    found = i;                    
                    break;
                }
            }
            if (found!==false) {
                this.events[eventname].splice(found,1);
            }
        }
    },
   /**
    * sets or gets style
    * to make all similar functions to be built similar way?
    */
    style:function(key_name,key_value){
        if(arguments.length==2){
            //try {
                this.setStyle(key_name,key_value);
            //}
            //catch(e){
            //    alert("wrong property:"+key_name+":"+key_value);
            //}
        }
        else {
            if (typeof(key_name)=='object'){
                var this1 = this;
                rei.each(key_name,function(key){
                    this1.style(key,key_name[key]);
                });
            } 
            else {
                return this.getStyle(key_name);
            }
        }
    },    
    setStyle:function(key_name,key_value){
        if ((key_name=='opacity')){
            if (rei.Browser.trident){
                if ((key_value==1) && (typeof(this.origin.filters['alpha'])!=='undefined')){//remove alpha filter
                    this.origin.filters['alpha'].enabled = false;
                }
                else {
                    this.origin.style.filter = "alpha(opacity:"+(key_value*100)+")";
                }
            }
            else {
                this.origin.style[key_name] = key_value;
            }
        }
        else if((key_name=='float') || (key_name=='cssFloat') || (key_name=='styleFloat')){
            if (rei.Browser.trident){
                this.origin.style.styleFloat = key_value;
            }
            else {
                this.origin.style.cssFloat = key_value;
            }
        }
        else {
            this.origin.style[key_name] = key_value;
        }
    },
   /*
    * returns style
    * TODO make it crossbrowser too?
    */
    getStyle:function(key_name){
        return this.origin.style[key_name];
    },
    
   /**
    * sets or retrieve property
    *
    */
    prop:function(key_name,key_value){
        if(arguments.length==2){
            this.setProp(key_name,key_value);
                
        }
        else {
            if (typeof(key_name)=='object'){
                var this1 = this;
                rei.each(key_name,function(key){
                    this1.prop(key,key_name[key]);
                });
            } 
            else {
                return this.getProp(key_name);
            }
        }    
    },

   /**
    * sets property
    */
    setProp:function(key_name,key_value){
        try {
            this.origin[key_name] = key_value;        
        }
        catch(e){
            alert("wrong property:"+key_name+":"+key_value);
        }
    },
   /*
    * returns property
    * TODO make it crossbrowser too?
    */
    getProp:function(key_name){
        if (key_name=='text') return (this.origin['text'] || this.origin['innerText']); 
        return this.origin[key_name];
    },    
    //credits of prototype lib
    hasClass:function(className){
        return rei.Element.hasClass(this.origin.className,className)
    },
    addClass:function(className){
        if (!this.hasClass(className)){
            this.origin.className = this.origin.className+' '+className;
        }
    },
    removeClass:function(className){
        this.origin.className = this.origin.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
    },
        
    putBefore:function(el){
        if (el.origin) el = el.origin //it's rei.Element
        el.parentNode.insertBefore(this.origin,el);
    },
            
    putAfter:function(el){
        if (el.origin) el = el.origin //it's rei.Element
        if (el.nextSibling){
            el.parentNode.insertBefore(this.origin,el.nextSibling);
        }
        else {
            el.parentNode.appendChild(this.origin);
        }
    },
    putInto:function(el){
        if (el.origin) el = el.origin //it's rei.Element
        el.appendChild(this.origin);
    },
    //the same as ordinal appand child but argument can be also an array
    appendChild:function(el){
        if (el.origin) el = el.origin //it's rei.Element
        if (el.constructor === Array) {
          var i,len = el.length;
          for(i=0;i<len;i++){
            this.appendChild(el[i]);
          }
          return;
        }
        this.origin.appendChild(el);
    },
    remove:function(el){
        if (el) {
            if (el.origin)  el = el.origin //it's rei.Element
        }
        else el = this.origin;        
        el.parentNode.removeChild(el);
    }
});
 
rei.Element.get = function (e){
    if (e.origin) return e;
 	if (e._z!==undefined) return e._z;
    return new this(e); //I cant believe its working!
};

rei.Element.id = function(id){
    var e = document.getElementById(id);
    if (e){
       return this.get(e);
    }
    return null;
}

rei.Element.hasClass = function(elementClassName,className){
    return (elementClassName.length > 0 && (elementClassName == className ||
            new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
}

rei.Element.create = function(name,props,styles,children){
    var el = this.get(document.createElement(name));
    if (props) el.prop(props);
    if (styles) el.style(styles);
    if (children) el.appendChild(children)
    return el;
};

/**
 * 
 */
rei.Element.implement({
    getCoordinates:function(){
        function getPOL(obj){
            var x;
            x = obj.offsetLeft;
            if (obj.offsetParent != null)
            x += getPOL(obj.offsetParent);
            return x;       
        };
        function getPOT(obj){
            var y;
            y = obj.offsetTop;
            if (obj.offsetParent != null)
            y += getPOT(obj.offsetParent);
            return y;
        };
        return {top:getPOT(this.origin),left:getPOL(this.origin),width:this.origin.clientWidth,height:this.origin.clientHeight};
    }
});

/**
 * XHTML Selector
 */
 
(function(){
   /**
    * if element is complies with search it get applied to res
    *
    */
    function appendResult(search,el,res,constr){//private function
        var fail = false;
        rei.each(search, function(key){
            if (key=='depth') return;
            if (key=='tag' || key=='tagName') { //tag name is case insensitive
                if (el.tagName.toUpperCase()!=(search[key]).toUpperCase()){
                  fail = true;
                  return false;
                }
                return;
            }
            if (key=='class'){
                if (!rei.Element.hasClass(el.className,search['class'])){
                  fail = true;
                  return false;
                }
                return;
            }
            if (!el[key]) {fail = true; return false;};
            if (el[key]!==search[key]) {fail = true; return false;};
        });
        if (fail) return;
        res.elements.push(constr.get(el));
        
    };
    
    function selectElement(el, search, res, constr){
        constr = constr || rei.Element;
        
        var tagname = search['tagName'] || search['tag'];// || 'div'; //shorthands
        var depth = search['depth'] || -1; //-1 recursive, 1 - only first level childs, 2 frist level and second level and so on
        
        if ((tagname) && (depth===-1)){
            var els = el.getElementsByTagName(tagname);
            var i,els_l = els.length;        
            for(i=0;i<els_l;i++){
                appendResult(search,els[i],res,constr);     
            }
        }
        else { //all elements
            var child;
            for(child = el.firstChild;child;child = child.nextSibling){
                if (child.nodeType!=1) continue; //need only elements
                appendResult(search,child,res,constr);
                if (depth === -1) selectElement(child, search, res, constr);
                else if (depth > 1) {search[depth] = depth--; selectElement(child, search, res, constr)} 
            }
        }
    };
    
    rei.Element.implement({
        select:function(search,result,constr){
            var res = result || (new rei.CompoundElement());
            selectElement(this.origin,search,res,constr);
            return res;
        },
        children:function(search,result,constr){
            search['depth'] = 1;
            return this.select(search,result,constr); 
        }    
    });
    
    rei.Element.select = function(search){
    	return rei.Document.select(search,null,this); //
    }
})();
 
rei.select = function(search){
    return rei.Document.select(search);
}
 
rei.CompoundElement = rei.Class.extend({
    elements:[], //list of rei.Element items
    construct:function(){
        this.elements = [];
    },
   /**
    * selects among all children
    */
    select:function(search, result){
        var result = result || new rei.CompoundElement();
        var i,len = this.elements.length;
        for(i=0;i<len;i++){
            this.elements[i].select(search, result);
        }
        return result;
    },
   /**
    * selects among all children
    */
    children:function(search, result){
        search['depth'] = 1;
        return this.select(search,result1); 
    },    
   /**
    * call function for each element
    */
    each:function(fn){
        //var args = args || []
        var i,len = this.elements.length;
        for(i=0;i<len;i++){
            if (fn.apply(this.elements[i],[this.elements[i]])===false) 
                break;
        }
    },
   /**
    *
    */
    get:function(key){
        return this.elements[key];
    },
   /**
    * assign for all properties of element
    */
    prop:function(name,value){
    	this.each(function(el){el.prop(name,value)});
    }
}); 
/**
 * 
 */ 
(function(){
    var events = {
        on:function(eventname,fn,remove){
            this.each(function(el){
                el.on(eventname,fn,remove);
            })
        },      
        addClass:function(className){
            this.each(function(el){
                el.addClass(className);
            })
        },
        removeClass:function(className){
            this.each(function(el){
                el.removeClass(className);
            })
        },
        setProp:function(prop,value){
            this.each(function(el){
                el.setProp(prop,value);
            })
        }               
    }
    rei.each(['click','mousedown','mouseup','mousemove','mouseover','mouseout','focus','blur','change','keypress','keydown','keyup'],
        function(key,event){
            events[event] = new Function("fn","remove","this.on('"+[event]+"',fn,remove)");
        }
    )
    rei.CompoundElement.implement(events);
})();

 
/**
 * cross browser event object
 */

rei.Event = rei.Class.extend({
    origin:null,
    owner:null,
    construct:function(e){
        this.origin = e;
        if (!e) return;
        e._z = this;
     
        this.shift = e.shiftKey,
        this.ctrl  = e.ctrlKey,
        this.alt   = e.altKey,
        this.meta  = e.metaKey
    },
   /**
    * get Page coordinates
    */
    page:function(){
        var doc = document;
            doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.documentElement : doc.body;
        
        return {
            x: this.origin.pageX || this.origin.clientX + doc.scrollLeft,
            y: this.origin.pageY || this.origin.clientY + doc.scrollTop
        };
    },
   /**
    * get key code
    */
    keyCode:function(){
        return this.origin.which || this.origin.keyCode;
    },
    
    keyCodes:{
        '13':'enter',
        '38':'up',
        '40':'down',
        '37':'left',
        '39':'right',
        '27':'esc',
        '32':'space',
        '8':'backspace',
        '9':'tab',
        '46':'delete'
    },
    
   /**
    *
    */
    key:function(){
        var code = this.keyCode();
        var key = this.keyCodes[code];
        if (this.origin.type == 'keydown'){
                var fKey = code - 111;
                if (fKey > 0 && fKey < 13) key = 'f' + fKey;
        }
        key = key || String.fromCharCode(code).toLowerCase();
        return key;    
    },
   /**
    * stops booble
    */
    stop:function(){
        if (this.origin.stopPropagation){
            this.origin.stopPropagation();
        }
        else {
            this.origin.cancelBubble = true;//ie
        }
    },
   /**
    * cancels default behaviour
    */
    cancel:function(){
        if (this.origin.stopPropagation){
            this.origin.preventDefault();
        }
        else {
            this.origin.returnValue = false;//ie
        }
    },
    halt:function(){
        this.stop();
        this.cancel();
    }
});

rei.Event.get = function(e){
  if (e._z) return e._z;
  return new rei.Event(e);
}

 
/**
 * Browser detection
 *
 */
rei.Browser = {	
	platform: (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase(),
	version: (navigator.userAgent.toLowerCase().match(/.+(?:rv|it|ra|ie|me)[\/: ]([\d.]+)/) || [-1,-1])[1]
};

if (window.opera) rei.Browser.presto = true;
else if (window.ActiveXObject) rei.Browser.trident = true;
else if (!navigator.taintEnabled) rei.Browser.webkit = true;
else if (document.getBoxObjectFor != null) rei.Browser.gecko = true;

/**
 * domready event
 */
rei.Document = {
    isReady:false,
    queue:[],    
    bind:function(){
		// Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
		// If IE is used and is not in a frame
		// Continually check to see if the document is ready
		if ( rei.Browser.trident && window == top ) (function(){
			if (this.isReady) return;
			try {
				// If IE is used, use the trick by Diego Perini
				// http://javascript.nwbox.com/IEContentLoaded/
				document.documentElement.doScroll("left");
			} catch( error ) {
				setTimeout( arguments.callee, 0 );
				return;
			}
			// and execute any waiting functions
			rei.Document.invokeQueue();
		})();
		if ( rei.Browser.presto || rei.Browser.webkit ) {
			var numStyles;
			(function(){
				if (this.isReady) return;
				if ( document.readyState != "loaded" && document.readyState != "complete" ) {
					setTimeout( arguments.callee, 0 );
					return;
				}
				rei.Document.invokeQueue();
			})();
			return;
		}
		if (document.addEventListener){
  			document.addEventListener( "DOMContentLoaded", function(){rei.Document.invokeQueue()}, false );
		}
    },
    invokeQueue:function(){
    	this.isReady = true;
      	var i;
      	for (i=0;i<this.queue.length;i++){
        	var f = this.queue[i];
        	f();
      	}
    },
    ready:function(f){
		if (this.isReady) {
   	 		f();
    	}
    	else {
	  		this.queue.push(f);
    	}
  	},
  	getHeight:function(){
  	    var de = this.dom.body.parentNode;
        var db = this.dom.body;
        //return ((db.clientHeight>de.clientHeight)?db.clientHeight:de.clientHeight);
        return ((db.scrollHeight>de.scrolleight)?db.scrollHeight:de.scrollHeight);
  	},
  	getWidth:function(){
        var de = this.dom.body.parentNode;
        var db = this.dom.body;
        //return ((db.clientWidth>de.clientWidth)?db.clientWidth:de.clientWidth);  	
        return ((db.scrollWidth>de.scrollWidth)?db.clientWidth:de.scrollWidth);
  	},
  	getClientHeight:function(){
        var de = this.dom.body.parentNode;
        var db = this.dom.body;
        if (window.opera) {
            return db.clientHeight;     
        }
        if (document.compatMode=='CSS1Compat'){
            return de.clientHeight;     
        }
        else {
            return db.clientHeight;
        }  	
  	},
  	getClientWidth:function(){
        var de = this.dom.body.parentNode;
        var db = this.dom.body;
        if(window.opera){
            return db.clientWidth;
        }
        if (document.compatMode=='CSS1Compat'){
            return de.clientWidth;
        }
        else {
            return db.clientWidth;
        }  	
  	}
}

rei.copy(rei.Document,new rei.Element(document));
rei.Document.bind();
rei.document = rei.Document;

/**
 * debugger
 */
rei.console = {
  log:function(msg){
    if (typeof(console)!=='undefined'){
        console.log("%s",msg);
    } 
    else {
        if (typeof Debugger !=='undefined'){
            Debugger.trace(msg);
        }
    }
  }
}

rei.log = function(m){
  rei.console.log(m);
};

/**
 * inputs
 * thanks to prototype
 */
rei.Form = rei.Element.extend({
    construct:function(owner){
        //this.base(owner);
		owner._zf = this;
    	this.events = {};
    	this.dom = this.origin = owner; //remove origin in the future
    	this.shortEvents(['click','mousedown','mouseup','mousemove','mouseover','mouseout','focus','blur','change','keypress','keydown','keyup']);
    	this.css = this.style;
    	this.p = this.prop;
    },
    getValues:function(){
        //var objForm;
        var submitDisabledElements=false;
        if(arguments.length > 1 && arguments[1]==true)
            submitDisabledElements=true;var prefix="";
        if(arguments.length > 2)
            prefix=arguments[2];
        var values = {};
            var formElements=this.origin.elements;
            for(var i=0;i < formElements.length;i++){
                if(!formElements[i].name) continue;
                if(formElements[i].name.substring(0,prefix.length)!=prefix) continue;
                if(formElements[i].type && (formElements[i].type=='radio' 
                || formElements[i].type=='checkbox') && formElements[i].checked==false) continue;
                if(formElements[i].disabled && formElements[i].disabled==true && submitDisabledElements==false) continue;
                var name=formElements[i].name;
                if(name){
                    values[name] = formElements[i].value;
                }
            }
        //}
        return values;
    }
});

rei.Form.get = function (e){
    if (e.origin) return e;
 	if (e._zf!==undefined) return e._zf;
    return new this(e);
};


(function(){
    var events = {}
    rei.each(['submit','reset'],
        function(key,event){
            events[event] = new Function("fn","remove","this.on('"+[event]+"',fn,remove)");
        }
    )
    rei.Form.implement(events);
})();



/**
 * Delayer function execution with delay
 * autostart defaultvalue is = true
 */

rei.delay = function(fn,timeout,autostart,repeat){
    var d = new rei.Delayer(fn, timeout,(autostart===false?false:true), repeat);
    return d;
};

rei.Delayer = rei.Class.extend({
    fn:null,
    delay:null,
    repeat:false,
    timeout:null,
    construct:function(fn, delay, autostart, repeat){
        this.fn = fn;
        this.delay = delay;
        this.repeat = repeat;
        if (autostart) this.call();
    },
    start:function(){
        this.stop();
        this.call();
    },
    call:function(){
        var this1 = this;
        this.timeout = window.setTimeout(function(){
            if (this1.repeat && (this1.timeout!==null)){ //if timeout==null that means amimation was stoppend
                this1.call();
            }
            else {
                this1.timeout = null;
            }
            this1.fn();
        },this.delay);
    },
    stop:function(){
        if (this.timeout){
            window.clearTimeout(this.timeout);
            this.timeout = null;
        }
    }
});

/**
 * Animation
 */

rei.Animators = {};
 
/**
 * values should be two items array - first and last points
 */
rei.Animators.Line = {
  calculate:function(values,value_name,data,duration,rate,count_steps){
    var i,v=values[0],dx = (values[1]-values[0])/(count_steps-1);
    for(i=0;i<count_steps;i++){
        data[i][value_name] = v;
        v = v+dx;
    }
  }
};

rei.Animators.Ease = {
  calculate:function(values,value_name,data,duration,rate,count_steps){
  
  }
};
 
rei.Animation = rei.Class.extend({
    onBegin:null,
    onComplete:null,    
    onStep:null,
    
    animation:null,
    duration:300,
    rate:12, //count of frames per second
    step_number:0,
    count_steps:-1,
    //timeout:null,
    delayer:null,
    data:null, //array of animation data
    animator:rei.Animators.Line, //default anumator
    construct:function(props){
      rei.copy(this,props);
      this.data = [];
      this.count_steps = Math.ceil(this.duration*this.rate/1000);
      var i;
      for(i=0;i<this.count_steps;i++){
        this.data[i] = {};
      }      
      var this1 = this;
      rei.each(this.animation,function(key){
        this1.animator.calculate(this1.animation[key], key,this1.data, this1.duration, this1.rate, this1.count_steps);
      });
      this.delayer = rei.delay(function(){this1.doStep()},1000/this.rate,false,true);
    },
    start:function(){
      this.stop(); 
      this.step_number = 0;
      this.delayer.start();
    },
    stop:function(){
      this.delayer.stop();
    },
    doStep:function(){
        var data = this.data[this.step_number];
        if (this.step_number==0 && this.onBegin) this.onBegin(data);       
        this.onStep(data);      
        if (this.step_number>=this.count_steps-1) {
            if (this.onComplete) this.onComplete(data);
            this.delayer.stop();
        }
        this.step_number++;
    }
});

(function (){

rei.getDocumentHeight = function(){
    return rei.document.getHeight();    
}

rei.getDocumentWidth = function(){
    return rei.document.getWidth();
}

rei.getScreenHeight = function(){
    return rei.document.getClientHeight();
}

rei.getScreenWidth = function(){
    return rei.document.getClientWidth();
}

rei.getScrollTop = function(){
    return document.documentElement.scrollTop || document.body.scrollTop;
}

rei.getScrollLeft = function(){
    return document.documentElement.scrollLeft || document.body.scrollLeft;
}


})();

})();