/**
 * some useful functions to use with the project
 * 
 * 
 */

z = {};


/**
 * z.copy(dst,src) copy properties from source to destination
 * 
 * copy all properties except prototype
 * @param dst destination object
 * @param src source object
 */
z.copy = function(dst,src){
      for(key in src){
        if (key!=='prototype'){
          dst[key] = src[key];
        }
      }
};

/**
 * z.ccopy(dst,src) copy all properties and build links to inherited methods
 * 
 *  * 
 * @param dst destination object
 * @param src source object 
 */
z.ccopy = function(dst,src){
    for(key in src){
      if (key!=='prototype'){
       	  if ((typeof(dst[key])=='function') && typeof(src[key])=='function'){
       		 var back = dst[key];
       		 dst[key] = src[key]
       		 dst[key].base = back;        
          }
          else {
        	 dst[key] = src[key];
          }
      }
    }
};

/**
 * z.each(obj,fn) iterates trough an object
 * 
 * 
 * @param obj itable object
 * @param fn function to be called on each iteration
 */
z.each = function(obj,fn){
    for(key in obj){
        if (obj.constructor.prototype[key]!==obj[key]){
            if (fn(key,obj[key])===false) break;
        }
    }
};

/**
 * z.bind(fn,obj) binds the function with object
 * 
 * 
 * @param fn function to bind
 * @param obj object to be bound
 * @returns bound function
 */
z.bind = function(fn,obj){
    return function(){
        return fn.apply(obj,arguments);
    }
}

/**
 * z.Class
 *
 * base class of all classes
 */
z.Class = function(){};
/**
 * z.Class.base
 *
 * access to parent method
 */
z.Class.prototype.base = function(){
	return arguments.callee.caller.base.apply(this,arguments);
}

/**
 * z.Class.extend
 *
 * extends class
 *
 * @returns constructor for newly created class
 * @param obj class definition 
 */
z.Class.extend = function(obj){
	var this1 = this;
    var obj1 = function(){
    	var c = obj.construct || this1.prototype.construct || z.Class;
    	c.apply(this,arguments);
    }
    z.copy(obj1.prototype,this.prototype); //copy inherited properties
    z.ccopy(obj1.prototype,obj); //add new properties
    z.ccopy(obj1,this); //copy static properties
    return obj1;
}

/**
 * z.Class.implement
 *
 * implements new methods for class
 */
z.Class.implement = function(obj){
    z.ccopy(this.prototype,obj);
}

/**
 * z.Class.stat
 *
 * adds definition of static functions of class
 */
z.Class.stat = function(obj){
	z.ccopy(this,obj);
}   


/**
 * delayer.js - Execution of function with delay
 * autostart defaultvalue is = true
 * @depends base.js
 * @example examples/core/empty
 *  
 */

z.delay = function(fn,timeout,autostart,repeat){
    var d = new z.Delayer(fn, timeout,(autostart===false?false:true), repeat);
    return d;
};

z.Delayer = z.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(){
        	this1.callTimeout();
        },this.delay);
    },
    callTimeout:function(){    
        this.fn();
        if (this.repeat && (this.timeout!==null)){ //if timeout==null that means amimation was stoppend
            this.call();
        }
        else {
            this.timeout = null;
        }
    },
    stop:function(){
        if (this.timeout){
            window.clearTimeout(this.timeout);
            this.timeout = null;
        }
    }
});


