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;

namespace MegaManRipoff.MainGameClasses
{
    /// <summary>
    /// An enemy that hides behind a shield. He pops out occasionally to shoot the player.
    /// </summary>
    class SniperJoe : Enemy
    {
        #region Member Variables

        /// <summary>
        /// The texture for Sniper Joes.
        /// </summary>
        static Texture2D _texture;

        /// <summary>
        /// The maximum health for Sniper Joes.
        /// </summary>
        static int _maxHealth;

        /// <summary>
        /// The damage that Sniper Joes deal.
        /// </summary>
        static int _damage;

        /// <summary>
        /// Holds the possible states of the Sniper Joe.
        /// </summary>
        enum State
        {
            Standing, EyeFlash, PrepareToShoot, Shooting
        }

        /// <summary>
        /// The current state of this Sniper Joe.
        /// </summary>
        State _currentState;

        /// <summary>
        /// The previous state of this Sniper Joe.
        /// </summary>
        State _previousState;

        /// <summary>
        /// The animation set for this Sniper Joe.
        /// </summary>
        Dictionary<State, Animation> _animationSet;

        /// <summary>
        /// An all-purpose timer. Its use differs depending on the current state.
        /// </summary>
        int _timer;

        /// <summary>
        /// The length of time to wait while standing, in ticks.
        /// </summary>
        static int _standingDuration;

        /// <summary>
        /// The length of time to wait after the eye has flashed, in ticks.
        /// </summary>
        static int _eyeFlashDuration;

        /// <summary>
        /// The length of time to stay in the shooting position after shooting
        /// a bullet, in ticks.
        /// </summary>
        static int _shootingDuration;

        /// <summary>
        /// The amount of damage Sniper Joe bullets deal.
        /// </summary>
        static int _bulletDamage;

        /// <summary>
        /// The horizontal speed of Sniper Joe bullets.
        /// </summary>
        static int _bulletSpeed;

        /// <summary>
        /// The sound effect used when the Sniper Joe's eye flashes.
        /// </summary>
        static SoundEffect _eyeFlashSound;

        /// <summary>
        /// The sound effect used when the Sniper Joe shoots.
        /// </summary>
        static SoundEffect _shootSound;

        #endregion

        #region Properties

        /// <summary>
        /// Gets this Sniper Joe's hitbox.
        /// </summary>
        public override Rectangle Hitbox
        {
            get
            {
                if (_currentState == State.Standing || _currentState == State.EyeFlash)
                    return new Rectangle((int)_position.X - 19, (int)_position.Y - 36, 38, 36);
                else
                    return new Rectangle((int)_position.X - 13, (int)_position.Y - 42, 26, 42);
            }
        }

        /// <summary>
        /// Gets the amount of damage Sniper Joes deal.
        /// </summary>
        public override int Damage
        {
            get { return _damage; }
        }

        #endregion

        public SniperJoe(Room room, Vector2 position)
            : base(room, position)
        {
            _health = _maxHealth;

            _currentState = State.Standing;
            _direction = Direction.Left;
            _timer = _standingDuration;
        }

        #region Methods

        /// <summary>
        /// Initialises the Sniper Joe's static fields depending on the game's difficulty.
        /// </summary>
        /// <param name="gameDifficulty">The game's current difficulty setting.</param>
        new public static void Initialise(GameDifficulty gameDifficulty)
        {
            switch (gameDifficulty)
            {
                case GameDifficulty.Easy:
                    _standingDuration = 60;
                    _eyeFlashDuration = 56;
                    _shootingDuration = 120;
                    _bulletSpeed = 5;
                    _bulletDamage = 4;
                    break;
                case GameDifficulty.Hard:
                    _standingDuration = 30;
                    _eyeFlashDuration = 56;
                    _shootingDuration = 40;
                    _bulletSpeed = 9;
                    _bulletDamage = 3;
                    break;
                default:
                    _standingDuration = 60;
                    _eyeFlashDuration = 56;
                    _shootingDuration = 70;
                    _bulletSpeed = 7;
                    _bulletDamage = 4;
                    break;
            }

            _maxHealth = 8;
            _damage = 5;
        }

        /// <summary>
        /// Initialises this Sniper Joe's animations.
        /// </summary>
        protected override void InitialiseAnimations()
        {
            _animationSet = new Dictionary<State, Animation>();

            //Standing.
            Animation standing = new Animation(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(5, 32, 47, 43), new Vector2(-23, -42), 0), 0);
            _animationSet.Add(State.Standing, standing);

