using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using Sdl;
using StarEngine.Core;

namespace StarEngine.Sdl
{
    /// <summary>
    /// 入力を表します。
    /// </summary>
    internal sealed class Input : IInput, IDisposable
    {
        private static Dictionary<Keys, SDL.SDLKey> KeyboardKeyMap = new Dictionary<Keys, SDL.SDLKey>();

        static Input()
        {
            for (int i = 0; i < Keys.Z - Keys.A + 1; i++)
                KeyboardKeyMap.Add(Keys.A + i, SDL.SDLKey.SDLK_a + i);

            for (int i = 0; i < Keys.D9 - Keys.D0 + 1; i++)
                KeyboardKeyMap.Add(Keys.D0 + i, SDL.SDLKey.SDLK_0 + i);

            for (int i = 0; i < Keys.F15 - Keys.F1 + 1; i++)
                KeyboardKeyMap.Add(Keys.F1 + i, SDL.SDLKey.SDLK_F1 + i);

            for (int i = 0; i < Keys.NumPad9 - Keys.NumPad0 + 1; i++)
                KeyboardKeyMap.Add(Keys.NumPad0 + i, SDL.SDLKey.SDLK_KP0 + i);

            KeyboardKeyMap.Add(Keys.Add, SDL.SDLKey.SDLK_KP_PLUS);
            KeyboardKeyMap.Add(Keys.Back, SDL.SDLKey.SDLK_BACKSPACE);
            KeyboardKeyMap.Add(Keys.CapsLock, SDL.SDLKey.SDLK_CAPSLOCK);
            KeyboardKeyMap.Add(Keys.Clear, SDL.SDLKey.SDLK_CLEAR);
            KeyboardKeyMap.Add(Keys.Decimal, SDL.SDLKey.SDLK_KP_PERIOD);
            KeyboardKeyMap.Add(Keys.Delete, SDL.SDLKey.SDLK_DELETE);
            KeyboardKeyMap.Add(Keys.Divide, SDL.SDLKey.SDLK_KP_DIVIDE);
            KeyboardKeyMap.Add(Keys.Down, SDL.SDLKey.SDLK_DOWN);
            KeyboardKeyMap.Add(Keys.End, SDL.SDLKey.SDLK_END);
            KeyboardKeyMap.Add(Keys.Enter, SDL.SDLKey.SDLK_RETURN);
            KeyboardKeyMap.Add(Keys.Escape, SDL.SDLKey.SDLK_ESCAPE);
            KeyboardKeyMap.Add(Keys.Help, SDL.SDLKey.SDLK_HELP);
            KeyboardKeyMap.Add(Keys.Home, SDL.SDLKey.SDLK_HOME);
            KeyboardKeyMap.Add(Keys.Insert, SDL.SDLKey.SDLK_INSERT);
            KeyboardKeyMap.Add(Keys.LControlKey, SDL.SDLKey.SDLK_LCTRL);
            KeyboardKeyMap.Add(Keys.Left, SDL.SDLKey.SDLK_LEFT);
            KeyboardKeyMap.Add(Keys.LMenu, SDL.SDLKey.SDLK_LALT);
            KeyboardKeyMap.Add(Keys.LShiftKey, SDL.SDLKey.SDLK_LSHIFT);
            KeyboardKeyMap.Add(Keys.LWin, SDL.SDLKey.SDLK_LSUPER);
            KeyboardKeyMap.Add(Keys.Multiply, SDL.SDLKey.SDLK_KP_MULTIPLY);
            KeyboardKeyMap.Add(Keys.NumLock, SDL.SDLKey.SDLK_NUMLOCK);
            KeyboardKeyMap.Add(Keys.PageDown, SDL.SDLKey.SDLK_PAGEDOWN);
            KeyboardKeyMap.Add(Keys.PageUp, SDL.SDLKey.SDLK_PAGEUP);
            KeyboardKeyMap.Add(Keys.RControlKey, SDL.SDLKey.SDLK_RCTRL);
            KeyboardKeyMap.Add(Keys.Right, SDL.SDLKey.SDLK_RIGHT);
            KeyboardKeyMap.Add(Keys.RMenu, SDL.SDLKey.SDLK_RALT);
            KeyboardKeyMap.Add(Keys.RShiftKey, SDL.SDLKey.SDLK_RSHIFT);
            KeyboardKeyMap.Add(Keys.RWin, SDL.SDLKey.SDLK_RSUPER);
            KeyboardKeyMap.Add(Keys.Scroll, SDL.SDLKey.SDLK_SCROLLOCK);
            KeyboardKeyMap.Add(Keys.Space, SDL.SDLKey.SDLK_SPACE);
            KeyboardKeyMap.Add(Keys.Subtract, SDL.SDLKey.SDLK_KP_MINUS);
            KeyboardKeyMap.Add(Keys.Tab, SDL.SDLKey.SDLK_TAB);
            KeyboardKeyMap.Add(Keys.Up, SDL.SDLKey.SDLK_UP);
        }

