
/****************************************************************************/
/*Copyright (c) 2011, Florent DEVILLE.                                      */
/*All rights reserved.                                                      */
/*                                                                          */
/*Redistribution and use in source and binary forms, with or without        */
/*modification, are permitted provided that the following conditions        */
/*are met:                                                                  */
/*                                                                          */
/* - Redistributions of source code must retain the above copyright         */
/*notice, this list of conditions and the following disclaimer.             */
/* - Redistributions in binary form must reproduce the above                */
/*copyright notice, this list of conditions and the following               */
/*disclaimer in the documentation and/or other materials provided           */
/*with the distribution.                                                    */
/* - The names of its contributors cannot be used to endorse or promote     */
/*products derived from this software without specific prior written        */
/*permission.                                                               */
/* - The source code cannot be used for commercial purposes without         */
/*its contributors' permission.                                             */
/*                                                                          */
/*THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS       */
/*"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT         */
/*LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS         */
/*FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE            */
/*COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       */
/*INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,      */
/*BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;          */
/*LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER          */
/*CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT        */
/*LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN         */
/*ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
/*POSSIBILITY OF SUCH DAMAGE.                                               */
/****************************************************************************/

using GE.Visualisation;
using GE.Physics.Shapes;
using GE.Physics;
using GE.Manager;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace GE.World.Entities
{
    /// <summary>
    /// Enemy robocop
    /// </summary>
    class RobocopEntity : EnemyEntity
    {
        /// <summary>
        /// Robocop inner state
        /// </summary>
        enum eState
        {
            eStateProtect,
            eStateJump, 
            eStateShoot,
            eStateJumpBack,
            eStateNone,
            eStateCount
        }

        /// <summary>
        /// Enemy inner state
        /// </summary>
        eState _innerState;

        /// <summary>
        /// Flag set when the inner state was changed
        /// </summary>
        bool _bPreState;

        /// <summary>
        /// Texture containing all the sprites
        /// </summary>
        int _iIdTexture;

        /// <summary>
        /// Protect sprite
        /// </summary>
        int _iIdSpriteProtect;

        /// <summary>
        /// Jump sprite
        /// </summary>
        int _iIdSpriteJump;

        /// <summary>
        /// Shoot sprite
        /// </summary>
        int _iIdSpriteShoot;

        /// <summary>
        /// Bullet sprite
        /// </summary>
        int _iIdSpriteBullet;

        /// <summary>
        /// Shooting animation
        /// </summary>
        int _iIdAnimShoot;

        /// <summary>
        /// Current frame of the animation
        /// </summary>
        int _iAnimationCurrentFrame;

        /// <summary>
        /// Current time of animation
        /// </summary>
        int _iAnimationCurrentTime;

        /// <summary>
        /// Flag set when the animation is over
        /// </summary>
        bool _bAnimationOver;

        /// <summary>
        /// Direction the robocop is facing
        /// </summary>
        int _iDirection;

        /// <summary>
        /// Physic shape
        /// </summary>
        //DynamicShapeRectangle _shape;

        /// <summary>
        /// Bullet damages
        /// </summary>
        int _iBulletDamages;

        #region Properties
#if !GAME
        public static string EDITOR_TILESET { get { return "enemysheet.xml"; } }
        public static string EDITOR_SPRITE { get { return "robocop_protect"; } }
#endif
        public int Direction { set { _iDirection = value; } }
        public int BulletDamages { set { _iBulletDamages = value; } }
        #endregion

        /// <summary>
        /// Structure containing all the specific data used in protect state
        /// </summary>
        struct structProtectState
        {
            /// <summary>
            /// Start time of the Protect state
            /// </summary>
            public int _iTimeStart;

            /// <summary>
            /// Life time of the protect state
            /// </summary>
            public const int TIME_MAX = 3000;

            /// <summary>
            /// Threshold to go to jump state
            /// </summary>
            public const float THRESHOLD = 0.990f;

            /// <summary>
            /// Max distance between the robocop and the player to shoot
            /// </summary>
            public const int MAX_DISTANCE = 50;

            /// <summary>
            /// Max distance squared between the robocop and the player to shoot
            /// </summary>
            public const int MAX_DISTANCE_SQUARED = MAX_DISTANCE * MAX_DISTANCE;

            public void reset()
            {
                _iTimeStart = TimeClock.Clock.instance.millisecs;
            }
        }

        /// <summary>
        /// Data for the protect state
        /// </summary>
        structProtectState _dataProtectState;

        /// <summary>
        /// Structure containing all the specific data used in shoot state
        /// </summary>
        struct structShootState
        {
            public bool _bShoot;
            public const int MAX_DISTANCE_SQUARED = structProtectState.MAX_DISTANCE_SQUARED;
            public const int TIME_BETWEEN_SHOOT = 1000;
            public int _iTimeLastShoot;
            public bool _bShootOver;

            public void reset()
            {
                _bShoot = false;
                _iTimeLastShoot = -1;
                _bShootOver = false;
            }
        }

        /// <summary>
        /// Data for the shoot state
        /// </summary>
        structShootState _dataShootState;

        /// <summary>
        /// Structure containing all the specific data used in jump state
        /// </summary>
        struct structJumpState
        {
            public int _iTimeStartJump;
            public const int LIFE_TIME_JUMP = 1000;
            public const int JUMP_SPEED_MAX = 4;
            public const int FALL_SPEED = 4;
            public int _iInitialHeight;

            public void reset(int initialHeight)
            {
                _iTimeStartJump = TimeClock.Clock.instance.millisecs;
                _iInitialHeight = initialHeight;
            }
        }

        /// <summary>
        /// Data for the jump state
        /// </summary>
        structJumpState _dataJumpState;

        /// <summary>
        /// Position to spawn the bullets
        /// </summary>
        Vector2 _v2BulletSpawnPosition;

        /// <summary>
        /// Id of the explosion animation
        /// </summary>
        int _iIdAnimationExplosion;

        /// <summary>
        /// The position offset to display the explosion
        /// </summary>
        Vector2 _v2AnimationExplosionOffsetPosition;

        /// <summary>
        /// Constructor
        /// </summary>
        public RobocopEntity()
            : base()
        {
            _innerState = eState.eStateNone;
            _bPreState = true;
            _iIdTexture = -1;
            _iIdSpriteProtect = -1;
            _iIdSpriteJump = -1;
            _iIdAnimShoot = -1;
            _iIdSpriteShoot = -1;
            _iAnimationCurrentFrame = -1;
            _iAnimationCurrentTime = -1;
            _bAnimationOver = false;
            _iDirection = 1;
            _shape = Physics.Physics.Instance.createDynamicRectangle(0, 0, Vector2.Zero, this);
            _shape._bCollisionEnable = false;
            _dataProtectState = new structProtectState();
            _dataShootState = new structShootState();
            _dataJumpState = new structJumpState();
            _iIdAnimationExplosion = -1;
            _v2AnimationExplosionOffsetPosition = Vector2.Zero;

        }

        /// <summary>
        /// Initialise the entity. Load all the data which are not loaded during the level loading
        /// </summary>
        public override void init()
        {
            _iIdTexture = Visu.Instance.loadTilset("enemysheet.xml");
            _iIdSpriteProtect = Visu.Instance.getSpriteId(_iIdTexture, "robocop_protect");
            _iIdSpriteJump = Visu.Instance.getSpriteId(_iIdTexture, "robocop_jump");
            _iIdAnimShoot = Visu.Instance.getAnimationID("Robocop_Shoot");
            _iIdSpriteBullet = Visu.Instance.getSpriteId(_iIdTexture, "robocop_bullet");
            _iIdSpriteShoot = Visu.Instance.getSpriteId(_iIdTexture, "robocop_shoot_2");
            _iIdAnimationExplosion = Visu.Instance.getAnimationID("Little_Explosion");

            int iWidth = Visu.Instance.getSpriteWidth(_iIdTexture, _iIdSpriteProtect);
            int iHeight = Visu.Instance.getSpriteHeight(_iIdTexture, _iIdSpriteProtect);
            _shape.resize(iWidth, iHeight);
            _shape._iGroup = (int)ePhysicGroup.ePhysicEnemy;
            _v2BulletSpawnPosition = Position + new Vector2(0, 20);
            _v2AnimationExplosionOffsetPosition = new Vector2(iWidth / 2, iHeight / 2);

            base.init();
        }
        /// <summary>
        /// Activator
        /// </summary>
        public override void activate()
        {
            _bActive = true;
            _shape._bCollisionEnable = true;
            _shape._v2position = Position;
            _innerState = eState.eStateProtect;   
        }

        /// <summary>
        /// Update
        /// </summary>
        public override void update()
        {
            switch (_innerState)
            {
                case eState.eStateProtect:
                    updateStateProtect();
                    break;
                case eState.eStateShoot:
                    updateStateShoot();
                    break;
                case eState.eStateJump:
                    updateStateJump();
                    break;
                case eState.eStateJumpBack:
                    updateStateJumpBack();
                    break;
            }

            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlayer);
            if (res != null)
            {
                res.Entity.hurt(_iDamages);
            }
        }

        /// <summary>
        /// Update the Protect state
        /// </summary>
        private void updateStateProtect()
        {
            if (_bPreState)
            {
                _bPreState = false;
                _dataProtectState.reset();
            }

            if (playerJumpOver())
            {
                _innerState = eState.eStateJump;
                _bPreState = true;
                return;
            }

            //max time elapsed, go to shoot
            if (TimeClock.Clock.instance.millisecs > _dataProtectState._iTimeStart + structProtectState.TIME_MAX)
            {
                _innerState = eState.eStateShoot;
                _bPreState = true;
                return;
            }

            //if the player is close, shoot
            float distance = (World.Instance.PlayerPosition - Position).LengthSquared();
            if (distance < structProtectState.MAX_DISTANCE_SQUARED)
            {
                _innerState = eState.eStateShoot;
                _bPreState = true;
                return;
            }

            //choose ramdomly if we jump
            System.Random rand = new System.Random(TimeClock.Clock.instance.millisecs);
            float fRandom = (float)rand.Next(1000);
            fRandom = fRandom / 1000f;
            if (fRandom > structProtectState.THRESHOLD)
            {
                _innerState = eState.eStateJump;
                _bPreState = true;
                return;
            }
        }

        /// <summary>
        /// Update the shoot state
        /// </summary>
        private void updateStateShoot()
        {
            if (_bPreState)
            {
                _bPreState = false;
                _dataShootState.reset();
            }

            //shoot state over
            if (_dataShootState._bShootOver)
            {
                if (_bAnimationOver)
                {
                    _innerState = eState.eStateProtect;
                    _bPreState = true;
                }
                return;
            }

            //robocop shot already
            if (_dataShootState._bShoot)
            {
                //if the player is close and the time elapsed, shoot again
                float fDistance = (Position - World.Instance.PlayerPosition).LengthSquared();
                if (fDistance < structShootState.MAX_DISTANCE_SQUARED && TimeClock.Clock.instance.millisecs > _dataShootState._iTimeLastShoot + structShootState.TIME_BETWEEN_SHOOT)
                {
                    _dataShootState._iTimeLastShoot = TimeClock.Clock.instance.millisecs;
                    BulletManager.Instance.activate(_iIdTexture, _iIdSpriteBullet, 2000, _iBulletDamages, new Vector2(_iDirection * 3, 0), _v2BulletSpawnPosition, eSide.eSideEnemy);
                
                }
                else if (TimeClock.Clock.instance.millisecs > _dataShootState._iTimeLastShoot + structShootState.TIME_BETWEEN_SHOOT)//the time elapsed
                {
                    _dataShootState._bShootOver = true;
                    return;
                }
            }
            else //robocop didn't shoot
            {
                if (_bAnimationOver)
                {
                    _dataShootState._bShoot = true;
                    _dataShootState._iTimeLastShoot = TimeClock.Clock.instance.millisecs;
                    BulletManager.Instance.activate(_iIdTexture, _iIdSpriteBullet, 1000, _iBulletDamages, new Vector2(_iDirection * 3, 0), _v2BulletSpawnPosition, eSide.eSideEnemy);
                }
            }
        }

        /// <summary>
        /// Update the jump state
        /// </summary>
        private void updateStateJump()
        {
            if (_bPreState)
            {
                _bPreState = false;
                _dataJumpState.reset((int)Position.Y);
            }

            //check if we still jump
            if (TimeClock.Clock.instance.millisecs < _dataJumpState._iTimeStartJump + structJumpState.LIFE_TIME_JUMP)
            {
                float fElapsedTime = TimeClock.Clock.instance.millisecs - _dataJumpState._iTimeStartJump;
                float fRatio = 1 - fElapsedTime / structJumpState.LIFE_TIME_JUMP;
                float fUpSpeed = structJumpState.JUMP_SPEED_MAX * fRatio;
                Position -= new Vector2(0, fUpSpeed);
                _shape._v2position = Position;
            }
            else //no jump, fall
            {
                Position += new Vector2(0, structJumpState.FALL_SPEED);
                if (Position.Y > _dataJumpState._iInitialHeight) //fall over
                {
                    Position = new Vector2(Position.X, _dataJumpState._iInitialHeight);
                    _innerState = eState.eStateProtect;
                    _bPreState = true;
                }
                _shape._v2position = Position;
            }
        }

        private void updateStateJumpBack()
        {
            if (_bPreState)
            {
                _bPreState = false;
                _dataJumpState.reset((int)Position.Y);
            }
            

            //check if we still jump
            if (TimeClock.Clock.instance.millisecs < _dataJumpState._iTimeStartJump + structJumpState.LIFE_TIME_JUMP)
            {
                float fElapsedTime = TimeClock.Clock.instance.millisecs - _dataJumpState._iTimeStartJump;
                float fRatio = 1 - fElapsedTime / structJumpState.LIFE_TIME_JUMP;
                float fUpSpeed = structJumpState.JUMP_SPEED_MAX * fRatio;
                int iSideSpeed = 2 * _iDirection;
                Position -= new Vector2(iSideSpeed, fUpSpeed);
               
            }
            else //no jump, fall
            {
                int iSideSpeed = -2 * _iDirection;
                Position += new Vector2(iSideSpeed, structJumpState.FALL_SPEED);
                if (Position.Y > _dataJumpState._iInitialHeight) //fall over
                {
                    Position = new Vector2(Position.X, _dataJumpState._iInitialHeight);
                    _innerState = eState.eStateProtect;
                    _bPreState = true;
                }
            }

            _shape._v2position = Position;
        }

        /// <summary>
        /// Render
        /// </summary>
        public override void render()
        {
            //get the correct flip effect
            SpriteEffects flip = SpriteEffects.None;
            if (_iDirection > 0)
                flip = SpriteEffects.FlipHorizontally;

            //render
            switch (_innerState)
            {
                case eState.eStateProtect:
                    Visu.Instance.displaySprite(_iIdTexture, _iIdSpriteProtect, ScreenPosition, flip);
                    break;

                case eState.eStateJump:
                case eState.eStateJumpBack:
                    Visu.Instance.displaySprite(_iIdTexture, _iIdSpriteJump, ScreenPosition, flip);
                    break;

                case eState.eStateShoot:
                    if (!_dataShootState._bShoot)
                        _bAnimationOver = Visu.Instance.displayAnimation(_iIdAnimShoot, ScreenPosition, ref _iAnimationCurrentFrame, ref _iAnimationCurrentTime, flip);
                    else if (_dataShootState._bShoot && !_dataShootState._bShootOver)
                        Visu.Instance.displaySprite(_iIdTexture, _iIdSpriteShoot, ScreenPosition, flip);
                    else
                        _bAnimationOver = Visu.Instance.displayReverseAnimation(_iIdAnimShoot, ScreenPosition, ref _iAnimationCurrentFrame, ref _iAnimationCurrentTime, flip);
                    break;
            }
        }

        private bool playerJumpOver()
        {
            if (_iDirection > 0)
            {
                if (Position.X > World.Instance.PlayerPosition.X) return false;
            }
            {
                if (Position.X < World.Instance.PlayerPosition.X) return false;
            }
            if (Position.Y < World.Instance.PlayerPosition.Y) return false;

            return true;
        }

        public override void die()
        {
            _shape._bCollisionEnable = false;
            _bActive = false;
            Manager.ExplosionManager.Instance.activate(_iIdAnimationExplosion, Position + _v2AnimationExplosionOffsetPosition);
        }

        public override void hurt(int damages)
        {
            if (_innerState == eState.eStateProtect) return;

            HP -= damages;
            if(HP <= 0) die();
        }
    }
}
