#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
//using Xclna.Xna.Animation;
using System.Diagnostics;
#endregion

namespace IrelandsCall
{

    public enum GamePadButton
    {
        A,
        B,
        X,
        Y,
        LeftShoulder,
        LeftStick,
        RightShoulder,
        RightStick,
        Back,
        Start,
        DPadUp,
        DPadDown,
        DPadLeft,
        DPadRight
    }
    public static class XnaInputHelper
    {
        class PlayerControllerState
        {
            public TimeSpan PreviousGamePadButtonStateTimeSpan = TimeSpan.MaxValue;
            public bool A = false;
            public bool B = false;
            public bool X = false;
            public bool Y = false;
            public bool LeftShoulder = false;
            public bool LeftStick = false;
            public bool RightShoulder = false;
            public bool RightStick = false;
            public bool Back = false;
            public bool Start = false;
            public bool DPadUp = false;
            public bool DPadDown = false;
            public bool DPadRight = false;
            public bool DPadLeft = false;


            public bool APrev = false;
            public bool BPrev = false;
            public bool XPrev = false;
            public bool YPrev = false;
            public bool LeftShoulderPrev = false;
            public bool LeftStickPrev = false;
            public bool RightShoulderPrev = false;
            public bool RightStickPrev = false;
            public bool BackPrev = false;
            public bool StartPrev = false;
            public bool DPadUpPrev = false;
            public bool DPadDownPrev = false;
            public bool DPadRightPrev = false;
            public bool DPadLeftPrev = false;
        }

        private static PlayerControllerState[] ControllerState = new PlayerControllerState[4];

        static XnaInputHelper()
        {
            for (int i = 0; i < 4; i++) ControllerState[i] = new PlayerControllerState();
        }

        public static bool WasButtonPressed(GameTime gameTime, PlayerIndex player, GamePadButton gamePadButton)
        {
            PlayerControllerState cs;
            switch (player)
            {
                case PlayerIndex.One: cs = ControllerState[0]; break;
                case PlayerIndex.Two: cs = ControllerState[1]; break;
                case PlayerIndex.Three: cs = ControllerState[2]; break;
                case PlayerIndex.Four: cs = ControllerState[3]; break;
                default: cs = ControllerState[0]; break;
            }
            if (gameTime.TotalGameTime != cs.PreviousGamePadButtonStateTimeSpan)
            {
                GamePadState gps = GamePad.GetState(player);
                GamePadButtons bs = gps.Buttons;
                GamePadDPad gpdp = gps.DPad;
                KeyboardState ks = Keyboard.GetState();
                cs.APrev = cs.A;
                cs.BPrev = cs.B;
                cs.XPrev = cs.X;
                cs.YPrev = cs.Y;
                cs.LeftShoulderPrev = cs.LeftShoulder;
                cs.LeftStickPrev = cs.LeftStick;
                cs.RightShoulderPrev = cs.RightShoulder;
                cs.RightStickPrev = cs.RightStick;
                cs.BackPrev = cs.Back;
                cs.StartPrev = cs.Start;
                cs.DPadRightPrev = cs.DPadRight;
                cs.DPadLeftPrev = cs.DPadLeft;
                cs.DPadUpPrev = cs.DPadUp;
                cs.DPadDownPrev = cs.DPadDown;

                cs.A = (bs.A == ButtonState.Pressed || ks.IsKeyDown(Keys.A));
                cs.B = (bs.B == ButtonState.Pressed || ks.IsKeyDown(Keys.B));
                cs.X = (bs.X == ButtonState.Pressed || ks.IsKeyDown(Keys.X));
                cs.Y = (bs.Y == ButtonState.Pressed || ks.IsKeyDown(Keys.Y));
                cs.LeftShoulder = (bs.LeftShoulder == ButtonState.Pressed || ks.IsKeyDown(Keys.LeftShift));
                cs.LeftStick = (bs.LeftStick == ButtonState.Pressed || ks.IsKeyDown(Keys.LeftControl));
                cs.RightShoulder = (bs.RightShoulder == ButtonState.Pressed || ks.IsKeyDown(Keys.RightShift));
                cs.RightStick = (bs.RightStick == ButtonState.Pressed || ks.IsKeyDown(Keys.RightControl));
                cs.Back = (bs.Back == ButtonState.Pressed || ks.IsKeyDown(Keys.Back));
                cs.Start = (bs.Start == ButtonState.Pressed || ks.IsKeyDown(Keys.Enter));
                cs.DPadDown = (gpdp.Down == ButtonState.Pressed);
                cs.DPadUp = (gpdp.Up == ButtonState.Pressed);
                cs.DPadLeft = (gpdp.Left == ButtonState.Pressed);
                cs.DPadRight = (gpdp.Right == ButtonState.Pressed);

                cs.PreviousGamePadButtonStateTimeSpan = gameTime.TotalGameTime;
            }
            switch (gamePadButton)
            {
                case GamePadButton.A: return cs.A && !cs.APrev;
                case GamePadButton.B: return cs.B && !cs.BPrev;
                case GamePadButton.X: return cs.X && !cs.XPrev;
                case GamePadButton.Y: return cs.Y && !cs.YPrev;
                case GamePadButton.LeftShoulder: return cs.LeftShoulder && !cs.LeftShoulderPrev;
                case GamePadButton.LeftStick: return cs.LeftStick && !cs.LeftStickPrev;
                case GamePadButton.RightShoulder: return cs.RightShoulder && !cs.RightShoulderPrev;
                case GamePadButton.RightStick: return cs.RightStick && !cs.RightStickPrev;
                case GamePadButton.Back: return cs.Back && !cs.BackPrev;
                case GamePadButton.Start: return cs.Start && !cs.StartPrev;
                case GamePadButton.DPadDown: return cs.DPadDown && !cs.DPadDownPrev;
                case GamePadButton.DPadUp: return cs.DPadUp && !cs.DPadUpPrev;
                case GamePadButton.DPadLeft: return cs.DPadLeft && !cs.DPadLeftPrev;
                case GamePadButton.DPadRight: return cs.DPadRight && !cs.DPadRightPrev;
            }
            return false;
        }

