// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License

using System;
using System.Runtime.InteropServices;
using UnityEngine.Bindings;

namespace UnityEngine
{
    // A UnityGUI event.
    [StructLayout(LayoutKind.Sequential)]
    public sealed partial class Event
    {
        public Event()
        {
            m_Ptr = Internal_Create(0);
        }

        public Event(int displayIndex)
        {
            m_Ptr = Internal_Create(displayIndex);
        }

        // Copy an event
        public Event(Event other)
        {
            if (other == null)
                throw new ArgumentException("Event to copy from is null.");
            m_Ptr = Internal_Copy(other.m_Ptr);
        }

        ~Event()
        {
            if (m_Ptr != IntPtr.Zero)
            {
                Internal_Destroy(m_Ptr);
                m_Ptr = IntPtr.Zero;
            }
        }

        internal static void CleanupRoots()
        {
            // Required for application quite, so we can force GC to collect root objects before Unity managers are destroyed
            s_Current = null;
            s_MasterEvent = null;
        }

        [NonSerialized]
        internal IntPtr m_Ptr;

        [VisibleToOtherModules("UnityEngine.UIElementsModule")]
        internal void CopyFrom(Event e)
        {
            // Copies the event data without allocating a new event on the native side.
            if (e.m_Ptr != m_Ptr)
            {
                CopyFromPtr(e.m_Ptr);
            }
        }

        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
        [Obsolete("Use HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);", true)]
        public Ray mouseRay { get { return new Ray(Vector3.up, Vector3.up); } set {}}

        // Is Shift held down? (RO)
        public bool shift
        {
            get { return (modifiers & EventModifiers.Shift)  != 0; }
            set { if (!value) modifiers &= ~EventModifiers.Shift; else modifiers |= EventModifiers.Shift; }
        }

        // Is Control key held down? (RO)
        public bool control
        {
            get {return (modifiers & EventModifiers.Control)  != 0; }
            set { if (!value) modifiers &= ~EventModifiers.Control; else modifiers |= EventModifiers.Control; }
        }

        // Is Alt/Option key held down? (RO)
        public bool alt
        {
            get { return (modifiers & EventModifiers.Alt)  != 0; }
            set { if (!value) modifiers &= ~EventModifiers.Alt; else modifiers |= EventModifiers.Alt; }
        }

        // Is Command/Windows key held down? (RO)
        public bool command
        {
            get { return (modifiers & EventModifiers.Command)  != 0; }
            set { if (!value) modifiers &= ~EventModifiers.Command; else modifiers |= EventModifiers.Command; }
        }

        // Is Caps Lock on? (RO)
        public bool capsLock
        {
            get { return (modifiers & EventModifiers.CapsLock)  != 0; }
            set { if (!value) modifiers &= ~EventModifiers.CapsLock; else modifiers |= EventModifiers.CapsLock; }
        }

        // Is the current keypress on the numeric keyboard? (RO)
        public bool numeric
        {
            get { return (modifiers & EventModifiers.Numeric)  != 0; }
            set { if (!value) modifiers &= ~EventModifiers.Numeric; else modifiers |= EventModifiers.Numeric; }
        }

        // Is the current keypress a function key? (RO)
        public bool functionKey => (modifiers & EventModifiers.FunctionKey)  != 0;

        // The current event that's being processed right now.
        // TODO: set this to null outside the event loop.
        //
        public static Event current
        {
            get
            {
                // return null if Event.current is queried outside OnGUI
                // Only in editor because of backwards compatible.
                if (GUIUtility.guiDepth > 0)
                    return s_Current;
                else
                    return null;
            }
            set
            {
                s_Current = value ?? s_MasterEvent;
                Internal_SetNativeEvent(s_Current.m_Ptr);
            }
        }
        static Event s_Current;
        static Event s_MasterEvent;

        // Is this event a keyboard event? (RO)
        public bool isKey
        {
            get { EventType t = type; return t == EventType.KeyDown || t == EventType.KeyUp; }
        }

