using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;

namespace MegaManRipoff.MainGameClasses
{
    /// <summary>
    /// A bullet used by the Yellow Devil to represent part of 
    /// its body, both as it travels and when it has formed.
    /// </summary>
    class YellowDevilBlob : Bullet
    {
        #region Member Variables

        /// <summary>
        /// The Yellow Devil's texture, used when the blob has formed.
        /// </summary>
        static Texture2D _texture;

        /// <summary>
        /// The Yellow Devil instance this blob belongs to.
        /// </summary>
        YellowDevil _yellowDevil;

        /// <summary>
        /// Determines the dimensions of the grid used to align the Yellow Devil
        /// blobs once they have formed. This also determines the default size of
        /// the source rectangle used when the blob has formed.
        /// </summary>
        const int GridSize = 32;

        /// <summary>
        /// The position of the blob on the grid used to align the Yellow Devil
        /// blobs once they have formed. This is used to dictate where the blob
        /// will go to when travelling.
        /// </summary>
        Point _gridPosition;

        /// <summary>
        /// The offset of the alignment grid on the texture.
        /// </summary>
        static readonly Point _gridOffset = new Point(12, 0);

        /// <summary>
        /// Holds the states for the Yellow Devil blob.
        /// </summary>
        public enum State
        {
            Deforming, Travelling, Forming, Formed
        }

        /// <summary>
        /// The Yellow Devil blob's current state.
        /// </summary>
        State _currentState;

        /// <summary>
        /// The Yellow Devil blob's previous state.
        /// </summary>
        State _previousState;

        /// <summary>
        /// The blob's current direction.
        /// </summary>
        Entity.Direction _direction;

        /// <summary>
        /// The Yellow Devil blob's animations.
        /// </summary>
        Dictionary<State, Animation> _animationSet;

        /// <summary>
        /// The amount of damage Yellow Devil blobs deal.
        /// </summary>
        static int _setDamage;

        /// <summary>
        /// The speed that Yellow Devil blobs move at when travelling.
        /// </summary>
        static float _maxSpeed;

        /// <summary>
        /// A blobby sound effect, used when a blob starts travelling.
        /// </summary>
        static SoundEffect _travelSound;

        #endregion

        #region Properties

        /// <summary>
        /// Gets this Yellow Devil blob's hitbox.
        /// </summary>
        public override Rectangle Hitbox
        {
            get { return new Rectangle((int)_position.X, (int)_position.Y, GridSize, GridSize); }
        }

        /// <summary>
        /// Gets the Yellow Devil blob's current state.
        /// </summary>
        public State CurrentState
        {
            get { return _currentState; }
        }

        /// <summary>
        /// Gets the Yellow Devil blob's current direction.
        /// </summary>
        public Entity.Direction CurrentDirection
        {
            get { return _direction; }
        }

