/**
 * @class Ext.ux.grid.properties.ExtendedGrid
 * Creates an extended property grid (xpGrid) to view, edit, add and delete properties and values of an object
 * Uses a mix of a treepanel and a grid
 *
 * @extends Ext.panel.Panel
 * @requires Ext.ux.aceeditor.Panel
 *
 * @plugins: 'treeviewdragdrop'
 */

Ext.define('Ext.ux.grid.property.ExtendedGrid', {

	alias: [
		'widget.propertyGridExtended'
	],

	extend: 'Ext.panel.Panel',

	requires: [
		'Ext.ux.util.ace.Editor'
	],

	layout: 'fit',

	/**
	 * @property {Object} propertyIcons
	 * CSS classes mapped out
	 */
	iconClasses: {
		'object': 'fnyts16',
		'array': 'chart_organisation16',
		'string': 'font16',
		'number': 'sum16',
		'boolean': 'contrast16',
		'function': 'function16',
		'add': 'add16',
		'delete': 'delete16'
	},

	initComponent: function() {

		this.callParent(arguments);

		var xpGrid = this,
			treestore, tree,
			editplugin,
			addMenu;

		treestore = Ext.create('Ext.data.TreeStore', {
			fields: ['type', 'name', 'value', 'valueHidden'],
			root: {
				expanded: false,
				children: []
			},
			autoLoad: false,
			folderSort: false
		});

		editplugin = Ext.create('Ext.grid.plugin.CellEditing', {
			clicksToMoveEditor: 2,
			autoCancel: true,
			listeners: {
				'beforeedit': function(e, eOpts) {

					var tree = this;
					var record = e.record;
					var data = record.data;
					var index = e.colIdx;
					var column = e.column;

					if (data.type === 'object' && index === 1) {
						return false;
					}

					// NUMBER TYPE
					if (data.type == 'function' && index == 2) {
						isj = column;
					}

					// FUNCTION TYPE
					// Property type function and value column click
					// Intercept, prevent and load Ace Editor
					if (data.type == 'function' && index == 2) {

						var fnWindow = xpGrid.createFnEditorWindow(value);
						var fnEditor = fnWindow.down('[name="fnEditor"]');

						fnWindow.setTitle(data.name);
						fnWindow.on({
							close: {
								fn: function() {
									var value = fnEditor.getValue();
									var fnString = 'var fnString = ' + data.value;
									var returnFn = eval(fnString);

									record.set('value', fnString);
									record.commit();
								}
							}
						});
						return false;
					}
				},
				'edit': function() {

					console.log(arguments, 'arguments on edit in xpGrid');
					xpGrid.fireEvent('propertychange', arguments);
				}
			}
		});

		tree = xpGrid.add({
			xtype: 'treepanel',
			name: 'tree',
			store: treestore,
			plugins: [editplugin],
			useArrows: true,
			rootVisible: false,
			multiSelect: true,
			singleExpand: true,
			autoScroll:	true,
			border: false,

			viewConfig: {
				plugins: {
					ptype: 'treeviewdragdrop'
				},
				listeners: {
					'drop': function(node, data, overModel, dropPosition, eOpts) {
						xpGrid.fireEvent('propertyChange', arguments);
					}
				}
			},

			columns: [
				{
					xtype: 'treecolumn',
					name: 'type',
					text: 'Type',
					flex: 5,
					sortable: true,
					dataIndex: 'type',
					renderer: function(value) {
						switch (value) {
							case 'object': value = '<span style="color:lightblue;">' + value + '</span>'; break;
							case 'string': value = '<span style="color:green">' + value + '<span>'; break;
							case 'number': value = '<span style="color:red">' + value + '</span>'; break;
							case 'boolean': value = '<span style="color:purple">' + value + '</span>'; break;
							case 'array': value = '<span style="color:orange">' + value + '</span>'; break;
							case 'function': value = '<span style="color:darkgreen">' + value + '</span>'; break;
							default: value = value; break;
						}
						return value;
					}
				},
				{
					text: 'Name',
					name: 'name',
					flex: 4,
					sortable: true,
					dataIndex: 'name',
					editor:	{
						allowBlank:	false
					}
				},
				{
					name: 'value',
					text: 'Value',
					flex: 4,
					dataIndex: 'value',
					sortable: false,
					editor:	{
						allowBlank:	false
					}
				},
				{
					name: 'valueHidden',
					dataIndex: 'valueHidden',
					hidden:	true
				}
			],

			listeners: {
				itemcontextmenu: function(view, record, el, index, e) {

					var tree = this;

					e.preventDefault();

					// Select the record clicked on
					tree.selectPath(record.getPath());

					// Create the context menu
					var contextmenu = Ext.create('Ext.menu.Menu', {
						items: [
							{
								name: 'add',
								text: 'Add property',
								iconCls: xpGrid.iconClasses['add'],
								iconAlign: 'left',
								menu: [
									{
										text: 'String',
										name: 'string',
										iconCls: xpGrid.iconClasses['string'],
										handler: function(button) {
											// Send the item.name (type of property to create)
											// to the method that creates new properties in xpGrid
											xpGrid.createNewPropertyWindow(button.name);
										}
									},{
										text: 'Number',
										iconCls: xpGrid.iconClasses['number'],
										name: 'number',
										handler: function(button) {
											// Send the item.name (type of property to create)
											// to the method that creates new properties in xpGrid
											xpGrid.createNewPropertyWindow(button.name);
										}
									},{
										text: 'Boolean',
										iconCls: xpGrid.iconClasses['boolean'],
										name: 'boolean',
										handler: function(button) {
											// Send the item.name (type of property to create)
											// to the method that creates new properties in xpGrid
											xpGrid.createNewPropertyWindow(button.name);
										}
									},{
										text: 'Object',
										iconCls: xpGrid.iconClasses['object'],
										name: 'object',
										handler: function(button) {
											// Send the item.name (type of property to create)
											// to the method that creates new properties in xpGrid
											xpGrid.createNewPropertyWindow(button.name);
										}
									},{
										text: 'Function',
										iconCls: xpGrid.iconClasses['function'],
										name: 'function',
										handler: function(button) {
											// Send the item.name (type of property to create)
											// to the method that creates new properties in xpGrid
											xpGrid.createNewPropertyWindow(button.name);
										}
									},{
										text: 'Array',
										iconCls: xpGrid.iconClasses['array'],
										name: 'array',
										handler: function(button) {
											// Send the item.name (type of property to create)
											// to the method that creates new properties in xpGrid
											xpGrid.createNewPropertyWindow(button.name);
										}
									}
								]
							},{
								name: 'remove',
								text: 'Remove selected',
								iconCls: xpGrid.iconClasses['delete'],
								handler: function() {

									var tree = xpGrid.getTree();
									var node = tree.getSelectionModel().getSelection()[0];

									xpGrid.removeNode(node);

									contextmenu.destroy();

								}
							}
						]
					});

					// Find the user coordinates to use for placing context menu
					var coords = e.getXY();
					contextmenu.showAt([
						coords[0],
						coords[1]
					]);
				}
			}
		});

		// Get the tree view and assign tool tip to records
		tree.getView().on('render', function(view) {
			view.tip = Ext.create('Ext.tip.ToolTip', {
				// The overall target element.
				target: view.el,
				// Each grid row causes its own seperate show and hide.
				delegate: view.itemSelector,
				// Moving within the row should not hide the tip.
				trackMouse: true,
				// Render immediately so that tip.body can be referenced prior to the first show.
				renderTo: Ext.getBody(),
				listeners: {
					// Change content dynamically depending on which element triggered the show.
					beforeshow: function updateTipBody(tip) {
						var record = view.getRecord(tip.triggerElement),
							type = record.get('type'),
							name = record.get('name'),
							value = record.get('value');

						tip.update('Property type: ' + type + '<br/>Name: ' + name + '<br/>Value: ' + value);
					}
				}
			});
		});

		xpGrid.addObject({
			'somestring': 'a',
			'anotherstring': 'b',
			'onLoad': function() {

				Ext.define('Ext.ux.ip2.diddesigner.handlers.Ui', {

					extend: 'Ext.panel.Panel',
					alias: ['widget.didDesignerUi'],

					autoScroll: true,

					initComponent: function() {

						this.callParent();
					}


				});
			},
			'anotherobject': {
				'somearray': [
					{'whygoon': 'bbb'}
				]
			}
		});
	},


	/**
	 * @method addObject
	 * Add an object to the extended property grid
	 *
	 * @params {Object/Null} [object=null] The object to manage in the xpGrid
	 * @chainable
	 */
	addObject: function(object) {

		var xpGrid = this,
			node;

		node = xpGrid.createNodeFromObject(object);

		xpGrid.setRootNode(node);

		return xpGrid;
	},


	/**
	 * @method getObject
	 * Retrieve the object currently managed by the xpGrid
	 *
	 * @return {Object} object The object managed by the xpGrid
	 */
	getObject: function() {

		var xpGrid = this,
			node,
			object;

		node = xpGrid.getRootNode();

		object = xpGrid.createObjectFromNode(node);

		return object;
	},

	/**
	 * @method getRootNode
	 * Gets the root node of treeview - Mapped out from the treeview
	 * @private
	 *
	 * @return {Object} object The tree views root node
	 */
	getRootNode: function() {

		return this.getTree().getRootNode();
	},

	/**
	 * @method setRootNode
	 * Set the root node of the treeview
	 * @private
	 *
	 * @params {Object} object The object to manage (view and edit)
	 * @chainable
	 */
	setRootNode: function(node) {

		var xpGrid = this,
			tree = xpGrid.getTree();

		tree.setRootNode(node);

		return xpGrid;
	},


	/**
	 * @method createNodeFromObject
	 * Create a tree node object from any object
	 * @private
	 *
	 * @return {Object}
	 */
	createNodeFromObject: function(object) {

		var xpGrid = this;

		function buildChildren(object) {

			var newArray = [];

			Ext.Object.each(object, function(p, v) {

				var value,
					icon,
					type = typeof v;

				// JS wont give up array type freely...
				type = (Ext.isArray(v) ? 'array' : type);

				switch(type) {
					case 'string'	: value = v; icon = xpGrid.iconClasses['string']; break;
					case 'number'	: value = v; icon = xpGrid.iconClasses['number']; break;
					case 'boolean'	: value = v; icon = xpGrid.iconClasses['boolean']; break;
					case 'function'	: value = v; icon = xpGrid.iconClasses['function']; break;
					case 'object'	: value = ''; icon = xpGrid.iconClasses['object']; break;
					case 'array'	: value = ''; icon = xpGrid.iconClasses['array']; break;
					default			: value = v; break;
				}

				var propertyObject = {
					id:	ip2.unique(),
					type: type,
					name: p,
					value: value,
					iconCls: icon
				}

				// Recursive for objects/array
				if ( type === 'object' || type === 'array' ) {
					propertyObject.children = buildChildren( v );
				} else {
					propertyObject.leaf = true;
				}

				newArray.push(propertyObject);

			});

			return newArray;
		}

		return {
			text: '.',
			children: buildChildren(object)
		}
	},

	/**
	 * @method createObjectFromNode
	 * Convert a tree node object back to a regular object
	 * @private
	 *
	 * @params {Object} node The tree node to create an object from
	 * @return {Object} object The object created by the tree node
	 */
	createObjectFromNode: function(node) {

		var object;

		function cleanseNode(node) {
			Ext.each(node.childNodes, function( i, v ) {

				if ( (i.data.type == 'boolean' || i.data.type == 'number') && typeof i.data.value == 'string') {
					if ( i.data.type == 'number' ) {
						i.data.value = parseInt( i.data.value, 10 );
					} else {
						i.data.value = i.data.value == "true";
					}
				}

				if ( i.data.type == 'function' && typeof i.data.value == 'string' ) {

				}

				if (i.childNodes && i.childNodes.length > 0) {
					Ext.each(i, function(p) {
						cleanseNode(p);
					});
				}
			});
		}


		function objectFactory(node) {

			var newObject = {};

			if (node.childNodes.length > 0) {
				Ext.each(node.childNodes, function(i) {

					var name = i.data.name,
							value = i.data.value,
							type = i.data.type;

					switch(type) {
						case 'array'	: newObject[name] = []; break;
						case 'object'	: newObject[name] = {};	break;
						default			: newObject[name] = value; break;
					}

					// Has childnodes (i.e is array or object - act accordingly in recursive)
					if (i.childNodes && i.childNodes.length > 0) {
						Ext.each(i.childNodes, function(c, k) {
							if (type == 'array') {
								newObject[name].push(objectFactory(c));
							}
							if (type == 'object') {
								newObject[name][c.data.name] = objectFactory(c)[c.data.name];
							}
						});
					}

				});
			}

			else {
				var name = node.data.name,
					value = node.data.value,
					type = node.data.type;

				newObject[name] = value;
			}

			return newObject;
		}

		// Cleanse the node to set all properties correct
		cleanseNode(node);

		// Send the node through the internal factory function to create the object
		object = objectFactory(node);

		// Return the object
		return object;
	},


	/**
	 * @method addNode
	 * Add a node to the tree - i.e. add a property to the xpGrid
	 * @private
	 *
	 * @params {Object} node The tree node to create an object from
	 * @property {String} text Text of the node
	 *
	 * @return {Object} node The node created
	 */
	addNode: function(node) {

		var xpGrid = this,
			tree = xpGrid.getTree(),
			model = tree.getSelectionModel(),

			rootNode = tree.getRootNode(),
			selectedNode = model.getSelection()[0],
			newNode;

		// No nodes selected - we just append new node to root
		if ( !selectedNode ) {
			newNode = rootNode.appendChild(node);
		}
		// Some node is selected, find out how to append the new node now
		else {

			// Selected node is expandable - append to selected node
			if ( selectedNode.isExpandable() ) {
				newNode = selectedNode.appendChild(node);
			}

			// Selected is not expandable, insert before selected node via parentNode
			else {
				newNode = selectedNode.parentNode.insertBefore(node, selectedNode);
			}
		}

		// Expand path to newly created node and select it
		tree.selectPath(newNode.getPath());

		// Commit the root node
		rootNode.commit();

		// EVENT: New node has been created fire propertychange event for xpGrid
		xpGrid.fireEvent('propertychange', rootNode);

		return node;
	},

	removeNode: function(node) {

		var xpGrid = this,
			tree = xpGrid.getTree(),
			rootNode = tree.getRootNode(),
			parentNode = node.parentNode;


		node.remove(true);

		rootNode.commit();

		if ( parentNode ) {
			tree.selectPath(parentNode.getPath());
		}
		else {
			tree.getSelectionModel().deselectAll();
		}
	},

	/**
	 * @method createNewNode
	 * Create a node from a config object
	 * @private
	 *
	 * @params {Object} config Configuration of the node to create
	 * @property {String} name Name of the node
	 * @property {Mixed} value Value of the node
	 * @property {String} type Type of the node (String/Number/Boolean/Function/Object/Array)
	 * @return {Object} node The newly created node
	 */
	createNewNode: function(config) {

		var xpGrid = this,
			tree = xpGrid.getTree(),
			leaf, icon,
			node;

		leaf = ( config.type === 'array' || config.type === 'object' ) ? false : true;

		icon = xpGrid.iconClasses[config.type];

		node = {
			id: 'node-' + ip2.unique(),
			type: config.type,
			property: config.name,
			value: config.value,
			iconCls: icon,
			leaf: leaf
		};

		xpGrid.addNode(node);

		return node;
	},

	/**
	 * @method createNewPropertyWindow
	 * Create a window with input fields and buttons to create a new property entry
	 * @private
	 *
	 * @return {@link Ext.window.Window} The window opened with form
	 */
	createNewPropertyWindow: function(type) {

		var xpGrid = this;

		Ext.create('Ext.window.Window', {
			title: 'Create new ' + type,
			modal: true,
			width: 300,
			layout: 'fit',
			iconCls: xpGrid.iconClasses[type],
			items: [
				{
					xtype: 'form',
					id: 'propform',
					layout: 'anchor',
					bodyStyle: {
						padding: '10px'
					},
					items: [
						{
							xtype: 'textfield',
							fieldLabel: type + ' name',
							name: 'name',
							allowBlank: false,
							anchor: '100%'
						},{
							xtype: 'hiddenfield',
							name: 'type'
						}
					],
					// Reset and Submit buttons
					buttons: [
						{
							text: 'Reset',
							handler: function() {
								this.up('form').getForm().reset();
							}
						}, {
							text: 'Submit',
							formBind: true, //only enabled once the form is valid
							disabled: true,
							handler: function(button) {
								var form = button.up('form').getForm(),
									values = form.getValues();

								if ( form.isValid() ) {
									xpGrid.createNewNode(values);
									button.up('window').close();
								}
								// TODO: Add error handling here!
								else {

								}
							}
						}
					],
					listeners: {

						// On render of form create the value input field for the property creation
						// Also set the value for the property type and focus in name field
						'render': function(form) {

							var valueField = {
								'defaults': {
									xtype: 'textfield',
									fieldLabel: 'Value',
									anchor: '100%',
									name: 'value'
								},
								'string': {
									allowBlank: true
								},
								'number': {
									xtype: 'numberfield',
									value: 0,
									allowBlank: false
								},
								'boolean': {
									xtype: 'combo',
									store: Ext.create('Ext.data.Store', {
										fields: ['value', 'name'],
										data : [
											{
												"value": "false",
												"name": "false"
											},{
												"value": "true",
												"name": "true"
											}
										]
									}),
									queryMode: 'local',
									displayField: 'name',
									valueField: 'value',
									value: 'false',
									forceSelection: true,
									allowBlank: false
								},
								'function': {
									hidden: true,
									value: 'function(text) { var prefixed = "pf_" + name; return prefixed; }'
								},
								'object': {
									hidden: true,
									value: ''
								},
								'array': {
									hidden: true,
									value: ''
								}
							};

							// Add the value field to the form by merging the defaults with the
							// specific field object found via type
							form.add(1, Ext.Object.merge(valueField['defaults'], valueField[type]));

							// Set the value of the hidden input field for property type
							form.down('[name="type"]').on('render', function(cmp) {
								cmp.setValue(type);
							});

							// Set focus in the input field for property name
							form.down('[name="name"]').on('render', function(cmp) {
								cmp.focus();
							});

						}
					}
				}
			]

		}).show();
	},

	/**
	 * @method createFnEditorWindow
	 * Create a window with a function editor for editing values of function properties
	 * @private
	 *
	 * @params {String} value The stringed function
	 * @params {Boolean} [autoShow=true] Should the functions editor window autoshow
	 *
	 * @chainable
	 */
	createFnEditorWindow: function(value, autoShow) {

		autoShow || ( autoShow = true );

		var xpGrid = this,
			fnWindow;

		if ( typeof value === 'function' )
			value += '';

		fnWindow = Ext.create('Ext.window.Window', {
			width: 600,
			height: 400,
			autoShow: autoShow,
			layout: 'anchor',
			modal: true,
			iconCls: xpGrid.iconClasses['function'],
			items: [
				{
					//xtype: 'ip2Editor',
					name: 'functionsEditor',
					parser: 'javascript',
					anchor: '100% 100%',
					name: 'fnEditor',
					sourceCode: value
				}
			]
		});

		return fnWindow;
	},

	/**
	 * @method getTree
	 * Get the treepanel of the xpGrid
	 *
	 * @return {@link Ext.tree.panel} the treepanel of the xpGrid
	 */
	getTree: function() {

		return this.down('[name="tree"]');
	}
});
