(function($){
  
  
  /**
   * Use this to get or set the voteable option. 
   */
  var VOTEABLE_OPTION_NAME = "voteable";
  
  
  /**
   * Use this option to set or get the current rating value. 
   */
  var RATE_VALUE_OPTION_NAME = "rating";
  
  
  var opts = {};
  var currentRate = 0;
  
  $.fn.jrate = function(options) {
	
	var optionName = arguments[1];
	var optionValue = arguments[2];
	var argumentCount = arguments.length;
    return this.each(function(){
	  opts = $.extend({}, $.fn.jrate.defaults, options);
	  opts.container = $(this);
	  var isHalfRate = opts.halfRate;
	  if ((typeof options) == "string" && argumentCount == 2) {
			// getter
	  }
	  else if ((typeof options) == "string" && argumentCount == 3) {
			setOption(optionName, optionValue, this);
	  }
	  else if ((typeof options) == "object") {
		  opts["currentRate"] = opts.initRate;
		  
		  var e_jrate = $(this).addClass("jrate");
		  $("<div class='jrate_rates' style='float: left'></div>").appendTo(e_jrate);
			
		  for(var i=0; i<opts.maxRate; i++) {
			if(isHalfRate) {
			  $('<div id="jrate-leftrate-'+i+'" class="jrate-rate jrate-leftrate"></div>').appendTo(e_jrate.children(".jrate_rates"));
			  $('<div id="jrate-rightrate-'+i+'" class="jrate-rate jrate-rightrate"></div>').appendTo(e_jrate.children(".jrate_rates"));
			} else {
			  $('<div class="jrate-rate jrate-wholerate-'+i+'"></div>').appendTo(e_jrate.children(".jrate_rates"));
			}
		  }
		  $('<div class="jrate-terminator"></div>').appendTo(e_jrate.children(".jrate_rates"));
		  
		  //Get all rate elements
		  e_rates = e_jrate.children(".jrate_rates").children(".jrate-rate");
		  
		  //Fill initial value
		  var e_filledRates = getRatedElements(e_rates, opts["currentRate"], opts);
		  setFull(e_filledRates);
		  
		  //If voteable, set up hovering and click handlers
		e_rates.hover(
			function(e){ 
				if(getRatingContext(this).voteable) {
					setHover($(this)); 
				}
			}, function(){})
		  .click(function(e){
			if(!getRatingContext(this).voteable) {
				return;
			}	
			givenRate = getRateValue($(this), getRatingContext(this));
			//Call handler and record value if it returned true
			if(getRatingContext(this).onVote(givenRate)) {
			  getRatingContext(this)["currentRate"] = givenRate;
			}
		  });
		  
		// set the out handler on the container containing all the rate elements
		e_jrate.children(".jrate_rates").hover(function(){}, function() {
				if(getRatingContext(this).voteable) {
					removeHover($(this).children(".jrate-rate"), getRatingContext(this)); 
				}
		});
		this.opts = opts;
    }});
  };
  
  
  /**
   * Sets a option.
   * @param optionName The name of the option to set. 
   * @param optionValue The value of the option to set. 
   * @param ratingObject The rating object to set the option on.
   */
  function setOption(optionName, optionValue, ratingObject) {
	if (optionName == RATE_VALUE_OPTION_NAME) {
		setEmpty($(ratingObject).children(".jrate_rates").children(".jrate-rate"));
		var e_filledRates = getRatedElements($(ratingObject).children(".jrate_rates").children(".jrate-rate"), optionValue, getRatingContext(ratingObject));
		setFull(e_filledRates);
	}
	if (optionName == VOTEABLE_OPTION_NAME) {
		getRatingContext(ratingObject).voteable = optionValue;
	}
  }
  
  
  /**
   * Gets the object context for a given rating html element. The object context
   * stores allt he data for this rating object. 
   * @param ratingElement The rating html element to get the object context for. 
   * @return The object context for ratingElement. 
   */
  function getRatingContext(ratingElement) {
	if ($(ratingElement).attr("class").indexOf("jrate-rate") > -1) {
		// this is one of the child elemnts, one of the actual rating elements
		return $(ratingElement).parent().parent()[0].opts;
	}
	else if ($(ratingElement).attr("class").indexOf("jrate_rates") > -1) {
		// this is the element containing all the rates
		return $(ratingElement).parent()[0].opts;
	}
	return ratingElement.opts;
  }
  

  function isLeftRate(e_rate) {
    return e_rate.hasClass("jrate-leftrate");
  }
  
  function isRightRate(e_rate) {
    return e_rate.hasClass("jrate-rightrate");
  }
  
  function isWholeRate(e_rate) {
    return e_rate.hasClass("jrate-wholerate");
  }
  
  function setRateState(e_rate, full) {
    
    var className = "jrate-";
    if(isLeftRate(e_rate)) {
      className += "leftrate";
    } else if(isRightRate(e_rate)) {
      className += "rightrate";
    } else if(isWholeRate(e_rate)) {
      className += "wholerate";
    }
    
    //Add or remove full class
    if(full) {
      className += "-full";
      e_rate.addClass(className);
    } else {
      e_rate.removeClass(className + "-full");
    }
    
  }
  
  function setRateStates(e_rates, full) {
    for(var i=0; i<e_rates.length; i++) {
      setRateState(e_rates.eq(i), full);
    }
  }
  
  function setFull(e_rates) {
    setRateStates(e_rates, true);
  }
  
  function setEmpty(e_rates) {
    setRateStates(e_rates, false);
  }
  
  function isFull(e_rate){
    var f_hasClass = e_rate.hasClass;
    return f_hasClass("jrate-leftrate-full")
      || f_hasClass("jrate-rightrate-full")
      || f_hasClass("jrate-wholerate-full");
  }
  
  function isEmpty(e_rate) {
    return !isFull(e_Rate);
  }
  
  function getRatedElements(e_rates, rate, opts) {
    var divider = opts.halfRate?0.5:1;
    return e_rates.slice(0, Math.round(rate/divider));
  }
  
  function getAllRateElements(e_rate, opts) {
	return opts.container.children(".jrate-rate");
  }
  
  function setHover(e_hoveredRate) {
    setEmpty(e_hoveredRate.nextAll(".jrate-rate"));
    setFull(e_hoveredRate.prevAll(".jrate-rate").andSelf());
  }
  
  function removeHover(e_rates, opts) {
    setEmpty(e_rates);
  }
  
  function getRateValue(e_rate, opts) {
    var rateScalar = opts.halfRate?0.5:1;
    return e_rate.prevAll(".jrate-rate").length*rateScalar + rateScalar;
  }
  
})(jQuery);

$.fn.jrate.defaults = {
  maxRate: 5, 
  halfRate: true, 
  voteable: true, 
  initRate: 0, 
  onVote: function(e, voteValue){ return true; }
};
