using System;
using System.Threading;
using System.Collections;
using System.Windows.Forms;
using Microsoft.DirectX.DirectInput;
using Icarus.Input.ButtonEvents;
using Icarus.Input.MovementEvents;

namespace Icarus.Input {
    /// <summary>
    /// Summary description for UserInputEngine.
    /// </summary>
    public class InputEngine {

        #region Instance Variables
        /// <summary>
        /// General settings for the Input Engine.
        /// </summary>
        private InputSettings settings = InputSettings.Instance;
        /// <summary>
        /// Control Form for devices to be attached to.
        /// </summary>
        private Control controller = null;
        /// <summary>
        /// Keyboard input device.
        /// </summary>
        private Device keyboard = null;
        /// <summary>
        /// Mouse input device.
        /// </summary>
        private Device mouse = null;
        /// <summary>
        /// Joystick Input device.
        /// </summary>
        private Device joystick = null;
        /// <summary>
        /// Queue of keyboard events that have yet been processed by the system.
        /// </summary>
        private ArrayList keyboardEventQueue = new ArrayList();
        /// <summary>
        /// Queue of mouse events that have yet been processed by the system.
        /// </summary>
        private ArrayList mouseEventQueue = new ArrayList();
        /// <summary>
        /// Queue of joystick events that have yet been processed by the system.
        /// </summary>
        private ArrayList joystickEventQueue = new ArrayList();        
        /// <summary>
        /// Array list of all key bindings for the keyboard.
        /// </summary>
        private ArrayList boundKeys = new ArrayList();
        /// <summary>
        /// Array list of all button bindings for the mouse.
        /// </summary>
        private ArrayList boundMouseButtons = new ArrayList();
        /// <summary>
        /// Array list of all button bindings for the joystick.
        /// </summary>
        private ArrayList boundJoystickButtons = new ArrayList();
        /// <summary>
        /// Thread that constantly polls for input received.
        /// </summary>
        private Thread listenLoopThread = null;
        /// <summary>
        /// Sorts and creates button and movement events and stores them in a queue.
        /// </summary>
        private QueueEventSorter sorter = new QueueEventSorter();
        #endregion

        #region Singleton Stuff
        /// <summary>
        /// The singleton class instance of this class.
        /// </summary>
        private static InputEngine instance = new InputEngine();
    
        /// <summary>
        /// Private singleton constructor.
        /// </summary>
        private InputEngine() {        
            boundKeys = new ArrayList();
            boundMouseButtons = new ArrayList();
            boundJoystickButtons = new ArrayList();
        }

        /// <summary>
        /// Accessor/Modifier for the singleton class instance.
        /// </summary>
        public static InputEngine Instance {
            get { return instance; }
        }
        #endregion

        #region Initialization
        /// <summary>
        /// Initializes and configures the devices, based on the given Controller.
        /// </summary>
        /// <param name="owner">The Controller Form that owns these devices.</param>
        public void Initialize(Control owner) {      
            controller = owner;
            InitDevices();
            ConfigureDevices();
        }

        /// <summary>
        /// Initializes the devices from the system.
        /// </summary>
        private void InitDevices() {      
            keyboard = new Device(SystemGuid.Keyboard);
            if (keyboard == null) {
                Console.WriteLine(" -- No keyboard found.");
            } 

            mouse = new Device(SystemGuid.Mouse);
            if(mouse == null) {
                Console.WriteLine(" -- No mouse found.");
            } 

            foreach (DeviceInstance di in Manager.GetDevices(DeviceClass.GameControl, EnumDevicesFlags.AttachedOnly)) {
                joystick = new Device(di.InstanceGuid);        
                break;
            } 
            if(joystick == null) {
                Console.WriteLine(" -- No joystick found.");
            }
        }

        /// <summary>
        /// Configures and acquires the devices for use.
        /// </summary>
        private void ConfigureDevices() {
            if (keyboard != null) {
                //keyboard.SetCooperativeLevel(controller, CooperativeLevelFlags.Foreground);
                keyboard.Acquire();        
            }
      
            if (mouse != null) {
                //mouse.Properties.AxisModeAbsolute = true;
                //mouse.SetCooperativeLevel(controller, CooperativeLevelFlags.Foreground);
                mouse.Acquire();
            }

            if (joystick != null) {
                //Set joystick axis ranges.
                foreach (DeviceObjectInstance doi in joystick.Objects) {
                    if ((doi.ObjectId & (int) DeviceObjectTypeFlags.Axis) != 0) {
                        joystick.Properties.SetRange(ParameterHow.ById, doi.ObjectId, new InputRange(-5000,5000));
                    }
                }
                //joystick.Properties.AxisModeAbsolute = true;
                //joystick.SetCooperativeLevel(controller, CooperativeLevelFlags.Foreground);
                joystick.Acquire();
            }       
        }
        #endregion

