/**
 * @class Ext.ux.ip2.dmdeditor.Form
 * View a DMD entity in tab panel with forms in portals
 * Extends a form panel in order to be able to properly serialize
 *
 * @extend Ext.ux.form.JsonForm
 *
 *
 */
/*jshint maxdepth:10 */

Ext.define('Ext.ux.dmd.entityeditor.Form', {

	requires: [
		//'Ext.ux.plugin.data.Tracker',
		// 'Ext.ux.ip2.container.Widget',
		// 'Ext.ux.ip2.dmdeditor.Affiliation',
		// 'Ext.ux.ip2.xpdeditor.Panel',
		// 'Ext.ux.ip2.form.field.i18n.Text',
		// 'Ext.ux.ip2.form.field.dmd.LoveTrigger'
	],

	extend: 'Ext.ux.form.JsonForm',

	alias: ['widget.entityformeditor', 'widget.entityFormEditor'],

	plugins: [
		{
			ptype: 'datatracker'
		}
	],

	config: {

		action: 'new',

		closable: true,

		entityName: null,

		dmdPath: null,

		trackerParent: null,

		keys: null,

		model: null,

		batchName: null,

		//parentEntity: null,

		//parentKeys: null,

		//parentStore: null,

		permission: null,

		preventHeader: false,

		uiName: 'common'
	},

	// TODO: Ugly hax to find it on .up('[entityEditor=true]') in widgets
	entityEditor: true,

	_allFields: null,

	_gridXType: 'ip2pagingGrid',

	layout: 'border',

	dirty: false,

	fields: null,

	forceClose: false,

	/**
	 * @property {Object}
	 * The key of the form to use when updating or creating the record
	 * @private
	 */
	key: null,

	initComponent: function() {

		isj = this;

		// Kick off by adding a ip2.waitmask!
		ip2.waitMask.show();

		var form = this;

		// JsonForm applies our config, so apply trackResetOnLoad before calling parent
		Ext.apply(form.initialConfig, {
			trackResetOnLoad: true
		});

		form.initConfig(form.initialConfig);

		form.setBatchName(ip2.unique('entityEditor-'));

		form.dockedItems = [];

		if ( !form.getPreventHeader() ) {

			form.dockedItems.push({
				xtype: 'header',
				baseCls:'x-panel-header',
				name: 'header',
				tools: [
					{
						type: 'close',
						tooltip: 'Close',
						handler: function(event, toolEl, header) {

							var form = header.up();

							form.close();
						}
					}
				]
			});
		}

		form.dockedItems.push({
			xtype: 'toolbar',
			name: 'toolbar',
			dock: 'top',
			items: [
				{
					xtype: 'button',
					text: ip2.i18n({
						da: 'Gem',
						en: 'Save'
					}),
					name: 'save',
					syncDependency: true,
					iconCls: 'ip2-icon-data-disk',
					handler: function(self) {

						var form = self.up().up();

						form.save();
					}
				},
				{
					xtype: 'button',
					text: ip2.i18n({
						da: 'Gem og luk',
						en: 'Save and Close'
					}),
					name: 'saveandclose',
					syncDependency: true,
					iconCls: 'ip2-icon-data-next',
					handler: function(self) {

						var form = self.up().up();

						form.forceClose = true;

						form.save();
					}
				},
				{
					xtype: 'button',
					text: ip2.i18n({da:'Slet',en:'Delete'}),
					name: 'delete',
					iconCls: 'ip2-icon-data-delete',
					disabled: true,
					handler: function(self) {
						Ext.MessageBox.show({
							msg: ip2.i18n({
								da: 'Vil du slette',
								en: 'Confirm delete'
							}),
							buttons: Ext.MessageBox.YESNOCANCEL,
							icon: Ext.MessageBox.WARNING,
							fn: function(btn) {

								if ( btn == 'yes' ) {

									var form = self.up().up();

									form.forceClose = true;

									form.remove();
								}
							}
						});
					}
				},
				{
					xtype: 'button',
					text: ip2.i18n({
						da: 'Luk',
						en: 'Close'
					}),
					name: 'close',
					iconCls: 'pf-door_exit',
					handler: function(self) {

						var form = self.up().up();

						form.close();
					}
				}
			]
		});

		form.items = [
			{
				xtype: 'tabpanel',
				region: 'center',
				name: 'mainpanel'
			},
			{
				xtype: 'tabpanel',
				region: 'south',
				name: 'childpanel',
				title: 'Child models',
				height: 200,
				split: true,
				collapsible: true,
				hidden: true
			}
		];

		form.callParent();

		form._allFields = [];

		form.on({
			// Intercept closing of the form, check if dirty
			'beforeclose': {
				fn: function(self) {

					if ( self.isDirty() && self.forceClose !== true ) {

						Ext.MessageBox.show({
							msg: ip2.i18n({
								'da': 'Vil du gemme ændringer?',
								'en': 'Do you wish to save changes?'
							}),
							buttons: Ext.MessageBox.YESNOCANCEL,
							icon: Ext.MessageBox.WARNING,
							fn: function(btn) {

								if ( btn == 'yes' ) {

									self.forceClose = true;

									self.save();
								}

								if ( btn == 'no' ) {

									self.forceClose = true;

									self.close();
								}
							}
						});

						return false;
					}
				}
			},
			'validitychange': {
				fn: function(self, valid) {

					self.owner.syncButtons({
						valid: valid
					});
				}
			},
			'dirtychange': {
				fn: function(self, dirty) {

					self.owner.syncButtons({
						dirty: dirty
					});
				}
			},
			'dmdloaded': {
				fn: function(self, record) {

					self.buildUi();

					self.loadRecord(record);

					self.updateTitle();

					ip2.waitMask.hide();
				}
			},
			'dmdcreated': {
				fn: function(self, record, values) {

					self.syncingParent(record, values, 'create');

					self.syncingWidgets(record, values, 'create');

					if ( self.forceClose ) {
						self.close();
					}
				}
			},
			'dmdupdated': {
				fn: function(self, record, values) {

					self.syncingParent(record, values, 'update');

					self.syncingWidgets(record, values, 'update');

					if ( self.forceClose ) {
						self.close();
					}

				}
			},
			'recordcreated': {
				fn: function(self, record, values) {

					//console.log('created a record', self, record, values);
				}
			},
			'recordupdated': {
				fn: function(self, record, values) {

					//console.log('updated a record', self, record, values);
				}
			}
		});

		// Done with all initializing
		// Now look through config and determine load type
		var trackerParent = form.getTrackerParent();

		// Do we have a trackerParent? If so wait for plugin and check to set
		if ( trackerParent ) {

			if ( form._isDataTrackerReady ) {

				form.initTrackerParent()
					.initDmdSetup();
			}
			else {

				form.on({
					'datatrackerinit': {
						fn: function() {

							form.initTrackerParent()
								.initDmdSetup();
						}
					}
				});
			}
		}
		// No data tracker parent to get values from - do initDmdSetup
		else {

			form.initDmdSetup();
		}

		return form;
	},

	dmdSetup: function() {

		var form = this,
			Model = form.getModel(),
			keys = form.getKeys(),
			entityName = form.getEntityName(),
			dmdPath = form.getDmdPath();

		// PERMISSIONS: Set the permission for this form based as found in models entity
		form.setPermission(Model.prototype.entity.permission);

		if ( !entityName ) {

			form.setEntityName(Model.prototype.entity.name);
		}
		if ( !dmdPath ) {

			form.setDmdPath(Model.dmdFile);
		}

		return form;
	},

	dmdKeysSetup: function() {

		var form = this,
			model = form.getModel(),
			keys = form.getKeys(),
			record = form.record;

		// We were not supplied any keys, lets try to get
		if (!keys) {

			// If record supplied, extract keys;
			if (record) {

				keys = ip2.extractKeys(model, record);
			}
		}

		// Sanitize the keys by stringing if object
		if (Ext.isObject(keys)) {

			keys = ip2.JSON.stringify(keys);
		}

		if (!keys) {

			Ext.Error.raise({
				description: 'Coudn\'t extract any keys from config',
				message: 'No keys were found from the configuration for this entity editor',
				debug: 'warning',
				ignore: true
			});
		}

		form.setKeys(keys);

		return form;
	},

	dmdStoreSetup: function() {

		var form = this,
			Model = form.getModel(),
			dmdPath = form.getDmdPath(),
			uiName = form.getUiName();

		var keys = form.getKeys();

		/*form.store = ip2.store({
			type: 'batch',
			model: Model,
			autoSync: false,
			batchName: form.getBatchName()
		});*/

		// STORE: Setup a store for the editor
		form.store = Ext.create('Ext.data.Store', {
			model: Model,
			autoSync: false
		});

		form.store.proxy.extraParams = {
			storeId: ip2.storeId(),
			action: 'getRow',
			context: 'form',
			dmdFile: dmdPath,
			uiName: uiName,
			key: keys
		};

		return form;
	},

	initDmdRecord: function() {

		var form = this,
			Model = form.getModel(),
			model = Model.prototype,
			store = form.store,
			record = form.record,
			action = form.getAction();

		if ( action == 'new' ) {

			record = new Model();

			form.store.insert(0, record);

			form.fireEvent('dmdloaded', form, record);
		}
		else if ( action == 'update' ) {

			// We have been given a record, and need not load one
			if ( record ) {

				store.insert(0, record); // insert record into store, so store.save() can be used

				form.fireEvent('dmdloaded', form, record);
			}
			// Store needs to load one for us
			else {

				store.load(function(records) {

					form.fireEvent('dmdloaded', form, records[0]);
				});
			}
		}

		return form;
	},

	initDmdSetup: function() {

		var form = this,
			model = form.getModel(),
			dmdPath = form.getDmdPath(),
			entityName = form.getEntityName();

		// No model supplied from neither trackerParent nor config!
		if ( !model ) {

			// Do we have a dmdPath and a entity named to load then?
			if ( dmdPath && entityName ) {

				// TODO: Load this model into the resources library
				// ... and form.dmdModelSetup(model); form.dmdSetup(); etc..
			}
		} else {

			form.dmdSetup()
				.dmdKeysSetup()
				.dmdStoreSetup()
				.initDmdRecord();
		}

		return form;
	},

	initTrackerParent: function() {

		var form = this,
			parent = form.getDataParent(),
			model = parent.getModel(),
			record = parent.getRecord();

		// Check for a model in the parent and none in config!
		if ( model && !form.getModel() ) {

			// Set form model
			form.setModel(model);
		}

		return form;
	},

	buildUi: function() {

		var form = this,
			action = form.getAction(),
			name = form.getUiName(),
			model = form.getModel().prototype,
			entity = model.entity,
			ui = entity.ui.forms[name],
			dmd = model.dmdModel;

		var mainPanel = form.down('[name="mainpanel"]'),
			childPanel = form.down('[name="childpanel"]');

		var portalPanel,
			portalContainer,

			modelChildren,

			xpdPortlets = [],
			xpdTabs = [],
			xpdChildTabs = [],

			widgetPortlets = [],
			widgetTabs = [],
			widgetChildTabs = [],


			affiliationPortlets = [],
			affiliationTabs = [],
			affiliationChildTabs = [],

			tempArray = [],
			fieldComponents = [];

		var PORTLET_DEFAULTS = {
				padding: 0,
				bodyPadding: 0
			},
			PADDING_DEFAULTS = 15;

		// Clean the panel containers main and child
		mainPanel.removeAll();
		childPanel.removeAll();


		// Prepare the ui.layout object
		ui.layout || ( ui.layout = {} );

		// Create the container (tab), portal panel and its columns based on the UI
		function createPortalPanel(ui) {

			// 1. Prepare portal container and portal panel configs

			var container,
				portal;

			var layout = ui.layout.portlets || {};

			//	Create the portal panel container config
			container = {
				xtype: 'panel',
				title: 'System',
				autoScroll: true,
				items: []
			};

			Ext.Object.merge(container, layout);

			// Create the portal panel config
			portal = {
				xtype: 'portalpanel',
				name: 'portalpanel',
				items: []
			};

			// Push the portal config to the container items
			container.items.push(portal);


			//	2. Create the portal column configs for the portal panel

			// Set columnIndex to 1 as default (minimum 1 column)
			var columnIndex = 1;

			// Examine the portal portlets to find out how many columns are needed
			var prop;

			for ( prop in ui.items ) {
				var index = ui.items[prop].position.column;
				columnIndex = index > columnIndex ? index : columnIndex;
			}

			var i = 0;
			// Create the columns now we know how many we need
			for ( ; i < columnIndex; i++ ) {

				// Push the portal column config to the portal panel items
				portal.items.push({
					xtype: 'portalcolumn',
					name: 'portalcolumn-' + (i + 1),
					items: []
				});
			}


			// Return the portal
			return {
				portal: portal,
				container: container
			};
		}

		// Iterate the portlets in the UI
		function createPortlets(ui, portalPanel) {

			var portletsMap = [],
				portlet,
				prop, i;

			// Build portlets + items and map the portlet to portletsMap
			for ( prop in ui.items ) {

				var uiItem = ui.items[prop],
					columnPosition = uiItem.position.column - 1,
					rowPosition = uiItem.position.row - 1,
					type = uiItem.type;

				// If column not yet mapped in portletsMap
				if ( !portletsMap[columnPosition] )
					portletsMap[columnPosition] = [];

				// Create the portlet config - merge with defaults
				portlet = Ext.Object.merge({}, PORTLET_DEFAULTS, {
					xtype: 'portlet',
					title: ip2.dmdTranslate(model, uiItem.title),
					//layout: 'anchor',
					items: [
						{
							xtype: 'container',
							padding: PADDING_DEFAULTS,
							items: []
						}
					]
				});

				portletsMap[columnPosition][rowPosition] = portlet;

				//////////////////////////////////////////////////////////////////
				//	Create the portlet items									//
				//	Types:														//
				//	- portlet													//
				//	- affiliation												//
				//	- widget													//
				//////////////////////////////////////////////////////////////////

				switch ( type ) {

				case 'portlet':
					createPortletTypePortlet(uiItem, portlet);
					break;

				case 'affiliations':
					createPortletTypeAffilation(uiItem, portlet);
					break;

				case 'widgets':
					createPortletTypeWidget(uiItem, portlet);
					break;

				default:
					createPortletTypePortlet(uiItem, portlet);
					break;
				}
			}


			function createPortletTypePortlet(uiItem, portlet) {

				Ext.Array.each(uiItem.columns, function(item) {

					var name,
						lookup,
						layout,
						isXpd,
						fullName;

					// Get the column lookup name
					// Check if it is a borrowed column from another entity and
					name = ( item.from ) ? (item.from + '_' + item.name) : item.name;

					name = name;

					// Get the look up column object
					lookup = model.columns[name];

					layout = ui.layout[name.toLowerCase()] || {};

					Ext.Object.merge(item, lookup);

					// Check if the lookup is a XPD
					isXpd = item.xpdpath;


					//////////////////////////////////////////////////////////////////
					//	Check XPD column items										//
					//////////////////////////////////////////////////////////////////
					if ( isXpd ) {

						var title = item.title;

						var xpdConfig = {
							xtype: 'ip2XpdComponent',
							name: name,
							dmd: dmd,
							entity: entity,
							column: name,
							trackerParent: form,
							xpdPath: item.xpdpath // TODO: Do I have to send the XPD path now?
						};

						if ( layout.listAs ) {

							xpdConfig.listAs = layout.listAs;
						}

						// Work out how to render this XPD!
						switch ( layout.renderAs ) {

						// XPD component should render as a tab
						case 'tab':
						case 'fulltab':
							xpdConfig.title = title;
							xpdTabs.push(xpdConfig);
							break;

						// XPD component should render as a portlet (default)
						default:

							// Push the XPD portlet to the xpdPortlets array
							// This array is iterated as last to avoid conflicts with
							// other portlets and positions
							xpdPortlets.push(Ext.Object.merge({}, PORTLET_DEFAULTS, {
								title: ip2.dmdTranslate(model, title),
								xtype: 'portlet',
								items: [ xpdConfig ]
							}));
							break;
						}

						// Add the field name to fieldComponents array
						fieldComponents.push(xpdConfig.name);
					}

					// NOT XPD TYPE: Not a XPD column, treat as normal
					else {

						// Create the config object to add for this column item
						var config = form.initComponentConfig(item);

						// Push the fieldConfig to the portlet items container!
						portlet.items[0].items.push(config);

						// Add the field name to fieldComponents array
						fieldComponents.push(config.name);
					}

				});
			}

			function createPortletTypeAffilation(uiItem, portlet) {

				var affiliations = entity.affiliations,
					affiliation = affiliations[uiItem.component];
			}

			function createPortletTypeWidget(uiItem, portlet) {

				var widgets = entity.widgets,
					widget = widgets[uiItem.component];

				portlet.title = widget.title;

				portlet.items[0].items.push({
					xtype: 'ip2widgetcontainer',
					src: widget.path,
					border: false,
					trackerParent: form
				});
			}

			// DISTRIBUTE: Iterate portletsMap to distribute portlet configs to columns
			for ( i = 0; i < portletsMap.length; i++ ) {

				// Iterate mapped columns for portlets
				for ( var y = 0; y < portletsMap[i].length; y++ ) {

					// Push the portlet to the right indexed portalcolumn in portalPanel
					portalPanel.items[i].items.push(portletsMap[i][y]);
				}
			}
		}

		// Create widgets mentioned in the UI
		function createWidgets(portalPanel) {

			var name,
				widget,
				layout;

			for ( name in entity.widgets ) {

				widget = entity.widgets[name];

				layout = ui.layout[name];

				if ( layout ) {
					switch ( layout.renderAs ) {

					case 'fulltab':
						createWidgetTypeTab(widget, portalPanel);
						break;

					case 'tab':
						createWidgetTypeChildTab(portalPanel);
						break;

					case 'portlet':
						createWidgetTypePortlet(portalPanel);
						break;

					default:
						break;
					}
				}
			}

			function createWidgetTypeTab(widget, panel) {

				widgetTabs.push({
					xtype: 'ip2widgetcontainer',
					title: ip2.dmdTranslate(model, widget.title),
					src: widget.path,
					border: false,
					trackerParent: form,
					layout: 'fit'
				});

			}

			function createWidgetTypeChildTab(widget, panel) {

			}

			function createWidgetTypePortlet(widget, panel) {

			}
		}

		// Create the affiliations mentioned in the UI
		function createAffiliations(portalPanel) {

			var name;

			for ( name in form.affiliations ) {

				var affiliation = form.affiliations[name],
					affiliationConfig,
					url = ( form.recs && form.recs[0] && form.recs[0].raw ) ? form.recs[0].raw[name] : '';

				affiliationConfig = {
					xtype: 'ip2WebContainer',
					src: url,
					border: false
				};

				switch ( affiliation.renderAs ) {

				// Affiliation rendered as tab
				case 'fulltab':

					affiliationConfig.title = affiliation.title;
					affiliationTabs.push(affiliationConfig);
					break;

				// Affiliation rendered as tab
				case 'tab':

					affiliationConfig.title = affiliation.title;
					affiliationChildTabs.push(affiliationConfig);

					break;

				// Affiliation rendered as list
				// What is list?
				case 'list':
					break;

				// Affiliation rendered as portlet (default)
				// TODO: This way with the position and row is not a good idea!
				default:

					// Find the correct column based on affiliation position info
					var column = portalPanel.items[affiliation.position.column - 1];

					// Is the row index position taken? If so add as last
					if ( column.items[affiliation.position.row - 1] ) {
						column.items[column.items.length] = affiliationConfig;
					}

					// The row index position is vacant, occupy it
					else {
						column.items[affiliation.position.row - 1] = Ext.Object.merge({}, PORTLET_DEFAULTS, {
							xtype: 'portlet',
							title: ip2.dmdTranslate(model, affiliation.title),
							items: [ affiliationConfig ]
						});
					}

					break;
				}
			}
		}

		function distributeWidgets(mainPanel, childPanel, portalPanel, widgetTabs, widgetChildTabs, widgetPortlets) {

			Ext.Array.each(widgetTabs, function(tab) {

				var index = tab.index || 0;

				if ( index ) {
					mainPanel.insert(index, tab);
				}
				else {
					mainPanel.add(tab);
				}
			});

			Ext.Array.each(widgetChildTabs, function(tab) {

				var index = tab.index || 0;

				if ( index ) {
					childPanel.insert(index, tab);
				}
				else {
					childPanel.add(tab);
				}
			});
		}

		// Iterate the arrays xpdTabs, xpdChildTabs & xpdPortlets to distribute the XPD component
		// to the correct container
		function distributeXpds(mainPanel, childPanel, portalPanel, xpdTabs, xpdChildTabs, xpdPortlets) {


			Ext.Array.each(xpdPortlets, function(portlet) {

				var last = portalPanel.items.length - 1;

				portalPanel.items[last].items.push(portlet);
			});

			// XPD panel tabs
			Ext.Array.each(xpdTabs, function(tab) {

				var index = tab.index || 0;

				if ( index ) {
					mainPanel.insert(index, tab);
				}
				else {
					mainPanel.add(tab);
				}
			});

			// XPD child panel tabs
			Ext.Array.each(xpdChildTabs, function(tab) {

				var index = tab.index || 0;

				if ( index ) {
					childPanel.insert(index, tab);
				}
				else {
					childPanel.add(tab);
				}
			});
		}

		// Iterate the arrays affiliationsTabs, affiliationChildTabs & affiliationPortlets to distribute the
		// affiliation component to the correct container
		function distributeAffiliations(mainPanel, childPanel, portalPanel, affiliationTabs, affiliationChildTabs, affiliationPortlets) {

			var i = 0;

			for ( ; i < affiliationPortlets.length; i++ ) {

			}

			// Affiliation tabs
			for ( i = 0; i < affiliationTabs.length; i++ ) {

				mainPanel.add(affiliationTabs[i]);
			}

			// Affiliation child tabs
			for ( i = 0; i < affiliationChildTabs.length; i++ ) {

				childPanel.add(affiliationChildTabs[i]);
			}
		}

		// Iterate the array modelChildren to distribute the components to the correct container
		function distributeModelChildren(mainPanel, childPanel, portalPanel, modelChildren, ui) {

			var i = 0,
				len = modelChildren.length,
				last;

			for ( ; i < len; i++ ) {

				var modelChild = modelChildren[i],
					split = modelChild.model.getName().split('!'),
					childName = split[split.length - 1],
					renderAs = 'childtab';

				// Do we have a layout for this one!
				if ( ui.layout[childName] ) {
					renderAs = ui.layout[childName].renderAs;
				}

				switch (renderAs) {

				case 'portlet':
					last = portalPanel.items.length - 1;

					var container = {
						xtype: 'container',
						items: [ modelChild ]
					};

					portalPanel.items[last].items.push(container);
					break;

				case 'tab':

					mainPanel.add(modelChild);
					break;

				default:
					childPanel.add(modelChild);
					break;

				}
			}
		}

		// Remove names in fieldComponents from relationalFields and add remains as hidden fields
		function createHiddenFields(relationalFields, fieldComponents) {

			var i = 0,
				index;

			// Remove doublets in relationalFields matched against fieldComponents
			// This returns all fields that does not have a field in form
			for ( ; i < fieldComponents.length; i++ ) {

				index = relationalFields.indexOf(fieldComponents[i]);

				if ( index > -1 ) {

					relationalFields.splice(index, 1);
				}
			}

			var idProperty = model.idProperty;

			for ( i = 0; i < relationalFields.length; i++ ) {

				var relationalFieldName = relationalFields[i];

				if ( relationalFieldName == idProperty )
					relationalFieldName = relationalFieldName;

				form.add({
					xtype: 'hidden',
					name: relationalFieldName
				});
			}
		}


		// Iterate the columns - populate arrays to run through later
		// in order to find fields that needs a hidden input field (not mentioned in the UI of the DMD)
		for ( name in model.columns ) {

			// Array for all column fields from record
			form._allFields.push(name);

			// Trimmed later against fieldComponents for those not directly mentioned in the DMD UI
			// Trimming happens in the function createHiddenFields
			tempArray.push(name);
		}


		// Create the portal panel, the portal columns and prepare for the column fields to be inserted
		var portalCmps = createPortalPanel(ui);
		portalPanel = portalCmps.portal;
		portalContainer = portalCmps.container;


		// Populate the portlets with column fields, and organize and populate the xpd arrays (xpdTabs, xpdChildTabs, xpdPortlets)
		// Those arrays are later used for distribution function
		createPortlets(ui, portalPanel);

		createWidgets(portalPanel);

		// Populate the affiliation arrays (affiliationTabs, affiliationChildTabs, affiliationPortlets)
		// Those arrays are later used for distribution function
		createAffiliations(portalPanel);

		// Create the child model array
		// Used later on for distribution function
		modelChildren = form.createRelationComponents(model.childModels);

		distributeWidgets(mainPanel, childPanel, portalPanel, widgetTabs, widgetChildTabs, widgetPortlets);

		// Distribute the XPD components
		distributeXpds(mainPanel, childPanel, portalPanel, xpdTabs, xpdChildTabs, xpdPortlets);

		// Distribute the Affiliation components
		distributeAffiliations(mainPanel, childPanel, portalPanel, affiliationTabs, affiliationChildTabs, affiliationPortlets);

		// Distribute the Model Children components
		distributeModelChildren(mainPanel, childPanel, portalPanel, modelChildren, ui);

		// Does the south region (childPanel) have items? If so then show it
		if ( childPanel.items.items.length ) {
			childPanel.show();
		}

		var portletsPos = 0,
			portletsIndex = portalContainer.index,
			lastPos = mainPanel.items.getCount();

		if ( portletsIndex ) {

			if ( Ext.isString(portletsIndex) ) {

				switch ( portletsIndex ) {

				case 'first':
					portletsIndex = 0;
					break;
				case 'last':
					portletsIndex = lastPos;
					break;
				default:
					portletsIndex = +(portletsIndex);
				}
			}

			portletsPos = Ext.isNumber(portletsIndex) ? portletsIndex : portletsPos;
		}

		if ( portalContainer.disabled || portalContainer.hidden )
			portletsPos = lastPos;

		// Add the portalcontainer as first item
		// Re-reference the new instanciated object (was config)
		portalContainer = mainPanel.insert(portletsPos, portalContainer);

		// Do the hidden fields thing (TODO: update this comment!)
		createHiddenFields(tempArray, fieldComponents);

		mainPanel.setActiveTab(0);

		if ( action != 'new' ) {

			form.down('[name=delete]').setDisabled(false);
		}
	},

	/**
	 * @method createRelationComponents
	 *
	 * @param [Object] model The model to build relational components from (one on many)
	 */
	createRelationComponents: function(model) {

		var me = this,
			tabs = [],
			xtype = me._gridXType;

		if ( model.dmd ) {

			for ( var i = 0; i < model.items.length; i++ ) {

				var rel = model.items[i],
					entityName = '',
					show = false;

				if ( rel.between.entity == me.model.prototype.entity.name ) {

					entityName = rel.and.entity;
					show = (rel.property && rel.property.shownAsChild.between) ? rel.property.shownAsChild.between : false;
				}

				else {

					entityName = rel.between.entity;
					show = (rel.property && rel.property.shownAsChild.and) ? rel.property.shownAsChild.and : false;
				}

				var m = ip2.getModelInformation(me.model.prototype.modelName);

				if ( show ) {

					var t = {
						xtype: xtype,
						autoScroll: true,
						childPanel: true,
						parentEntity: m.entity,
						parentKeys: me.key,
						model: ip2.model({
							dmd: model.dmd,
							entityName: entityName
						}),
						parentPanel: me,
						layout: 'fit'
					};

					if ( me.window ) {
						t.window = me.window;
					}

					tabs.push(t);
				}
			}
		}

		else {

			var o;

			for (o in model) {

				var n = o.replace(/[_]/g,'.');

				tabs.push({
					xtype: xtype,
					autoScroll: true,
					model: n + '.ip2data:generic' // TODO!!! - maybe not always the generic
				});
			}
		}
		return tabs;
	},

	getFormTitles: function() {

		var form = this,
			titles = '',
			keys = ip2.JSON.parse(form.getKeys()),
			i = 0,
			sep;

		form.getForm().getFields().each(function(field) {

			var column = field.column,
				value = field.getValue();

			if ( column && column.isTitle && value.length ) {

				sep = !i ? '' : '/';

				// Try for language object value
				value = ip2.i18n(value);

				titles += sep + value;
			}
		});

		// No titles declared in columns, create by keys
		if ( !titles.length ) {

			ip2.each(keys, function(value) {

				if ( i < 2 ) {
					sep = !i ? '' : ' / ';
					titles += sep + value + ' ';
				}

				i++;
			});
		}

		return titles;
	},

	getToolbar: function() {

		return this.down('[name=toolbar]');
	},

	getWidgets: function() {

		var form = this;

		function GetWidgets(items) {

			this.items = [];
			this.length = 0;

			this.initialize(items);
		}

		GetWidgets.prototype = {
			initialize: function(items) {

				var self = this;

				items.each(function(item) {

					if ( item.widget ) {
						self.items.push(item);
					}
					if ( item.items && item.items.getCount ) {
						if ( item.items.getCount() )
							self.initialize(item.items);
					}
				});
			},
			each: function(fn, scope) {

				scope || ( scope = this );

				var items = this.items,
					item,
					i = 0,
					len = items.length;

				for ( ; i < len; i++ ) {
					item = items[i];
					if ( Ext.isFunction(fn) )
						fn.call(scope, item, i, items);
				}
			}
		};

		return new GetWidgets(form.items);
	},

	/**
	 * @method initComponentConfig
	 * @param {Object} field The looked up column with data
	 * @return {Object} xtype The xtype configuration
	 */
	initComponentConfig: function(field) {

		var form = this,
			model = form.getModel().prototype,
			action = form.getAction(),
			component,
			length;

		var MIN_SIZE = 10,
			MAX_SIZE = 64;

		// TODO ALEEEEERT - The whole relation AS change has hit me hard now!!

		component = Ext.Object.merge({}, field, field.column, field.column.editor, {
			fieldLabel: field.column.header,
			allowBlank: field.isMandatory ? false : true,
			listeners: {},
			hideLabel: false
		});

		// TODO: Figure out a PROPER setup of the configuration, as it is now I am just scraping like a mad man!
		// Also - this next thing is a hack!
		if ( field.xtype )
			component.xtype = field.xtype;

		length = component.len ? +(component.len) : null;

		// Manage the length of the component
		if ( length && length <= MAX_SIZE ) {
			component.maxLength = length;
			component.size = ( length <= MIN_SIZE ) ? MIN_SIZE : length;

		} else {
			component.anchor = '100%';
		}

		// Component is readOnly if disable - TODO: Talk to Claus about this!
		if ( component.disabled ) {

			component.readOnly = true;
			component.disabled = false;
		}

		// Id columns are always readOnly!
		if ( component.isIdColumn ) {

			component.readOnly = true;
		}

		// Primary key columns are not allowed blank
		if ( component.isPrimaryKeyColumn ) {

			component.allowBlank = false;

			// Component is not readonly if creating a new!
			component.readOnly = ( action == 'new' ) ? false : true;
		}

		// Kick off the form's tabindex if not present already!
		if ( !form.tabIndex )
			form.tabIndex = 0;

		// Update the tabindex if we are allowed to use this component
		if ( !component.readOnly )
			component.tabIndex = form.tabIndex++;


		// TODO: Fix the whole AS thing!!
		// Set listener for change on borrow fields (from) - set the parentkey(s) as dirty!
		/*
			if ( component.from ) {

				// TODO: Can run into problems with managment of dirty state of primaryField
				// TODO: Make sure primaryField asks dependent fields instead!
				component.listeners.change = function(self, newValue, oldValue) {

					var entity,
						primaryKeys,
						primaryKey,
						primaryField,
						originalValue = self.originalValue,
						i = 0;

					if ( newValue && oldValue ) {

						entity = model.dmdModel.entities[self.from];
						primaryKeys = entity.primaryKey;

						for ( ; i < primaryKeys.length; i++ ) {

							primaryKey = primaryKeys[i];
							primaryField = form.getForm().findField(primaryKey);

							if ( primaryField )
								primaryField.dirty = ( newValue === originalValue ) ? primaryField.dirty : true;
						}
					}
				};
			}
		*/


		// Special xtype situations
		switch (component.xtype) {

		case 'i18ntextfield':
			break;

		case 'checkbox':
			break;

		case 'ip2LoveTriggerField':

			if ( !component.childKeys.length || component.readOnly ) {

				component.xtype = 'textfield';
			}
			else {

				component.pageSize = 10;

				component.entityParent = {
					keys: component.parentKeys,
					titles: component.parentTitles,
					name: component.parentEntity
				};

				component.entityChild = {
					keys: component.childKeys
				};

				// If a foreign relation - add a select listener!
				if ( model.entity.name != component.editor.parentEntity ) {

					component.listeners = {
						'select': {
							fn: function(self, records) {

								var data;

								if ( records ) {

									data = records[0].data;

									form.setValuesFromForeignRelation(self, data);
								}
							}
						}
					};

				}
			}

			break;

		default:
			break;
		}


		// User is not allowed to edit - we lock the component!
		var permission = form.getPermission();

		if ( !permission.edit.form ) {

			component.readOnly = true;
		}

		return component;
	},

	/**
	 * @method isDirty
	 * Check if the form is dirty
	 * @returns [Boolean] dirty Is the form dirty or not
	 */
	isDirty: function() {

		var form = this,
			formDirty = false,
			widgetDirty = false,
			isDirty = false;

		formDirty = Ext.Array.some(form._allFields, function(name) {

			var field = form.getForm().findField(name),
				force,
				isDirty = false;

			if ( field ) {

				force = field.dirty;

				isDirty = force === true ? force : field.isDirty();
			}

			return isDirty;
		});

		form.getWidgets().each(function(widget) {

			var cmp = widget.getWidgetComponent();

			if ( cmp ) {

				if ( Ext.isFunction(cmp.isDirty) && cmp.isDirty() ) {

					widgetDirty = true;
				}
			}

		});

		isDirty = formDirty || widgetDirty;

		return isDirty;
	},

	isValid: function() {

		return this.getForm().isValid();
	},

	/**
	 * @method remove
	 * Delete the current record being edited
	 * @param {Function} callback An optional callback run when record has been deleted
	 * @return {Boolean} error
	 */
	remove: function() {

		var form = this,
			record = form.getRecord();

		// Setup the proxy for a destroy
		form.setProxyDestroy();

		form.store.remove(record);

		form.store.save();

		return form;
	},

	syncButtons: function(settings) {

		settings || ( settings = {} );

		var form = this,
			action = form.getAction(),
			toolbar = form.getToolbar(),
			isValid = settings.valid || form.isValid(),
			isDirty = settings.dirty || form.isDirty();

		toolbar.items.each(function(button) {

			// TODO: Remove the valid
			if ( button.syncDependency ) {

				if ( isValid && isDirty ) {

					button.enable();
				}
				else {

					button.disable();
				}

				if ( button.name == 'delete' && action == 'new' ) {
					button.disable();
				}

			}
		});

		return form;
	},

	setProxyDestroy: function() {

		var form = this,
			keys = ip2.getParentKeys(form);

		if ( Ext.typeOf(keys) == 'string' ) {
			keys = ip2.JSON.parse(keys);
		}

		form.store.proxy.extraParams.action = 'deleteRow';
		form.store.proxy.extraParams.key = keys;

		// TODO: Ask what these are for...
		if ( Ext.isEmpty(form.parentKeys) ) {
			delete form.store.proxy.extraParams.parentKeys;
			delete form.store.proxy.extraParams.parentEntity;
		}
		else {
			form.store.proxy.extraParams.parentKeys = form.parentKeys;
			form.store.proxy.extraParams.parentEntity = form.parentEntity;
		}
	},

	setProxySave: function(values) {

		var form = this,
			action = form.getAction(),
			keys = ip2.getParentKeys(form);

		if ( Ext.typeOf(keys) == 'string' ) {
			keys = ip2.JSON.parse(keys);
		}

		form.store.proxy.extraParams.action = 'updateRow';
		form.store.proxy.extraParams.key = ( action == 'new' ) ? {} : keys;

		/*
			if ( Ext.isEmpty(form.parentKeys) ) {
				delete form.store.proxy.extraParams.parentKeys;
				delete form.store.proxy.extraParams.parentEntity;
			}
			else {
				form.store.proxy.extraParams.parentKeys = form.parentKeys;
				form.store.proxy.extraParams.parentEntity = form.parentEntity;
			}
		*/

		return form;
	},

	/**
	 * @method save
	 * Save the record being edited - new or current
	 * @param {Function} callback An optional callback run when record has been saved
	 * @return {Boolean} error
	 */
	save: function() {

		var form = this,
			widgets = form.getWidgets(),
			action = form.getAction(),
			store = form.store,
			record = form.getRecord(),
			dirtyOnly = action == 'new' ? false : true,
			values = form.getValues(false, dirtyOnly),
			dmdDirty;

		// Setup the proxy for a save
		form.setProxySave(values);

		dmdDirty = form.isRecordDirty(record.data, values);

		// We need to update the DMD first as it is dirty or a new
		if ( dmdDirty || action == 'new' ) {

			ip2.setRecord(record, values);

			store.on({
				'write': {
					fn: function(store, operation) {

						var record,
							response,
							keys,
							eventName = action == 'new' ? 'dmdcreated' : 'dmdupdated';

						record = form.getRecord();

						response = ip2.JSON.parse(operation.response.responseText);

						// Set the form keys
						keys = ip2.JSON.stringify(response.rows);

						form.setKeys(keys);

						// Update the forms record width updated keys and value object
						ip2.each(response.rows, function(value, prop) {

							// Update current used record with returned values
							record.set(prop, value);

							// Update the values object with returned key values
							values[prop] = value;
						});

						form.fireEvent(eventName, form, record, values);

						/*
							// Sync the parent if any
							form.syncParent(action, values);

							// Sync widgets if any
							form.syncWidgets(action);

							// Change the form's action to update from new
							form.setAction('update');

							if ( form._forceClose !== true ) {

								// Rebuild the UI now we no longer are in new mode
								form.buildUi();

								// Re-Load the record into the newly re-build form UI
								form.loadRecord(record);

								form.updateTitle();
							}



							form.fireEvent('recordcreated', form, record, values);
						*/
					},
					single: true
				}
			});

			store.save();
		}
		// Only relational / widget data is being saved
		else {

			form.fireEvent('dmdupdated', form, record, values);
		}


		/*
			var form = this,
				action = form.getAction(),
				values,
				record,
				recordIsDirty,
				widgetsUpdate;

			// Setup the proxy for a save
			form.setProxySave();

			// Get the form values (stringed = false / dirtyonly = true)
			values = form.getForm().getValues(false, true);

			// There are dirty values to save - now determine if we have nativly DMD values or widget values
			if ( Ext.Object.getSize(values) ) {

				// Get the form DMD record
				record = form.getRecord();

				recordIsDirty = form.isRecordDirty(record.data, values);

				// Set the record in one go as object
				ip2.setRecord(record, values);

				// Sync the store
				// The store listener "write" takes over from here
				form.store.save();

				// The store listener "write" was not fired and could not deal with widgets
				if ( action != 'new' && !recordIsDirty ) {

					widgetsUpdate = form.syncWidgets('update');

					// Since store did not fire an event on recordupdated we will
					if ( widgetsUpdate ) {

						form.fireEvent('recordupdated', form, record, values);
					}

					if ( form.forceClose ) {

						form.close();
					}
				}
			}
			// In case some widgets etc. dont deliver values to the form for some reason
			else {

				// ...them we do wanna ask the widgets if they wish to update IF we are updating!
				if ( action == 'update' ) {

					// Ask each widget to update
					widgetsUpdate = form.syncWidgets('update');

					// Since store did not fire an event on recordupdated we will
					if ( widgetsUpdate ) {

						form.fireEvent('recordupdated', form, record, values);
					}

					// Since we slipped by all checks nativly - check now if to close form
					if ( form.forceClose ) {

						form.close();
					}
				}
			}

		*/

		return form;
	},

	isRecordDirty: function(record, data) {

		var isDirty = false;

		Ext.Object.each(record, function(property, value) {

			if ( data[property] && data[property] != value ) {

				isDirty = true;

				return false;
			}
		});

		return isDirty;
	},

	/**
	 * @method setValuesFromForeignRelation
	 * Change values in the form fields when a triggerfield is changed
	 * @param {Object} triggerField The field that triggered this method
	 * @param {Object} data The data brought to us by the triggerField
	 */
	setValuesFromForeignRelation: function(triggerField, data) {

		var form = this,
			prefix = triggerField.parentEntity + '_';

		Ext.iterate(data, function(name, value) {

			name = prefix + name;

			// QUESTION: I see lowercased names! - should they be uppercased also?

			// Do we have a field prefixed like this?
			var match = Ext.Array.some(form._allFields, function(field) {
				return field == name;
			});

			// If we have a match, find the field in the form
			if ( match ) {

				var field = form.getForm().findField(name);

				// Set the value - obviously
				field.setValue(value);

				// Reset the original value to match the new value
				field.resetOriginalValue();
			}
		});
	},


	syncingParent: function(record, values, action) {

		var form = this,
			dataTracks = form.getDataTrack(),
			id = record.getId();

		dataTracks.each(function(track) {

			var store = track.getStore();

			if ( action == 'create' ) {

				store.insert(0, record);
			}
			else if ( action == 'update' ) {

				record = store.getById(id);

				ip2.setRecord(record, values);
			}
		});

		return form;
	},

	syncingWidgets: function(record, values, action) {

		var form = this,
			widgets = form.getWidgets(),
			id = record.getId();

		widgets.each(function(owner) {

			var widget = owner.getWidgetComponent();

			widget.doSave();
		});
	},


	syncParent: function(action, data) {

		var form = this,
			store = form.parentStore || false,
			key = form.getRecord().data.ID || form.getRecord().data.id,
			record;

		if ( !store )
			return false;

		if ( action == 'destroy' ) {

			record = store.getById(key);

			store.remove(record);
		}

		if ( action == 'create' ) {

			var Model = store.proxy.getModel();

			record = new Model();

			ip2.setRecord(record, data);

			store.insert(0, record);
		}

		if ( action == 'update' ) {

			record = store.getById(key);

			ip2.setRecord(record, data);
		}

		return form;
	},

	syncWidgets: function(action) {

		var form = this,
			widgets,
			widgetsUpdate = false;

		// Get all the forms widgets and iterate them
		widgets = form.getWidgets();

		widgets.each(function(item) {

			// Get the actual widget component from the widget wrapper
			var widget = item.getWidgetComponent();

			if ( widget ) {

				// Fire the doSave method if creating or updating
				if ( action == 'create' || action == 'update' ) {

					// Check for method
					if ( Ext.isFunction(widget.doSave) ) {

						if ( widget.isDirty() ) {

							widgetsUpdate = true;
						}

						widget.doSave();
					}
				}

				// Fire the doDelete method if destroying
				if ( action == 'destroy' ) {

					// Check for method
					if ( Ext.isFunction(widget.doDelete) ) {

						widget.doDelete();

						widgetsUpdate = true;
					}
				}

			}
		});

		return widgetsUpdate;
	},

	/**
	 * @method updateTitle
	 * Updates the titles of this form based on private properties
	 */
	updateTitle: function() {

		var form = this,
			header = form.down('[name=header]'),
			model = form.getModel().prototype,
			titles = form.getFormTitles(),
			headerTitle,
			tabTitle,
			action = form.getAction(),
			iconCls;

		if ( !header ) return form;

		if ( action == 'new' ) {

			headerTitle = ip2.i18n({
				'da': 'Opretter',
				'en': 'Creating'
			});

			headerTitle += ' ' + ip2.dmdTranslate(model.dmdModel, model.dmdModel.title);

			tabTitle = ip2.i18n({
				'da': 'Opretter ny',
				'en': 'Creating new'
			});

			iconCls = 'ip2-icon-row-add';
		}
		else {

			var modelTitle = ip2.dmdTranslate(model, model.dmdModel.title),
				entityTitle = ip2.dmdTranslate(model, model.entity.title);

			if ( modelTitle == entityTitle ) {

				headerTitle = entityTitle + ': ' + ip2.i18n(titles);
			}
			else {

				headerTitle = modelTitle + ' - ' + entityTitle + ': ' + ip2.i18n(titles);
			}

			tabTitle = entityTitle + ': ' + ip2.i18n(titles);

			iconCls = 'ip2-icon-row-edit';
		}

		header.setIconCls(iconCls);
		header.setTitle(headerTitle);

		if ( form.el && form.el.dom ) {

			form.setTitle(tabTitle);
		}
		else {
			form.on({
				'render': {
					fn: function() {

						form.setTitle(tabTitle);
					},
					single: true
				}
			});
		}

		return form;
	}
});