        public static Input Create()
        {
            if (Instance != null && !Instance.IsDisposed)
                throw new InvalidOperationException("Input オブジェクトは同時に二つ生成できません。");
            return Instance = new Input();
        }
        private static Input Instance;

        /// <summary>
        /// SDL で使用する、ジョイスティックのポインタの配列。
        /// </summary>
        private IntPtr[] SdlJoysticks;

        private Dictionary<Keys, int> KeyboardStatus = new Dictionary<Keys, int>();
        private Dictionary<int, GamePadStatus> GamePadStatuses = new Dictionary<int, GamePadStatus>();
        private Dictionary<MouseButtons, int> MouseButtonStatus = new Dictionary<MouseButtons, int>();

        private Input()
        {
            foreach (KeyValuePair<Keys, SDL.SDLKey> pair in KeyboardKeyMap)
                this.KeyboardStatus[pair.Key] = 0;

            SDL.SDL_JoystickEventState((int)SDL.SDL_ENABLE);
            this.SdlJoysticks = new IntPtr[SDL.SDL_NumJoysticks()];
            for (int i = 0; i < this.SdlJoysticks.Length; i++)
            {
                this.SdlJoysticks[i] = SDL.SDL_JoystickOpen(i);
                if (this.SdlJoysticks[i] == IntPtr.Zero)
                    throw new Exception("Joystick の取得に失敗しました。");

                int gamePadButtonCount = SDL.SDL_JoystickNumButtons(this.SdlJoysticks[i]);
                this.GamePadStatuses.Add(i, new GamePadStatus(gamePadButtonCount));
            }

            this.MouseButtonStatus.Add(MouseButtons.Left, 0);
            this.MouseButtonStatus.Add(MouseButtons.Middle, 0);
            this.MouseButtonStatus.Add(MouseButtons.Right, 0);
        }

        public void Dispose()
        {
            this.IsDisposed = true;
            foreach (IntPtr joystick in this.SdlJoysticks)
                SDL.SDL_JoystickClose(joystick);
        }

        public bool IsDisposed
        {
            get { return this.isDisposed; }
            private set { this.isDisposed = value; }
        }
        private bool isDisposed = false;

        public unsafe void Update(Screen screen)
        {
            SDL.SDL_JoystickUpdate();

            byte* sdlKeyboardState = (byte*)SDL.SDL_GetKeyState(IntPtr.Zero);
            foreach (KeyValuePair<Keys, SDL.SDLKey> pair in KeyboardKeyMap)
                if (sdlKeyboardState[(int)pair.Value] == 1)
                    this.KeyboardStatus[pair.Key]++;
                else
                    this.KeyboardStatus[pair.Key] = 0;

            for (int i = 0; i < this.GamePadCount; i++)
            {
                IntPtr sdlJoystick = this.SdlJoysticks[i];

                GamePadStatus status = this.GamePadStatuses[i];

                if (SDL.SDL_JoystickGetAxis(sdlJoystick, 1) > 3200)
                    status[GamePadDirections.Down]++;
                else
                    status[GamePadDirections.Down] = 0;
                
                if (SDL.SDL_JoystickGetAxis(sdlJoystick, 0) < -3200)
                    status[GamePadDirections.Left]++;
                else
                    status[GamePadDirections.Left] = 0;
                
                if (SDL.SDL_JoystickGetAxis(sdlJoystick, 0) > 3200)
                    status[GamePadDirections.Right]++;
                else
                    status[GamePadDirections.Right] = 0;

                if (SDL.SDL_JoystickGetAxis(sdlJoystick, 1) < -3200)
                    status[GamePadDirections.Up]++;
                else
                    status[GamePadDirections.Up] = 0;

                for (int j = 0; j < status.ButtonCount; j++)
                    if (SDL.SDL_JoystickGetButton(sdlJoystick, j) == 1)
                        status[j]++;
                    else
                        status[j] = 0;
            }

            int x, y;
            byte sdlMouseButtons = SDL.SDL_GetMouseState(out x, out y);

            if ((sdlMouseButtons & SDL.SDL_BUTTON(SDL.SDL_BUTTON_LEFT)) != 0)
                this.MouseButtonStatus[MouseButtons.Left]++;
            else
                this.MouseButtonStatus[MouseButtons.Left] = 0;

            if ((sdlMouseButtons & SDL.SDL_BUTTON(SDL.SDL_BUTTON_MIDDLE)) != 0)
                this.MouseButtonStatus[MouseButtons.Middle]++;
            else
                this.MouseButtonStatus[MouseButtons.Middle] = 0;

            if ((sdlMouseButtons & SDL.SDL_BUTTON(SDL.SDL_BUTTON_RIGHT)) != 0)
                this.MouseButtonStatus[MouseButtons.Right]++;
            else
                this.MouseButtonStatus[MouseButtons.Right] = 0;
            
            this.MouseLocation = screen.ToLogicalLocation(new Point(x, y));
        }