z.json = {
/**
 * z.json.toJSONString(value)
 * 
 */		
  toJSONString:function(value){
  		if (value==null) return 'null';
        switch (typeof value) {
        case 'object':
        	if (value.constructor === Array){
				return this.arrayToJSONString(value);
			}
			else {
				return this.objectToJSONString(value);
			}
        case 'string':
           	return this.stringToJSONString(value);
        case 'number':
           	return this.numberToJSONString(value);
        case 'boolean':
           	return this.booleanToJSONString(value);
        }  
  },
/**
 * z.json.arrayToJSONString(value) 
 */
  arrayToJSONString:function(value) {
        var a = [], i,          // Loop counter.
            l = value.length,
            v;          // The value to be stringified.
        for (i = 0; i < l; i += 1) {
            v = value[i];
            a.push(this.toJSONString(v));
        }
        return '[' + a.join(',') + ']';
  },
  /**
   * z.json.booleanToJSONString(value) 
   */  
  booleanToJSONString:function (value) {
        return String(value);
  },
  /**
   * z.json.dateToJSONString(value) 
   */    
  dateToJSONString:function (value) {
        function f(n) {
            return n < 10 ? '0' + n : n;
        }
        return '"' + value.getUTCFullYear() + '-' +
                f(value.getUTCMonth() + 1) + '-' +
                f(value.getUTCDate()) + 'T' +
                f(value.getUTCHours()) + ':' +
                f(value.getUTCMinutes()) + ':' +
                f(value.getUTCSeconds()) + 'Z"';
  },
  /**
   * z.json.numberToJSONString(value) 
   */      
  numberToJSONString:function (value) {
        return isFinite(value) ? String(value) : 'null';
  },
  /**
   * z.json.objectToJSONString(value) 
   */        
  objectToJSONString:function (value) {
        var a = [],     // The array holding the partial texts.
            k,          // The current key.
            v;          // The current value.
        if (value==null) return 'null';
        for (k in value) {
            if (typeof k === 'string' && Object.prototype.hasOwnProperty.apply(value, [k])) {
                v = value[k];
                switch (typeof v) {
                case 'object':
                	if (v.constructor === Array){
						a.push(this.stringToJSONString(k) + ':' + this.arrayToJSONString(v));
					}
					else {
						a.push(this.stringToJSONString(k) + ':' + this.objectToJSONString(v));
					}
				break;
                case 'string':
                	a.push(this.stringToJSONString(k) + ':' + this.stringToJSONString(v));
                break;
                case 'number':
                	a.push(this.stringToJSONString(k) + ':' + this.numberToJSONString(v));
                break;
                case 'boolean':
                    a.push(this.stringToJSONString(k) + ':' + this.booleanToJSONString(v));
                break;
                }
            }
        }
        return '{' + a.join(',') + '}';
  },
  /**
   * z.json.stringToJSONString(value) 
   */          
  stringToJSONString:function (value) {
        var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        };
        if (/["\\\x00-\x1f]/.test(value)) {
                return '"' + value.replace(/[\x00-\x1f\\"]/g, function (a) {
                    var c = m[a];
                    if (c) {
                        return c;
                    }
                    c = a.charCodeAt();
                    return '\\u00' +
                        Math.floor(c / 16).toString(16) +
                        (c % 16).toString(16);
                }) + '"';
            }
            return '"' + value + '"';
  },
  /**
   * z.json.parseJSON(value,filter) 
   */            
  parseJSON:function (value,filter) {
    var j;
    function walk(k, v) {
                var i;
                if (v && typeof v === 'object') {
                    for (i in v) {
                        if (Object.prototype.hasOwnProperty.apply(v, [i])) {
                            v[i] = walk(i, v[i]);
                        }
                    }
                }
                return filter(k, v);
    }
    if (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/.test(value.
                    replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) {
                j = eval('(' + value + ')');
                return typeof filter === 'function' ? walk('', j) : j;
    }
    else {
    	if (this.debug){
    		alert(value);
    	}
    	throw new SyntaxError('parseJSON');
    }
  }
}


/**
 * ajax helper
 */
var SosAjax = {
	timer:false,
	requests:[],
	url:'/ajax.php',
	interval:10000,
	regular:[], //functions to be called each time server requested
	to_call:[], //functions to be called during current interval,
	forse:false,
	cycle:false,
	started:false,
	clearRegulars:function(){
		this.regular = [];
	},
	start:function(interval){
		if (this.started) return;
		this.cycle = true;
		this.interval = interval;
		//this.callRemote();
		var this1 = this;
		this.timer = setTimeout(function(){this1.callRemote()},this.interval);
		this.started = true;
	},
	stop:function(){
		if (!this.started) return;
		if (this.timer){
			clearTimeout(this.timer);
			this.timer = false;
		}
		this.cycle = false;
		this.started = false;
	},
	callRemote:function(){
		this.timer = false;
		var data = [],i,this1 = this;
		
		for(i=0;i<this.to_call.length;i++){
			data.push(this.to_call[i]);
		}
		for(i=0;i<this.regular.length;i++){
			//if there are calback - call it
			var r = this.regular[i];
			if (typeof r[1].call != 'undefined'){ //this is callback
				args = r[1].call();
				data.push([r[0],args]);
			}
			else {
				data.push(this.regular[i]);
			}
		}
		this.to_call = []; //clean up function list
		//console.log(data);
		jQuery.ajax({
			'url':this.url,
			'type':'POST',
			'dataType':'json',
			'data':'requests='+encodeURIComponent(z.json.toJSONString(data)),
			'success':function(d){this1.dataReady(d)}});		
	},
	dataReady:function(data){
		var this1 = this,i,ii;
		for(i=0;i<data.length;i++){
			
			var d = data[i];
			var func_name = d[0];
			var args = d[1];
			var params = [];
            for(var ii=0;ii<args.length;ii++){
                params[ii] = "args["+ii+"]";
            }
            var s = func_name+"("+params.join(",")+")";
            eval(s);
		}
		
		//if forse call it right away
		if (this.force){
			this.forse = false;
			this.callRemote();
			return;
		}
		if (!this.cycle) return;
		//call it again
		this.timer = setTimeout(function(){this1.callRemote()},this.interval);
		
	},
	addRegular:function(func_name,args){
		args = args || [];
		this.regular.push([func_name,args])
	},
	/**
	 * adds delayed function call
	 */
	add:function(func_name,args){
		args = args || [];
		this.to_call.push([func_name,args]);
	},
	/**
	 * calls ajax imediately
	 */
	call:function(func_name,args){
		args = args || [];
		this.to_call.push([func_name,args]);
		//if timeout set clear it and call callRemote
		if (this.cycle){
			if (this.timer){
				clearTimeout(this.timer);
				this.callRemote();
			}
			else { //if timer not set that means responce did not arrive yet - set forse flag
				this.forse = true;
			}
		}
		else {
			this.callRemote();
		}
	}
}