        // Is this event a mouse event? (RO)
        public bool isMouse
        {
            get
            {
                EventType t = type;
                return t == EventType.MouseMove
                    || t == EventType.MouseDown
                    || t == EventType.MouseUp
                    || t == EventType.MouseDrag
                    || t == EventType.ContextClick
                    || t == EventType.MouseEnterWindow
                    || t == EventType.MouseLeaveWindow;
            }
        }

        // Is this event a scroll wheel event? (RO)
        public bool isScrollWheel
        {
            get { EventType t = type; return t == EventType.ScrollWheel; }
        }

        // Create a keyboard event.
        public static Event KeyboardEvent(string key)
        {
            Event evt = new Event(0) {type = EventType.KeyDown};
            if (string.IsNullOrEmpty(key))
                return evt;
            int startIdx = 0;
            bool found = false;
            do
            {
                found = true;
                if (startIdx >= key.Length)
                {
                    found = false; break;
                }
                switch (key[startIdx])
                {
                    case '&': // Alt
                        evt.modifiers |= EventModifiers.Alt; startIdx++;
                        break;
                    case '^': // Ctrl
                        evt.modifiers |= EventModifiers.Control; startIdx++;
                        break;
                    case '%':
                        evt.modifiers |= EventModifiers.Command; startIdx++;
                        break;
                    case '#':
                        evt.modifiers |= EventModifiers.Shift; startIdx++;
                        break;
                    default:
                        found = false;
                        break;
                }
            }
            while (found);
            string subStr = key.Substring(startIdx, key.Length - startIdx).ToLowerInvariant();
            switch (subStr)
            {
                case "[0]":         evt.character = '0'; evt.keyCode = KeyCode.Keypad0; break;
                case "[1]":         evt.character = '1'; evt.keyCode = KeyCode.Keypad1; break;
                case "[2]":         evt.character = '2'; evt.keyCode = KeyCode.Keypad2; break;
                case "[3]":         evt.character = '3'; evt.keyCode = KeyCode.Keypad3; break;
                case "[4]":         evt.character = '4'; evt.keyCode = KeyCode.Keypad4; break;
                case "[5]":         evt.character = '5'; evt.keyCode = KeyCode.Keypad5; break;
                case "[6]":         evt.character = '6'; evt.keyCode = KeyCode.Keypad6; break;
                case "[7]":         evt.character = '7'; evt.keyCode = KeyCode.Keypad7; break;
                case "[8]":         evt.character = '8'; evt.keyCode = KeyCode.Keypad8; break;
                case "[9]":         evt.character = '9'; evt.keyCode = KeyCode.Keypad9; break;
                case "[.]":         evt.character = '.'; evt.keyCode = KeyCode.KeypadPeriod; break;
                case "[/]":         evt.character = '/'; evt.keyCode = KeyCode.KeypadDivide; break;
                case "[-]":         evt.character = '-'; evt.keyCode = KeyCode.KeypadMinus; break;
                case "[+]":         evt.character = '+'; evt.keyCode = KeyCode.KeypadPlus; break;
                case "[=]":         evt.character = '='; evt.keyCode = KeyCode.KeypadEquals; break;
                case "[equals]":    evt.character = '='; evt.keyCode = KeyCode.KeypadEquals; break;
                case "[enter]":     evt.character = '\n'; evt.keyCode = KeyCode.KeypadEnter; break;
                case "up":          evt.keyCode = KeyCode.UpArrow; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "down":        evt.keyCode = KeyCode.DownArrow; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "left":        evt.keyCode = KeyCode.LeftArrow; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "right":       evt.keyCode = KeyCode.RightArrow; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "insert":      evt.keyCode = KeyCode.Insert; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "home":        evt.keyCode = KeyCode.Home; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "end":         evt.keyCode = KeyCode.End; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "pgup":        evt.keyCode = KeyCode.PageDown; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "page up":     evt.keyCode = KeyCode.PageUp; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "pgdown":      evt.keyCode = KeyCode.PageUp; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "page down":   evt.keyCode = KeyCode.PageDown; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "backspace":   evt.keyCode = KeyCode.Backspace; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "delete":      evt.keyCode = KeyCode.Delete; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "tab":         evt.keyCode = KeyCode.Tab; break;
                case "f1":          evt.keyCode = KeyCode.F1; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f2":          evt.keyCode = KeyCode.F2; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f3":          evt.keyCode = KeyCode.F3; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f4":          evt.keyCode = KeyCode.F4; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f5":          evt.keyCode = KeyCode.F5; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f6":          evt.keyCode = KeyCode.F6; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f7":          evt.keyCode = KeyCode.F7; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f8":          evt.keyCode = KeyCode.F8; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f9":          evt.keyCode = KeyCode.F9; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f10":         evt.keyCode = KeyCode.F10; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f11":         evt.keyCode = KeyCode.F11; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f12":         evt.keyCode = KeyCode.F12; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f13":         evt.keyCode = KeyCode.F13; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f14":         evt.keyCode = KeyCode.F14; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "f15":         evt.keyCode = KeyCode.F15; evt.modifiers |= EventModifiers.FunctionKey; break;
                case "[esc]":       evt.keyCode = KeyCode.Escape; break;
                case "return":      evt.character = '\n'; evt.keyCode = KeyCode.Return; evt.modifiers &= ~EventModifiers.FunctionKey; break;
                case "space":       evt.keyCode = KeyCode.Space; evt.character = ' '; evt.modifiers &= ~EventModifiers.FunctionKey; break;
                default:
                    if (subStr.Length != 1)
                    {
                        try
                        {
                            evt.keyCode = (KeyCode)Enum.Parse(typeof(KeyCode), subStr, true);
                        }
                        catch (ArgumentException)
                        {
                            Debug.LogError(UnityString.Format("Unable to find key name that matches '{0}'", subStr));
                        }
                    }
                    else
                    {
                        evt.character = subStr.ToLower()[0];
                        evt.keyCode = (KeyCode)evt.character;
                        if (evt.modifiers != 0)
                            evt.character = (char)0;
                    }
                    break;
            }
            return evt;
        }

