using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using SlatedGameToolkit.Framework.Graphics.Window;
using SlatedGameToolkit.Framework.StateSystem.States;
using SlatedGameToolkit.Framework.Utilities;
using SlatedGameToolkit.Framework.Graphics;
using SlatedGameToolkit.Framework.Graphics.OpenGL;
using SlatedGameToolkit.Framework.Logging;

namespace SlatedGameToolkit.Framework.StateSystem
{
    public sealed class StateManager : IDisposable {
        public Thread thread;
        public Color backgroundColour;
        private IState currentState;
        private IState nextState;
        private Dictionary<string, IState> states;

        /// <summary>
        /// Instantiates a game state manager with an initial state, and a set of states to be added at the start.
        /// States can later be added or removed.
        /// None of the parameters can be null, and the initial state must exist in initial set of states.
        /// </summary>
        /// <param name="initialState">The name of the initial state.</param>
        /// <param name="states">The initial set of game states to be added.</param>
        internal StateManager() {
            backgroundColour = Color.Orange;
            
            this.states = new Dictionary<string, IState>();
        }

        internal void Initialize(IState initialState) {
            if (initialState == null) throw new ArgumentNullException("initialState");
            thread = Thread.CurrentThread;
            currentState = initialState;
            addState(initialState);
        }

        internal void update(double timeStep) {
            if (nextState != null) {
                if (currentState.Deactivate() & nextState.Activate()) {
                    currentState = nextState;
                    nextState = null;
                }
            }
            currentState.Update(timeStep);
        }
        
        internal void render(double delta) {
            WindowContext windowContext = WindowContextsManager.CurrentWindowContext;
            windowContext.Context.ClearColor(backgroundColour.RedAsFloat(), backgroundColour.GreenAsFloat(), backgroundColour.BlueAsFloat(), 1f);
            windowContext.Context.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            currentState.Render(delta);
            windowContext.SwapBuffer();
        }

        /// <summary>
        /// Begins a state change. The current stage will be notified, and so will the state that is being changed to.
        /// </summary>
        /// <param name="name">The name of the state that we are turning to.</param>
        public void changeState(string name) {
            if (thread != Thread.CurrentThread) throw new ThreadStateException("State cannot be changed from a different thread.");
            if (!states.TryGetValue(name, out nextState)) throw new ArgumentException("The requested state to change to does not exist in this manager.");
        }

        /// <summary>
        /// Adds the given states to this manager under each of their own respective names.
        /// Initializes all the states.
        /// If there are two states of the same name, the first state in the array is added and the second is discarded.
        /// The discarded state will not be initialized and dispose will not be called on it.
        /// </summary>
        /// <param name="states">The states to add.</param>
        /// <returns>True if all the states were unique, and false if there were repetitions determined by name.</returns>
        public bool addStates(IState[] states) {
            if (states == null || states.Length == 0) throw new ArgumentException("The array of states cannot be null, and cannot be of size 0");
            bool unique = true;
            foreach (IState state in states)
            {
                unique = unique && addState(state);
            }
            return unique;
        }

        /// <summary>
        /// Adds the given state to this manager under it's own name.
        /// Initializes the state as well.
        /// Will not iniitialize the state if this method returns false.
        /// </summary>
        /// <param name="state">The state to be added to this manager.</param>
        /// <returns>False if a state of this name has already been registered.</returns>
        public bool addState(IState state) {
            if (thread != Thread.CurrentThread) throw new ThreadStateException("Cannot add a state from a different thread.");
            try {
                Logger.Log("Adding state: " + state.getName(), LogLevel.WARNING);
                this.states.Add(state.getName(), state);
            } catch (ArgumentException) {
                return false;
            }
            state.Initialize(this);
            return true;
        }

        /// <summary>
        /// Removes the state given the name of the state.
        /// If the state doesn't exist, nothing happens.
        /// Will call dispose on the state.
        /// </summary>
        /// <param name="name">The name of the state.</param>
        /// <returns>False if the state is being used, or the state doesn't exist.</returns>
        public bool removeState(string name) {
            if (thread != Thread.CurrentThread) throw new ThreadStateException("Cannot remove a state from a different thread.");
            if (states[name] == currentState) return false;
            IState state = states[name];
            Logger.Log("Removing state: " + name, LogLevel.DEBUG);
            try {
                state.Deinitialize();
            } catch (Exception e) {
                Logger.Log(e.ToString(), LogLevel.WARNING);
                Logger.Log("Failed to deinitialize state: " + state.getName(), LogLevel.WARNING);
            }
            return states.Remove(name);
        }

        /// <summary>
        /// Removes all the states in this state manager except for the current one.
        /// Disposes of the removed states.
        /// </summary>
        public void removeAllStates() {
            Logger.Log("Beginning to remove all states...", LogLevel.DEBUG);
            foreach (String state in this.states.Keys)
            {
                removeState(state);
            }
            Logger.Log("Completed removing all states...", LogLevel.DEBUG);
        }

        public void Dispose()
        {
            if (currentState == null) return;
            currentState = null;
            removeAllStates();
        }
    }
}