Ext.define("Portfolio.util.Xpd", {

	singleton: true,

	alternateClassName: [
		"ip2.xpd"
	],

	requires: [
		"Portfolio.util.Handler"
	],

	ALLOWED_CHILDS: 1,
	ALLOWED_PARENTS: 2,
	ALL_PROPERTIES_KEY: "all",
	XPD_DEFAULTLANGUAGE: "en",

	DEFAULT_KEY: "__default__",

	privates: {
		cf: function(s_name, o_properties, o_data, a_filter) {

			a_filter = a_filter || [];

			Ext.Object.each(o_properties.filter, function(o_value, s_key) {

				if (!Ext.isObject(o_value)) {
					var s_value = o_value;

					o_value = {
						value: s_value
					};
				}

				//var o_filter = Ext.Object.merge({
				var o_filter = Ext.apply({
					value: o_data[s_key],
					property: o_properties.databasebind
				}, o_value);

				if (o_properties.as) {
					o_filter.as = o_properties.as;
				}

				var value = o_filter.value;

				if (Ext.isString(value)) {
					o_filter.value = ip2.microTemplate(value, o_data);
				}

				// Map out the value to the s_key o.value -> o.where etc.
				o_filter[s_key] = o_filter.value;
				delete o_filter.value;

				if (!i_self.hasFilter(a_filter, o_filter) && o_filter[s_key]) {
					a_filter.push(o_filter);
				}
			});

			return a_filter;
		},

		hasFilter: function (a_filter, o_filter) {

			var b_found = false;

			var s_filter = Ext.encode(o_filter);

			Ext.Array.each(a_filter, function(o_value) {

				var s_current = Ext.encode(o_value);

				if (s_current == s_filter) {
					b_found = true;
					return false;
				}
			});

			return b_found;
		}
	},

	createFilter: function(o_collection, o_data, a_filter) {

		var i_self = this;

		a_filter = a_filter || [];
		o_data = o_data || {};

		Ext.Object.each(o_collection.properties, function (o_property, s_key) {

			var a_result = i_self.cf(s_key, o_property, o_data, a_filter);

			Ext.Array.each(a_result, function (o_value) {
				if (!i_self.hasFilter(a_filter, o_value)) {
					a_filter.push(o_value);
				}
			});
		});

		return a_filter;
	},

	translate: function(xpd, value, lang) {

		var i_self = this;

		value = value || "";

		var defaultLanguage;

		if (xpd.defaultLanguage) {
			defaultLanguage = xpd.defaultLanguage;
		}

		if (!lang) {
			lang = ip2.session.getLanguage();
		}

		lang = lang || defaultLanguage || i_self.XPD_DEFAULTLANGUAGE;

		var languages = xpd.language;
		var chkValue = value.toLowerCase();
		var ret;

		if (!languages[lang]) {
			lang = "en";
		}

		if (!languages[lang]) {
			lang = "da";
		}

		if (languages && languages[lang]) {
			ret = languages[lang].text[chkValue] !== undefined ? languages[lang].text[chkValue] : value;
		} // TODO: This only checks if the wanted language exists, check for fallback to the default language for possible other value here!
		else {
			ret = value;
		}

		return ret;
	},

	flattenLayout: function(collections, xpdLayout) {

		var i_self = this;

		var defaultsId = i_self.DEFAULT_KEY;

		// First prepare the layout object - add default values
		Ext.Object.each(xpdLayout, function(context, contextKey) {

			var defaults = context[defaultsId] || {};

			// Itereate over the context for the layout object
			Ext.Object.each(context, function(layout, propertyId) {

				if (propertyId != defaultsId) {

					// Extend the layout for this collection id
					context[propertyId] = Ext.Object.merge({}, defaults, layout);
				}
			});
		});

		if (!Ext.Object.isEmpty(xpdLayout)) {

			// Go through the flattened collections and extend layout on to
			Ext.Object.each(collections, function(collection) {

				var properties = collection.properties;

				Ext.Object.each(properties, function(field, name) {

					field.layout = field.layout || {};

					Ext.Object.each(xpdLayout, function(context, contextKey) {

						var defaults = context[name] || (context[defaultsId] || {});

						if (!field.layout[contextKey]) {
							field.layout[contextKey] = defaults;
						}
						else {
							field.layout[contextKey] = Ext.Object.merge({}, defaults, field.layout[contextKey]);
						}
					});
				});
			});
		}

		return xpdLayout;
	},

	flatten: function(xpd, against, includeName) {

		var i_self = this;

		// Which property will we be flatten against defaults to id:
		against = against || "id";

		// Make sure includeName have to be set to `true to include the name when returning:
		includeName = (includeName === true);

		if (xpd.hasOwnProperty("against")) {
			if (xpd.against === against) {
				return xpd;
			}
			else {
				throw new Error(".flatten: The given XPD is flattened, but not against: " + against + ", but rather: " + xpd.against);
			}
		}
		else {

			// deepClone the xpd we are getting. This way we are allowed to modify each collection.
			xpd = Ext.apply({}, xpd);

			var collections = i_self.flattenItems({
				// map out collections and properties to our fake xpd collection
				collections: xpd.collections,
				properties: xpd.properties,
				layout: xpd.uilayout,
				widgets: xpd.widgets
			}, against, includeName);

			var all = {};
			Ext.Object.each(collections, function(coll) {
				Ext.Object.each(coll.properties, function(prop, name) {
					all[name] = prop;
				});
			});
			collections[i_self.ALL_PROPERTIES_KEY] = all;

			// Process the xpd.layout object - sanitize first
			xpd.layout = xpd.layout || {};

			var layout = i_self.flattenLayout(collections, xpd.layout);

			// Return an object to the user:
			return {
				// What have the xpd been flatten against?
				against: against,

				// Did we include the name ({name: ...})?
				includeName: includeName,

				// Map the idProperty from the xpd object.
				idProperty: xpd.idProperty,

				// Map out the path if any is specified
				path: xpd.path || xpd._path,

				// Map the language from the xpd object.
				language: xpd.language,

				// Map out the propertyRulesPath if any is specified
				propertyRulesPath: xpd.propertyRulesPath,

				// Map out the groups if any is specified // TODO: Have another .xpd that holds the groups!!
				groups: xpd.groups,

				// Iterate each collection and its children.
				// Start with a fake xpd collection:
				collections: collections,

				// Layout object - added by Igor
				layout: layout,

				// Filter so we can filter the collection if needed
				filter: xpd.filter || "",

				// CID map if we need to place a cid to an inner properties
				cidMap: xpd.cidMap || {},

				// convert to pre convert properties in data before collection initialization
				convert: xpd.convert || undefined
			};
		}
	},

	flattenItems: function(parent, against, includeName) {

		var i_self = this;
		// We return an object like this:
		// { 1: {properties}, 2: {properties} }
		var o_result = {};
		

		if (parent.hasOwnProperty("collections")) {

			var o_clone = Ext.clone(parent.collections);

			Ext.Object.each(o_clone, function(key, child) {

				var o_copy = Ext.clone(child);
				// Copy all parents properties to the child, if it doesn"t allready have them.
				o_copy.properties = o_copy.properties ? Ext.clone(o_copy.properties) : {};

				// ip2.defaults(o_copy.properties, parent.properties);
				var child_properties = Ext.clone(o_copy.properties);
				var child_widgets = o_copy.widgets ? Ext.clone(o_copy.widgets) : [];
				var child_layouts = o_copy.layout ? Ext.clone(o_copy.layout) : {};
				var parent_properties = parent.properties ? Ext.clone(parent.properties) : {};
				var parent_widgets = parent.widgets ? Ext.clone(parent.widgets) : [];
				var parent_layouts = parent.layout ? Ext.clone(parent.layout) : {};

				Ext.Object.each(parent_properties, function(s_key, parent_value) {

					if (!child_properties.hasOwnProperty(s_key)) {
						child_properties[s_key] = Ext.clone(parent_value);
					}
					else {
						Ext.applyIf(child_properties[s_key], parent_value);
					}
					
				});

				if (o_copy.$delete) {
					Ext.Array.each(o_copy.$delete, function(s_key) {
						delete child_properties[s_key];
					});
				}

				Ext.apply(o_copy.properties, child_properties);

				if (Ext.isArray(parent_widgets) && Ext.isArray(child_widgets)) {

					Ext.Array.each(parent_widgets, function(o_parent) {

						var b_found = false;

						Ext.Array.each(child_widgets, function(o_child) {

							if (o_parent.name == o_child.name) {
								b_found = true;
							}
							if (b_found) {
								return true;
							}
						});

						if (!b_found) {
							child_widgets.push(o_parent);
						}
					});
				}

				o_copy.widgets = child_widgets;
				
				Ext.Object.each(parent_layouts, function(s_layout, value) {

					if (!child_layouts.hasOwnProperty(s_layout)) {

						child_layouts[s_layout] = Ext.clone(value);
					}
				});

				o_copy.layout = child_layouts;

				// Does the child have any collections itself then iterate through them

				if (o_copy.hasOwnProperty("collections")) {

					var o_collections = i_self.flattenItems(o_copy, against, includeName);
					Ext.Object.merge(o_result, o_collections);
				}

				// Do we have to include the name?
				if (includeName) {
					o_copy.name = key;
				}

				// push our child to the return object
				// If against is name we will have to use the key from the collection iteration:
				if (against === "name") {
					o_result[key] = o_copy;
				}
				else {
					o_result[o_copy[against]] = o_copy;
				}
			});
		}

		return o_result;
	},

	processGroups: function(xpd, data, dynamicNS) {

		var i_self = this;

		var DEFAULT_TITLE = "untitled group";

		if (xpd.hasOwnProperty("against")) {
			xpd = i_self.flatten(xpd);
		}
		else if (xpd.against !== "id") {
			throw new Error(".processGroups: Can only handle xpd flattened agaist collection id (.id)");
		}

		data = data || {};

		var o_collection = xpd.collections[data[xpd.idProperty]] || {
			properties: xpd.collections.all
		};

		var ret = {};

		if (o_collection) {

			var o_properties = o_collection.properties || {};
			var o_xpdGroups = xpd.groups || {};

			Ext.Object.each(data, function(attr, key) {

				if (!o_properties[key]) {
					return;
				}

				var o_property = o_properties[key];
				var a_groups = o_property.groups;

				Ext.Object.each(a_groups, function(s_group) {

					if (o_xpdGroups[s_group].renderIn && o_xpdGroups[s_group].renderIn.webshop) {
						if (!ret[s_group]) {
							ret[s_group] = {
								title: (o_xpdGroups[s_group] || {}).title || DEFAULT_TITLE,
								properties: {}
							};
						}

						var value = attr || "";

						if (o_property["enum"] && dynamicNS) {
							value = dynamicNS.getEnumValue(attr + "-text");
							if (value < "") {
								value = dynamicNS.getEnumValue(attr);
							}
						}

						ret[s_group].properties[key] = {
							title: i_self.translate(xpd, o_property.title),
							value: String(value),
							name: key
						};
					}
				});
			});
		}

		return ret;
	},

	allowedChilds: function(xpd, xpdCollection) {

		var i_self = this;

		if (xpd.hasOwnProperty("against")) {
			xpd = i_self.flatten(xpd);
		}
		else if (xpd.against !== "id") {
			throw new Error(".allowedChilds: Can only handle xpd flattened agaist collection id (.id)");
		}

		var collectionId = typeof xpdCollection == "number" ? xpdCollection : xpdCollection.id;

		return i_self.allowedRelations(xpd, collectionId, i_self.ALLOWED_CHILDS);
	},

	allowedParents: function(xpd, xpdCollection) {

		var i_self = this;

		if (xpd.hasOwnProperty("against")) {
			xpd = i_self.flatten(xpd);
		}
		else if (xpd.against !== "id") {
			throw new Error(".allowedParents: Can only handle xpd flattened agaist collection id (.id)");
		}

		var collectionId = typeof xpdCollection == "number" ? xpdCollection : xpdCollection.id;

		return i_self.allowedRelations(xpd, collectionId, i_self.ALLOWED_PARENTS);
	},

	allowedRelation: function(xpdFlatten, collectionId, type) {

		var i_self = this;

		var allowed = {};
		var xpdColl = xpdFlatten.collections[collectionId];

		Ext.Object.each(xpdFlatten.collections, function(coll, key) {
			if (key == i_self.ALL_PROPERTIES_KEY) {
				return;
			}

			if (type == i_self.ALLOWED_PARENTS) {
				if (coll.allowedchilds) {
					Ext.Array.each(coll.allowedchilds, function(id) {
						if (xpdColl.id == id) {
							allowed[coll.id] = coll;
						}
					});
				}
				else {
					allowed[coll.id] = coll;
				}
			}
			else {
				if (coll.allowedparents) {
					Ext.Array.each(coll.allowedparents, function(id) {
						if (xpdColl.id == id) {
							allowed[coll.id] = coll;
						}
					});
				}
				else {
					allowed[coll.id] = coll;
				}
			}
		});

		var ret;

		if (type == i_self.ALLOWED_PARENTS) {
			if (xpdColl.allowedparents) {
				ret = Ext.Array.map(xpdColl.allowedparents, function(id) {
					return allowed[id];
				});
			}
		}
		else {
			if (xpdColl.allowedchilds) {
				ret = Ext.Array.map(xpdColl.allowedchilds, function(id) {
					return allowed[id];
				});
			}
		}

		if (!ret) {
			ret = [];
			Ext.Object.each(allowed, function(coll) {
				ret.push(coll);
			});
		}

		return ret;
	},

	fixDatabasebind: function(str) {

		var r_split = /[^ ,]+/g;
		var r_preparse = /([^, (]+)\(([^)]+)\)/g;

		return (str.join ? str.join(',') : str).replace(r_preparse, function(_, a, b) {
			return a + '.' + (b.match(r_split) || []).join(',' + a + '.');
		}).match(r_split) || [];
	},

	getCollectionByKey: function (o_xpd, value, s_key) {

		var i_self = this;

		s_key = s_key || "name";

		var o_found = null;

		Ext.Object.each(o_xpd.collections, function (o_col) {
			if (o_col[s_key] == value) {
				o_found = o_col;
				return false;
			}
		});

		return o_found;
	}
});
