/*****
* rateYo - v2.2.0
* http://prrashi.github.io/rateyo/
* Copyright (c) 2014 Prashanth Pamidi; Licensed MIT
*****/

(function ($) {
	"use strict";

	// The basic svg string required to generate stars
	var BASICSTAR = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"+
									"<svg version=\"1.1\""+
												"xmlns=\"http://www.w3.org/2000/svg\""+
												"viewBox=\"0 12.705 512 486.59\""+
												"x=\"0px\" y=\"0px\""+
												"xml:space=\"preserve\">"+
										"<polygon "+
															"points=\"256.814,12.705 317.205,198.566"+
																			" 512.631,198.566 354.529,313.435 "+
																			"414.918,499.295 256.814,384.427 "+
																			"98.713,499.295 159.102,313.435 "+
																			"1,198.566 196.426,198.566 \"/>"+
									"</svg>";

	// The Default values of different options available in the Plugin
	var DEFAULTS = {

		starWidth : "32px",
		normalFill: "gray",
		ratedFill : "#f39c12",
		numStars  : 5,
		maxValue  : 5,
		precision : 1,
		rating    : 0,
		fullStar  : false,
		halfStar  : false,
		readOnly  : false,
		spacing   : "0px",
		rtl       : false,
		multiColor: null,
		onInit    : null,
		onChange  : null,
		onSet     : null,
		starSvg   : null
	};

	//Default colors for multi-color rating
	var MULTICOLOR_OPTIONS = {

		startColor: "#c0392b", //red
		endColor  : "#f1c40f"  //yellow
	};

	// http://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
	function isMobileBrowser () {
		var check = false;
		/* jshint ignore:start */
		(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
		/* jshint ignore:end */
		return check;
	}

	function checkPrecision (value, minValue, maxValue) {

		/*
		 * This function removes the unnecessary precision, at Min and Max Values
		 */

		// Its like comparing 0.0 with 0, which is true
		if (value === minValue) {

			value = minValue;
		}
		else if(value === maxValue) {

			value = maxValue;
		}

		return value;
	}

	function checkBounds (value, minValue, maxValue) {

		/*
		 * Check if the value is between min and max values, if not, throw an error
		 */

		var isValid = value >= minValue && value <= maxValue;

		if(!isValid){

				throw Error("Invalid Rating, expected value between "+ minValue +
										" and " + maxValue);
		}

		return value;
	}

	function isDefined(value) {

		// Better way to check if a variable is defined or not
		return typeof value !== "undefined";
	}

	// Regex to match Colors in Hex Format like #FF00FF
	var hexRegex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i;

	var hexToRGB = function (hex) {

		/*
		 * Extracts and returns the Red, Blue, Green Channel values,
		 * in the form of decimals
		 */

		if (!hexRegex.test(hex)) {

			return null;
		}

		var hexValues = hexRegex.exec(hex),
		r = parseInt(hexValues[1], 16),
		g = parseInt(hexValues[2], 16),
		b = parseInt(hexValues[3], 16);

		return {r:r, g:g, b:b};
	};

	function getChannelValue(startVal, endVal, percent) {

		/*
		 * Returns a value between `startVal` and `endVal` based on the percent
		 */

		var newVal = (endVal - startVal)*(percent/100);

		newVal = Math.round(startVal + newVal).toString(16);

		if (newVal.length === 1) {

			newVal = "0" + newVal;
		}

		return newVal;
	}

	function getColor (startColor, endColor, percent) {

		/*
		 * Given the percentage( `percent` ) of `endColor` to be mixed
		 * with the `startColor`, returns the mixed color.
		 * Colors should be only in Hex Format
		 */

		if (!startColor || !endColor) {

			return null;
		}

		percent = isDefined(percent)? percent : 0;

		startColor = hexToRGB(startColor);
		endColor = hexToRGB(endColor);

		var r = getChannelValue(startColor.r, endColor.r, percent),
		b = getChannelValue(startColor.b, endColor.b, percent),
		g = getChannelValue(startColor.g, endColor.g, percent);

		return "#" + r + g + b;
	}

	function SGRateYo ($node, options) {

	 	/*
		* The Contructor, whose instances are used by plugin itself
		*/

		// Storing the HTML element as a property, for future access
		this.node = $node.get(0);

		var that = this;

		// Remove any stuff that is present inside the container, and add the plugin class
		$node.empty().addClass("jq-ry-container");

		/*
		 * Basically the plugin displays the rating using two rows of stars lying one above
		 * the other, the row that is on the top represents the actual rating, and the one
		 * behind acts just like a background.
		 *
		 * `$groupWrapper`: is an element that wraps both the rows
		 * `$normalGroup`: is the container for row of stars thats behind and
		 *                 acts as background
		 * `$ratedGroup`: is the container for row of stars that display the actual rating.
		 *
		 * The rating is displayed by adjusting the width of `$ratedGroup`
		 */
		var $groupWrapper = $("<div/>").addClass("jq-ry-group-wrapper")
																	 .appendTo($node);

		var $normalGroup = $("<div/>").addClass("jq-ry-normal-group")
																	.addClass("jq-ry-group")
																	.appendTo($groupWrapper);

		var $ratedGroup = $("<div/>").addClass("jq-ry-rated-group")
																 .addClass("jq-ry-group")
																 .appendTo($groupWrapper);

		/*
		 * Variable `step`: store the value of the rating for each star
		 *                  eg: if `maxValue` is 5 and `numStars` is 5, value of each star
		 *                      is 1.
		 * Variable `starWidth`: stores the decimal value of width of star in units of px
		 * Variable `percentOfStar`: stores the percentage of width each star takes w.r.t
		 *                           the container
		 * Variable `spacing`: stores the decimal value of the spacing between stars
		 *                     in the units of px
		 * Variable `percentOfSpacing`: stores the percentage of width of the spacing
		 *                              between stars w.r.t the container
		 */
		var step, starWidth, percentOfStar, spacing,
				percentOfSpacing, containerWidth, minValue = 0;

		/*
		 * `currentRating` contains rating that is being displayed at the latest point of
		 * time.
		 *
		 * When ever you hover over the plugin UI, the rating value changes
		 * according to the place where you point the cursor, currentRating contains
		 * the current value of rating that is being shown in the UI
		 */
		var currentRating = options.rating;

		// A flag to store if the plugin is already being displayed in the UI
		var isInitialized = false;

		function showRating (ratingVal) {

			/*
			 * The function is responsible for displaying the rating by changing
			 * the width of `$ratedGroup`
			 */

			if (!isDefined(ratingVal)) {

				ratingVal = options.rating;
			}

			// Storing the value that is being shown in `currentRating`.
			currentRating = ratingVal;

			var numStarsToShow = ratingVal/step;

			// calculating the percentage of width of $ratedGroup with respect to its parent
			var percent = numStarsToShow*percentOfStar;

			if (numStarsToShow > 1) {

				// adding the percentage of space that is taken by the gap the stars
				percent += (Math.ceil(numStarsToShow) - 1)*percentOfSpacing;
			}

			setRatedFill(options.ratedFill);

			percent = options.rtl ? 100 - percent : percent;

			$ratedGroup.css("width", percent + "%");
		}

		function setContainerWidth () {

			/*
			 * Set the width of the `this.node` based on the width of each star and
			 * the space between them
			 */

			containerWidth = starWidth*options.numStars + spacing*(options.numStars - 1);

			percentOfStar = (starWidth/containerWidth)*100;

			percentOfSpacing = (spacing/containerWidth)*100;

			$node.width(containerWidth);

			showRating();
		}

		function setStarWidth (newWidth) {

			/*
			 * Set the width and height of each SVG star, called whenever one changes the
			 * `starWidth` option
			 */

			// The width and height of the star should be the same
			var starHeight = options.starWidth = newWidth;

			starWidth = window.parseFloat(options.starWidth.replace("px", ""));

			$normalGroup.find("svg")
									.attr({width : options.starWidth,
												 height: starHeight});

			$ratedGroup.find("svg")
								 .attr({width : options.starWidth,
												height: starHeight});

			setContainerWidth();

			return $node;
		}

		function setSpacing (newSpacing) {

			/*
			 * Set spacing between the SVG stars, called whenever one changes
			 * the `spacing` option
			 */

			options.spacing = newSpacing;

			spacing = parseFloat(options.spacing.replace("px", ""));

			$normalGroup.find("svg:not(:first-child)")
									.css({"margin-left": newSpacing});

			$ratedGroup.find("svg:not(:first-child)")
								 .css({"margin-left": newSpacing});

			setContainerWidth();

			return $node;
		}

		function setNormalFill (newFill) {

			/*
			 * Set the background fill of the Stars, called whenever one changes the
			 * `normalFill` option
			 */

			options.normalFill = newFill;

			var $svgs = (options.rtl ? $ratedGroup : $normalGroup).find("svg");

			$svgs.attr({fill: options.normalFill});

			return $node;
		}

		/*
		 * Store the recent `ratedFill` option in a variable
		 * so that if multiColor is unset, we can use the perviously set `ratedFill`
		 * from this variable
		 */
		var ratedFill = options.ratedFill;

		function setRatedFill (newFill) {

			/*
			 * Set ratedFill of the stars, called when one changes the `ratedFill` option
			 */

			/*
			 * If `multiColor` option is set, `newFill` variable is dynamically set
			 * based on the rating, what ever set as parameter will be discarded
			 */
			if (options.multiColor) {

				var ratingDiff = currentRating - minValue,
						percentCovered = (ratingDiff/options.maxValue)*100;

				var colorOpts  = options.multiColor || {},
						startColor = colorOpts.startColor || MULTICOLOR_OPTIONS.startColor,
						endColor   = colorOpts.endColor || MULTICOLOR_OPTIONS.endColor;

				newFill = getColor(startColor, endColor, percentCovered);
			} else {

				ratedFill = newFill;
			}

			options.ratedFill = newFill;

			var $svgs = (options.rtl ? $normalGroup : $ratedGroup).find("svg");

			$svgs.attr({fill: options.ratedFill});

			return $node;
		}

		function setRtl (newValue) {

			newValue = !!newValue;

			options.rtl = newValue;

			setNormalFill(options.normalFill);
			showRating();
		}

		function setMultiColor (colorOptions) {

			/*
			 * called whenever one changes the `multiColor` option
			 */

			options.multiColor = colorOptions;

			// set the recently set `ratedFill` option, if multiColor Options are unset
			setRatedFill(colorOptions ? colorOptions : ratedFill);
		}

		function setNumStars (newValue) {

			/*
			 * Set the number of stars to use to display the rating, called whenever one
			 * changes the `numStars` option
			 */

			options.numStars = newValue;

			step = options.maxValue/options.numStars;

			$normalGroup.empty();
			$ratedGroup.empty();

			for (var i=0; i<options.numStars; i++) {

				$normalGroup.append($(options.starSvg || BASICSTAR));
				$ratedGroup.append($(options.starSvg || BASICSTAR));
			}

			setStarWidth(options.starWidth);
			setNormalFill(options.normalFill);
			setSpacing(options.spacing);

			showRating();

			return $node;
		}

		function setMaxValue (newValue) {

			/*
			 * set the Maximum Value of rating to be allowed, called whenever
			 * one changes the `maxValue` option
			 */

			options.maxValue = newValue;

			step = options.maxValue/options.numStars;

			if (options.rating > newValue) {

				setRating(newValue);
			}

			showRating();

			return $node;
		}

		function setPrecision (newValue) {

			/*
			 * Set the precision of the rating value, called if one changes the
			 * `precision` option
			 */

			options.precision = newValue;

			setRating(options.rating);

			return $node;
		}

		function setHalfStar (newValue) {

			/*
			 * This function will be called if one changes the `halfStar` option
			 */

			options.halfStar = newValue;

			return $node;
		}

		function setFullStar (newValue) {

			/*
			 * This function will be called if one changes the `fullStar` option
			 */

			options.fullStar = newValue;

			return $node;
		}

		function round (value) {

			/*
			 * Rounds the value of rating if `halfStar` or `fullStar` options are chosen
			 */

			var remainder = value%step,
					halfStep = step/2,
					isHalfStar = options.halfStar,
					isFullStar = options.fullStar;

			if (!isFullStar && !isHalfStar) {

				return value;
			}

			if (isFullStar || (isHalfStar && remainder > halfStep)) {

				value += step - remainder;
			} else {

				value = value - remainder;

				if (remainder > 0) {

					value += halfStep;
				}
			}

			return value;
		}

		function calculateRating (e) {

			/*
			 * Calculates and returns the rating based on the position of cursor w.r.t the
			 * plugin container
			 */

			var position = $normalGroup.offset(),
					nodeStartX = position.left,
					nodeEndX = nodeStartX + $normalGroup.width();

			var maxValue = options.maxValue;

			// The x-coordinate(position) of the mouse pointer w.r.t page
			var pageX = e.pageX;

			var calculatedRating = 0;

			// If the mouse pointer is to the left of the container
			if(pageX < nodeStartX) {

				calculatedRating = minValue;
			}else if (pageX > nodeEndX) { // If the mouse pointer is right of the container

				calculatedRating = maxValue;
			}else { // If the mouse pointer is inside the continer

				/*
				 * The fraction of width covered by the pointer w.r.t to the total width
				 * of the container.
				 */
				var calcPrcnt = ((pageX - nodeStartX)/(nodeEndX - nodeStartX));

				if (spacing > 0) {

		/*
					 * If there is spacing between stars, take the percentage of width covered
					 * and subtract the percentage of width covered by stars and spacing, to find
					 * how many stars are covered, the number of stars covered is the rating
					 *
					 * TODO: I strongly feel that this logic can be improved!, Please help!
					 */
					calcPrcnt *= 100;

					var remPrcnt = calcPrcnt;

					while (remPrcnt > 0) {

						if (remPrcnt > percentOfStar) {

							calculatedRating += step;
							remPrcnt -= (percentOfStar + percentOfSpacing);
						} else {

							calculatedRating += remPrcnt/percentOfStar*step;
							remPrcnt = 0;
						}
					}
				} else {

					/*
					 * If there is not spacing between stars, the fraction of width covered per
					 * `maxValue` is the rating
					 */
					calculatedRating = calcPrcnt * (options.maxValue);
				}

				// Round the rating if `halfStar` or `fullStar` options are chosen
				calculatedRating = round(calculatedRating);
			}

			if (options.rtl) {

				calculatedRating = maxValue - calculatedRating;
			}

			return calculatedRating;
		}

		function setReadOnly (newValue) {

			/*
			 * UnBinds mouse event handlers, called when whenever one changes the
			 * `readOnly` option
			 */

			options.readOnly = newValue;

			$node.attr("readonly", true);

			unbindEvents();

			if (!newValue) {

				$node.removeAttr("readonly");

				bindEvents();
			}

			return $node;
		}

		function setRating (newValue) {

			/*
			 * Sets the rating of the Plugin, Called when option `rating` is changed
			 * or, when `rating` method is called
			 */

			var rating = newValue;

			var maxValue = options.maxValue;

			if (typeof rating === "string") {

				// If rating is given in percentage, maxValue should be 100
				if (rating[rating.length - 1] === "%") {

					rating = rating.substr(0, rating.length - 1);
					maxValue = 100;

					setMaxValue(maxValue);
				}

				rating = parseFloat(rating);
			}

			checkBounds(rating, minValue, maxValue);

			rating = parseFloat(rating.toFixed(options.precision));

			checkPrecision(parseFloat(rating), minValue, maxValue);

			options.rating = rating;

			showRating();

			if (isInitialized) {

				$node.trigger("rateyo.set", {rating: rating});
			}

			return $node;
		}

		function setOnInit (method) {

			/*
			 * set what method to be called on Initialization
			 */

			options.onInit = method;

			return $node;
		}

		function setOnSet (method) {

			/*
			 * set what method to be called when rating is set
			 */

			options.onSet = method;

			return $node;
		}

		function setOnChange (method) {

			/*
			 * set what method to be called rating in the UI is changed
			 */

			options.onChange = method;

			return $node;
		}

		this.rating = function (newValue) {

			/*
			 * rating getter/setter
			 */

			if (!isDefined(newValue)) {

				return options.rating;
			}

			setRating(newValue);

			return $node;
		};

		this.destroy = function () {

			/*
			 * Removes the Rating UI by clearing the content, and removing the custom classes
			 */

			if (!options.readOnly) {

				unbindEvents();
			}

			SGRateYo.prototype.collection = deleteInstance($node.get(0), this.collection);

			$node.removeClass("jq-ry-container").children().remove();

			return $node;
		};

		this.method = function (methodName) {

			/*
			 * Method to call the methods of RateYo Instance
			 */

			if (!methodName) {

				throw Error("Method name not specified!");
			}

			if (!isDefined(this[methodName])) {

				throw Error("Method " + methodName + " doesn't exist!");
			}

			var args = Array.prototype.slice.apply(arguments, []),
					params = args.slice(1),
					method = this[methodName];

			return method.apply(this, params);
		};

		this.option = function (optionName, param) {

			/*
			 * Method to get/set Options
			 */

			if (!isDefined(optionName)) {

				return options;
			}

			var method;

			switch (optionName) {

				case "starWidth":

					method = setStarWidth;
					break;
				case "numStars":

					method = setNumStars;
					break;
				case "normalFill":

					method = setNormalFill;
					break;
				case "ratedFill":

					method = setRatedFill;
					break;
				case "multiColor":

					method = setMultiColor;
					break;
				case "maxValue":

					method = setMaxValue;
					break;
				case "precision":

					method = setPrecision;
					break;
				case "rating":

					method = setRating;
					break;
				case "halfStar":

					method = setHalfStar;
					break;
				case "fullStar":

					method = setFullStar;
					break;
				case "readOnly":

					method = setReadOnly;
					break;
				case "spacing":

					method = setSpacing;
					break;
				case "rtl":

					method = setRtl;
					break;
				case "onInit":

					method = setOnInit;
					break;
				case "onSet":

					method = setOnSet;
					break;
				case "onChange":

					method = setOnChange;
					break;
				default:

					throw Error("No such option as " + optionName);
			}

			return isDefined(param) ? method(param) : options[optionName];
		};

		function onMouseEnter (e) {

			/*
			 * If the Mouse Pointer is inside the container, calculate and show the rating
			 * in UI
			 */

			var rating = calculateRating(e).toFixed(options.precision);

			var maxValue = options.maxValue;

			rating = checkPrecision(parseFloat(rating), minValue, maxValue);

			showRating(rating);

			$node.trigger("rateyo.change", {rating: rating});
		}

		function onMouseLeave () {
			if (isMobileBrowser()) {
				return;
			}

			/*
			 * If mouse leaves, revert the rating in UI to previously set rating,
			 * when empty value is passed to showRating, it will take the previously set
			 * rating
			 */

			showRating();

			$node.trigger("rateyo.change", {rating: options.rating});
		}

		function onMouseClick (e) {

			/*
			 * On clicking the mouse inside the container, calculate and set the rating
			 */

			var resultantRating = calculateRating(e).toFixed(options.precision);
			resultantRating = parseFloat(resultantRating);

			that.rating(resultantRating);
		}

		function onInit(e, data) {

			if(options.onInit && typeof options.onInit === "function") {

				/* jshint validthis:true */
				options.onInit.apply(this, [data.rating, that]);
			}
		}

		function onChange (e, data) {

			if(options.onChange && typeof options.onChange === "function") {

				/* jshint validthis:true */
				options.onChange.apply(this, [data.rating, that]);
			}
		}

		function onSet (e, data) {

			if(options.onSet && typeof options.onSet === "function") {

				/* jshint validthis:true */
				options.onSet.apply(this, [data.rating, that]);
			}
		}

		function bindEvents () {

			$node.on("mousemove", onMouseEnter)
					 .on("mouseenter", onMouseEnter)
					 .on("mouseleave", onMouseLeave)
					 .on("click", onMouseClick)
					 .on("rateyo.init", onInit)
					 .on("rateyo.change", onChange)
					 .on("rateyo.set", onSet);
		}

		function unbindEvents () {

			$node.off("mousemove", onMouseEnter)
					 .off("mouseenter", onMouseEnter)
					 .off("mouseleave", onMouseLeave)
					 .off("click", onMouseClick)
					 .off("rateyo.init", onInit)
					 .off("rateyo.change", onChange)
					 .off("rateyo.set", onSet);
		}

		setNumStars(options.numStars);
		setReadOnly(options.readOnly);

		if (options.rtl) {

			setRtl(options.rtl);
		}

		this.collection.push(this);
		this.rating(options.rating, true);

		isInitialized = true;
		$node.trigger("rateyo.init", {rating: options.rating});
	}

	SGRateYo.prototype.collection = [];

	function getInstance (node, collection) {

		/*
		 * Given a HTML element (node) and a collection of RateYo instances,
		 * this function will search through the collection and return the matched
		 * instance having the node
		 */

		var instance;

		$.each(collection, function () {

			if(node === this.node){

				instance = this;
				return false;
			}
		});

		return instance;
	}

	function deleteInstance (node, collection) {

		/*
		 * Given a HTML element (node) and a collection of RateYo instances,
		 * this function will search through the collection and delete the
		 * instance having the node, and return the modified collection
		 */

		$.each(collection, function (index) {

			if (node === this.node) {

				var firstPart = collection.slice(0, index),
						secondPart = collection.slice(index+1, collection.length);

				collection = firstPart.concat(secondPart);

				return false;
			}
		});

		return collection;
	}

	function _rateYo (options) {

		var rateYoInstances = SGRateYo.prototype.collection;

		/* jshint validthis:true */
		var $nodes = $(this);

		if($nodes.length === 0) {

			return $nodes;
		}

		var args = Array.prototype.slice.apply(arguments, []);

		if (args.length === 0) {

			//If args length is 0, Initialize the UI with default settings
			options = args[0] = {};
		}else if (args.length === 1 && typeof args[0] === "object") {

			//If an Object is specified as first argument, it is considered as options
			options = args[0];
		}else if (args.length >= 1 && typeof args[0] === "string") {

			/*
			 * if there is only one argument, and if its a string, it is supposed to be a
			 * method name, if more than one argument is specified, the remaining arguments
			 * except the first argument, will be passed as a params to the specified method
			 */

			var methodName = args[0],
					params = args.slice(1);

			var result = [];

			$.each($nodes, function (i, node) {

				var existingInstance = getInstance(node, rateYoInstances);

				if(!existingInstance) {

					throw Error("Trying to set options before even initialization");
				}

				var method = existingInstance[methodName];

				if (!method) {

					throw Error("Method " + methodName + " does not exist!");
				}

				var returnVal = method.apply(existingInstance, params);

				result.push(returnVal);
			});

			/*
			 * If the plugin in being called on only one jQuery Element, return only the
			 * first value, to support chaining.
			 */
			result = result.length === 1? result[0]: result;

			return result;
		}else {

			throw Error("Invalid Arguments");
		}

		/*
		 * if only options are passed, extend default options, and if the plugin is not
		 * initialized on a particular jQuery element, initalize RateYo on it
		 */
		options = $.extend({}, DEFAULTS, options);

		return $.each($nodes, function () {

							 var existingInstance = getInstance(this, rateYoInstances);

							 if (!existingInstance) {

								 return new SGRateYo($(this), $.extend({}, options));
							 }
					 });
	}

	function rateYo () {

		/* jshint validthis:true */
		return _rateYo.apply(this, Array.prototype.slice.apply(arguments, []));
	}

	window.SGRateYo = SGRateYo;
	$.fn.rateYo = rateYo;

}(window.jQuery));
