﻿var astoria = (function () {
	var utility = {
		keys: function (o) {
			var keys = [];

			for (var x in o) {
				keys.push(x);
			}

			return keys;
		},

		isEmptyObject: function (o) {
			for (var x in o)
				return false;

			return true;
		},

		toDate: (function () {
		    var regex = /^\/Date\((\d+)\)\//;
		    
		    return function (input) {
		        var date = input;
		        
		        if (typeof date === 'string') {
					if (!regex.test(date))
						return date;
				
					date = date.replace(regex, '$1');
		            date = parseInt(date);
		            date = date || 0;
		            
		            return new Date(date);
		        } else if (typeof date === 'number') {
		            return new Date(date);
		        } else {
		            return null;
		        }
		    };
		})()
	};

	this.utility = $.extend({}, utility);

	function drill() {
		var prev = this;
		var args = $.makeArray(arguments);

		var query = prev.apply(args);

		return $.extend(function () {
			return drill.apply(query, arguments);
		}, query);
	};

	return {
		query: function () {
			return drill.apply(new Query(), arguments);
		}
	};

	function Query(tree) {
		tree = $.extend(true, {}, tree);

		$.extend(this, {

			// GET
			'fetch': function (opts, callback) {
				if (typeof opts === 'function') {
					callback = opts;
					opts = {};
				}

				dispatch(urlify(tree, opts), 'GET', null, callback);
			},

			// POST
			'create': function (data, callack, urlencode) {
				dispatch(urlify(tree), 'POST', data, callack, urlencode);
			},

			// MERGE
			'update': function (data, callack) {
				dispatch(urlify(tree), 'MERGE', data, callack);
			},

			// DELETE
			'destroy': function (callack) {
				dispatch(urlify(tree), 'DELETE', null, callack);
			},

			// apply new arguments and return a new query
			'apply': function (args) {
				function recurse(next, args) {
					if ($.isArray(next)) {
						$.each(next, function (i, o) {
							recurse(o, args);
						});
					} else {
						if (utility.isEmptyObject(next)) {
							$.each(args, function (i, a) {
								if (typeof a === 'object') {
									for (var x in a) {
										next[x] = {};
										recurse(next[x], a[x]);
									}
								} else {
									next[a.toString()] = {};
								}
							});
						} else {
							for (var x in next) {
								recurse(next[x], args);
							}
						}
					}

					return next;
				};

				var next = $.extend(true, {}, tree);

				return new Query(recurse(next, args));
			}
		});

		function dispatch(urls, type, data, call, urlencode) {
			var done = 0;
			var result = [];

			result.length = urls.length;
			
			$.each(urls, function (i) {
				$.ajax({
					url: this,
					type: type == 'MERGE' ? 'POST' : type,
					data: data ? (urlencode ? data : JSON.stringify(data)) : null,
					dataType: 'json',
					contentType: urlencode ? 'application/x-www-form-urlencoded' : 'application/json',
					processData: urlencode ? true : false,
					success: function (data) {
						var fixDates = function (o) {
							if ($.isArray(o)) {
								$.each(o, function () {
									fixDates(this);
								});
							} else {
								for (var x in o) {
									if (typeof o[x] === 'string') {										
										o[x] = utility.toDate(o[x]);
									} else if (typeof o[x] === 'object') {
										fixDates(o[x]);
									}
								}
							}
						};

						if (data && data.d)
							fixDates(data.d);

						done++;
						result[i] = data ? (data.d ? data.d : data) : null;

						if (done == result.length) {
							if ($.isFunction(call)) {
								call.apply(this, result);
							}
						}
					},
					dataFilter: function (data) {
						return data.replace(/\\\'/g, "'");
					},
					beforeSend: function (xhr) {
						if (type == 'MERGE')
							xhr.setRequestHeader('x-http-method', 'MERGE');
					}
				});
			});
		}

		function urlify(tree, opts) {
			var path = {};
			var urls = [];

			if (!opts)
				opts = {};

			$.each(flatten(tree), function (i, f) {
				if (f.length == 2)
					f.push('*');

				var key = f.slice(0, 2).join('/');
				var val = f.slice(2);

				if (val.length) {
					if (typeof path[key] === 'undefined')
						path[key] = [];

					path[key].push(val);
				}
			});

			for (var x in path) {
				var key = x;

				var select = {};
				var expand = {};

				$.each(path[key], function (x, val) {
					select[val.join('/')] = null;

					val.pop();

					if (val.length)
						expand[val.join('/')] = null;
				});

				var url = '/' + x + '?';

				select = utility.keys(select).join(',');
				expand = utility.keys(expand).join(',');

				expand = expand != '' ? expand : null
				select = select != '' ? select : null;
				select = select != '*' ? select : null;

				opts['$select'] = select;
				opts['$expand'] = expand;

				for (var x in opts) {
					if (opts[x] != null)
						url += '&' + escape(x) + '=' + escape(opts[x]);
				}

                if (url.slice(-1) == '?') {
                    url = url.slice(0, -1);
                }

				urls.push(url);
			}

			return urls;
		}

		function flatten(tree) {
			function visit(tree, part, parts) {
				if (utility.isEmptyObject(tree)) {
					parts.push(part.slice(0));
				} else {
					$.each(tree, function (k, v) {
						part.push(k);
						visit(v, part, parts);
						part.pop();
					});

				}

				return parts;
			};

			return visit(tree, [], []);
		}
	};
})();
