(function($, F){
	F.Breakpoints = F.Component.extend(/** @lends FooTable.Breakpoints */{
		/**
		 * Contains the logic to calculate and apply breakpoints for the plugin.
		 * @constructs
		 * @extends FooTable.Component
		 * @param {FooTable.Table} table -  The parent {@link FooTable.Table} this component belongs to.
		 * @returns {FooTable.Breakpoints}
		 */
		construct: function(table){
			// call the base class constructor
			this._super(table, true);

			/* PROTECTED */
			/**
			 * This provides a shortcut to the {@link FooTable.Table#options} object.
			 * @protected
			 * @type {FooTable.Table#options}
			 */
			this.o = table.o;

			/* PUBLIC */
			/**
			 * The current breakpoint.
			 * @type {FooTable.Breakpoint}
			 */
			this.current = null;
			/**
			 * An array of {@link FooTable.Breakpoint} objects created from parsing the options.
			 * @type {Array.<FooTable.Breakpoint>}
			 */
			this.array = [];
			/**
			 * Whether or not breakpoints cascade. When set to true all breakpoints larger than the current will be hidden along with it.
			 * @type {boolean}
			 */
			this.cascade = this.o.cascade;
			/**
			 * Whether or not to calculate breakpoints on the width of the parent element rather than the viewport.
			 * @type {boolean}
			 */
			this.useParentWidth = this.o.useParentWidth;
			/**
			 * This value is updated each time the current breakpoint changes and contains a space delimited string of the names of the current breakpoint and all those smaller than it.
			 * @type {string}
			 */
			this.hidden = null;

			/* PRIVATE */
			/**
			 * This value is set once when the {@link FooTable.Breakpoints#array} is generated and contains a space delimited string of all the breakpoint class names.
			 * @type {string}
			 * @private
			 */
			this._classNames = '';

			// check if a function was supplied to override the default getWidth
			this.getWidth = F.checkFnValue(this, this.o.getWidth, this.getWidth);
		},

		/* PROTECTED */
		/**
		 * Checks the supplied data and options for the breakpoints component.
		 * @instance
		 * @protected
		 * @param {object} data - The jQuery data object from the parent table.
		 * @fires FooTable.Breakpoints#"preinit.ft.breakpoints"
		 */
		preinit: function(data){
			var self = this;
			/**
			 * The preinit.ft.breakpoints event is raised before any UI is created and provides the tables jQuery data object for additional options parsing.
			 * Calling preventDefault on this event will disable the entire plugin.
			 * @event FooTable.Breakpoints#"preinit.ft.breakpoints"
			 * @param {jQuery.Event} e - The jQuery.Event object for the event.
			 * @param {FooTable.Table} ft - The instance of the plugin raising the event.
			 * @param {object} data - The jQuery data object of the table raising the event.
			 */
			return this.ft.raise('preinit.ft.breakpoints', [data]).then(function(){
				self.cascade = F.is.boolean(data.cascade) ? data.cascade : self.cascade;
				self.o.breakpoints = F.is.hash(data.breakpoints) ? data.breakpoints : self.o.breakpoints;
				self.getWidth = F.checkFnValue(self, data.getWidth, self.getWidth);
				if (self.o.breakpoints == null) self.o.breakpoints = { "xs": 480, "sm": 768, "md": 992, "lg": 1200 };
				// Create a nice friendly array to work with out of the breakpoints object.
				for (var name in self.o.breakpoints) {
					if (!self.o.breakpoints.hasOwnProperty(name)) continue;
					self.array.push(new F.Breakpoint(name, self.o.breakpoints[name]));
					self._classNames += 'breakpoint-' + name + ' ';
				}
				// Sort the breakpoints so the largest is checked first
				self.array.sort(function (a, b) {
					return b.width - a.width;
				});
			});
		},
		/**
		 * Initializes the class parsing the options into a sorted array of {@link FooTable.Breakpoint} objects.
		 * @instance
		 * @protected
		 * @fires FooTable.Breakpoints#"init.ft.breakpoints"
		 */
		init: function(){
			var self = this;
			/**
			 * The init.ft.breakpoints event is raised before any UI is generated.
			 * Calling preventDefault on this event will disable the entire plugin.
			 * @event FooTable.Breakpoints#"init.ft.breakpoints"
			 * @param {jQuery.Event} e - The jQuery.Event object for the event.
			 * @param {FooTable.Table} ft - The instance of the plugin raising the event.
			 */
			return this.ft.raise('init.ft.breakpoints').then(function(){
				self.current = self.get();
			});
		},
		/**
		 * Whenever the table is drawn this ensures the correct breakpoint class is applied to the table.
		 * @instance
		 * @protected
		 */
		draw: function(){
			this.ft.$el.removeClass(this._classNames).addClass('breakpoint-' + this.current.name);
		},

		/* PUBLIC */
		/**
		 * Calculates the current breakpoint from the {@link FooTable.Breakpoints#array} and sets the {@link FooTable.Breakpoints#current} property.
		 * @instance
		 * @returns {FooTable.Breakpoint}
		 */
		calculate: function(){
			var self = this, current = null, hidden = [], breakpoint, prev = null, width = self.getWidth();
			for (var i = 0, len = self.array.length; i < len; i++) {
				breakpoint = self.array[i];
				// if the width is smaller than the smallest breakpoint set the smallest as the current.
				// if the width is larger than the largest breakpoint set the largest as the current.
				// otherwise if the width is somewhere in between check all breakpoints testing if the width
				// is greater than the current but smaller than the previous.
				if ((!current && i == len -1)
					|| (width >= breakpoint.width && (prev instanceof F.Breakpoint ? width < prev.width : true))) {
					current = breakpoint;
				}
				if (!current) hidden.push(breakpoint.name);
				prev = breakpoint;
			}
			hidden.push(current.name);
			self.hidden = hidden.join(' ');
			return current;
		},
		/**
		 * Supplied a columns breakpoints this returns a boolean value indicating whether or not the column is visible.
		 * @param {string} breakpoints - A space separated string of breakpoint names.
		 * @returns {boolean}
		 */
		visible: function(breakpoints){
			if (F.is.emptyString(breakpoints)) return true;
			if (breakpoints === 'all') return false;
			var parts = breakpoints.split(' '), i = 0, len = parts.length;
			for (; i < len; i++){
				if (this.cascade ? F.str.containsWord(this.hidden, parts[i]) : parts[i] == this.current.name) return false;
			}
			return true;
		},
		/**
		 * Performs a check between the current breakpoint and the previous breakpoint and performs a redraw if they differ.
		 * @instance
		 * @fires FooTable.Breakpoints#"before.ft.breakpoints"
		 * @fires FooTable.Breakpoints#"after.ft.breakpoints"
		 */
		check: function(){
			var self = this, bp = self.get();
			if (!(bp instanceof F.Breakpoint)
				|| bp == self.current)
				return;

			/**
			 * The before.ft.breakpoints event is raised if the breakpoint has changed but before the UI is redrawn and is supplied both the current breakpoint
			 * and the next "new" one that is about to be applied.
			 * Calling preventDefault on this event will prevent the next breakpoint from being applied.
			 * @event FooTable.Breakpoints#"before.ft.breakpoints"
			 * @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.Breakpoint} current - The current breakpoint.
			 * @param {FooTable.Breakpoint} next - The breakpoint that is about to be applied.
			 */
			self.ft.raise('before.ft.breakpoints', [self.current, bp]).then(function(){
				var previous = self.current;
				self.current = bp;
				return self.ft.draw().then(function(){
					/**
					 * The after.ft.breakpoints event is raised after the breakpoint has changed and the UI is redrawn and is supplied both the "new" current breakpoint
					 * and the previous one that was replaced.
					 * @event FooTable.Breakpoints#"after.ft.breakpoints"
					 * @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.Breakpoint} current - The current breakpoint.
					 * @param {FooTable.Breakpoint} previous - The breakpoint that was just replaced.
					 */
					self.ft.raise('after.ft.breakpoints', [self.current, previous]);
				});
			});
		},
		/**
		 * Attempts to return a {@link FooTable.Breakpoint} instance when passed a {@link FooTable.Breakpoint},
		 * the {@link FooTable.Breakpoint#name} string or if nothing is supplied the current breakpoint.
		 * @instance
		 * @param {(FooTable.Breakpoint|string|number)} [breakpoint] - The breakpoint to retrieve.
		 * @returns {FooTable.Breakpoint}
		 */
		get: function(breakpoint){
			if (F.is.undef(breakpoint)) return this.calculate();
			if (breakpoint instanceof F.Breakpoint) return breakpoint;
			if (F.is.string(breakpoint)) return F.arr.first(this.array, function (bp) { return bp.name == breakpoint; });
			if (F.is.number(breakpoint)) return breakpoint >= 0 && breakpoint < this.array.length ? this.array[breakpoint] : null;
			return null;
		},
		/**
		 * Gets the width used to determine breakpoints whether it be from the viewport, parent or a custom function.
		 * @instance
		 * @returns {number}
		 */
		getWidth: function(){
			if (F.is.fn(this.o.getWidth)) return this.o.getWidth(this.ft);
			if (this.useParentWidth == true) return this.getParentWidth();
			return this.getViewportWidth();
		},
		/**
		 * Gets the tables direct parents width.
		 * @instance
		 * @returns {number}
		 */
		getParentWidth: function(){
			return this.ft.$el.parent().width();
		},
		/**
		 * Gets the current viewport width.
		 * @instance
		 * @returns {number}
		 */
		getViewportWidth: function(){
			return Math.max(document.documentElement.clientWidth, window.innerWidth, 0);
		}
	});

	F.components.internal.register('breakpoints', F.Breakpoints, 10);

})(jQuery, FooTable);