        #region Accessors/Modifiers
        /// <summary>
        /// The current queue of inputs from the keyboard.
        /// </summary>
        public ArrayList KeyboardEventQueue {
            get {                
                ArrayList eventQueue = (ArrayList) keyboardEventQueue.Clone();
                keyboardEventQueue.Clear();
                return eventQueue;
            }
        }

        /// <summary>
        /// The current queue of inputs from the mouse.
        /// </summary>
        public ArrayList MouseEventQueue {
            get { 
                ArrayList eventQueue = (ArrayList) mouseEventQueue.Clone();
                mouseEventQueue.Clear();
                return eventQueue; 
            }
        }

        /// <summary>
        /// The current queue of inputs from the joystick.
        /// </summary>
        public ArrayList JoystickEventQueue {
            get { 
                ArrayList eventQueue = (ArrayList) joystickEventQueue.Clone();
                joystickEventQueue.Clear();
                return eventQueue; 
            }
        }
        #endregion

        #region Device Capturing
        /// <summary>
        /// Updates all devices.
        /// </summary>
        private void UpdateDevices() {
            UpdateKeyboard();
            UpdateMouse();
            UpdateJoystick();
            
            int MAXSIZE;
            MAXSIZE = settings.MaxKeyboardEvents;
            if (keyboardEventQueue.Count >= MAXSIZE) {
                keyboardEventQueue = keyboardEventQueue.GetRange(0, MAXSIZE);
            }            
            MAXSIZE = settings.MaxMouseEvents;
            if (mouseEventQueue.Count >= MAXSIZE) {
                mouseEventQueue = mouseEventQueue.GetRange(0, MAXSIZE);
            }
            MAXSIZE = settings.MaxJoystickEvents;
            if (joystickEventQueue.Count >= MAXSIZE) {
                joystickEventQueue = joystickEventQueue.GetRange(0, MAXSIZE);
            }
        }

        /// <summary>
        /// Updates the keyboard event queue based on the actual input to the keyboard.
        /// </summary>
        private void UpdateKeyboard() {
            if (keyboard != null) {
                try {
                    keyboardEventQueue = sorter.AddKeyboardEvents(keyboardEventQueue, keyboard.GetPressedKeys());
                } catch (NotAcquiredException) {
                }
            }
        }

        /// <summary>
        /// Updates the mouse event queue based on the actual input to the mouse.
        /// </summary>
        private void UpdateMouse() {
            if (mouse != null) {
                try {
                    MouseState state = mouse.CurrentMouseState;
                    mouseEventQueue = sorter.AddMouseMovementEvent(mouseEventQueue, state.X, state.Y, state.Z);
                    mouseEventQueue = sorter.AddMouseEvents(mouseEventQueue, state.GetMouseButtons());
                } catch (NotAcquiredException) {
                }
            }
        }

        /// <summary>
        /// Updates the joystick event queue based on the actual input to the joystick.
        /// </summary>
        private void UpdateJoystick() {
            if (joystick != null) {
                try {
                    JoystickState state = joystick.CurrentJoystickState;
                    joystickEventQueue = sorter.AddJoystickMovementEvent(joystickEventQueue, state.X, state.Y, state.Z);
                    joystickEventQueue = sorter.AddJoystickEvents(joystickEventQueue, state.GetButtons());
                } catch (NotAcquiredException) {
                }
            }
        }
        #endregion

        #region Key/Button Binding
        /// <summary>
        /// Binds an action to a keyboard key.
        /// </summary>
        /// <param name="name">Name of the key.</param>
        /// <param name="action">Name of the action.</param>
        public void bindKey (string name, string action) {            
            makeBinding(name, action, boundKeys);
        }

        /// <summary>
        /// Binds an action to a mouse button.
        /// </summary>
        /// <param name="name">Name of the button.</param>
        /// <param name="action">Name of the action.</param>
        public void bindMouseButton (string name, string action) {
            makeBinding(name, action, boundMouseButtons);
        }

        /// <summary>
        /// Binds an action to a joystick button.
        /// </summary>
        /// <param name="name">Name of the button.</param>
        /// <param name="action">Name of the action.</param>
        public void bindJoystickButton (string name, string action) {
            makeBinding(name, action, boundJoystickButtons);
        }

