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

namespace GE.World.Entities
{
    class CutmanEntity : WorldEntity
    {
        /// <summary>
        /// List of inner state
        /// </summary>
        enum eState
        {
            eStateIdle,
            eStateChasePlayer,
            eStateJump,
            eStateThrow,
            eStateAsleep,
            eStateFall,
            eStateCount
        }

        enum eAnimation
        {
            eAnimScissorIdle,
            eAnimScissorRun,
            eAnimNoScissorIdle,
            eAnimNoScissorRun,
            eAnimThrow,
            eAnimCount
        }

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

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

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

        /// <summary>
        /// Flag set when the scissor is thrown
        /// </summary>
        bool _bScissorThrown;

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

        /// <summary>
        /// Array containing the animations id
        /// </summary>
        int[] _iIdAnimations;

        /// <summary>
        /// Animation frame counter
        /// </summary>
        int _iAnimationCurrentFrame;

        /// <summary>
        /// Animation time
        /// </summary>
        int _iAnimationCurrentTime;

        /// <summary>
        /// Trigger zone
        /// </summary>
        Rectangle _trigger;

        /// <summary>
        /// Total hp
        /// </summary>
        int _iTotalHealth;

        /// <summary>
        /// Life time of the enemy states
        /// </summary>
        int[] _iStateLifeTime;

        /// <summary>
        /// Offset to display animation with scissor
        /// </summary>
        Vector2 _v2OffsetScissor = new Vector2(0, -17);

        /// <summary>
        /// Birth time of the crrent state
        /// </summary>
        int _iCurrentStateBirthTime;

        /// <summary>
        /// running speed
        /// </summary>
        Vector2 _v2RunningSpeed = new Vector2(4, 0);

        /// <summary>
        /// Maximum distance before the jump
        /// </summary>
        const int DISTANCE_STOP_CHASE = 70;

        /// <summary>
        /// Falling speed
        /// </summary>
        Vector2 _v2FallingSpeed = new Vector2(0, 6);

        /// <summary>
        /// Texture of the jump sprite
        /// </summary>
        int _iIdTextureJump;

        /// <summary>
        /// Sprite jump
        /// </summary>
        int _iIdSpriteScissorJump;

        /// <summary>
        /// Sprite jump
        /// </summary>
        int _iIdSpriteNoScissorJump;

        /// <summary>
        /// The target for a jump
        /// </summary>
        Vector2 _v2JumpTarget;

        /// <summary>
        /// The last time we called the jump update
        /// </summary>
        int _iLastCallJump;

        /// <summary>
        /// The initial position for a jump
        /// </summary>
        Vector2 _v2PositionStartJump;

        /// <summary>
        /// The distance to jump
        /// </summary>
        float fDistanceToJump;

        /// <summary>
        /// Height of a jump
        /// </summary>
        const int JUMP_HEIGHT = 150;

        /// <summary>
        /// number generator
        /// </summary>
        System.Random _random;

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

        /// <summary>
        /// The scissor entity
        /// </summary>
        CutmanScissorEntity _scissorEntity;

        /// <summary>
        /// Explosion animation
        /// </summary>
        int _iIdExplosionAnimation;

        #region Properties

#if !GAME

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

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

        /// <summary>
        /// Set the rectangle used as a trigger to activate the entity
        /// </summary>
        public Rectangle Trigger { set { _trigger = value; } }

        /// <summary>
        /// Setthe total health value
        /// </summary>
        public int TotalHealth { set { _iTotalHealth = value; } }

        

        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        public CutmanEntity()
            : base()
        {
            _innerState = eState.eStateCount;
            _bPreState = true;
            _iDirection = -1;
            _bScissorThrown = false;
            _shape = Physics.Physics.Instance.createDynamicRectangle(0, 0, new Vector2(0, -17), this);
            _shape._iGroup = (int)ePhysicGroup.ePhysicEnemy;

            _iIdAnimations = new int[(int)eAnimation.eAnimCount];
            _iAnimationCurrentFrame = -1;
            _iAnimationCurrentTime = -1;

            _iStateLifeTime = new int[(int)eState.eStateCount];
            _random = new System.Random(TimeClock.Clock.instance.millisecs);
            _scissorEntity = World.Instance.createBlankCutmanScissorEntity();
            _scissorEntity.Cutman = this;
        }