        public static bool IsButtonDown(GameTime gameTime, PlayerIndex player, GamePadButton gamePadButton)
        {
            PlayerControllerState cs;
            switch (player)
            {
                case PlayerIndex.One: cs = ControllerState[0]; break;
                case PlayerIndex.Two: cs = ControllerState[1]; break;
                case PlayerIndex.Three: cs = ControllerState[2]; break;
                case PlayerIndex.Four: cs = ControllerState[3]; break;
                default: cs = ControllerState[0]; break;
            }
            if (gameTime.TotalGameTime != cs.PreviousGamePadButtonStateTimeSpan)
            {
                GamePadState gps = GamePad.GetState(player);
                GamePadButtons bs = gps.Buttons;
                GamePadDPad gpdp = gps.DPad;
                KeyboardState ks = Keyboard.GetState();
                cs.APrev = cs.A;
                cs.BPrev = cs.B;
                cs.XPrev = cs.X;
                cs.YPrev = cs.Y;
                cs.LeftShoulderPrev = cs.LeftShoulder;
                cs.LeftStickPrev = cs.LeftStick;
                cs.RightShoulderPrev = cs.RightShoulder;
                cs.RightStickPrev = cs.RightStick;
                cs.BackPrev = cs.Back;
                cs.StartPrev = cs.Start;
                cs.DPadRightPrev = cs.DPadRight;
                cs.DPadLeftPrev = cs.DPadLeft;
                cs.DPadUpPrev = cs.DPadUp;
                cs.DPadDownPrev = cs.DPadDown;

                cs.A = (bs.A == ButtonState.Pressed || ks.IsKeyDown(Keys.A));
                cs.B = (bs.B == ButtonState.Pressed || ks.IsKeyDown(Keys.B));
                cs.X = (bs.X == ButtonState.Pressed || ks.IsKeyDown(Keys.X));
                cs.Y = (bs.Y == ButtonState.Pressed || ks.IsKeyDown(Keys.Y));
                cs.LeftShoulder = (bs.LeftShoulder == ButtonState.Pressed || ks.IsKeyDown(Keys.LeftShift));
                cs.LeftStick = (bs.LeftStick == ButtonState.Pressed || ks.IsKeyDown(Keys.LeftControl));
                cs.RightShoulder = (bs.RightShoulder == ButtonState.Pressed || ks.IsKeyDown(Keys.RightShift));
                cs.RightStick = (bs.RightStick == ButtonState.Pressed || ks.IsKeyDown(Keys.RightControl));
                cs.Back = (bs.Back == ButtonState.Pressed || ks.IsKeyDown(Keys.Back));
                cs.Start = (bs.Start == ButtonState.Pressed /*|| ks.IsKeyDown(Keys.Enter)*/);
                cs.DPadDown = (gpdp.Down == ButtonState.Pressed);
                cs.DPadUp = (gpdp.Up == ButtonState.Pressed);
                cs.DPadLeft = (gpdp.Left == ButtonState.Pressed);
                cs.DPadRight = (gpdp.Right == ButtonState.Pressed);

                cs.PreviousGamePadButtonStateTimeSpan = gameTime.TotalGameTime;
            }
            switch (gamePadButton)
            {
                case GamePadButton.A: return cs.A;
                case GamePadButton.B: return cs.B;
                case GamePadButton.X: return cs.X;
                case GamePadButton.Y: return cs.Y;
                case GamePadButton.LeftShoulder: return cs.LeftShoulder;
                case GamePadButton.LeftStick: return cs.LeftStick;
                case GamePadButton.RightShoulder: return cs.RightShoulder;
                case GamePadButton.RightStick: return cs.RightStick;
                case GamePadButton.Back: return cs.Back;
                case GamePadButton.Start: return cs.Start;
                case GamePadButton.DPadDown: return cs.DPadDown;
                case GamePadButton.DPadUp: return cs.DPadUp;
                case GamePadButton.DPadLeft: return cs.DPadLeft;
                case GamePadButton.DPadRight: return cs.DPadRight;
            }
            return false;
        }
    }





