Ext.define("Ext.ux.data.dmd.Proxy", {

	statics: {
		url: "/ip2-services/ip2dmdproxy.aspx",
		requests: new Ext.util.MixedCollection(),
		delay: 5,
		counter: 0,
		counterIncrement: 5,
		interval: null,
		processRecordToBatch: function (o_row, i_record, o_params) {

			var i_self = this;
			var o_batch = {};

			if (i_record) {
				// Set the key property for batch transaction
				var idProperty = i_record.idProperty;
				var idValue = i_record.get(idProperty);
				var o_key = {};
				o_key[idProperty] = idValue;

				Ext.apply(o_batch, {
					key: o_key,
					row: o_row
				});
			}

			Ext.apply(o_batch, o_params);

			return o_batch;
		},
		/**
		 * @method getActionParams
		 *
		 */
		getActionParams: function (i_request) {

			var i_self = this;
			var o_params = Ext.apply({}, i_request.getParams());
			var result = [];

			// Batch objects use type as their key
			o_params.type = i_self.type;

			// Attach an _action property to be used on return of response to concat possible array of batches
			o_params._action = i_request.getAction();
			o_params._id = i_request._id;

			// Overload sort property
			// Can come as array of stringed objects or string - we export as string
			// "LCDLCDTKN ASC, LCDCOD DESC" from
			// [{property: "LCDLCDTKN", "direction": "ASC"}]
			if (o_params.sort) {

				var value = o_params.sort;
				var a_sorters = Ext.decode(o_params.sort, true) || [];

				if (Ext.isString(value)) {
					a_sorters = Ext.Array.map(value.split(","), function (s_value) {
						var a_values = Ext.Array.filter(s_value.split(" "), Boolean);
						return Ext.apply({}, {
							property: a_values[0],
							direction: a_values[1] ? a_values[1] : "ASC"
						});
					});
				}
				else {
					a_sorters.push(value);
				}

				var a_result = [];

				Ext.Array.each(a_sorters, function(o_sorter) {
					a_result.push(o_sorter.property + " " + o_sorter.direction);
				})

				o_params.sort = a_result.join(", ");
			}

			var a_records = i_request.records;

			if (a_records && a_records.length > 1) {
				Ext.Array.each(i_request.jsonData.rows, function (o_data, n_index) {
					var i_record = a_records[n_index];
					var o_batch = Ext.ux.data.dmd.Proxy.processRecordToBatch(o_data, i_record, o_params);
					result.push(o_batch);
				});
			}
			else if (a_records && a_records.length == 1) {
				var o_data = i_request.jsonData.rows;
				var i_record = a_records[0];
				var o_batch = Ext.ux.data.dmd.Proxy.processRecordToBatch(o_data, i_record, o_params);
				result.push(o_batch);
			}
			else if (!a_records) {
				result.push(o_params);
			}

			switch (o_params.type) {

			case "get":
				Ext.Array.each(result, function (o_batch, n_index, a_contex) {

					delete o_batch.page;
					delete o_batch.fullresponse;

					a_contex[n_index] = o_batch;
				});
			break;

			case "upsert":

				var a_blacklist = [
					"context",
					"uiName"
				];

				Ext.Array.each(result, function (o_batch, n_index, a_contex) {

					Ext.Array.each(a_blacklist, function (s_key) {
						if (o_batch[s_key] != null) {
							delete o_batch[s_key];
						}
					});

					o_batch.fullresponse = true;

					a_contex[n_index] = o_batch;
				});

			break;

			case "delete":

				var a_blacklist = [
					"context",
					"uiName"
				];

				Ext.Array.each(result, function (o_batch, n_index, a_contex) {

					Ext.Array.each(a_blacklist, function (s_key) {
						if (o_batch[s_key] != null) {
							delete o_batch[s_key];
						}
					});

					a_contex[n_index] = o_batch;
				});

			break;

			default:
			break;
			}

			return result;
		},
		actionMap: {
			create: {
				type: "upsert",
				fn: function (i_request) {
					return Ext.ux.data.dmd.Proxy.getActionParams.call(this, i_request);
				}
			},
			read: {
				type: "get",
				fn: function (i_request) {
					return Ext.ux.data.dmd.Proxy.getActionParams.call(this, i_request);
				}
			},
			update: {
				type: "upsert",
				fn: function (i_request) {
					return Ext.ux.data.dmd.Proxy.getActionParams.call(this, i_request);
				}
			},
			destroy: {
				type: "delete",
				fn: function (i_request) {
					return Ext.ux.data.dmd.Proxy.getActionParams.call(this, i_request);
				}
			}
		},
		doBatchRequest: function (i_request) {

			var i_controller = this;

			i_controller.counter = 0;

			i_controller.requests.add(i_request);

			if (!i_controller.interval) {

				i_controller.interval = setInterval(function () {

					if (i_controller.counter >= i_controller.delay) {

						var i_requests = i_controller.requests;
						var i_batch = i_requests.clone();

						i_requests.clear();

						clearInterval(i_controller.interval);
						i_controller.counter = 0;
						i_controller.interval = null;

						i_controller.executeBatch(i_batch);
					}
					else {
						i_controller.counter += i_controller.counterIncrement;
					}
				}, i_controller.counterIncrement);
			}
		},
		buildBatch: function (i_requests) {

			var i_controller = this;
			var o_map = i_controller.actionMap;
			var a_batch = [];

			i_requests.each(function (i_request) {

				var s_action = i_request.getAction();
				var i_map = o_map[s_action];

				i_request._id = ip2.unique("request-");

				var a_requestBatch = i_map.fn(i_request);
				a_batch.push(a_requestBatch);

				//var o_params = i_map.fn(i_request);
				//a_batch.push(o_params);
			});

			return a_batch;
		},
		executeBatch: function (i_requests) {

			var i_controller = this;
			var a_batch = i_controller.buildBatch(i_requests);

			// WONT WORK - NEED SOMETHING TO DISTINGUISH THE DIFFERENT REQUESTS APART
			// LIKE 2 DIFFERENT GET REQUESTS NEEDS TO CALL BACK ONLY THOSE BELONGING
			// MAY NEED HELP FROM SERVICE??

			var getBaseInfo = function (o_map, s_key) {

				var n_total = o_map[s_key].total;
				var n_start = 0;

				if (s_key == "read") {
					n_start += o_map.create.total;
				}
				if (s_key == "update") {
					n_start += o_map.create.total + o_map.read.total;
				}
				if (s_key == "destroy") {
					n_start += o_map.create.total + o_map.read.total + o_map.update.total;
				}

				return {
					start: n_start,
					total: n_total
				};
			};

			// Build a control map in case we have multiple batches for same operation!
			var o_controlMap = {
				create: {
					request: [],
					total: 0,
					weight: 1
				},
				read: {
					request: [],
					total: 0,
					weight: 2
				},
				update: {
					request: [],
					total: 0,
					weight: 3
				},
				destroy: {
					request: [],
					total: 0,
					weight: 4
				}
			};

			// SORT BATCH ARRAYS BASED ON CRUD WEIGHTS IN CONTROL MAP
			a_batch.sort(function (a, b) {

				var a_return = 0;
				var b_return = 0;
				var o_first;

				o_first = a[0];
				a_return = o_controlMap[o_first._action].weight;

				o_first = b[0];
				b_return = o_controlMap[o_first._action].weight;

				return a_return - b_return;
			});

			// SORT EACH CRUD ARRAY BASED ON REQUEST ID
			Ext.Array.each(a_batch, function (a_requestBatch, n_index, a_contex) {
				a_requestBatch.sort(function (a, b) {
					var a_return = Number(a._id.replace("request-", ""));
					var b_return = Number(b._id.replace("request-", ""));
					return a_return - b_return;
				});
				a_contex[n_index] = a_requestBatch;
			});

			// Create a flat payloadbatch of all batches and keep track of
			// request affiliation
			var a_payloadBatch = [];

			Ext.Array.each(a_batch, function (batch) {
				Ext.Array.each(batch, function (o_original, n_index) {

					var o_batch = Ext.apply({}, o_original);

					var s_id = o_batch._id;
					var o_control = o_controlMap[o_batch._action];

					o_control.total++;

					// Add a object to the request with total if it does not exist
					// { name: "request-2", total: 0 }
					var o_requestControl;

					Ext.Array.some(o_control.request, function (o_request) {
						if (o_request.name == s_id) {
							o_requestControl = o_request;
							return true;
						}
					});

					// No request control object in array - push it in
					if (!o_requestControl) {
						o_requestControl = {
							name: s_id,
							total: 0
						};
						o_control.request.push(o_requestControl);
					}

					o_requestControl.total++;

					delete o_batch._id;
					delete o_batch._action;

					a_payloadBatch.push(o_batch);
				});
			});

			Ext.Ajax.request({
				url: i_controller.url,
				jsonData: {
					batch: a_payloadBatch
				},
				success: function (i_response, o_options) {

					Ext.USE_NATIVE_JSON = false; // DO THIS TO ALLOW THE PARSE OF FUNCTIONS

					var o_result = Ext.decode(i_response.responseText, true) || {
						batch: []
					};

					//Ext.USE_NATIVE_JSON = true;

					var a_batchResponse = o_result.batch || [];

					i_requests.each(function (i_request, n_index) {

						// This request can have more batches - so we concat from
						// info in o_controlMap
						var s_action = i_request.getAction();
						var s_id = i_request._id;

						var o_base = getBaseInfo(o_controlMap, s_action);
						var n_start = o_base.start;

						// Now find the start for this request and total so we can
						// iterate the right range in a_batchResponse for this requests
						// batch replies - then overload the response
						var o_control = o_controlMap[s_action];
						var o_requestControl;

						Ext.Array.some(o_control.request, function (o_request) {
							if (o_request.name != s_id) {
								n_start += o_request.total;
							}
							else {
								o_requestControl = o_request;
								return true;
							}
						});

						var n_total = n_start + o_requestControl.total;

						// Response can be row or rows - must deliver as
						// { totalRows: x, rows: [{LCDLCDTKN: 1, xxxx:xxx....}]}
						// Use all properties apart from row or rows to merge on top of json

						var o_dummy;
						var a_rows = [];
						var o_json = {};

						for (n_start; n_start < n_total; n_start++) {

							var o_batch = a_batchResponse[n_start];
							o_dummy = o_dummy || o_batch;

							if (o_batch.row) {
								a_rows.push(o_batch.row);
							}
							else if (o_batch.rows) {
								a_rows = o_batch.rows;
							}
						}

						// Make sure all row keys are uppercased
						Ext.Array.each(a_rows, function (o_row) {
							Ext.Object.each(o_row, function (s_key, value, o_context) {
								var s_upperCaseKey = s_key;
								if (s_key != s_upperCaseKey) {
									o_context[s_upperCaseKey] = value;
									delete o_context[s_key];
								}
							});
						});

						Ext.apply(o_json, o_dummy);

						delete o_json.row;
						delete o_json.rows;

						Ext.apply(o_json, {
							rows: a_rows
						});

						i_response = {
							responseText: Ext.encode(o_json),
							success: true
						};

						i_request.callback.call(i_request.getProxy(), i_request.getParams(), true, i_response);
					});
				},
				failure: function(o_response, o_options) {
				}
			});
		}
	},

	requires: [
		"Ext.data.proxy.Ajax"
	],

	extend: "Ext.data.proxy.Ajax",

	alias: "proxy.dmd",

	fields: null,

	constructor: function (o_config) {

		var Proxy = Ext.ux.data.dmd.Proxy;
		var i_proxy = this;

		// Set reader defaults

		Ext.apply(o_config, {
			reader: Ext.apply({
				type: "dmd",
				rootProperty: "rows",
				totalProperty: "totalRows",
				nameProperty: "name"
			}, o_config.reader),
			writer: Ext.apply({
				type: "dmd",
				rootProperty: "rows",
				nameProperty: "name"
			}, o_config.writer)
		});

		Ext.apply(i_proxy, {
			url: Proxy.url
		});

		i_proxy.callParent([o_config]);
	},

	extractResponseData: function(o_response) {

		var i_proxy = this;
		var i_reader = i_proxy.getReader();

		var o_data = Ext.decode(o_response.responseText);

		var b_asRow = o_data.hasOwnProperty("row");
		var a_result = Ext.Array.from(o_data.rows || o_data.row);

		if (b_asRow) {
			delete o_data.row;
		}

		var a_selected = Ext.Array.from(o_data.selected);

		if (a_selected.length > 0) {
			o_data[i_reader.getTotalProperty()] += a_selected.length;
			a_result = a_selected.concat(a_result);
		}

		o_data[i_reader.getRootProperty()] = a_result;

		return o_data;
	},

	doRequest: function(i_operation, f_callback, i_scope) {

		if (isNaN(i_operation.start) || isNaN(i_operation.page)) {
			if (isNaN(i_operation.start)) {
				i_operation.start = 0;
			}
			if (isNaN(i_operation.page)) {
				i_operation.page = 1;
			}
		}
		var Proxy = Ext.ux.data.dmd.Proxy;
		var i_proxy = this;
		var i_writer = i_proxy.getWriter();
		var i_request = i_proxy.buildRequest(i_operation, f_callback, i_scope);

		if (i_operation.allowWrite()) {
			i_request = i_writer.write(i_request);
		}

		Ext.apply(i_request, {
			headers: i_proxy.headers,
			timeout: i_proxy.timeout,
			scope: i_proxy,
			callback: i_proxy.createRequestCallback(i_request, i_operation),
			method: i_proxy.getMethod(i_request),
			disableCaching: false,
			proxy: i_proxy
		});
		i_request.setUrl(Ext.urlAppend(Proxy.url, Ext.String.format("{0}={1}", this.getCacheString(), Ext.Date.now())));

		Proxy.doBatchRequest(i_request);
	}
});
