(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);
