
/****************************************************************************/
/*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.World;
using GE.TimeClock;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using System;

namespace GE.World.Entities
{
    /// <summary>
    /// The boss Bombman
    /// </summary>
    class BombmanEntity : WorldEntity
    {
        /// <summary>
        /// Bombman's inner state
        /// </summary>
        enum eState
        {
            eStatePrepareAttack,
            eStateAttack,
            eStateJumpOnPlayer,
            eStateJumpBackward,
            eStateJumpForward,
            eStateAsleep,
            eStateCount
        }

        #region Variable

        /// <summary>
        /// Shape of the trigger which activate the entity
        /// </summary>
        StaticShapeRectangle _triggerShape;

        /// <summary>
        /// Bombman's inner state
        /// </summary>
        eState _innerState;

        /// <summary>
        /// Total health point
        /// </summary>
        int _iTotalHealth;

        /// <summary>
        /// Texture id
        /// </summary>
        int _iIdTexture;

        /// <summary>
        /// Attack sprite
        /// </summary>
        int _iIdSpriteAttack;

        /// <summary>
        /// Sprite of Bombman preparing to attack
        /// </summary>
        int _iIdPrepareAttack;

        /// <summary>
        /// Jump forward
        /// </summary>
        int _iIdJumpForward;

        /// <summary>
        /// Jump backward
        /// </summary>
        int _iIdJumpBackward;

        /// <summary>
        /// Collision box
        /// </summary>
        DynamicShapeRectangle _shape;

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

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

        /// <summary>
        /// Birth time of the states
        /// </summary>
        int[] _iStateBirthTime;

        /// <summary>
        /// Life time the enemy stays in PrepareAttack state
        /// </summary>
        const int STATE_PREPAREATTACK_LIFETIME = 1000;

        /// <summary>
        /// Generate random number
        /// </summary>
        Random _randomGenerator;

        /// <summary>
        /// the maximum height the enemy can reach in a jump
        /// </summary>
        const int JUMP_HEIGHT = 100;

        /// <summary>
        /// Life time the enemy stays in any jump state
        /// </summary>
        const int STATE_JUMP_LIFETIME = 1000;

        /// <summary>
        /// Save the player's position for a JumpOnPlayer
        /// </summary>
        Vector2 _v2PlayerPosition;

        /// <summary>
        /// Save the enemy start position for any jump
        /// </summary>
        Vector2 _v2StartPosition;

        /// <summary>
        /// Store the distance between the enemy and the player
        /// </summary>
        float _fDistanceWithPlayer;

        /// <summary>
        /// Distance for the forward and backward jump
        /// </summary>
        const float JUMP_DISTANCE = 200;

        /// <summary>
        /// The distance used to jump
        /// </summary>
        float _fJumpDistance;

        /// <summary>
        /// Time of the last call of the jump function
        /// </summary>
        int _iLastCallJump;

        /// <summary>
        /// The bomb thrown by Bombman
        /// </summary>
        BombmanBombEntity _bomb;

        /// <summary>
        /// Position offset when the enemy is in Attack state
        /// </summary>
        Vector2 _v2PositionOffsetAttackLeft;

        /// <summary>
        /// Position offset when the enemy is in Attack state
        /// </summary>
        Vector2 _v2PositionOffsetAttackRight;

        /// <summary>
        /// Offset for the bomb's spawn position when throwing to the right
        /// </summary>
        Vector2 _v2OffsetSpawnPositionBomb;

        /// <summary>
        /// the bomb damages
        /// </summary>
        int _iBombDamages;

        /// <summary>
        /// Animation explosion
        /// </summary>
        int _iIdExplositionAnimation;

        #endregion

        #region Properties

#if !GAME

        public static string EDITOR_TILESET { get { return "bombmansheet.xml"; } }

        public static string EDITOR_SPRITE { get { return "bombman_3"; } }
#endif
        public int BombDamages { set { _iBombDamages = value; } }

        /// <summary>
        /// Set the rectangle used as a trigger to activate the entity
        /// </summary>
        public Rectangle Trigger
        {
            set
            {
                _triggerShape = new StaticShapeRectangle(value.Width, value.Height, Vector2.Zero, new Vector2(value.X, value.Y), 0, this);
            }
        }
        /// <summary>
        /// Setthe total health value
        /// </summary>
        public int TotalHealth { set { _iTotalHealth = value; } }

        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        public BombmanEntity()
            : base()
        {
            _innerState = eState.eStateAsleep;
            _iIdTexture = -1;
            _iIdSpriteAttack = -1;
            _iIdPrepareAttack = -1;
            _iIdJumpForward = -1;
            _iIdJumpBackward = -1;
            _iDirection = -1;
            _iStateBirthTime = new int[(int)eState.eStateCount] { 0, 0, 0, 0, 0, 0 };
            _randomGenerator = new Random(Clock.instance.millisecs);
            _v2PositionOffsetAttackLeft = new Vector2(-8, 12);
            _v2PositionOffsetAttackRight = new Vector2(0, 12);
            _v2OffsetSpawnPositionBomb = new Vector2(35, 0);
            _iBombDamages = 0;
            _bomb = World.Instance.createBomb();
            _triggerShape = new StaticShapeRectangle(0, 0, Vector2.Zero, Vector2.Zero, 0, null);
        }

        /// <summary>
        /// Initiator : Initialise the entity. Load all the data which are not loaded during the level loading.
        /// </summary>
        public override void init()
        {
            _iIdTexture = Visu.Instance.loadTilset("bombmansheet.xml");
            _iIdPrepareAttack = Visu.Instance.getSpriteId(_iIdTexture, "bombman_3");
            _iIdSpriteAttack = Visu.Instance.getSpriteId(_iIdTexture, "bombman_4");
            _iIdJumpForward = Visu.Instance.getSpriteId(_iIdTexture, "bombman_5");
            _iIdJumpBackward = Visu.Instance.getSpriteId(_iIdTexture, "bombman_7");
            _iIdExplositionAnimation = Visu.Instance.getAnimationID("Big_Explosion");

            int iWdith = Visu.Instance.getSpriteWidth(_iIdTexture, _iIdSpriteAttack) - 40;
            int iHeight = Visu.Instance.getSpriteHeight(_iIdTexture, _iIdSpriteAttack);
            _shape = Physics.Physics.Instance.createDynamicRectangle(iWdith, iHeight, Vector2.UnitX * -15, this);
            _shape._bCollisionEnable = false;
            _shape._iGroup = (int)ePhysicGroup.ePhysicEnemy;

            _side = eSide.eSideEnemy;
        }

        /// <summary>
        /// Activate the entity
        /// </summary>
        public override void activate()
        {
            _bActive = true;
            _shape._bCollisionEnable = true;
            _shape._v2position = Position;
            changeInnerState(eState.eStateAsleep);
            _iLastCallJump = -1;

            Vector2 direction = World.Instance.PlayerPosition - Position;
            if (direction.X <= 0)
                _iDirection = -1;
            else
                _iDirection = 1;

            _iHealthPoint = _iTotalHealth;
        }

        /// <summary>
        /// Change the enemy inner state
        /// </summary>
        /// <param name="newState"></param>
        private void changeInnerState(eState newState)
        {
            _innerState = newState;
            _bPreState = true;
            _iLastCallJump = -1;
#if DEBUG
            System.Diagnostics.Debug.WriteLine(newState.ToString());
#endif
        }

        /// <summary>
        /// Update
        /// </summary>
        public override void update()
        {
            _v2PreviousPosition = _v2Position;

            updateDirection();

            switch (_innerState)
            {
                case eState.eStateAttack:
                    updateStateAttack();
                    break;

                case eState.eStateJumpBackward:
                    updateStateJumpBackward();
                    break;

                case eState.eStateJumpForward:
                    updateStateJumpForward();
                    break;

                case eState.eStateJumpOnPlayer:
                    updateStateJumpOnPlayer();
                    break;

                case eState.eStatePrepareAttack:
                    updateStatePrepareAttack();
                    break;

                case eState.eStateAsleep:
                    updateStateAsleep();
                    break;
            }

            updatePlayerCollision();
        }

        /// <summary>
        /// Update the state asleep
        /// </summary>
        private void updateStateAsleep()
        {
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_triggerShape, (int)ePhysicGroup.ePhysicPlayer);
            if (res != null)
            {
                GE.Manager.EnemyManager.Instance.deactivate();
                changeInnerState(eState.eStatePrepareAttack);
            }
        }

        /// <summary>
        /// Update the Attack state
        /// </summary>
        private void updateStateAttack()
        {
            //pre state
            if (_bPreState)
            {
                _bPreState = false;
                _iStateBirthTime[(int)_innerState] = Clock.instance.millisecs;

                //throw a bomb
                if(_iDirection == -1)
                    _bomb.Position = Position;
                else
                    _bomb.Position = Position + _v2OffsetSpawnPositionBomb;

                _bomb.Target = World.Instance.PlayerPosition;
                _bomb.Damages = _iBombDamages;
                _bomb.activate();
            }
            
            //wait for the bomb to explode
            if (_bomb.Active) return;

            changeInnerState(eState.eStatePrepareAttack);
        }

        /// <summary>
        /// Update the state JumpBackward
        /// </summary>
        private void updateStateJumpBackward()
        {
            //pre state
            if (_bPreState)
            {
                _bPreState = false;
                _iStateBirthTime[(int)_innerState] = Clock.instance.millisecs;
                _v2StartPosition = Position;
                _fJumpDistance = JUMP_DISTANCE;
                if (_iDirection == 1)
                    _fJumpDistance = -JUMP_DISTANCE;
            }

            //jump
            bool jumpRes = jump(_fJumpDistance, _v2StartPosition, _iStateBirthTime[(int)_innerState], STATE_JUMP_LIFETIME);
            _shape._v2position = Position;

            //jump is over, quit state
            if (jumpRes)
            {
                changeInnerState(eState.eStatePrepareAttack);
                return;
            }

            //check collision
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlatform);
            if (res != null)
            {
                Position += res.Overlap;
                _shape._v2position = Position;
            }
        }

        /// <summary>
        /// Update the state JumpForward
        /// </summary>
        private void updateStateJumpForward()
        {
            //pre state
            if (_bPreState)
            {
                _bPreState = false;
                _iStateBirthTime[(int)_innerState] = Clock.instance.millisecs;
                _v2StartPosition = Position;
                _fJumpDistance = JUMP_DISTANCE;
                if (_iDirection == -1)
                    _fJumpDistance = -JUMP_DISTANCE;
            }

            //jump
            bool jumpRes = jump(_fJumpDistance, _v2StartPosition, _iStateBirthTime[(int)_innerState], STATE_JUMP_LIFETIME);
            _shape._v2position = Position;

            //jump is over, quit state
            if (jumpRes)
            {
                changeInnerState(eState.eStatePrepareAttack);
                return;
            }

            //check collision
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlatform);
            if (res != null)
            {
                Position += res.Overlap;
                _shape._v2position = Position;
            }

        }

        /// <summary>
        /// Update the state JumpOnPlayer
        /// </summary>
        private void updateStateJumpOnPlayer()
        {
            //pre state
            if (_bPreState)
            {
                _bPreState = false;
                _iStateBirthTime[(int)_innerState] = Clock.instance.millisecs;
                _v2PlayerPosition = World.Instance.PlayerPosition;
                _v2StartPosition = Position;
                _fJumpDistance = _fDistanceWithPlayer;
            }

            //jump
            bool jumpRes = jump(_fJumpDistance, _v2StartPosition, _iStateBirthTime[(int)_innerState], STATE_JUMP_LIFETIME);
            _shape._v2position = Position;

            //jump is over, quit state
            if (jumpRes)
            {
                changeInnerState(eState.eStatePrepareAttack);
                return;
            }

            //check collision
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlatform);
            if (res != null)
            {
                Position += res.Overlap;
                _shape._v2position = Position;
            }
        }

        /// <summary>
        /// Update the state PrepareAttack
        /// </summary>
        private void updateStatePrepareAttack()
        {
            //pre state
            if (_bPreState)
            {
                _bPreState = false;
                _iStateBirthTime[(int)_innerState] = Clock.instance.millisecs;
            }

            //time didn't elapse, quit
            if (Clock.instance.millisecs <= _iStateBirthTime[(int)_innerState] + STATE_PREPAREATTACK_LIFETIME) return;

            //state is over, generate a random number to go to the next state
            int iRandomNumber = _randomGenerator.Next(100);

            const int THRESHOLD_ATTACK = 70;
            const int THRESHOLD_JUMPONPLAYER = 80;
            const int THRESHOLD_JUMPFORWARD =90;
            const int THRESHOLD_JUMPBACKWARD = 99;

            if (iRandomNumber <= THRESHOLD_ATTACK)
                changeInnerState(eState.eStateAttack);
            else if (iRandomNumber <= THRESHOLD_JUMPONPLAYER)
                changeInnerState(eState.eStateJumpOnPlayer);
            else if (iRandomNumber <= THRESHOLD_JUMPFORWARD)
                changeInnerState(eState.eStateJumpForward);
            else if(iRandomNumber <= THRESHOLD_JUMPBACKWARD)
                changeInnerState(eState.eStateJumpBackward);

        }

        /// <summary>
        /// Render
        /// </summary>
        public override void render()
        {
            Vector2 v2AttackOffset = _v2PositionOffsetAttackLeft;
            SpriteEffects flip = SpriteEffects.None;
            if (_iDirection == 1)
            {
                flip = SpriteEffects.FlipHorizontally;
                v2AttackOffset = _v2PositionOffsetAttackRight;
            }

            switch (_innerState)
            {
                case eState.eStateAttack:
                    Visu.Instance.displaySprite(_iIdTexture, _iIdSpriteAttack, ScreenPosition + v2AttackOffset, flip);
                    break;

                case eState.eStateJumpBackward:
                    Visu.Instance.displaySprite(_iIdTexture, _iIdJumpBackward, ScreenPosition, flip);
                    break;

                case eState.eStateJumpForward:
                    Visu.Instance.displaySprite(_iIdTexture, _iIdJumpForward, ScreenPosition, flip);
                    break;

                case eState.eStateJumpOnPlayer:
                    Visu.Instance.displaySprite(_iIdTexture, _iIdJumpForward, ScreenPosition, flip);
                    break;

                case eState.eStatePrepareAttack:
                    Visu.Instance.displaySprite(_iIdTexture, _iIdPrepareAttack, ScreenPosition, flip);
                    break;
            }

            if(_innerState != eState.eStateAsleep)
                GE.Gui.Hud.HeadUpDisplay.Instance.renderBossLifeBar(_iTotalHealth, _iHealthPoint);

            

#if DEBUG
            Vector2[] obb = _shape.getOrientedBoundingBox();
            for (int i = 0; i < 4; i++)
                obb[i] -= World.Instance.CameraPosition;

            Visu.Instance.displayPolygon(obb);


#endif
        }

        /// <summary>
        /// Make the enemy jump
        /// </summary>
        /// <param name="distance"></param>
        /// <returns>False by default, true when the jump is over</returns>
        private bool jump(float distance, Vector2 startPosition, int startTime, int lifeTime)
        {
            if(_iLastCallJump == -1)
                _iLastCallJump = Clock.instance.millisecs;

            //calculate the time elapsed
            float timeElapsed = Clock.instance.millisecs - startTime;
            float timeStep = Clock.instance.millisecs - _iLastCallJump;

            //check if the enemy is back to the ground
            if (_v2Position.Y > startPosition.Y)
            {
                _v2Position.Y = startPosition.Y;
                _iLastCallJump = Clock.instance.millisecs;
                return true;
            }

            //y interpolation
            float ratio = timeElapsed / lifeTime;
            float delta = (float)Math.PI * ratio;
            _v2Position.Y = _v2StartPosition.Y - (float)Math.Sin(delta) * JUMP_HEIGHT;

            //x interpolation
            float fStep = distance / lifeTime;
            _v2Position.X += fStep * timeStep;

            _iLastCallJump = Clock.instance.millisecs;
            return false;
        }

        /// <summary>
        /// Update the direction that the enemy is facing
        /// </summary>
        private void updateDirection()
        {
            _fDistanceWithPlayer = World.Instance.PlayerPosition.X - Position.X;
            if (_fDistanceWithPlayer < 0)
                _iDirection = -1;
            else
                _iDirection = 1;
        }

        /// <summary>
        /// Check collision with the player
        /// </summary>
        private void updatePlayerCollision()
        {
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlayer);
            if (res == null) return;

            World.Instance.PlayerEntity.hurt(Damages);
        }

        public override void die()
        {
            Manager.ExplosionManager.Instance.activate(_iIdExplositionAnimation, Position);
            _shape._bCollisionEnable = false;
            base.die();
            World.Instance.gameOver();
        }
    }
}
