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

namespace ECOT.Engine
{
    /// <summary>
    /// The state manager is a component which manages one or more GameState
    /// instances. It maintains a stack of screens, calls their Update and Draw
    /// methods at the appropriate times, and automatically routes input to the
    /// topmost active state.
    /// </summary>
    public class StateManager : DrawableGameComponent
    {
        #region Fields

        List<GameState> screens = new List<GameState>();
        List<GameState> screensToUpdate = new List<GameState>();
        Dictionary<string, GameState> states = new Dictionary<string, GameState>();

        IGraphicsDeviceService graphicsDeviceService;

        SpriteBatch spriteBatch;
        Texture2D blankTexture;

        bool traceEnabled;

        #endregion

        #region Properties


        /// <summary>
        /// Expose access to our Game instance (this is protected in the
        /// default GameComponent, but we want to make it public).
        /// </summary>
        new public Game Game
        {
            get { return base.Game; }
        }


        /// <summary>
        /// Expose access to our graphics device (this is protected in the
        /// default DrawableGameComponent, but we want to make it public).
        /// </summary>
        new public GraphicsDevice GraphicsDevice
        {
            get { return base.GraphicsDevice; }
        }


        /// <summary>
        /// A default SpriteBatch shared by all the screens. This saves
        /// each state having to bother creating their own local instance.
        /// </summary>
        public SpriteBatch SpriteBatch
        {
            get { return spriteBatch; }
        }


        /// <summary>
        /// If true, the manager prints out a list of all the screens
        /// each time it is updated. This can be useful for making sure
        /// everything is being added and removed at the right times.
        /// </summary>
        public bool TraceEnabled
        {
            get { return traceEnabled; }
            set { traceEnabled = value; }
        }


        public GameState this[string name]
        {
            get { return this.states[name]; }
        }


        #endregion

        #region Initialization

        /// <summary>
        /// Constructs a new state manager component.
        /// </summary>
        public StateManager(Game game)
            : base(game)
        {
            game.IsFixedTimeStep = false;

            graphicsDeviceService = (IGraphicsDeviceService)game.Services.GetService(
                                                        typeof(IGraphicsDeviceService));

            if (graphicsDeviceService == null)
                throw new InvalidOperationException("No graphics device service.");
        }


        /// <summary>
        /// Load your graphics content.
        /// </summary>
        /// 
        protected override void LoadContent()
        {
            //// Load content belonging to the state manager.
            //if (loadAllContent)
            //{
            //    spriteBatch = new SpriteBatch(GraphicsDevice);
            //    blankTexture = Kernel.Instance.Medias.Get<Texture2D>(@"Bootstrap\Blank");
            //}

            // Tell each of the screens to load their content.
            foreach (GameState screen in screens)
            {
                screen.LoadGraphicsContent(true);
            }
            base.LoadContent();
        }

        //protected override void LoadGraphicsContent(bool loadAllContent)
        //{
        //    // Load content belonging to the state manager.
        //    if (loadAllContent)
        //    {
        //        spriteBatch = new SpriteBatch(GraphicsDevice);
        //        blankTexture = Kernel.Instance.Medias.Get<Texture2D>(@"Bootstrap\Blank");
        //    }

        //    // Tell each of the screens to load their content.
        //    foreach (GameState screen in screens)
        //    {
        //        screen.LoadGraphicsContent(loadAllContent);
        //    }
        //}


        /// <summary>
        /// Unload your graphics content.
        /// </summary>
        protected override void UnloadGraphicsContent(bool unloadAllContent)
        {
            // Tell each of the screens to unload their content.
            foreach (GameState screen in screens)
            {
                screen.UnloadGraphicsContent(unloadAllContent);
            }
        }


        #endregion

        #region Update and Draw

