var Cookie = {
	set:function(name, value, expires){
		expires = expires || new Date(2020, 01, 01);
		document.cookie = '{0}={1};expires={2};'.format( name, escape(value), expires.toGMTString());
	},
	get:function( name ){
		var re = new RegExp('[\s;]*\\b'+name+'=([^;]*)');
		var value = document.cookie.match(re);
		return value && value[1];
	},
	remove:function( name ){
		var d = new Date();
		d.setDate( d.getDate() - 1 );
		Cookie.set( name, '', d );
	}
};

var DOM = {
	//setea la visibilidad de un elemento, si no recibe show, busca el inverso al actual.
	toggle:function( el, show ){
		el = this.$(el);
		if( show === undefined )
			show = el.style.display == 'block';
		el.style.display = show ? 'block' : 'none';
	},
	//copia todo, de los argumentos al primero.
	extend: function( o ){
		var i=1, j, s;
		while( s = arguments[i++] ){
			for( j in s )
				o[j] = s[j];
		}
		return o;
	},
	//si recibe un tagname, genera el elemento, si es un html string, lo parsea.
	create:function( html, one ){
		if( /[<\s]/.test(html) == false )
			return document.createElement( html );
		var div = document.createElement('div');
		div.innerHTML = html;
		var nodes = List.filter( div.childNodes, function( n ){
			return n.nodeType == 1;
		});
		return ( one || nodes.length == 1) ? nodes[0] : nodes;
	},
	//agrega una serie de elementos/html/tags a otro elemento
	append:function( parent ){
		parent = this.$(parent);
		for( var i = 1, child; i < arguments.length; ++i ){
			child = arguments[i];
			if( typeof child == 'string' )
				child = DOM.create(child);
			parent.appendChild( child );
		}
		return child;		
	},
	//similar a append, pero agrega hacia adentro, nesteando
	nest:function( parent ){
		for( var i = 1; i < arguments.length; ++i )
			parent = DOM.append( parent, arguments[i] );
		return parent;	
	},
	prepend:function( parent, child ){
		child = DOM.append( parent, child );
		this.$(parent).insertBefore( child, this.$(parent).firstChild );
		return child;
	},
	$:function(id){
		return typeof id == 'string' ? document.getElementById(id) : id;
	},
	//devuelve los elementos con el tagName dado, puede ser solo hijos de context si se manda.
	$$:function( tag,context ){
		switch( typeof context ){
			case 'string': tag = context;
			case 'undefined': context = document;
		}
		return context.getElementsByTagName( tag || '*' );
	},
	//vacia un elemento DOM
	empty:function( el ){
		while( el.firstChild )
			el.removeChild( el.firstChild );
	},
	//agrega el texto 'txt' al elemento 'el', mandar empty=true para vaciarlo.
	text:function( el, txt, empty ){
		el = this.$(el);
		if(	empty )	
			this.empty( el );
		el.appendChild(document.createTextNode(txt));
		return el;
	},
	//genera el querystring, con los elementos del form, puede recibir solo el id.
	parseForm:function( form ){
		if( typeof form == 'string' ) form = DOM.$(form);
		var els = List.filter( form.elements, function( el ){
			switch( !!el.name && el.nodeName ){
				case 'INPUT':return (!(/checkbox|radio/).test(el.type) || el.checked);
				case 'SELECT':
				case 'TEXTAREA': return true;
				default: return false;
			}
		});
		return List.map( els, function( el ){
			return encodeURIComponent(el.name) + '=' + encodeURIComponent(el.value);
		}).join('&');
	},
	//agrega una nueva clase css al elemento
	addClass:function( elem, c ){
		if( !DOM.hasClass(elem,c) )
			elem.className += ( elem.className ? " " : "" ) + c;
	},
	//le saca una clase al elemento
	removeClass: function( elem, c ){
		elem.className = List.filter( elem.className.split(' '),function(css){
			return css !== c;
		}).join(' ');
	},
	//chequea si el elemento tiene o no esta clase.
	hasClass: function( elem, c ) {
		return !!elem.className && (' '+elem.className+' ').indexOf(' '+c+' ') != -1;
	},
	next:function( node, tag ){
		tag = tag.toUpperCase();
		while( node && node.nodeName != tag )
			node = node.nextSibling;
		return node;
	},
	prev:function( node, tag ){
		tag = tag.toUpperCase();
		while( node && node.nodeName != tag )
			node = node.previousSibling;
		return node;
	},
	first:function( node, tag ){
		return DOM.next( node.firstChild, tag );
	},
	offset:function( el ){
		var off = { top:0, left:0 };
		while( el ){
			off.top += el.offsetTop;
			off.left += el.offsetLeft;
			el = el.offsetParent;
		}
		return off;
	},
	remove:function( el ){
		if( el && el.parentNode )
			el.parentNode.removeChild(el);
	},
	ensure:function( node, tag ){
		return this.first( node, tag ) || this.append( node, tag );
	}
};