        public override void init()
        {
            _iIdAnimations[(int)eAnimation.eAnimNoScissorIdle] = Visu.Instance.getAnimationID("Cutman_NoScissor_Idle");
            _iIdAnimations[(int)eAnimation.eAnimNoScissorRun] = Visu.Instance.getAnimationID("Cutman_NoScissor_Run");
            _iIdAnimations[(int)eAnimation.eAnimScissorIdle] = Visu.Instance.getAnimationID("Cutman_Scissor_Idle");
            _iIdAnimations[(int)eAnimation.eAnimScissorRun] = Visu.Instance.getAnimationID("Cutman_Scissor_Run");
            _iIdAnimations[(int)eAnimation.eAnimThrow] = Visu.Instance.getAnimationID("Cutman_Throw");
            _iIdTextureJump = Visu.Instance.loadTilset("cutmansheet.xml");
            _iIdSpriteScissorJump = Visu.Instance.getSpriteId(_iIdTextureJump, "cutman_jump");
            _iIdSpriteNoScissorJump = Visu.Instance.getSpriteId(_iIdTextureJump, "cutman_no_scissor_jump");
            _iIdExplosionAnimation = Visu.Instance.getAnimationID("Big_Explosion");

            //set the hit box
            Animation animationNoScissorIdle = Visu.Instance.getAnimation(_iIdAnimations[(int)eAnimation.eAnimNoScissorIdle]);
            int iIdTexture = animationNoScissorIdle.indexTexture;
            int iIdSprite = animationNoScissorIdle.idFirstSprite;
            int iWidth = Visu.Instance.getSpriteWidth(iIdTexture, iIdSprite);
            int iHeight = Visu.Instance.getSpriteHeight(iIdTexture, iIdSprite);
            _shape.resize(iWidth-20, iHeight);

            _iStateLifeTime[(int)eState.eStateIdle] = 1000;
            _iStateLifeTime[(int)eState.eStateJump] = 1000;

            base.init();
        }

        public override void activate()
        {
            _shape._bCollisionEnable = true;
            _shape._v2position = Position;
            setState(eState.eStateAsleep);
            
            base.activate();
        }

        public override void update()
        {
            _v2PreviousPosition = Position;

            if(_innerState != eState.eStateJump && _innerState != eState.eStateFall)
                updateGravity();

            updateDirection();

            switch (_innerState)
            {
                case eState.eStateAsleep:
                    updateStateAsleep();
                    break;
                case eState.eStateIdle:
                    updateStateIdle();
                    break;
                case eState.eStateChasePlayer:
                    udpateStateChase();
                    break;
                case eState.eStateJump:
                    updateStateJump();
                    break;
                case eState.eStateFall:
                    updateStateFall();
                    break;
                case eState.eStateThrow:
                    updateStateThrow();
                    break;
            }

            //check collision with player
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlayer);
            if (res != null)
            {
                res.Entity.hurt(20);
            }
        }