        /// <summary>
        /// Allows each state to run logic.
        /// </summary>
        public override void Update(GameTime gameTime)
        {
            // Read the keyboard and gamepad.
            Kernel.Instance.Input.Update();

            // Make a copy of the master state list, to avoid confusion if
            // the process of updating one state adds or removes others.
            screensToUpdate.Clear();

            foreach (GameState screen in screens)
                screensToUpdate.Add(screen);

            bool otherScreenHasFocus = !Game.IsActive;
            bool coveredByOtherScreen = false;

            // Loop as long as there are screens waiting to be updated.
            while (screensToUpdate.Count > 0)
            {
                // Pop the topmost state off the waiting list.
                GameState screen = screensToUpdate[screensToUpdate.Count - 1];

                screensToUpdate.RemoveAt(screensToUpdate.Count - 1);

                // Update the state.
                screen.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);

                if (screen.StateStatus == StateStatus.Active)
                {
                    // If this is the first active state we came across,
                    // give it a chance to handle input.
                    if (!otherScreenHasFocus)
                    {
                        screen.HandleInput(Kernel.Instance.Input);
                        otherScreenHasFocus = true;
                    }

                    // If this is an active non-popup, inform any subsequent
                    // screens that they are covered by it.
                    if (!screen.IsPopup)
                        coveredByOtherScreen = true;
                }
            }

            // Print debug trace?
            if (traceEnabled)
                TraceScreens();
        }


        /// <summary>
        /// Prints a list of all the screens, for debugging.
        /// </summary>
        void TraceScreens()
        {
            List<string> screenNames = new List<string>();

            foreach (GameState screen in screens)
                screenNames.Add(screen.GetType().Name);

            Trace.WriteLine(string.Join(", ", screenNames.ToArray()));

        }


        /// <summary>
        /// Tells each state to draw itself.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            foreach (GameState screen in screens)
            {
                if (screen.StateStatus == StateStatus.Hidden)
                    continue;

                screen.Draw(gameTime);
            }
        }


        #endregion

        #region Public Methods

        /// <summary>
        /// Registers a new game state to the state manager.
        /// </summary>
        public void SwitchTo(string newState, string oldState, params object[] args)
        {
            this.states[newState].StateManager = this;

            if (oldState != null && oldState != string.Empty)
                this.states[oldState].Exit();
            this.states[newState].SetParameters(args);
            this.states[newState].LoadGraphicsContent(true);

            /// HACK
            if (!screens.Contains(this.states[newState]))
                screens.Add(this.states[newState]);
        }

        public void SwitchTo(string newState, string oldState)
        {
            this.SwitchTo(newState, oldState, null);
        }

        public void RegisterState(string name, GameState state)
        {
            this.states[name] = state;
        }

        public void UnregisterState(string name)
        {
            this.states.Remove(name);
        }

        /// <summary>
        /// Removes a state from the state manager. You should normally
        /// use GameState.ExitScreen instead of calling this directly, so
        /// the state can gradually transition off rather than just being
        /// instantly removed.
        /// </summary>
        public void RemoveState(GameState screen)
        {
            screen.UnloadGraphicsContent(true);

            screens.Remove(screen);
            screensToUpdate.Remove(screen);
        }

        /// <summary>
        /// Expose an array holding all the screens. We return a copy rather
        /// than the real master list, because screens should only ever be added
        /// or removed using the RegisterState and RemoveState methods.
        /// </summary>
        public GameState[] GetCurrentStates()
        {
            return screens.ToArray();
        }

        public bool IsRegistered(Type type)
        {
            foreach (GameState state in this.screens)
                if (state.GetType().Equals(type))
                    return true;
            return false;
        }

        /// <summary>
        /// Helper draws a translucent black fullscreen sprite, used for fading
        /// screens in and out, and for darkening the background behind popups.
        /// </summary>
        public void FadeBackBufferToBlack(int alpha)
        {
            Viewport viewport = GraphicsDevice.Viewport;

            spriteBatch.Begin();

            spriteBatch.Draw(blankTexture,
                             new Rectangle(0, 0, viewport.Width, viewport.Height),
                             new Color(0, 0, 0, (byte)alpha));

            spriteBatch.End();
        }

        #endregion
    }
}