var Form = {
	save:function( form, cb, parse ){
		form = DOM.$(form);
		Tip.delayed(Lex.get('submitting'));
		Remote.request( form.method, form.action, DOM.parseForm(form), function(response){
			if( Tip.delay )
				Tip.abortDelay();
			setTimeout( Tip.hide, 0 );
			response = response.trim();
			if( parse )
				response = parse( response );
			if(response == 'OK'){
				Analytics.send( form.getAttribute('action',2) );				
				if( form.userName )
					Cookie.set('userName', form.userName.value);
				if( form.userEmail )
					Cookie.set('userEmail', form.userEmail.value);
				revertSave();
				
				var callback = Form.oneCallback;
				if( callback ){
					callback.call(this);
					Form.oneCallback = null;
				}
				
				history.back();
				if( cb ) cb();
			}else if(response == 'CONFIRM'){
				location = 'index.html';
			}else{
				alert( response );
			}
		});
	},
	saveLogin:function( cb, parse ){
		Form.save('doLogin', cb, parse);
	},
	saveComment:function( cb ){
		Form.save('addComment', cb);
	},
	saveDescription:function( cb ){
		Form.save('addDescription', cb);
	},
	saveStore:function( cb ){
		Form.save('addStore', cb);
	},
	saveMessage:function( cb ){
		Form.save('addMessage', cb);
	}
};

var List = {
	//itera el objeto 'obj' (hash o array-like), y llama fn por cada posicion, se puede mandar un scope (this)
	each:function( obj, fn, scope ){
		if( !obj ) return;
		var i, l;
		if( obj.length === undefined ){
			for( i in obj )
				if( fn.call( scope, obj[i], i ) === false )
					break;
		}else{
			for( i=0, l=obj.length; i < l; i++ )
				if( fn.call( scope, obj[i], i ) === false )
					break;
		}
	},
	//genera un nuevo objeto, modificando cada posicion con la funcion fn.
	map:function( obj, fn, scope ){
		var m = obj.length === undefined ? { } : [ ];
		if( typeof fn == 'string' ){
			var attr = fn;
			fn = function( v ){ return v[attr];	};
		}
		this.each( obj, function( v, k ){
			m[k] = fn.call( scope, v, k );
		});
		return m;
	},
	//devuelve un nuevo objeto, con los elementos que pasan el filtro, devolver 'true' para dejarlo.
	filter:function( obj, fn, scope ){
		var f = obj.length === undefined ? { } : [ ];
		this.each( obj, function( v, k ){
			if( fn.call(scope, v, k) )
				f.push ? f.push( v ) : (f[k] = v);
		});
		return f;
	},
	//devuelve el primer indice en el que se encuentra 'val' en 'obj' o -1 si no esta.
	indexOf:function( obj, val ){
		var pos = -1;
		this.each( obj, function( v, k ){
			if( v == val ){
				pos = k;
				return false;
			}
		});
		return pos;
	}
};
//manejo de Ajax
var Remote = {
	//generic request
	request:function( method, url, params, async, callback ){
		if( requestsLocked ) return;
		requestsLocked = true;
		if( typeof params != 'string' ){
			callback = async; async = params; params = null;
		}
		if( typeof async == 'function' )
			callback = async;
		async = async !== false;
		var ajax = new XMLHttpRequest(), response = null;
		method = method && method.toUpperCase() || 'GET';

		if( method == 'GET' ){
			if( params ){
				url += url.indexOf('?') == -1 ? '?' : '&';
				url += params;
			}
			params = null;
		}
		ajax.open( method, url, async );
		ajax.onreadystatechange = function(){
			if( ajax.readyState != 4 ) return;
			requestsLocked = false;
			if( ajax.status == 200 ){
				response = ajax.responseText;
				if( callback ){
					try{ callback( response ); }
					finally{ setTimeout( Tip.hide, 0 );	}
				}
			}else{
				Tip.abortDelay();
				Tip.flash( Lex.get('conn_failed') );
			}
		};
		if( method == 'POST' ){
			ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
			ajax.setRequestHeader("Content-length", (params||'').length);
			ajax.setRequestHeader("Connection", "close");
		}
		ajax.send(params);
		return response;
	},
	post:function( url, params, async, callback ){
		return this.request( 'POST', url, params, async, callback );
	},
	get:function( url, params, async, callback ){
		return this.request( 'GET', url, params, async, callback );
	}
};