            //Eye flash.
            Animation eyeFlash = new Animation(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(59, 32, 47, 43), new Vector2(-23, -42), 2), 0);
            eyeFlash.AddFrame(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(113, 32, 47, 43), new Vector2(-23, -42), 2));
            eyeFlash.AddFrame(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(59, 32, 47, 43), new Vector2(-23, -42), 2));
            eyeFlash.AddFrame(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(5, 32, 47, 43), new Vector2(-23, -42), 0));
            _animationSet.Add(State.EyeFlash, eyeFlash);

            //Prepare to shoot.
            Animation prepareToShoot = new Animation(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(163, 27, 54, 48), new Vector2(-30, -47), 2), 0);
            prepareToShoot.AddFrame(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(217, 27, 56, 48), new Vector2(-32, -47), 2));
            _animationSet.Add(State.PrepareToShoot, prepareToShoot);
            prepareToShoot.OnAnimationEnd += new AnimationEnd(prepareToShoot_OnAnimationEnd);

            //Shooting.
            Animation shooting = new Animation(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(275, 30, 47, 45), new Vector2(-23, -44), 2), 0);
            shooting.AddFrame(new Tuple<Rectangle, Vector2, int>(
                new Rectangle(324, 30, 47, 45), new Vector2(-23, -44), 0));
            _animationSet.Add(State.Shooting, shooting);
        }

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

            _eyeFlashSound = content.Load<SoundEffect>("Sounds\\SniperJoeEye");
            _shootSound = content.Load<SoundEffect>("Sounds\\EnemyBigShoot");
        }

        /// <summary>
        /// Handles the standing state - waits a while before causing the eye to
        /// flash.
        /// </summary>
        private void Standing()
        {
            //If the player has just shot, or the Joe is outside the camera view,
            //wait a little bit longer.
            if ((_room.PlayerOne != null && _room.PlayerOne.IsShooting)
                || !_room.Camera.IsInView(Hitbox))
                _timer = _standingDuration;

            //Decrement the timer; if it is now zero, make the Joe's eye flash, indicating
            //that it is about to shoot.
            if (--_timer <= 0)
            {
                //Set the timer and state.
                _currentState = State.EyeFlash;
                _timer = _eyeFlashDuration;
                
                //Play the sound effect.
                _eyeFlashSound.Play();
            }
        }

        /// <summary>
        /// Handles the eye flash state - waits a while before doing a flashy animation
        /// before shooting a bullet.
        /// </summary>
        private void EyeFlash()
        {
            //Decrement the timer; if it is now zero, prepare to shoot.
            if (--_timer <= 0)
            {
                //Set the state. The animation end event will take care of the rest.
                _currentState = State.PrepareToShoot;
            }
        }

        /// <summary>
        /// Creates a Sniper Joe bullet.
        /// </summary>
        private void CreateBullet()
        {
            //If we're in the camera view, create the bullet.
            if (_room.Camera.IsInView(Hitbox))
            {
                //Calculate the position of the new bullet.
                Vector2 bulletPosition = new Vector2(_position.X + (14 * (int)_direction),
                                                     _position.Y - 27);

                _room.Add(new BigBullet(_room,
                                        bulletPosition,
                                        this,
                                        _bulletDamage,
                                        new Vector2(_bulletSpeed * (int)_direction,
                                                    0),
                                        false));

                //Play a sound effect.
                _shootSound.Play();
            }
        }

        /// <summary>
        /// Handles the shooting state - waits a while before going back to standing.
        /// </summary>
        private void Shooting()
        {
            //Decrement the timer; if it is now zero, go back to standing.
            if (--_timer <= 0)
            {
                //Set the timer and state.
                _currentState = State.Standing;
                _timer = _standingDuration;
            }
        }

        /// <summary>
        /// Updates the reference to the previous state.
        /// </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>
        /// Changes whether or not to deflect bullets based on the current state.
        /// </summary>
        private void ChangeDeflectStatus()
        {
            switch (_currentState)
            {
                case State.Standing:
                case State.EyeFlash:
                    _deflectBullets = true;
                    break;
                default:
                    _deflectBullets = false;
                    break;
            }
        }

        /// <summary>
        /// Updates the Sniper Joe.
        /// </summary>
        /// <param name="gameTime">The current snapshot of time.</param>
        public override void Update(GameTime gameTime)
        {
            //Move according to gravity.
            _speed = EntityHelper.InduceGravity(_speed);
            EntityHelper.VerticalCollision(_room, Hitbox, ref _position, ref _speed);

            switch (_currentState)
            {
                case State.Standing:
                    Standing();
                    break;
                case State.EyeFlash:
                    EyeFlash();
                    break;
                case State.Shooting:
                    Shooting();
                    break;
            }

            ChangeDeflectStatus();

            //Update visuals.
            FacePlayer();
            UpdatePreviousState();
            _animationSet[_currentState].Update();

            base.Update(gameTime);
        }

        /// <summary>
        /// Draws the Sniper Joe to the screen.
        /// </summary>
        /// <param name="spriteBatch">The sprite batch to draw to.</param>
        public override void Draw(SpriteBatch spriteBatch)
        {
            //Account for post-hit invincibility flashing.
            if ((_invincibiltyTimer / 2) % 2 == 0)
            {
                //Get the necessary details from the current animation frame.
                Rectangle currentFrameSource = _animationSet[_currentState].CurrentFrame.Item1;
                Vector2 currentFrameOffset = _animationSet[_currentState].CurrentFrame.Item2;

                //Decide how to flip the Metool based on its direction.
                SpriteEffects directionEffects;
                if (_direction == Direction.Right)
                    directionEffects = SpriteEffects.None;
                else
                    directionEffects = SpriteEffects.FlipHorizontally;

                spriteBatch.Draw(_texture,
                                    _position + new Vector2(currentFrameOffset.X, currentFrameOffset.Y),
                                    currentFrameSource,
                                    Color.White,
                                    0,
                                    Vector2.Zero,
                                    1,
                                    directionEffects,
                                    0.2f);
            }
        }

        #endregion

        #region Event Handles

        /// <summary>
        /// Handle for the event called when the prepare to shoot animation finishes.
        /// </summary>
        /// <param name="sender">The animation that finished.</param>
        /// <param name="e">Event arguments.</param>
        void prepareToShoot_OnAnimationEnd(object sender, EventArgs e)
        {
            //Set the state and timer.
            _currentState = State.Shooting;
            _timer = _shootingDuration;

            //Create the bullet.
            CreateBullet();
        }

        #endregion
    }
}