        // Calculate the hash code
        public override int GetHashCode()
        {
            int hc = 1;
            if (isKey)
                hc =  (ushort)keyCode;
            if (isMouse)
                hc = mousePosition.GetHashCode();
            hc = hc * 37 | (int)modifiers;
            return hc;
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;
            if (ReferenceEquals(this, obj))
                return true;
            if (obj.GetType() != GetType())
                return false;

            Event rhs = (Event)obj;
            // We are ignoring Caps Lock for modifiers, so that Key Combinations will still work when Caps Lock is down.
            if (type != rhs.type || (modifiers & ~EventModifiers.CapsLock) != (rhs.modifiers & ~EventModifiers.CapsLock))
                return false;
            if (isKey)
                return keyCode == rhs.keyCode;
            if (isMouse)
                return mousePosition == rhs.mousePosition;
            return false;
        }

        public override string ToString()
        {
            if (isKey)
            {
                if (character == 0)
                    return UnityString.Format("Event:{0}   Character:\\0   Modifiers:{1}   KeyCode:{2}", type, modifiers, keyCode);

                return "Event:" + type + "   Character:" + (int)(character) + "   Modifiers:" + modifiers + "   KeyCode:" + keyCode;
            }
            if (isMouse)
                return UnityString.Format("Event: {0}   Position: {1} Modifiers: {2}", type, mousePosition, modifiers);

            if (type == EventType.ExecuteCommand || type == EventType.ValidateCommand)
                return UnityString.Format("Event: {0}  \"{1}\"", type, commandName);

            return "" + type;
        }

        // Use this event.
        public void Use()
        {
            if (type == EventType.Repaint || type == EventType.Layout)
                Debug.LogWarning(UnityString.Format("Event.Use() should not be called for events of type {0}", type));
            Internal_Use();
        }
    }
}