String.formatRegex = /\{([^{}]+)\}/g;

DOM.extend( String.prototype, {
	trim: function(){
		return this.replace(/^\s+|\s+$/g, '');
	},
	format: function(){
		var	params = arguments.length > 1 ? arguments : arguments[0];
		return this.replace( String.formatRegex,function( all, i ){
			var ns = i.split('.'), p = params;
			do p = p[ns.shift()]; while( p && ns.length );
			return p === undefined ? all : p;
		});
	},
	truncate:function( len, suffix ){
		if( this.length <= len ) return this;
		suffix = suffix || '...';
		return this.substring( 0, len - suffix.length ).trim() + suffix;
	},
	capitalize:function(){
		return this.charAt(0).toUpperCase() + this.slice(1);
	}
});

function Cache( key, value ){
	if( value !== undefined )
		Cache[key] = value;
	return Cache[key];
};

var Tip = {
	elm:null,
	show:function( message ){
		if( message )
			DOM.text( Tip.elm, message, true );
		if( Tip.showing || Tip.hiding ) return;
		Tip.elm.style.display = 'block';
		Tip.hiding = false;
		Tip.showing = true;
		Opacity.go( Tip.elm, 0.8, {
			from:FX.get( Tip.elm, 'opacity' )
		});
	},
	hide:function(){
		if( Tip.hiding || !Tip.showing ) return;
		Tip.abortDelay();
		Tip.hiding = true;
		Tip.showing = false;
		Tip.elm.style.display = 'block';
		Opacity.go( Tip.elm, 0, {after:function(){
			Tip.elm.style.display = 'none';
			Tip.hiding = false;
		}});
	},
	flash:function( message, intv ){
		Tip.show( message );
		setTimeout( Tip.hide, intv || 2000 );
	},
	abortDelay:function(){
		clearTimeout(Tip.delay);
		Tip.delay = null;
	},
	delayed:function( message ){
		Tip.delay = Tip.delay || setTimeout(function(){
			Tip.delay = null;
			Tip.show( message );
		}, 1000 );
	}
};

var SecondaryLink = {
	/**
	 * Parse the items and display a M and/or a R if those items
	 * has messages or reviews (respectively)
	 * @param items
	 * 		The items collection
	 * @param page
	 * 		The page where the items are rendered
	 * @param reviewHandler
	 * 		The review link method name handler
	 * @param messageHandler
	 * 		The message link method name handler
	 * @param from
	 * 		An optional parameter used only for on demand loading. This is used to know where to start parsing.
	 */
	parseItems:function( items, page, reviewHandler, messageHandler, from ){
		// There are several ul's, because of the grouper
		var 
			uls = DOM.$$("ul", page.getList()),
			ul, lis, li, totLi = 0, cityIndex = 0,
			now = new Date, itm;

		for(var i = 0; i < uls.length; i++){
			ul = uls[i];
			lis = DOM.$$('li', ul);
			totLi += lis.length;

			for(var j = 0; j < lis.length; j++){
				li = lis[j];
				itemIndex = (totLi - lis.length) + j;

				if( from != null && totLi < from )
					break;

				if((itemIndex > (items.length - 1))){
					itemIndex -= from;

					if(itemIndex < 0)
						break;
				}

				itm = items[itemIndex];

				if( itm.anyReview )
					// Doing this way because with addEventListener always had the same city id.
					DOM.append( li, '<a href="#" class="rlink" onclick="' + reviewHandler + '(' + itm.id + ')">'+ Lex.get('to_rev') + '</a>');

				if( itm.anyMessageUntil && itm.anyMessageUntil > now )
					// It has non expired messages
					DOM.append( li, '<a href="#" class="mlink" onclick="' + messageHandler + '(' + itm.id + ')">'+ Lex.get('to_msg') + '</a>');
			}
		}
	}
};

var HtmlUtil = {
	convertNewLines:function(s){
		return s.replace(/\n/g, '<br />');
	}
};

var FormatUtil = {
	parseUserName:function( e ){
		return e.userName ? Lex.get('by') + ' ' + e.userName.split(' ')[0] : Lex.get('anonymous');
	}
};