    /// <summary>
    /// Performs update-rate-independent exponential blends between two values.  
    /// The BlendRate property can be used to adjust the rate at which the class blends new input values 
    /// into the old value.
    /// </summary>
    public class Vector3Blender
    {
        /// <summary>
        /// For a blend rate of 0, there is no blend -- the output will immediately assume the value of any input y.
        /// For a blend rate of 1, the output does not change from the previous input.
        /// </summary>
        public Vector3Blender(Vector3 initialValue, GameTime initialTime, float blendFactor) { _Target = initialValue; _Current = initialValue; _CurrentTotalGameTime = initialTime.TotalGameTime; BlendFactor = blendFactor; }
        private Vector3 _Target;
        private Vector3 _Current;
        private TimeSpan _CurrentTotalGameTime;
        private float _RawBlendFactor;
        private double _CachedExponentialBlendFactor;
        public Vector3 Target
        {
            get { return _Target; }
            set { _Target = value; }
        }
        /// <summary>
        /// Gets or sets the rate at which the old value blends into the new value.  
        /// The blend factor must range between 0 and 1.
        /// If the CurrentValue was x at time t_0, and the value of all subseqent inputs after time t_0 is y,
        /// then at time (t_0 + 1 second), CurrentValue will have blended exponentially to k*x + (1-k)*y
        /// where k is the blend rate parameter.
        /// For a blend rate of 0, there is no blend -- the output will immediately assume the value of any input y.
        /// For a blend rate of 1, the output does not change from the previous input.
        /// </summary>
        public float BlendFactor
        {
            get { return _RawBlendFactor; }
            set
            {
                if (value < 0.0f || value > 1.0f) throw new ApplicationException("Blend factor outside range of 0.0..1.0");
                _RawBlendFactor = value;
                _CachedExponentialBlendFactor = -Math.Log(_RawBlendFactor);
            }
        }

        public void SetCurrent(Vector3 value, GameTime gameTime)
        {
            _Current = value; _CurrentTotalGameTime = gameTime.TotalGameTime;
        }
        public Vector3 GetCurrent(GameTime gameTime)
        {
            if (_CurrentTotalGameTime == gameTime.TotalGameTime) return _Current;
            UpdateCurrent(gameTime);
            return _Current;
        }

        private void UpdateCurrent(GameTime gameTime)
        {
            double k = _CachedExponentialBlendFactor;

            TimeSpan deltaT = gameTime.TotalGameTime - _CurrentTotalGameTime;

            double decayFactor = Math.Exp(-deltaT.TotalSeconds * k);

            _Current = new Vector3(
                (float)(_Current.X * decayFactor + _Target.X * (1 - decayFactor)),
                (float)(_Current.Y * decayFactor + _Target.Y * (1 - decayFactor)),
                (float)(_Current.Z * decayFactor + _Target.Z * (1 - decayFactor))
            );
            _CurrentTotalGameTime = gameTime.TotalGameTime;

        }
    }


    public class FloatBlender
    {
        /// <summary>
        /// For a blend rate of 0, there is no blend -- the output will immediately assume the value of any input y.
        /// For a blend rate of 1, the output does not change from the previous input.
        /// </summary>
        public FloatBlender(float initialValue, GameTime initialTime, float blendFactor) { _Target = initialValue; _Current = initialValue; _CurrentTotalGameTime = initialTime.TotalGameTime; BlendFactor = blendFactor; }
        private float _Target;
        private float _Current;
        private TimeSpan _CurrentTotalGameTime;
        private float _RawBlendFactor;
        private double _CachedExponentialBlendFactor;
        public float Target
        {
            get { return _Target; }
            set { _Target = value; }
        }
        /// <summary>
        /// Gets or sets the rate at which the old value blends into the new value.  
        /// The blend factor must range between 0 and 1.
        /// If the CurrentValue was x at time t_0, and the value of all subseqent inputs after time t_0 is y,
        /// then at time (t_0 + 1 second), CurrentValue will have blended exponentially to k*x + (1-k)*y
        /// where k is the blend rate parameter.
        /// For a blend rate of 0, there is no blend -- the output will immediately assume the value of any input y.
        /// For a blend rate of 1, the output does not change from the previous input.
        /// </summary>
        public float BlendFactor
        {
            get { return _RawBlendFactor; }
            set
            {
                if (value < 0.0f || value > 1.0f) throw new ApplicationException("Blend factor outside range of 0.0..1.0");
                _RawBlendFactor = value;
                _CachedExponentialBlendFactor = -Math.Log(_RawBlendFactor);
            }
        }