        /// <summary>
        /// Gets the position of the blob on the grid used to align the Yellow Devil
        /// blobs once they have formed.
        /// </summary>
        public Point GridPosition
        {
            get { return _gridPosition; }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new Yellow Devil blob.
        /// </summary>
        /// <param name="room">The current room.</param>
        /// <param name="yellowDevil">The Yellow Devil instance this bullet belongs to.</param>
        /// <param name="gridPosition">The position of the blob on the grid used to align the Yellow Devil
        /// blobs once they have formed.</param>
        public YellowDevilBlob(Room room, YellowDevil yellowDevil, Point gridPosition)
            : base(room, Vector2.Zero, yellowDevil, _setDamage, Vector2.Zero, false, false)
        {
            _yellowDevil = yellowDevil;
            _gridPosition = gridPosition;

            //Place the blob at its destination.
            _position = Destination();

            //Set the state and direction.
            _currentState = State.Formed;
            _direction = _yellowDevil.CurrentDirection;

            //Initialise animations.
            InitialiseAnimations();
        }

        /// <summary>
        /// Creates a new Yellow Devil blob with a specific formed animation frame.
        /// </summary>
        /// <param name="room">The current room.</param>
        /// <param name="yellowDevil">The Yellow Devil instance this bullet belongs to.</param>
        /// <param name="gridPosition">The position of the blob on the grid used to align the Yellow Devil
        /// blobs once they have formed.</param>
        /// <param name="sourceRectangle">The animation frame source rectangle once formed.</param>
        /// <param name="sourceRectangle">The animation frame offset once formed.</param>
        public YellowDevilBlob(Room room, YellowDevil yellowDevil, Point gridPosition,
            Rectangle sourceRectangle, Vector2 offset)
            : base(room, Vector2.Zero, yellowDevil, _setDamage, Vector2.Zero, false, false)
        {
            _yellowDevil = yellowDevil;
            _gridPosition = gridPosition;

            //Place the blob at its destination.
            _position = Destination();

            //Set the state and direction.
            _currentState = State.Formed;
            _direction = _yellowDevil.CurrentDirection;

            //Initialise animations.
            InitialiseAnimations(sourceRectangle, offset);;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Initialises the Yellow Devil blob's static fields dependent on the difficulty.
        /// </summary>
        /// <param name="gameDifficulty">The game's current difficulty setting.</param>
        public static void Initialise(GameDifficulty gameDifficulty)
        {
            switch (gameDifficulty)
            {
                case GameDifficulty.Easy:
                    _setDamage = 3;
                    _maxSpeed = 4;
                    break;
                case GameDifficulty.Hard:
                    _setDamage = 3;
                    _maxSpeed = 6;
                    break;
                default:
                    _setDamage = 4;
                    _maxSpeed = 5;
                    break;
            }
        }

        /// <summary>
        /// Initialises this Yellow Devil blob's formed sprite. This differs depending where it
        /// is in the alignment grid.
        /// </summary>
        protected override void InitialiseAnimations()
        {
            _animationSet = new Dictionary<State, Animation>();

            //Get the details from the grid and make a new animation frame from it.
            Rectangle gridFrame = new Rectangle((_gridPosition.X * GridSize) + _gridOffset.X,
                                                (_gridPosition.Y * GridSize) + _gridOffset.Y,
                                                GridSize,
                                                GridSize);

            Animation formed = new Animation(new Tuple<Rectangle, Vector2, int>(
                gridFrame, Vector2.Zero, 0), 0);
            _animationSet.Add(State.Formed, formed);

            //Initialise the other animations.
            InitialiseOtherAnimations(gridFrame, Vector2.Zero);
        }

        /// <summary>
        /// Initialises this Yellow Devil blob's formed animation using the specified
        /// source rectangle and offset.
        /// </summary>
        protected void InitialiseAnimations(Rectangle sourceRectangle, Vector2 offset)
        {
            _animationSet = new Dictionary<State, Animation>();

            //Get the details from the grid and make a new animation frame from it.
            Animation formed = new Animation(new Tuple<Rectangle, Vector2, int>(sourceRectangle, offset, 0), 0);
            _animationSet.Add(State.Formed, formed);

            //Initialise the other animations.
            InitialiseOtherAnimations(sourceRectangle, offset);
        }

        /// <summary>
        /// Initialises this Yellow Devil blob's travelling, forming and deforming animation. This
        /// is called by both overloads for InitialiseAnimations().
        /// </summary>
        /// <param name="sourceRectangle">The source rectangle for this Yellow Devil blob's
        /// formed animation frame.</param>
        /// <param name="offset">The offset for this Yellow Devil blob's formed animation
        /// frame.</param>
        private void InitialiseOtherAnimations(Rectangle sourceRectangle, Vector2 offset)
        {
            //Travelling.
            Animation travelling = new Animation(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(0, 128, 40, 36), new Vector2(-4, -3), 8), 0);
            travelling.AddFrame(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(40, 128, 40, 36), new Vector2(-4, -3), 8));
            travelling.AddFrame(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(80, 128, 40, 36), new Vector2(-4, -3), 8));
            travelling.AddFrame(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(40, 128, 40, 36), new Vector2(-4, -3), 8));
            _animationSet.Add(State.Travelling, travelling);