        /// <summary>
        /// Adds a BoundAction of the given name and action to the given bindingList.
        /// </summary>
        /// <param name="name">Name of the key/button.</param>
        /// <param name="action">Name of the action.</param>
        /// <param name="bindingList">List of BoundActions to be added to.</param>
        private void makeBinding(string name, string action, ArrayList bindingList) {
            foreach (BoundAction ba in bindingList) {
                if (ba.gs_keyname == name) {                    
                    if (action != null) {
                        ba.gs_action = action;
                    } else {
                        bindingList.Remove(ba);
                    }
                    return;
                }
            }
            if (action != null) {
                bindingList.Add(new BoundAction(name, action));
            }
        }        
        #endregion

        #region Bound Key/Button Action Requests
        /// <summary>
        /// Gets the action bound to the specified keyboard key.
        /// </summary>
        /// <param name="name">Key to get action for.</param>
        /// <returns>The action for the key.</returns>
        public string getKeyAction (string name) {            
            return getAction(name, boundKeys);
        }
		
		/// <summary>
		/// Gets the action bound to the specified keyboard key.
		/// </summary>
		/// <returns>The action for the key.</returns>
		public string getKeyAction () {
            if (keyboard == null) {
                return null;
            }

			Key[] keys = keyboard.GetPressedKeys();
            if (keys.Length < 1) {
                return null;
            }
            else return getKeyAction(keys[0].ToString());
		}

		/// <summary>
		/// Gets the action bound to the specified mouse button.
		/// </summary>
		/// <param name="name">Button to get action for.</param>
		/// <returns>The action for the button.</returns>
		public string getMouseButtonAction (string name) {
			return getAction(name, boundMouseButtons);
		}

		/// <summary>
		/// Gets the action bound to the specified mouse button.
		/// </summary>
		/// <returns>The action for the button.</returns>
		public string getMouseButtonAction () 		{
			if (mouse == null) return null;

			byte[] buttons = mouse.CurrentMouseState.GetMouseButtons();
			if (buttons.Length < 1) return null;
			else return getMouseButtonAction(buttons[0].ToString());
		}

		/// <summary>
		/// Gets the action bound to the specified joystick button.
		/// </summary>
		/// <param name="name">Button to get action for.</param>
		/// <returns>The action for the button.</returns>
		public string getJoystickButtonAction (string name) {
			return getAction(name, boundJoystickButtons);
		}

		/// <summary>
		/// Gets the action bound to the specified joystick button.
		/// </summary>
		/// <returns>The action for the button.</returns>
		public string getJoystickButtonAction () {
			if (joystick == null) return null;

			byte[] keys = joystick.CurrentJoystickState.GetButtons();
            if (keys.Length < 1) {
                return null;
            }
            else {
                for (int i = 0; i < keys.Length; i++) {
                    if (keys[i] != 0) 
                        return getJoystickButtonAction(i.ToString());
                }
            }

            return null;
		}

		/// <summary>
		/// Returns the current action. 
		/// If several input devices are invoked at the same time, 
		/// first joystick will be chosen, then mouse, then keyboard.
		/// </summary>
		/// <returns>String value of the action to be performed.</returns>
		public string getCurrentButtonAction() {
			string action = null;

			action = getJoystickButtonAction();
            if (action != null) {
                return action;
            }

			action = getMouseButtonAction();
            if (action != null) {
                return action;
            }

			action = getKeyAction();
            if (action != null) {
                return action;
            }

			return null;
		}

        /// <summary>
        /// Gets the action bound to the key/button in the given list of BoundActions.
        /// </summary>
        /// <param name="name">The key/button to get the action for.</param>
        /// <param name="bindingList">List of BoundActions to search in.</param>
        /// <returns>The action for the key/button.</returns>
        private string getAction(string name, ArrayList bindingList) {
            foreach (BoundAction ba in bindingList) {
                if (ba.gs_keyname == name) {
                    return ba.gs_action;                    
                }
            }
            return null;
        }        
        #endregion

        #region Listening Thread Logic
        /// <summary>
        /// Starts the thread looping and polling for new input on devices.
        /// </summary>    
        public void StartListening() {      
            if (listenLoopThread == null) {
                listenLoopThread = new Thread(new ThreadStart(LoopAndListen));
                listenLoopThread.Start();
            }
        }

        /// <summary>
        /// Stops the thread from looping and polling for new input on devices
        /// </summary>
        public void StopListening() {
            if (listenLoopThread != null) {
                listenLoopThread.Abort();
                listenLoopThread = null;
            }      
        }

        /// <summary>
        /// Continually updates input information from all devices.
        /// </summary>
        private void LoopAndListen() {
            while (true) {        
                UpdateDevices();                
                Thread.Sleep(settings.LoopIntervalTime);
            }
        }
        #endregion

    }
}
