package net.eidiot.particles
{
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Rectangle;
    //**************************************************
    //                    SnowSprite
    //**************************************************
    //--------------------------------------
    //  Events
    //--------------------------------------
    /**
     *  Dispatched when the snow is stoped and all the snowflakes are cleared.
     *  
     *  @eventType net.eidiot.snow.SnowSprite.CLEAR_COMPLETE
     */
    [Event(name="clearComplete", type="net.eidiot.snow.SnowSprite")]
    /**
     * A snow system as a Sprite.
     * 
     * @example 
     * The simplest way to use it:<br />
     * <code>addChild(new SnowSystem(640, 480));</code>
     * 
     * @author eidiot ( http://eidiot.net )
     */    
    public class SnowSystem extends Sprite
    {
        //======================================================================
        //                            Constant
        //======================================================================
        /** Constant for the clearComplete event. */
        public static const CLEAR_COMPLETE:String = "clearComplete";
        //======================================================================
        //                            Variable
        //======================================================================
        // ---------- set by constructor ----------
        private var m_showWidth:Number;
        private var m_showHeight:Number;
        
        // ---------- set by method ----------
        private var m_skinClass:Class;
        
        private var m_minSize : Number = 5;
        private var m_maxSize : Number = 8;
        
        private var m_minAlpha : Number = 0.5;
        private var m_maxAlpha : Number = 1;
        
        private var m_minXV:Number = 0;
        private var m_maxXV:Number = 1;
        
        private var m_minYV:Number = 1;
        private var m_maxYV:Number = 5;
        
        private var m_margin : Number = 10;
        private var m_landHeight : Number = 60;
        
        private var m_spawnRate:Number = 1;
        
        // ---------- getter ----------
        private var m_isStop:Boolean = false;
        
        // ---------- can't be set ----------
        private var m_collector : Array = new Array();
        private var m_spawnValue : Number = 0;
        
        // ---------- variables for calculation ----------
        private var m_spawnLeft:Number;
        private var m_spawnWidth:Number;
        private var m_spawnRight:Number;
        private var m_landY:Number;
        //======================================================================
        //                            Constructor
        //======================================================================
        /**
         * Create a snow system.
         * 
         * @param showWidth        The width of the system to show.
         * @param showHeight    The height of the system to show.
         * @param skinClass        The class to create a snowflake.
         * @param autoStart        If start snow immediately.
         */
        
        public function SnowSystem(showWidth:Number, showHeight:Number, skinClass:Class = null, autoStart:Boolean = true)
        {
            m_showWidth = showWidth;
            m_showHeight = showHeight;
            m_skinClass = skinClass ? skinClass : DefaultSkin;
            
            scrollRect = new Rectangle(0, 0, showWidth, showHeight);
            mouseEnabled = mouseChildren = false;
            
            setMargin(m_margin);
            setLandHeight(m_landHeight);
            
            if (autoStart) start();
        }
        //======================================================================
        //                            Property
        //======================================================================
        /**
         * Is the sonw stoped.
         */        
        public function get isStop():Boolean
        {
            return m_isStop;
        }
        //======================================================================
        //                            Public Method
        //======================================================================
        /**
         * Start snow.
         * 
         * @param clear        If clear all the snowflakes before start.
         */        
        public function start(clear:Boolean = false):void
        {
            if (clear) while (numChildren > 0) removeChildAt(0);
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
            m_isStop = false;
        }
        /**
         * Stop snow.
         * 
         * @param clear     If clear all the snowflakes after stop.
         */        
        public function stop(clear:Boolean = false):void
        {
            m_collector = new Array();
            if (clear) while (numChildren > 0) removeChildAt(0);
            if (numChildren == 0) clearOver();
            m_isStop = true;
        }
        /**
         * Set the skin class.
         * 
         * @param skinClass        The class to create a snowflake.
         */        
        public function setSkin(skinClass:Class):void
        {
            m_skinClass = skinClass;
        }
        /**
         * Set the size range of snowflakes.
         * 
         * @param min        The minimum size of snowflakes.
         * @param max        The maximum size of snowflakes.
         */        
        public function setSize(min:Number, max:Number):void
        {
            m_minSize = min;
            m_maxSize = max;
        }
        /**
         * Set the alpha range of snowflakes.
         * 
         * @param min        The minimum alpha of snowflakes.
         * @param max        The maximum alpha of snowflakes.
         */            
        public function setAlpha(min:Number, max:Number):void
        {
            m_minAlpha = min;
            m_maxAlpha = max;
        }
        /**
         * Set the x velocity range of snowflakes.
         * 
         * @param min        The minimum x velocity of snowflakes.
         * @param max        The maximum x velocity of snowflakes.
         */    
        public function setXV(min:Number, max:Number):void
        {
            m_minXV = min;
            m_maxXV = max;
        }
        /**
         * Set the y velocity range of snowflakes.
         * 
         * @param min        The minimum y velocity of snowflakes.
         * @param max        The maximum y velocity of snowflakes.
         */    
        public function setYV(min:Number, max:Number):void
        {
            m_minYV = min;
            m_maxYV = max;
        }
        /**
         * Set the height of the land where snowflakes disappear.
         * 
         * @param landHeight    The height of the land.
         */        
        public function setLandHeight(landHeight:Number):void
        {
            m_landHeight = landHeight;
            m_landY = m_showHeight - m_landHeight;
        }
        /**
         * Set the margin where snowflakes spawned outside the show scope.
         * 
         * @param margin     Where snowflakes spawned outside the show scope.
         */        
        public function setMargin(margin:Number):void
        {
            m_margin = margin;
            m_spawnLeft = -m_margin;
            m_spawnWidth = m_showWidth + m_margin * 2;
            m_spawnRight = m_spawnLeft + m_spawnWidth;
        }
        /**
         * Set how many snowflakes spawned per-frame.
         * Can be noninteger and be less than 1.
         * 
         * @param spawnRate    How many snowflakes spawned per-frame.
         */        
        public function setSpawnRate(spawnRate:Number):void
        {
            m_spawnRate = spawnRate;
        }
        //======================================================================
        //                            Private Method
        //======================================================================
        private function spawnNew():void
        {
            var particle:SnowParticle = m_collector.length > 0 ? 
                                        m_collector.pop() : 
                                        new SnowParticle(m_skinClass);
            var distance : Number = Math.random();
            particle.place(m_spawnLeft + Math.random() * m_spawnWidth, 0);
            particle.setAlpha(m_minAlpha + (m_maxAlpha - m_minAlpha) * distance);
            particle.setSize(m_minSize + (m_maxSize - m_minSize) * distance);
            var xv:Number = m_minXV + (m_maxXV - m_minXV) * distance;
            if (Math.random() > 0.5) xv *= -1;
            var yv:Number = m_minYV + (m_maxYV - m_minYV) * distance;
            particle.setV(xv, yv);
            particle.setScope(m_spawnLeft, m_spawnRight, m_landY + m_landHeight * distance);
            addChild(particle);
        }
        private function clearOver():void
        {
            removeEventListener(Event.ENTER_FRAME, onEnterFrame);
            dispatchEvent(new Event(CLEAR_COMPLETE));
        }
        //======================================================================
        //                            Event Handler
        //======================================================================
        private function onEnterFrame(e:Event):void
        {
            // spawn new
            if (!m_isStop)
            {
                m_spawnValue += m_spawnRate;
                while (m_spawnValue >= 1)
                {
                    spawnNew();
                    m_spawnValue--;
                }
            }
            // update and delete old
            for (var i:int = 0; i < numChildren; i++)
            {
                var particle:SnowParticle = getChildAt(i) as SnowParticle;
                if (particle.update())
                {
                    removeChild(particle);
                    m_collector.push(particle);
                    i--;
                    if (m_isStop && numChildren == 0) clearOver();
                }
            }
        }
    }
}
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.display.Shape;
    import flash.display.GradientType;
    
    //**************************************************
    //                    DefaultSkin
    //**************************************************
    internal class DefaultSkin extends Shape
    {
        //======================================================================
        //                            Constructor
        //======================================================================
        public function DefaultSkin()
        {
            with (graphics)
            {
                var type:String = GradientType.RADIAL;
                var colors:Array = [0xE8FDFD, 0xE8FDFD, 0xFFFFFF];
                var alphas:Array = [1, 1, 0];
                var radios:Array = [0, 5, 15];
                beginGradientFill(type, colors, alphas, radios);
                drawCircle(0, 0, 5);
                endFill();
            }
        }
    }
    //**************************************************
    //                    SnowParticle
    //**************************************************
    internal class SnowParticle extends Sprite
    {
        //======================================================================
        //                            Variable
        //======================================================================
        // ---------- set by method ----------
        private var m_xv:Number;
        private var m_yv:Number;
        
        private var m_left:Number;
        private var m_right:Number;
        private var m_bottom:Number;
        //======================================================================
        //                            Constructor
        //======================================================================
        public function SnowParticle(skinClass:Class)
        {
            addChild(new skinClass());
            mouseEnabled = mouseChildren = false;
        }
        //======================================================================
        //                            Internal Method
        //======================================================================
        internal function place(initX:Number, initY:Number):void
        {
            x = initX;
            y = initY;
        }
        internal function setAlpha(initAlpha:Number):void
        {
            alpha = initAlpha;
        }
        internal function setSize(size:Number):void
        {
            width = size;
            scaleY = scaleX;
        }
        internal function setV(xv:Number, yv:Number):void
        {
            m_xv = xv;
            m_yv = yv;
        }
        internal function setScope(left:Number, right:Number, bottom:Number):void
        {
            m_left = left;
            m_right = right;
            m_bottom = bottom;
        }
        internal function update():Boolean
        {
            x += m_xv;
            y += m_yv;
            if (x < m_left || x > m_right || y > m_bottom) return true;
            return false;
        }
    }