        public Keys[] GetPressedKeyboardKeys(int duration, int delay, int interval)
        {
            List<Keys> keys = new List<Keys>();

            foreach (KeyValuePair<Keys, int> pair in this.KeyboardStatus)
                if (IsPressed(pair.Value, duration, delay, interval))
                    keys.Add(pair.Key);

            return keys.ToArray();
        }

        public int[] GetPressedGamePadButtons(int deviceNumber, int duration, int delay, int interval)
        {
            if (!this.GamePadStatuses.ContainsKey(deviceNumber))
                return new int[0];

            List<int> buttons = new List<int>();

            GamePadStatus status = this.GamePadStatuses[deviceNumber];
            for (int i = 0; i < status.ButtonCount; i++)
                if (IsPressed(status[i], duration, delay, interval))
                    buttons.Add(i + 1);

            return buttons.ToArray();
        }

        public GamePadDirections GetPressedGamePadDirections(int deviceNumber, int duration, int delay, int interval)
        {
            if (!this.GamePadStatuses.ContainsKey(deviceNumber))
                return GamePadDirections.None;

            GamePadStatus status = this.GamePadStatuses[deviceNumber];

            GamePadDirections direction = GamePadDirections.None;

            if (IsPressed(status[GamePadDirections.Down], duration, delay, interval))
                direction |= GamePadDirections.Down;

            if (IsPressed(status[GamePadDirections.Left], duration, delay, interval))
                direction |= GamePadDirections.Left;

            if (IsPressed(status[GamePadDirections.Right], duration, delay, interval))
                direction |= GamePadDirections.Right;

            if (IsPressed(status[GamePadDirections.Up], duration, delay, interval))
                direction |= GamePadDirections.Up;

            return direction;
        }

        public int GamePadCount
        {
            get { return this.SdlJoysticks.Length; }
        }

        public MouseButtons GetPressedMouseButtons(int duration, int delay, int interval)
        {
            MouseButtons buttons = MouseButtons.None;

            if (IsPressed(this.MouseButtonStatus[MouseButtons.Left], duration, delay, interval))
                buttons |= MouseButtons.Left;

            if (IsPressed(this.MouseButtonStatus[MouseButtons.Middle], duration, delay, interval))
                buttons |= MouseButtons.Middle;

            if (IsPressed(this.MouseButtonStatus[MouseButtons.Right], duration, delay, interval))
                buttons |= MouseButtons.Right;

            return buttons;
        }

        public Point MouseLocation
        {
            get { return this.mouseCursorLocatin; }
            private set { this.mouseCursorLocatin = value; }
        }
        private Point mouseCursorLocatin = Point.Empty;

        internal static bool IsPressed(int status, int duration, int delay, int interval)
        {
            if (status <= 0 || duration == 0)
                return false;

            if (duration < 0)
                return true;

            if (status <= duration)
                return true;

            if (delay < 0)
                return false;

            if (status <= duration + delay)
                return false;

            if (0 <= interval)
                return (status - (duration + delay + 1)) % (interval + 1) == 0;

            return false;
        }

        /// <summary>
        /// 一つのゲームパッドの状態を表します。
        /// </summary>
        private class GamePadStatus
        {
            public GamePadStatus(int buttonCount)
            {
                this.ButtonCount = buttonCount;

                for (int i = 0; i < this.ButtonCount; i++)
                    this.ButtonStatus.Add(i, 0);

                this.DirectionStatus.Add(GamePadDirections.Down, 0);
                this.DirectionStatus.Add(GamePadDirections.Left, 0);
                this.DirectionStatus.Add(GamePadDirections.Right, 0);
                this.DirectionStatus.Add(GamePadDirections.Up, 0);
            }

            public int ButtonCount
            {
                get { return this.buttonCount; }
                private set { this.buttonCount = value; }
            }
            private int buttonCount;

            public int this[GamePadDirections direction]
            {
                get { return this.DirectionStatus[direction]; }
                set { this.DirectionStatus[direction] = value; }
            }
            private Dictionary<GamePadDirections, int> DirectionStatus = new Dictionary<GamePadDirections, int>();

            public int this[int button]
            {
                get { return this.ButtonStatus[button]; }
                set { this.ButtonStatus[button] = value; }
            }
            private Dictionary<int, int> ButtonStatus = new Dictionary<int, int>();
        }
    }
}
