(function($, F){



	F.Row = F.Class.extend(/** @lends FooTable.Row */{

		/**

		 * The row class containing all the properties for a row and its' cells.

		 * @constructs

		 * @extends FooTable.Class

		 * @param {FooTable.Table} table -  The parent {@link FooTable.Table} this component belongs to.

		 * @param {Array.<FooTable.Column>} columns - The array of {@link FooTable.Column} for this row.

		 * @param {(*|HTMLElement|jQuery)} dataOrElement - Either the data for the row (create) or the element (parse) for the row.

		 * @returns {FooTable.Row}

		 */

		construct: function (table, columns, dataOrElement) {

			/**

			 * The {@link FooTable.Table} for the row.

			 * @type {FooTable.Table}

			 */

			this.ft = table;

			/**

			 * The array of {@link FooTable.Column} for this row.

			 * @type {Array.<FooTable.Column>}

			 */

			this.columns = columns;



			this.created = false;

			this.define(dataOrElement);

		},

		/**

		 * This is supplied either the object containing the values for the row or the row element/jQuery object if it exists.

		 * If supplied the element we need to set the $el property and parse the cells from it using the column index.

		 * If we have an object we parse the cells from it using the column name.

		 * @param {(object|jQuery)} dataOrElement - The row object or element to define the row.

		 */

		define: function(dataOrElement){

			/**

			 * The jQuery table row object this instance wraps.

			 * @instance

			 * @protected

			 * @type {jQuery}

			 */

			this.$el = F.is.element(dataOrElement) || F.is.jq(dataOrElement) ? $(dataOrElement) : null;

			/**

			 * The jQuery toggle element for the row.

			 * @instance

			 * @protected

			 * @type {jQuery}

			 */

			this.$toggle = $('<span/>', {'class': 'footable-toggle fooicon fooicon-plus'});



			var isObj = F.is.hash(dataOrElement),

				hasOptions = isObj && F.is.hash(dataOrElement.options) && F.is.hash(dataOrElement.value);



			/**

			 * The value of the row.

			 * @instance

			 * @protected

			 * @type {Object}

			 */

			this.value = isObj ? (hasOptions ? dataOrElement.value : dataOrElement) : null;



			/**

			 * Contains any options for the row.

			 * @type {object}

			 */

			this.o = $.extend(true, {

				expanded: false,

				classes: null,

				style: null

			}, hasOptions ? dataOrElement.options : {});



			/**

			 * Whether or not this row is expanded and will display it's detail row when there are any hidden columns.

			 * @instance

			 * @protected

			 * @type {boolean}

			 */

			this.expanded = F.is.jq(this.$el) ? (this.$el.data('expanded') || this.o.expanded) : this.o.expanded;

			/**

			 * An array of CSS classes for the row.

			 * @instance

			 * @protected

			 * @type {Array.<string>}

			 */

			this.classes = F.is.jq(this.$el) && this.$el.attr('class') ? this.$el.attr('class').match(/\S+/g) : (F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : []));

			/**

			 * The inline styles for the row.

			 * @instance

			 * @protected

			 * @type {object}

			 */

			this.style = F.is.jq(this.$el) && this.$el.attr('style') ? F.css2json(this.$el.attr('style')) : (F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {}));



			/**

			 * The cells array. This is populated before the call to the {@link FooTable.Row#$create} method.

			 * @instance

			 * @type {Array.<FooTable.Cell>}

			 */

			this.cells = this.createCells();



			// this ensures the value contains the parsed cell values and not the supplied values

			var self = this;

			self.value = {};

			F.arr.each(self.cells, function(cell){

				self.value[cell.column.name] = cell.val();

			});

		},

		/**

		 * After the row has been defined this ensures that the $el property is a jQuery object by either creating or updating the current value.

		 * @instance

		 * @protected

		 * @this FooTable.Row

		 */

		$create: function(){

			if (this.created) return;

			(this.$el = F.is.jq(this.$el) ? this.$el : $('<tr/>'))

				.data('__FooTableRow__', this);



			this._setClasses(this.$el);

			this._setStyle(this.$el);



			if (this.ft.rows.toggleColumn == 'last') this.$toggle.addClass('last-column');



			this.$details = $('<tr/>', { 'class': 'footable-detail-row' })

				.append($('<td/>', { colspan: this.ft.columns.visibleColspan })

					.append($('<table/>', { 'class': 'footable-details ' + this.ft.classes.join(' ') })

						.append('<tbody/>')));



			var self = this;

			F.arr.each(self.cells, function(cell){

				if (!cell.created) cell.$create();

				self.$el.append(cell.$el);

			});

			self.$el.off('click.ft.row').on('click.ft.row', { self: self }, self._onToggle);

			this.created = true;

		},

		/**

		 * This is called during the construct method and uses the current column definitions to create an array of {@link FooTable.Cell} objects for the row.

		 * @instance

		 * @protected

		 * @returns {Array.<FooTable.Cell>}

		 * @this FooTable.Row

		 */

		createCells: function(){

			var self = this;

			return F.arr.map(self.columns, function(col){

				return col.createCell(self);

			});

		},

		/**

		 * Allows easy access to getting or setting the row's data. If the data is set all associated properties are also updated along with the actual element.

		 * Using this method also allows us to supply an object containing options and the data for the row at the same time.

		 * @instance

		 * @param {object} [data] - The data to set for the row. If not supplied the current value of the row is returned.

		 * @param {boolean} [redraw=true] - Whether or not to redraw the row once the value has been set.

		 * @returns {(*|undefined)}

		 */

		val: function(data, redraw){

			var self = this;

			if (!F.is.hash(data)){

				// get - check the value property and build it from the cells if required.

				if (!F.is.hash(this.value) || F.is.emptyObject(this.value)){

					this.value = {};

					F.arr.each(this.cells, function(cell){

						self.value[cell.column.name] = cell.val();

					});

				}

				return this.value;

			}

			// set

			this.collapse(false);

			var isObj = F.is.hash(data),

				hasOptions = isObj && F.is.hash(data.options) && F.is.hash(data.value);



			this.o = $.extend(true, {

				expanded: self.expanded,

				classes: self.classes,

				style: self.style

			}, hasOptions ? data.options : {});



			this.expanded = this.o.expanded;

			this.classes = F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : []);

			this.style = F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {});

			if (isObj) {

				if ( hasOptions ) data = data.value;

				if (F.is.hash(this.value)){

					for (var prop in data) {

						if (!data.hasOwnProperty(prop)) continue;

						this.value[prop] = data[prop];

					}

				} else {

					this.value = data;

				}

			} else {

				this.value = null;

			}



			F.arr.each(this.cells, function(cell){

				if (F.is.defined(self.value[cell.column.name])) cell.val(self.value[cell.column.name], false);

			});



			if (this.created){

				this._setClasses(this.$el);

				this._setStyle(this.$el);

				if (F.is.boolean(redraw) ? redraw : true) this.draw();

			}

		},

		_setClasses: function($el){

			var hasClasses = !F.is.emptyArray(this.classes),

				classes = null;

			$el.removeAttr('class');

			if (!hasClasses) return;

			else classes = this.classes.join(' ');

			if (!F.is.emptyString(classes)){

				$el.addClass(classes);

			}

		},

		_setStyle: function($el){

			var hasStyle = !F.is.emptyObject(this.style),

				style = null;

			$el.removeAttr('style');

			if (!hasStyle) return;

			else style = this.style;

			if (F.is.hash(style)){

				$el.css(style);

			}

		},

		/**

		 * Sets the current row to an expanded state displaying any hidden columns in a detail row just below it.

		 * @instance

		 * @fires FooTable.Row#"expand.ft.row"

		 */

		expand: function(){

			if (!this.created) return;

			var self = this;

			/**

			 * The expand.ft.row event is raised before the the row is expanded.

			 * Calling preventDefault on this event will stop the row being expanded.

			 * @event FooTable.Row#"expand.ft.row"

			 * @param {jQuery.Event} e - The jQuery.Event object for the event.

			 * @param {FooTable.Table} ft - The instance of the plugin raising the event.

			 * @param {FooTable.Row} row - The row about to be expanded.

			 */

			self.ft.raise('expand.ft.row',[self]).then(function(){

				self.__hidden__ = F.arr.map(self.cells, function(cell){

					return cell.column.hidden && cell.column.visible ? cell : null;

				});



				if (self.__hidden__.length > 0){

					self.$details.insertAfter(self.$el)

						.children('td').first()

						.attr('colspan', self.ft.columns.visibleColspan);



					F.arr.each(self.__hidden__, function(cell){

						cell.collapse();

					});

				}

				self.$el.attr('data-expanded', true);

				self.$toggle.removeClass('fooicon-plus').addClass('fooicon-minus');

				self.expanded = true;

			});

		},

		/**

		 * Sets the current row to a collapsed state removing the detail row if it exists.

		 * @instance

		 * @param {boolean} [setExpanded] - Whether or not to set the {@link FooTable.Row#expanded} property to false.

		 * @fires FooTable.Row#"collapse.ft.row"

		 */

		collapse: function(setExpanded){

			if (!this.created) return;

			var self = this;

			/**

			 * The collapse.ft.row event is raised before the the row is collapsed.

			 * Calling preventDefault on this event will stop the row being collapsed.

			 * @event FooTable.Row#"collapse.ft.row"

			 * @param {jQuery.Event} e - The jQuery.Event object for the event.

			 * @param {FooTable.Table} ft - The instance of the plugin raising the event.

			 * @param {FooTable.Row} row - The row about to be expanded.

			 */

			self.ft.raise('collapse.ft.row',[self]).then(function(){

				F.arr.each(self.__hidden__, function(cell){

					cell.restore();

				});

				self.$details.detach();

				self.$el.removeAttr('data-expanded');

				self.$toggle.removeClass('fooicon-minus').addClass('fooicon-plus');

				if (F.is.boolean(setExpanded) ? setExpanded : true) self.expanded = false;

			});

		},

		/**

		 * Prior to drawing this moves the details contents back to there original cells and detaches the toggle element from the row.

		 * @instance

		 * @param {boolean} [detach] - Whether or not to detach the row.

		 * @this FooTable.Row

		 */

		predraw: function(detach){

			if (this.created){

				if (this.expanded){

					this.collapse(false);

				}

				this.$toggle.detach();

				detach = F.is.boolean(detach) ? detach : true;

				if (detach) this.$el.detach();

			}

		},

		/**

		 * Draws the current row and cells.

		 * @instance

		 * @this FooTable.Row

		 */

		draw: function($parent){

			if (!this.created) this.$create();

			if (F.is.jq($parent)) $parent.append(this.$el);

			var self = this;

			F.arr.each(self.cells, function(cell){

				cell.$el.css('display', (cell.column.hidden || !cell.column.visible  ? 'none' : 'table-cell'));

				if (self.ft.rows.showToggle && self.ft.columns.hasHidden){

					if ((self.ft.rows.toggleColumn == 'first' && cell.column.index == self.ft.columns.firstVisibleIndex)

						|| (self.ft.rows.toggleColumn == 'last' && cell.column.index == self.ft.columns.lastVisibleIndex)) {

						cell.$el.prepend(self.$toggle);

					}

				}

				cell.$el.add(cell.column.$el).removeClass('footable-first-visible footable-last-visible');

				if (cell.column.index == self.ft.columns.firstVisibleIndex){

					cell.$el.add(cell.column.$el).addClass('footable-first-visible');

				}

				if (cell.column.index == self.ft.columns.lastVisibleIndex){

					cell.$el.add(cell.column.$el).addClass('footable-last-visible');

				}

			});

			if (this.expanded){

				this.expand();

			}

		},

		/**

		 * Toggles the row between it's expanded and collapsed state if there are hidden columns.

		 * @instance

		 * @this FooTable.Row

		 */

		toggle: function(){

			if (this.created && this.ft.columns.hasHidden){

				if (this.expanded) this.collapse();

				else this.expand();

			}

		},

		/**

		 * Handles the toggle click event for rows.

		 * @instance

		 * @param {jQuery.Event} e - The jQuery.Event object for the click event.

		 * @private

		 * @this jQuery

		 */

		_onToggle: function (e) {

			var self = e.data.self;

			// only execute the toggle if the event.target is one of the approved initiators

			if ($(e.target).is(self.ft.rows.toggleSelector)){

				self.toggle();

			}

		}

	});



})(jQuery, FooTable);

