
(function ($) {

    var pluginName = "starRating";
    var defaultOptions = {
        starCount: 5,
        readOnly: false,
        interval: 1,
        size: '30px'
    };

    var methods = {
        initialize: function(opts) {
            return this.each(function() {
                var $input = $(this);

                if ($input.data(pluginName.toLowerCase()) === true) {
                    if (console && console.log) {
                        console.log("star rating has already been initialized; skipping...");
                    }
                    return;
                }

                $input.attr("data-" + pluginName.toLowerCase(), "true");

                var options = _.extend({}, defaultOptions, opts);

                var $widget = $("<span />").addClass("star-rating").css({'font-size': options.size});
                var $emptySet = $("<span />").addClass("empty-set").appendTo($widget);
                var $filledSet = $("<span />").addClass("filled-set").appendTo($widget);

                options.$input = $input;
                options.$emptySet = $emptySet;

                for (var x = 1; x <= options.starCount; x++) {
                    $emptySet.append(
                        $("<span />").addClass("star empty")
                    );

                    $filledSet.append(
                        $("<span />").addClass("star full")
                    );
                }

                $widget.data(pluginName + ".options", options);

                $input.data(pluginName, true).hide().after($widget);

                privateMethods.updateStars($widget);

                if (!options.readOnly) {
                    $widget
                        .on("click." + pluginName, function(e) {
                            var value = privateMethods.calculateRating($widget, e.pageX);
                            privateMethods.setValue($widget, value);
                        })
                        .on("mousemove." + pluginName, function(e) {
                            var value = privateMethods.calculateRating($widget, e.pageX);
                            privateMethods.updateStars($widget, value);
                        })
                        .on("mouseleave." + pluginName, function (e) {
                            privateMethods.updateStars($widget);
                        });
                }

                $input
                    .on("change." + pluginName, function() {
                        privateMethods.updateStars($widget);
                    });
            });
        }
    };

    var privateMethods = {
        updateStars: function($widget, value) {
            var options = $widget.data(pluginName + ".options");
            value = (value == null ? (parseFloat(options.$input.val() || 0)) : value);
            $widget.find(".filled-set").css({width: privateMethods.calculateWidth($widget, value)});
        },

        setValue: function($widget, value) {
            var options = $widget.data(pluginName + ".options");
            options.$input.val(value);
            privateMethods.updateStars($widget);
        },

        calculateWidth: function($widget, value) {
            var options = $widget.data(pluginName + ".options");
            var width = options.$emptySet.width();
            return ((value / options.starCount) * 100).toString() + "%";
        },

        // Given a screen X coordinate, calculates the nearest valid value for this rating widget
        calculateRating: function($widget, screenX) {
            var options = $widget.data(pluginName + ".options");
            var offset = options.$emptySet.offset();
            var width = options.$emptySet.width();
            var ratio = (screenX - offset.left) / width;
            ratio = Math.max(0, Math.min(1, ratio));

            return Math.ceil(options.starCount * (1 / options.interval) * ratio) / (1 / options.interval);
        }
    };

    $.fn[pluginName] = function(method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || ! method) {
            return methods.initialize.apply(this, arguments);
        } else {
            $.error('Method ' +  method + ' does not exist on jQuery.' + pluginName);
        }
    };

})(jQuery);