        private void updateGravity()
        {
            setPosition(Position + _v2FallingSpeed);
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlatform);
            if (res != null)
            {
                if(res.Side == eCollisionSide.eBottom)
                    setPosition(Position + res.Overlap);
            }
        }

        private void updateDirection()
        {
            float fDirection = World.Instance.PlayerPosition.X - Position.X;
            if (fDirection > 0)
                _iDirection = 1;
            else
                _iDirection = -1;
        }

        private void updateStateAsleep()
        {
            if (_trigger.Contains((int)World.Instance.PlayerPosition.X, (int)World.Instance.PlayerPosition.Y))
            {
                EnemyManager.Instance.deactivate();
                setState(eState.eStateIdle);
            }

        }

        private void updateStateIdle()
        {
            //when the time is elapsed for this state, go to the next state
            if (TimeClock.Clock.instance.millisecs >= _iCurrentStateBirthTime + _iStateLifeTime[(int)eState.eStateIdle])
                setState(eState.eStateChasePlayer);
        }

        private void udpateStateChase()
        {
            //check if we can go to jump state
            float fDistance = World.Instance.PlayerPosition.X - Position.X;
            fDistance = System.Math.Abs(fDistance);

            if (fDistance <= DISTANCE_STOP_CHASE)
            {
                setState(eState.eStateJump);
                return;
            }

            //keep chasing
            Vector2 newPosition = Position + _v2RunningSpeed * _iDirection;
            setPosition(newPosition);

            //check collision
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlatform);
            if (res != null)
            {
                setPosition(_v2PreviousPosition);
                setState(eState.eStateJump);
                return;
            }

        }

        private void updateStateJump()
        {
            if (_bPreState)
            {
                _bPreState = false;
                _v2JumpTarget = World.Instance.PlayerPosition;
                _iLastCallJump = TimeClock.Clock.instance.millisecs;
                _v2PositionStartJump = Position;
                fDistanceToJump = World.Instance.PlayerPosition.X - Position.X;
            }
            
            //calculate the time elapsed
            float timeElapsed = Clock.instance.millisecs - _iCurrentStateBirthTime;
            float timeStep = Clock.instance.millisecs - _iLastCallJump;
            

            //y interpolation
            float ratio = timeElapsed / _iStateLifeTime[(int)eState.eStateJump];
            if(ratio > 1)
            {
                setState(eState.eStateFall);
                return;
            }
            float delta = (float)System.Math.PI * ratio;
            _v2Position.Y = _v2PositionStartJump.Y - (float)System.Math.Sin(delta) * JUMP_HEIGHT;

            //x interpolation
            float fStep = fDistanceToJump / _iStateLifeTime[(int)eState.eStateJump];
            _v2Position.X += fStep * timeStep;

            setPosition(_v2Position);

            //check collision with ground
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlatform);
            if (res != null)
            {
                setPosition(Position + res.Overlap);
                if (res.Side == eCollisionSide.eBottom)
                {
                    //setPosition(Position + res.Overlap);
                    //setState(eState.eStateIdle);
                    choosePathAfterJump();
                }
            }

            _iLastCallJump = TimeClock.Clock.instance.millisecs;
        }

        private void updateStateFall()
        {
            setPosition(Position + _v2FallingSpeed);
            CollisionResult res = Physics.Physics.Instance.checkFirstRegisteredCollisionEx(_shape, (int)ePhysicGroup.ePhysicPlatform);
            if (res != null)
            {
                setPosition(Position + res.Overlap);
                if (res.Side == eCollisionSide.eBottom)
                {
                    choosePathAfterJump();
                }
            }
        }

        private void updateStateThrow()
        {
            if (_iAnimationCurrentFrame == 1 && !_bScissorThrown)
            {
                _scissorEntity.Position = Position;
                _scissorEntity.Target = World.Instance.PlayerPosition;
                _scissorEntity.activate();
                _bScissorThrown = true;
            }

            if (_bAnimationOver)
            {
                choosePathAfterThrow();
                
            }
        }

        public override void render()
        {
            SpriteEffects flip = SpriteEffects.None;
            if (_iDirection == 1)
            {
                flip = SpriteEffects.FlipHorizontally;
            }

            switch (_innerState)
            {
                case eState.eStateIdle:
                    if (_bScissorThrown)
                        _bAnimationOver = Visu.Instance.displayAnimation(_iIdAnimations[(int)eAnimation.eAnimNoScissorIdle], ScreenPosition, ref _iAnimationCurrentFrame, ref _iAnimationCurrentTime, flip);
                    else
                        _bAnimationOver = Visu.Instance.displayAnimation(_iIdAnimations[(int)eAnimation.eAnimScissorIdle], ScreenPosition, ref _iAnimationCurrentFrame, ref _iAnimationCurrentTime, flip);
                    break;
                case eState.eStateChasePlayer:
                    if (_bScissorThrown)
                        _bAnimationOver = Visu.Instance.displayAnimation(_iIdAnimations[(int)eAnimation.eAnimNoScissorRun], ScreenPosition, ref _iAnimationCurrentFrame, ref _iAnimationCurrentTime, flip);
                    else
                        _bAnimationOver = Visu.Instance.displayAnimation(_iIdAnimations[(int)eAnimation.eAnimScissorRun], ScreenPosition, ref _iAnimationCurrentFrame, ref _iAnimationCurrentTime, flip);
                    break;
                case eState.eStateJump:
                case eState.eStateFall:
                    if (_bScissorThrown)
                        Visu.Instance.displaySprite(_iIdTextureJump, _iIdSpriteNoScissorJump, ScreenPosition, flip);
                    else
                        Visu.Instance.displaySprite(_iIdTextureJump, _iIdSpriteScissorJump, ScreenPosition, flip);
                    break;
                case eState.eStateThrow:
                        _bAnimationOver = Visu.Instance.displayAnimation(_iIdAnimations[(int)eAnimation.eAnimThrow], ScreenPosition, ref _iAnimationCurrentFrame, ref _iAnimationCurrentTime, 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
        }

        private void setState(eState newState)
        {
            _innerState = newState;
            _bPreState = true;
            _iAnimationCurrentFrame = -1;
            _iAnimationCurrentTime = -1;
            _iCurrentStateBirthTime = TimeClock.Clock.instance.millisecs;
            _bAnimationOver = false;
        }

        private void setPosition(Vector2 v)
        {
            _v2Position = v;
            _shape._v2position = v;
        }

        private void choosePathAfterJump()
        {
            if (_bScissorThrown)
            {
                int random = _random.Next(0, 2);
                switch (random)
                {
                    case 0:
                        setState(eState.eStateIdle);
                        break;
                    case 1:
                        setState(eState.eStateChasePlayer);
                        break;
                    case 2:
                        setState(eState.eStateJump);
                        break;
                }
            }
            else
            {
                int random = _random.Next(100);
                if (random < 50)
                    setState(eState.eStateThrow);
                else if (random < 60)
                    setState(eState.eStateIdle);
                else if (random < 80)
                    setState(eState.eStateChasePlayer);
                else
                    setState(eState.eStateJump);
            }
        }

        private void choosePathAfterThrow()
        {
            int random = _random.Next(0, 2);
            switch (random)
            {
                case 0:
                    setState(eState.eStateIdle);
                    break;
                case 1:
                    setState(eState.eStateChasePlayer);
                    break;
                case 2:
                    setState(eState.eStateJump);
                    break;
            }
        }

        public void setHasScissor(bool hasScissor)
        {
            _bScissorThrown = !hasScissor;
        }

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


}