        public void SetCurrent(float value, GameTime gameTime)
        {
            _Current = value; _CurrentTotalGameTime = gameTime.TotalGameTime;
        }
        public float GetCurrent(GameTime gameTime)
        {
            if (_CurrentTotalGameTime == gameTime.TotalGameTime) return _Current;
            UpdateCurrent(gameTime);
            return _Current;
        }

        private void UpdateCurrent(GameTime gameTime)
        {
            double k = _CachedExponentialBlendFactor;

            TimeSpan deltaT = gameTime.TotalGameTime - _CurrentTotalGameTime;

            double decayFactor = Math.Exp(-deltaT.TotalSeconds * k);

            _Current = (float)(_Current * decayFactor + _Target * (1 - decayFactor));
            _CurrentTotalGameTime = gameTime.TotalGameTime;

        }
    }

    public class QuaternionBlender
    {
        /// <summary>
        /// For a blend rate of 0, there is no blend -- the output will immediately assume the value of any input y.
        /// For a blend rate of 1, the output does not change from the previous input.
        /// </summary>
        public QuaternionBlender(Quaternion initialValue, GameTime initialTime, float blendFactor) { _Target = initialValue; _Current = initialValue; _CurrentTotalGameTime = initialTime.TotalGameTime; BlendFactor = blendFactor; }
        private Quaternion _Target;
        private Quaternion _Current;
        private TimeSpan _CurrentTotalGameTime;
        private float _RawBlendFactor;
        private double _CachedExponentialBlendFactor;
        public Quaternion Target
        {
            get { return _Target; }
            set { _Target = value; }
        }
        /// <summary>
        /// Gets or sets the rate at which the old value blends into the new value.  
        /// The blend factor must range between 0 and 1.
        /// If the CurrentValue was x at time t_0, and the value of all subseqent inputs after time t_0 is y,
        /// then at time (t_0 + 1 second), CurrentValue will have blended exponentially to k*x + (1-k)*y
        /// where k is the blend rate parameter.
        /// For a blend rate of 0, there is no blend -- the output will immediately assume the value of any input y.
        /// For a blend rate of 1, the output does not change from the previous input.
        /// </summary>
        public float BlendFactor
        {
            get { return _RawBlendFactor; }
            set
            {
                if (value < 0.0f || value > 1.0f) throw new ApplicationException("Blend factor outside range of 0.0..1.0");
                _RawBlendFactor = value;
                _CachedExponentialBlendFactor = -Math.Log(_RawBlendFactor);
            }
        }

        public void SetCurrent(Quaternion value, GameTime gameTime)
        {
            _Current = value; _CurrentTotalGameTime = gameTime.TotalGameTime;
        }
        public Quaternion GetCurrent(GameTime gameTime)
        {
            if (_CurrentTotalGameTime == gameTime.TotalGameTime) return _Current;
            UpdateCurrent(gameTime);
            return _Current;
        }

        private void UpdateCurrent(GameTime gameTime)
        {
            double k = _CachedExponentialBlendFactor;

            TimeSpan deltaT = gameTime.TotalGameTime - _CurrentTotalGameTime;

            double decayFactor = Math.Exp(-deltaT.TotalSeconds * k);

            _Current = Quaternion.Lerp(_Current, _Target, (float)(1-decayFactor));
            _CurrentTotalGameTime = gameTime.TotalGameTime;
        }
    }

    public class QuaterionUtil
    {
        /// <summary>
        /// Creates the quaternion that describes the rotation from v1 to v2.
        /// </summary>
        public static Quaternion CreateFromFromTo(Vector3 v1, Vector3 v2)
        {
            float d = Vector3.Dot(v1, v2);
            Vector3 axis = Vector3.Cross(v1, v2);
            float qw = (float)Math.Sqrt(v1.LengthSquared() * v2.LengthSquared()) + d;
            if (qw < 0.0001f) // Vectors are 180 degrees apart
            {
                Quaternion q = new Quaternion(-v1.Z, v1.Y, v1.X, 0);
                q.Normalize();
                return q;
            }
            else
            {
                Quaternion qq = new Quaternion(axis.X, axis.Y, axis.Z, qw);
                qq.Normalize();
                return qq;
            }
        }
    }

}