var VnObject = require ('./object');
var VnDate = require ('./date');
var LotIface = require ('./lot-iface');
var Value = require ('./value');

/**
 * Class to handle the URL.
 */
module.exports = new Class  
({
	Extends: VnObject
	,Implements: LotIface
	,Properties: {
		window:
		{
			type: Window
			,set: function (x)
			{
				this._window = x;
				x.addEventListener ('hashchange', this._hashChangedHandler);
				this._hashChanged ();
			}
			,get: function ()
			{
				return this._window;
			}
		},
		params:
		{ 
			type: Object
			,set: function (x)
			{
				this.setAll (x);
			}
			,get: function ()
			{
				return this._hashMap;
			}
		}
	}

	,initialize: function (props)
	{
		this._hash = null;
		this._hashMap = null;
		this._window = null;
		this._hashChangedHandler = this._hashChanged.bind (this);
		this.parent (props);
	}

	,get: function (key)
	{
		return this._hashMap[key];
	}

	,set: function (key, value)
	{
		var object = {};
		object[key] = value;
		this.assign (object);
	}
	
	/**
	 * Sets the hash part of the URL, respecting the current hash variables.
	 *
	 * @param {Object} object A key-value object
	 */
	,assign: function (object)
	{
		var newObject = {};

		for (var key in this._hashMap)
			newObject[key] = this._hashMap[key];
		for (var key in object)
			newObject[key] = object[key];
			
		this.setAll (newObject);
	}
	
	/**
	 * Sets the hash part of the URL.
	 *
	 * @param {Object} object A key-value object
	 */
	,setAll: function (object)
	{
		if (object)
		for (var key in object)
		if (object[key] === null || object[key] === undefined)
			delete object[key];

		var newHash = this.make (object);

		if (!object)
			object = {};

		if (!Value.equals (this._hashMap, object))
		{
			this._hashMap = object;
			this._hash = newHash;
			
			this._blockChanged = true;
			location.hash = newHash;
			this._blockChanged = false;

			this.changed ();
		}
	}

	/**
	 * Creates a URL with the given hash data.
	 *
	 * @param {Object} object A key-value object
	 * @param {boolean} add %true to combine with the current params, %false otherwise
	 * @return {String} The URL
	 */
	,make: function (object, add)
	{
		var hash = '#!';
		
		if (add && object)
		for (var key in this._hashMap)
		if (!object[key])
			object[key] = this._hashMap[key];

		for (var key in object)
		{
			if (hash.length > 2)
				hash += '&';

			hash += encodeURIComponent (key) +'='+ this.renderValue (object[key]);
		}
	
		return hash;
	}

	,_hashChanged: function ()
	{
		var newHash = location.hash;
	
		if (this._blockChanged || this._hash == newHash)
			return;

		var newMap = hashMap = {};
		var kvPairs = newHash.substr(2).split ('&');

		for (var i = 0; i < kvPairs.length; i++)
		{
			var kvPair = kvPairs[i].split ('=', 2);
			
			if (kvPair[0])
				newMap[decodeURIComponent (kvPair[0])] = this.parseValue (kvPair[1]);
		}

		if (!Value.equals (this._hashMap, newMap))
		{
			this._hashMap = newMap;
			this._hash = newHash;
			this.changed ();
		}
	}

	,renderValue: function (v)
	{
		switch (typeof v)
		{
			case 'number':
				return '(Number)'+ v;
			case 'boolean':
				return '(Boolean)'+ (v ? 'true' : 'false');
			case 'object':
			if (v instanceof Date)
				return '(Date)'+ VnDate.strftime (v, '%Y-%m-%d');
			else
				return '(Object)'+ JSON.stringify (v)
		}

		switch (v.charAt (0))
		{
			case '(':
			case '\\':
				return '\\'+ v;
		}

		return v;
	}

	,parseValue: function (v)
	{
		if (v == null)
			return v;

		v = decodeURIComponent (v);

		if (v === '')
			return v;

		var typeStr;

		switch (v.charAt(0))
		{
			case '(':
				var index = v.indexOf (')');
				typeStr = v.substr (1, index - 1);
				v = v.substr (index + 1);
				break;
			case '\\':
				v = v.substr (1);
				break;
		}

		if (v === '')
			return null;
		if (!typeStr)
			return v;

		switch (typeStr)
		{
			case 'Boolean':
				return (/^(true|1)$/i).test (v);
			case 'Number':
				return 0 + new Number (v);
			case 'Date':
				var date = new Date (v);
				date.setHours (0, 0, 0, 0);
				return date;
			case 'Object':
				return JSON.parse (v);
			default:
				return v;
		}
	}

	,_destroy: function ()
	{
		this._window.removeEventListener ('hashchange', this._hashChangedHandler);
		this._window = null;
		this.parent ();
	}
});