package meyermike.minild9.scripts 
{
    import flash.display.LineScaleMode;
    import flash.events.Event;
    import flash.filters.DisplacementMapFilterMode;
    import flash.geom.Rectangle;
    import meyermike.collision.CollisionInfo;
    import meyermike.GameObject;
    import meyermike.Layers;
    import meyermike.Main;
    import meyermike.minild9.Constants;
    import meyermike.minild9.Enemy;
    import meyermike.minild9.Music;
    import meyermike.minild9.Person;
    import meyermike.minild9.Physics;
    import meyermike.minild9.PlatformerCharacter;
    import meyermike.minild9.Player;
    import meyermike.script.Script;
    import meyermike.script.Step;
    import meyermike.script.step.CreateGameObjectFromBitmap;
    import meyermike.script.step.Loop;
    import meyermike.script.step.SpawnGameObject;
    import meyermike.script.step.Wait;
    import meyermike.Util;
    import meyermike.widget.TextField;
    
    /**
     * ...
     * @author Mike Meyer
     */
    public class Game extends Script
    {
        private static const WAIT_FOR_ANIMATION_DELAY:Number = 1.0;
        
        private var mTimer:TextField;
        private var mTimeRemaining:Number;
        private var mEarlyRestart:Number;
        
        [Embed(source = '../../../../assets/Fonts/DisposableDroidBB.ttf', fontFamily='TimerFont')]
        private static var Font:Class;
        
        /**
         * whether the level was lost.
         */
        private static var mLost:Boolean;
        
        /**
         * makes it easy to do the collision check with a callback
         */
        private var mElapsedTime:Number;
        
        public function Game()
        {
            this.mBeatsPerMinute = 240.0;
            super();
        }
        
        /**
         * steps to execute in this script
         */
        protected override function CreateScript():Step 
        {
            var steps:/*Step*/Array = [
                ];
            return new Loop(1, steps);
        }
        
        /**
         * initial setup
         */
        public override function Begin():void 
        {
            super.Begin();
            
            mLost = false;
            
            // timers
            mTimeRemaining = Constants.TIME_LIMIT;
            mEarlyRestart = 0.0;
            
            // player
            var player:Player = Layers.PlayerLayer.Add(Player) as Player;
            player.addEventListener(PlatformerCharacter.EVENT_DEATH, Lose);
            Main.sInstance.SetCameraFollowTarget(player);
            player.x = 100;
            player.y = 215;
            
            // platforms
            AddPlatforms();
            
            // NPCs
            AddNPCs();
            
            // time-display
            AddTimer();
            
            // time for new music
            Music.StartNewMusic();
        }
        
        /**
         * Add the platforms to the scene
         */
        private function AddPlatforms():void
        {
            var platform:GameObject;
            
            for ( var i:Number = 0; i < Constants.PLATFORMS.length; ++i )
            {
                var r:Rectangle = Constants.PLATFORMS[i];
                
                platform = Layers.PlatformLayer.Add(GameObject);
                
                // if we're reusing one, we can probably just resize it an 
                // not worry about drawing the rect
                platform.graphics.beginFill(Constants.PLATFORM_COLOR);
                platform.graphics.drawRect( 0, 0, r.width, r.height);
                platform.graphics.endFill();
                
                platform.x = r.x;
                platform.y = r.y;
            }
        }
        
        /**
         * Add the NPCs to the scene
         */
        private function AddNPCs():void
        {
            var person:Person;
            var goomba:Enemy;
            
            // faller - early
            person = Layers.NPCLayer.Add(Person) as Person;
            person.addEventListener(PlatformerCharacter.EVENT_DEATH, Lose);
            person.x = 325;
            person.y = -1200;
            
            // faller - late
            person = Layers.NPCLayer.Add(Person) as Person;
            person.addEventListener(PlatformerCharacter.EVENT_DEATH, Lose);
            person.x = 520;
            person.y = -4800;
            
            // faller - later
            person = Layers.NPCLayer.Add(Person) as Person;
            person.addEventListener(PlatformerCharacter.EVENT_DEATH, Lose);
            person.x = 560;
            person.y = -5100;
            
            // faller - latest
            person = Layers.NPCLayer.Add(Person) as Person;
            person.addEventListener(PlatformerCharacter.EVENT_DEATH, Lose);
            person.x = 540;
            person.y = -5400;
            
            // rescue from goomba - near start
            person = Layers.NPCLayer.Add(Person) as Person;
            person.addEventListener(PlatformerCharacter.EVENT_DEATH, Lose);
            person.x = 75;
            person.y = 220;
            
            // rescue from goomba - low right
            person = Layers.NPCLayer.Add(Person) as Person;
            person.addEventListener(PlatformerCharacter.EVENT_DEATH, Lose);
            person.x = 700;
            person.y = 220;
            
            // rescue from goomba - high right
            person = Layers.NPCLayer.Add(Person) as Person;
            person.addEventListener(PlatformerCharacter.EVENT_DEATH, Lose);
            person.x = 787;
            person.y = 140;
            
            // goomba - near start
            goomba = Layers.EnemyLayer.Add(Enemy) as Enemy;
            goomba.x = 200;
            goomba.y = 230;
            goomba.StartWalkingLeft();
            
            // goomba - low right
            goomba = Layers.EnemyLayer.Add(Enemy) as Enemy;
            goomba.x = 450;
            goomba.y = 200;
            goomba.StartWalkingRight();
            
            // goomba - high right
            goomba = Layers.EnemyLayer.Add(Enemy) as Enemy;
            goomba.x = 600;
            goomba.y = -3300;
            goomba.StartWalkingRight();
        }
        
        /**
         * Update and check timer
         */
        public override function Update(elapsedTime:Number):void 
        {
            super.Update(elapsedTime);
            mElapsedTime = elapsedTime;
            
            mTimeRemaining -= elapsedTime;
            
            // rescue-by-touching checks
            Layers.ForEachCartesianProductStyle(Layers.PlayerLayer, Layers.NPCLayer, RescueCheck);
            
            // enemy interaction
            Layers.ForEachCartesianProductStyle(Layers.PlayerLayer, Layers.EnemyLayer, PlayerEnemyCheck)
            Layers.ForEachCartesianProductStyle(Layers.NPCLayer, Layers.EnemyLayer, NPCEnemyCheck)
            
            Physics.Update(elapsedTime);
            
            UpdateTimer();
            
            // fail
            if ( (mTimeRemaining <= mEarlyRestart)
               ||(mTimeRemaining <= 0.0) )
            {
                if (mLost)
                {
                    Main.sInstance.SwitchToScript(new TryAgain());
                }
                else
                {
                    Main.sInstance.SwitchToScript(new Victory());
                }
            }
        }
        
        /**
         * clean up
         */
        public override function End():void 
        {
            super.End();
            
            // remove all those event listeners we set
            Layers.PlayerLayer.ForEach(RemoveDeathEventListener);
            Layers.NPCLayer.ForEach(RemoveDeathEventListener);
            
            Main.sInstance.SetCameraFollowTarget(null);
            Main.sInstance.ResetScrolling();
            RemoveTimer();
            Layers.RemoveAll();
        }
        
        /**
         * add the timer to the scene
         */
        private function AddTimer():void
        {
            // needs to be added above the scrolling area
            mTimer = new meyermike.widget.TextField();
            Main.sInstance.addChild(mTimer);
            mTimer.SetFont("TimerFont");
            mTimer.SetFontSize(36);
            mTimer.SetFontColor(Constants.COLOR_BRIGHT);
            UpdateTimer();
            mTimer.CenterOn(0.5 * Constants.STAGE_WIDTH, 14);
        }
        
        /**
         * Update the displayed time
         */
        private function UpdateTimer():void
        {
            if (mTimeRemaining > 0.0)
            {
                var minutes:Number = Math.floor( mTimeRemaining / 60.0 );
                var remainder:Number = mTimeRemaining - minutes;
                var seconds:Number = Math.floor( remainder );
                remainder = remainder - seconds;
                var decimal:Number = Math.floor(remainder * 100);
                
                var text:String = "";
                if (minutes < 10) 
                {
                    text += "0";
                }
                text += String(minutes);
                text += ":";
                if (seconds < 10) 
                {
                    text += "0";
                }
                text += seconds;
                text += ".";
                if (decimal < 10) 
                {
                    text += "0";
                }
                text += decimal;
                
                mTimer.SetText(text);
            }
            else
            {
                mTimer.SetText("00:00.00");
            }
        }
        
        /**
         * clean up that timer we added in a weird spot
         */
        private function RemoveTimer():void
        {
            Main.sInstance.removeChild(mTimer);
        }
        
        /**
         * Check for rescue
         */
        private function RescueCheck(player:Player, npc:Person):void
        {
            if (!player.IsDying())
            {
                var ci:CollisionInfo = Util.CheckAabbCollision(player, npc, mElapsedTime);
                
                if (null != ci)
                {
                    npc.Rescue();
                }
            }
        }
        
        /**
         * check player-enemy collisions
         */
        private function PlayerEnemyCheck(player:Player, enemy:Enemy):void
        {
            if (!player.IsDying())
            {
                if (!enemy.IsStomped())
                {
                    var ci:CollisionInfo = Util.CheckAabbCollision(player, enemy, mElapsedTime);
                    
                    if (null != ci)
                    {
                        // if above the enemy, stomp it
                        if (ci.normal.y < 0.0)
                        {
                            enemy.Stomp();
                            
                            // also bounce up
                            player.dy = -Constants.PLAYER_ENEMY_BOUNCE_SPEED;
                        }
                        else 
                        {
                            // not above the enemy.  player dies
                            player.StartDeath();
                        }
                    }
                }
            }
        }
        
        /**
         * check NPC-enemy collisions
         */
        private function NPCEnemyCheck(npc:Person, enemy:Enemy):void
        {
            if (!npc.IsDying())
            {
                if (!enemy.IsStomped())
                {
                    var ci:CollisionInfo = Util.CheckAabbCollision(npc, enemy, mElapsedTime);
                    
                    if (null != ci)
                    {
                        npc.StartDeath();
                    }
                }
            }
        }
        
        /**
         * something made the game unwinnable, restart early AFTER giving animations some time
         */
        private function BeginCountDownToRestart():void
        {
            mEarlyRestart = mTimeRemaining - WAIT_FOR_ANIMATION_DELAY;
        }
        
        /**
         * callback to the death of the player or an NPC
         */
        private function Lose(e:Event = null):void
        {
            if (!mLost)
            {
                mLost = true;
                BeginCountDownToRestart();
            }
            
            // swing the camera over to whatever died
            Main.sInstance.SetCameraFollowTarget(e.target as GameObject);
        }
        
        /**
         * clean up so that stuff gets garbage collected 
         * and we don't have multiple listeners
         */
        private function RemoveDeathEventListener(character:PlatformerCharacter):void
        {
            character.removeEventListener(PlatformerCharacter.EVENT_DEATH, Lose);
        }
    }
}