/**
 * @fileoverview This code creates some kind of "snowfall" on (x)html page
 * Tested in IE6, Opera7+, Gecko/20040206+
 *
 * This code is free software, so you can redistribute it and/or
 * modify it (even eat) under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation. 
 * See licence on http://www.gnu.org/copyleft/lesser.html (or in the package)
 *
 * This code is distributed 'cause it could be useful, but WITHOUT ANY GUARANTEE!!! 
 *
 * @author AKS {@link http://forum.dklab.ru/users/Aks/}
 * @version unknown ;) 
 */

/**
 * it is the anonymous object containing all necessary properties and methods for a "snowfall". 
 */

({

/**
 * use_images
 *
 * defines what will be used as a "snowflakes" - <img> or <span>
 *
 * @type {Boolean} 
 */

  use_images : true,

/**
 * images_amount
 *
 * quantity of the images which are available at your disposal 
 *
 * @type {Number} 
 */

  images_amount : 5,

/**
 * flakes_amount 
 *
 * quantity of "snowflakes" which is supposed to be placed on page 
 *
 * @type {Number}
 */

  flakes_amount : 50,

/**
 * flake_char 
 *
 * text symbol which will be presented as a "snowflake" (if use_images == false)
 *
 * @type {String}
 */

  flake_char : '*',

/**
 * images_path 
 *
 * the address on which images are located 
 *
 * @type {String}
 */

  images_path : 'images/flake',

/**
 * images_ext 
 *
 * expansion which is used for images  
 *
 * @type {String}
 */

  images_ext : '.gif',

/**
 * load_Snow_Progect
 *
 * redefines snow_Flakes_Constructor.prototype and calls add_Event_Handler for registration onload event handler
 *
 * @see #snow_Flakes_Proto
 * @see #snow_Flakes_Proto.add_Event_Handler
 * @see #snow_Flakes_Proto.apply_Own_Func
 * @see #create_Snow_Flakes
 */

  load_Snow_Progect : function() {
    var proto = this.snow_Flakes_Proto;
    if(this.use_images) this.preload_Snow_Images();
    this.snow_Flakes_Constructor.prototype = proto;
    proto.add_Event_Handler(window, 'onload', 
      proto.apply_Own_Func(this, 'create_Snow_Flakes'));
  },

/**
 * preload_Snow_Images
 *
 * it is necessary for preliminary loading images
 */
		
  preload_Snow_Images : function() {
    this.snow_images = [];
    for(var i = 0; i < this.images_amount; i++) {
      var src = this.images_path + i + this.images_ext;
      (this.snow_images[i] = new Image).src = src;
    }
  },  		

/**
 * create_Snow_Flakes
 *
 * recieves pointers on "snowflakes" and creates new instances of snow_Flakes_Constructor
 *
 * @see #paint_Snow_Flakes
 * @see #snow_Flakes_Constructor
 */

  create_Snow_Flakes : function() {
    var snow_flakes, tag_name = (this.use_images && 'img') || 'span',
    snow_drift = this.paint_Snow_Flakes();
    if(snow_drift) {
      snow_flakes = snow_drift.getElementsByTagName(tag_name);
      if(snow_flakes.length)
        for(var i = 0, l = snow_flakes.length; i < l; i++)
          new this.snow_Flakes_Constructor(snow_flakes[i]);
    }
  },

/**
 * paint_Snow_Flakes
 *
 * creates "snowdrift" and its contents
 *
 * @returns {Object} returns the "snowdrift" placed in a body of page.
 */

  paint_Snow_Flakes : function() {
    var snow_drift = document.createElement('div'), content = '',
    images_amount = this.use_images && this.snow_images.length;
    for(var i = 0; i < this.flakes_amount; i++) {
      if(this.use_images) {
        var random = Math.floor(Math.random() * images_amount);
        content += '<img src="' + this.snow_images[random].src + '" />';
      }
      else content += '<span>' + this.flake_char + '</span>';
    }
    snow_drift.id = 'snow_drift';
    snow_drift.innerHTML = content;
    return (document.body.appendChild(snow_drift));
  },

/**
 * snow_Flakes_Constructor
 *
 * will construct elements of a "snowfall"
 *
 * @constructor
 * @param {Object} <span> element containing one "snowflake"
 * @see #add_Event_Handler
 * @see #apply_Own_Func
 * @see #get_Start_Position
 * @returns {Object} returns new instance of snow_Flakes_Constructor
 */

  snow_Flakes_Constructor : function(flake) {
    if(flake && flake.style) {
      this.style = flake.style;
      this.add_Event_Handler(window, 'onresize', 
        this.apply_Own_Func(this, 'resize_Snow_Fall'));
      this.add_Event_Handler(window, 'onscroll', 
        this.apply_Own_Func(this, 'resize_Snow_Fall'));
      this.get_Start_Position();
    }
  },

/**
 * snow_Flakes_Proto
 *
 * will be the prototype for snow_Flakes_Constructor
 *
 * @see #snow_Flakes_Constructor
 * @returns {Object} returns new instance of snow_Flakes_Proto
 */

  snow_Flakes_Proto : new function() {

/**
 * page_padding
 *
 * padding from the right and bottom edges of page 
 */

    this.page_padding = 60;

/**
 * anim_delay
 *
 * delay for function of animation 
 *
 * @see #make_Snow_Storm
 */

    this.anim_delay = 100;

/**
 * canvas_element
 *
 * element according to which coordinates will be calculated. 
 */

    this.canvas_element = (document.compatMode &&
      document.compatMode == 'CSS1Compat') ? 'documentElement' : 'body';

/**
 * get_Page_Bounds
 *
 * counts measurements
 */

    this.get_Page_Bounds = function() {
      var element = this.canvas_element, pad = this.page_padding;
      this.window_x = (window.innerWidth || document[element].clientWidth) - pad;
      this.window_y = (window.innerHeight || document[element].clientHeight) - pad;
      this.page_x = window.scrollX || document[element].scrollLeft; 
      this.page_y = window.scrollY || document[element].scrollTop;
    };

/**
 * get_Start_Position
 *
 * counts starting positions
 *
 * @see #get_Page_Bounds
 * @see #make_Snow_Fall
 */

    this.get_Start_Position = function() {
      this.get_Page_Bounds();
      this.current_x = (this.x = Math.random() * this.window_x);                                                       
      this.y = Math.random() * this.window_y * 2 - this.window_y;
      this.step_y = (this.step_x = (Math.random() * 3 + this.anim_delay/100));
      this.bound_x = Math.floor(Math.random() * 15);
      this.make_Snow_Fall();
    };

/**
 * resize_Snow_Fall
 *
 * re-paints "snowfall" after resize and scroll events
 *
 * @see #apply_Own_Func
 * @see #get_Start_Position
 */

    this.resize_Snow_Fall = function() {
      window.clearTimeout(this.snow_timer);
      window.clearTimeout(this.start_timer);
      this.start_timer = window.setTimeout(
        this.apply_Own_Func(this, 'get_Start_Position'), 200);
    }; 

/**
 * make_Snow_Fall
 *
 * mounts new positions of a "snowflake"
 *
 * @see #apply_Own_Func
 */
   
    this.make_Snow_Fall = function() {
      if(Math.abs(this.current_x - this.x) > this.bound_x) this.step_x = -this.step_x;
      this.current_x += this.step_x;
      this.y += this.step_y;
      if(this.current_x >= this.window_x || this.y >= this.window_y) {
        this.current_x = (this.x = Math.random() * this.window_x);
        this.y = 0; 
      }
      this.style.left = this.current_x  + this.page_x + 'px';
      this.style.top = this.y + this.page_y + 'px';
      this.snow_timer = window.setTimeout(
        this.apply_Own_Func(this, 'make_Snow_Fall'), this.anim_delay); 
    };

/**
 * add_Event_Handler
 *
 * returns event handler
 *
 * @param {Object} the pointer to object which will be the target of event
 * @param {String} string which specifies type of event
 * @param {Function} pointer to the function which will be event listener
 * @returns {Function} returns one of specific methods of registration of events  listeners on 
 *   the event target, if browser doesn't support one of these methods, returns false
 */

    this.add_Event_Handler = function(o, e, f) {
      return ((o.addEventListener) ? o.addEventListener(e.substr(2), f, false) :
        ((o.attachEvent) ? o.attachEvent(e, f) : false));
    }; 

/**
 * apply_Own_Func
 *
 * it is used for a wrapper of anonymous function 
 *
 * @param {Object} specifies caller object 
 * @param {String} string which specifies a method of caller object
 * @returns {Function} returns the anonymous function causing the call of corresponding method of caller object 
 */
  
    this.apply_Own_Func = function(o, m) { return function() { o[m](); }};
  }
}).load_Snow_Progect();