            //Also add the forming and deforming animations. Even though they use the same
            //frame, they must use different animations because they call different events.
            Animation forming = new Animation(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(44, 166, 32, 32), Vector2.Zero, 4), 0);
            _animationSet.Add(State.Forming, forming);
            forming.OnAnimationEnd += new AnimationEnd(forming_OnAnimationEnd);

            Animation deforming = new Animation(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(44, 166, 32, 32), Vector2.Zero, 4), 0);
            _animationSet.Add(State.Deforming, deforming);
            deforming.OnAnimationEnd += new AnimationEnd(deforming_OnAnimationEnd);
        }

        /// <summary>
        /// Loads the assets required by the Yellow Devil blobs.
        /// </summary>
        /// <param name="content">The game's content manager.</param>
        public static void LoadContent(ContentManager content)
        {
            _texture = content.Load<Texture2D>("Images\\YellowDevil");

            _travelSound = content.Load<SoundEffect>("Sounds\\BlobTravel");
        }

        /// <summary>
        /// Gets this blob's destination vector based on the Yellow Devil's forming position,
        /// the direction the Yellow Devil is facing and this blob's position on the grid.
        /// </summary>
        private Vector2 Destination()
        {
            if (_yellowDevil.CurrentDirection == Entity.Direction.Left)
            {
                return new Vector2(_yellowDevil.FormingPosition.X + (_gridPosition.X * GridSize),
                                   _yellowDevil.FormingPosition.Y + (_gridPosition.Y * GridSize));
            }
            else
            {
                return new Vector2(_yellowDevil.FormingPosition.X - ((_gridPosition.X + 1) * GridSize),
                                   _yellowDevil.FormingPosition.Y + (_gridPosition.Y * GridSize));
            }
        }

        /// <summary>
        /// Tells this blob to start travelling.
        /// </summary>
        /// <param name="newDirection">The direction that the blob will move in.</param>
        public void StartTravelling(Entity.Direction newDirection)
        {
            //If we're already travelling, throw an exception.
            if (_currentState == State.Travelling)
                throw new InvalidOperationException("Attempted to start travelling whilst " +
                    "already travelling.");

            //Set our direction and speed vector.
            _direction = newDirection;
            _speed = new Vector2(_maxSpeed * (int)newDirection, 0);

            //Set the state to deforming to create a smoother animation before moving.
            _currentState = State.Deforming;

            //Play a sound effect if we're in the camera view.
            if (_room.Camera.IsInView(Hitbox))
                _travelSound.Play();
        }

        /// <summary>
        /// Loop used to increment the blob's position in its current direction, and become
        /// formed if we've reached our destination point yet.
        /// </summary>
        private void Travel()
        {
            //If our next step would result in us arriving at our destination...
            if ((_direction == Entity.Direction.Right && _position.X + _speed.X >= Destination().X)
                || (_direction == Entity.Direction.Left && _position.X + _speed.X <= Destination().X))
            {
                //Jump to the destination and stop moving.
                _position = Destination();
                _speed = Vector2.Zero;

                //Set our direction to the same as the Yellow Devil's.
                _direction = _yellowDevil.CurrentDirection;

                //Set the state to forming to create a smoother looking animation before stopping.
                _currentState = State.Forming;
            }
            //Otherwise, increment our position.
            else
            {
                _position += _speed;
            }
        }

        /// <summary>
        /// Updates the reference to the previous state, and restarts any animations.
        /// </summary>
        private void UpdatePreviousState()
        {
            //If the state is different to what it was last time, restart the
            //state's animation.
            if (_currentState != _previousState)
            {
                _animationSet[_currentState].ToFrame(0);
            }

            //Update the previous state.
            _previousState = _currentState;
        }

        /// <summary>
        /// Updates the Yellow Devil blob.
        /// </summary>
        /// <param name="gameTime">The current snapshot of time.</param>
        public override void Update(GameTime gameTime)
        {
            //Travel, if necessary.
            if (_currentState == State.Travelling)
                Travel();

            //Update animations.
            _animationSet[_currentState].Update();
        }

        /// <summary>
        /// Recalculates the animation frame's offset according to if the sprite is flipped.
        /// This makes sure all the formed pieces align nicely.
        /// </summary>
        /// <param name="offset">The animation frame's original offset.</param>
        /// <param name="spriteEffects">The current direction.</param>
        /// <returns>Returns the recalculated offset.</returns>
        public Vector2 RecalculateOffset(Vector2 offset, SpriteEffects spriteEffects)
        {
            //If the sprite is not flipped and the offset is negative, or the sprite is
            //flipped and the offset is positive, make no changes.
            if ((spriteEffects == SpriteEffects.None && offset.X <= 0)
                || (spriteEffects == SpriteEffects.FlipHorizontally && offset.X >= 0))
                return offset;

            //Otherwise, zero the offset's horizontal component.
            return new Vector2(0, offset.Y);
        }

        /// <summary>
        /// Draws the Yellow Devil blob to the screen.
        /// </summary>
        /// <param name="spriteBatch">The sprite batch to draw to.</param>
        public override void Draw(SpriteBatch spriteBatch)
        {
            SpriteEffects directionEffects = SpriteEffects.None;
            Vector2 currentFrameOffset;

            //If the blob has formed, flip the sprite according to the Yellow Devil's
            //current direction and recalculate the offset accordingly.
            if (_currentState == State.Formed)
            {
                if (_direction == Entity.Direction.Right)
                    directionEffects = SpriteEffects.FlipHorizontally;

                //Recalculate the offset according to the direction.
                currentFrameOffset = RecalculateOffset(_animationSet[_currentState].CurrentFrame.Item2,
                                                       directionEffects);
            }
            //Otherwise, get the offset normally.
            else
            {
                currentFrameOffset = _animationSet[_currentState].CurrentFrame.Item2;
            }

            //Get the source rectangle too.
            Rectangle currentFrameSource = _animationSet[_currentState].CurrentFrame.Item1;

            //Finally drwa the blob.
            spriteBatch.Draw(_texture,
                             _position + currentFrameOffset,
                             currentFrameSource,
                             Color.White,
                             0,
                             Vector2.Zero,
                             1,
                             directionEffects,
                             0);
        }

        #endregion

        #region Event Handles

        /// <summary>
        /// Method called when the deforming animation ends.
        /// </summary>
        /// <param name="sender">The animation that ended.</param>
        /// <param name="e">Event arguments.</param>
        void deforming_OnAnimationEnd(object sender, EventArgs e)
        {
            //Change to the travelling state.
            _currentState = State.Travelling;
        }

        /// <summary>
        /// Method called when the forming animation ends.
        /// </summary>
        /// <param name="sender">The animation that ended.</param>
        /// <param name="e">Event arguments.</param>
        void forming_OnAnimationEnd(object sender, EventArgs e)
        {
            //Change to the formed state.
            _currentState = State.Formed;
        }

        #endregion
    }
}
