(function ($) {
    $.snowfall = function (element, options) {
        var defaults = {
            flakeCount: 40,
            flakeColor: '#ffffff',
            flakeIndex: 999999,
            minSize: 1,
            maxSize: 4,
            minSpeed: 1,
            maxSpeed: 4,
            round: false,
            shadow: false
        },
			options = $.extend(defaults, options),
			random = function random(min, max) {
			    return Math.round(min + Math.random() * (max - min));
			};

        $(element).data("snowfall", this);

        // Snow flake object
        function Flake(_x, _y, _size, _speed, _id) {
            // Flake properties
            this.id = _id;
            this.x = _x;
            this.y = _y;
            this.size = _size;
            this.speed = _speed;
            this.step = 0,
				this.stepSize = random(1, 10) / 100;

            var flakeMarkup = $(document.createElement("div")).attr({ 'class': 'snowfall-flakes', 'id': 'flake-' + this.id }).css({ 'width': this.size, 'height': this.size, 'background': options.flakeColor, 'position': 'absolute', 'top': this.y, 'left': this.x, 'fontSize': 0, 'zIndex': options.flakeIndex });

            if ($(element).get(0).tagName === $(document).get(0).tagName) {
                $('body').append(flakeMarkup);
                element = $('body');
            } else {
                $(element).append(flakeMarkup);
            }

            this.element = document.getElementById('flake-' + this.id);

            // Update function, used to update the snow flakes, and checks current snowflake against bounds
            this.update = function () {
                this.y += this.speed;

                if (this.y > (elHeight) - (this.size + 6)) {
                    this.reset();
                }

                this.element.style.top = this.y + 'px';
                this.element.style.left = this.x + 'px';

                this.step += this.stepSize;
                this.x += Math.cos(this.step);

                if (this.x > (elWidth) - widthOffset || this.x < widthOffset) {
                    this.reset();
                }
            }

            // Resets the snowflake once it reaches one of the bounds set
            this.reset = function () {
                this.y = 0;
                this.x = random(widthOffset, elWidth - widthOffset);
                this.stepSize = random(1, 10) / 100;
                this.size = random((options.minSize * 100), (options.maxSize * 100)) / 100;
                this.speed = random(options.minSpeed, options.maxSpeed);
            }
        }

        // Private vars
        var flakes = [],
				flakeId = 0,
				i = 0,
				elHeight = $(element).height(),
				elWidth = $(element).width(),
				widthOffset = 0,
				snowTimeout = 0;

        // This will reduce the horizontal scroll bar from displaying, when the effect is applied to the whole page
        if ($(element).get(0).tagName === $(document).get(0).tagName) {
            widthOffset = 25;
        }

        // Bind the window resize event so we can get the innerHeight again
        $(window).bind("resize", function () {
            elHeight = $(element).height();
            elWidth = $(element).width();
        });


        // initialize the flakes
        for (i = 0; i < options.flakeCount; i += 1) {
            flakeId = flakes.length;
            flakes.push(new Flake(random(widthOffset, elWidth - widthOffset), random(0, elHeight), random((options.minSize * 100), (options.maxSize * 100)) / 100, random(options.minSpeed, options.maxSpeed), flakeId));
        }

        // This adds the style to make the snowflakes round via border radius property 
        if (options.round) {
            $('.snowfall-flakes').css({ '-moz-border-radius': options.maxSize, '-webkit-border-radius': options.maxSize, 'border-radius': options.maxSize });
        }

        // This adds shadows just below the snowflake so they pop a bit on lighter colored web pages
        if (options.shadow) {
            $('.snowfall-flakes').css({ '-moz-box-shadow': '1px 1px 1px #555', '-webkit-box-shadow': '1px 1px 1px #555', 'box-shadow': '1px 1px 1px #555' });
        }

        // this controls flow of the updating snow
        function snow() {
            for (i = 0; i < flakes.length; i += 1) {
                flakes[i].update();
            }

            snowTimeout = setTimeout(function () { snow() }, 30);
        }

        snow();

        // Public Methods

        // clears the snowflakes
        this.clear = function () {
            $(element).children('.snowfall-flakes').remove();
            flakes = [];
            clearTimeout(snowTimeout);
        };
    };

    // Initialize the options and the plugin
    $.fn.snowfall = function (options) {
        if (typeof (options) == "object" || options == undefined) {
            return this.each(function (i) {
                (new $.snowfall(this, options));
            });
        } else if (typeof (options) == "string") {
            return this.each(function (i) {
                var snow = $(this).data('snowfall');
                if (snow) {
                    snow.clear();
                }
            });
        }
    };
})(jQuery);
