using System.Runtime.InteropServices;
using UnityEngine.InputSystem.Utilities;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;

namespace UnityEngine.InputSystem.LowLevel
{
    ////REVIEW: why is this passing the command by pointer instead of by ref?
    /// <summary>
    /// Delegate used by <see cref="InputSystem.onDeviceCommand"/>.
    /// </summary>
    public unsafe delegate long? InputDeviceCommandDelegate(InputDevice device, InputDeviceCommand* command);

    /// <summary>
    /// Delegate for executing <see cref="InputDeviceCommand"/>s inside <see cref="InputSystem.onFindLayoutForDevice"/>.
    /// </summary>
    /// <param name="command">Command to execute.</param>
    /// <seealso cref="InputSystem.onFindLayoutForDevice"/>
    /// <seealso cref="Layouts.InputDeviceFindControlLayoutDelegate"/>
    public delegate long InputDeviceExecuteCommandDelegate(ref InputDeviceCommand command);

    /// <summary>
    /// Data header for a command send to an <see cref="InputDevice"/>.
    /// </summary>
    /// <remarks>
    /// Commands are essentially synchronously processed events send directly
    /// to a specific device. Their primary use is to expose device-specific
    /// functions without having to extend the C# API used to communicate
    /// between input code and backend device implementations (which may sit
    /// in native code).
    ///
    /// Like input events, device commands use <see cref="FourCC"/> codes
    /// to indicate their type.
    /// </remarks>
    [StructLayout(LayoutKind.Explicit, Size = kBaseCommandSize)]
    public struct InputDeviceCommand : IInputDeviceCommandInfo
    {
        ////TODO: Remove kBaseCommandSize
        internal const int kBaseCommandSize = 8;
        public const int BaseCommandSize = 8;

        /// <summary>
        /// Generic failure code for <see cref="InputDevice.ExecuteCommand{TCommand}"/> calls.
        /// </summary>
        /// <remarks>
        /// Any negative return value for an <see cref="InputDevice.ExecuteCommand{TCommand}"/> call should be considered failure.
        /// </remarks>
        public const long GenericFailure = -1;

        public const long GenericSuccess = 1;

        [FieldOffset(0)] public FourCC type;
        [FieldOffset(4)] public int sizeInBytes;

        public int payloadSizeInBytes => sizeInBytes - kBaseCommandSize;

        public unsafe void* payloadPtr
        {
            get
            {
                fixed(void* thisPtr = &this)
                {
                    return ((byte*)thisPtr) + kBaseCommandSize;
                }
            }
        }

        public InputDeviceCommand(FourCC type, int sizeInBytes = kBaseCommandSize)
        {
            this.type = type;
            this.sizeInBytes = sizeInBytes;
        }

        public static unsafe NativeArray<byte> AllocateNative(FourCC type, int payloadSize)
        {
            var sizeInBytes = payloadSize + kBaseCommandSize;
            var buffer = new NativeArray<byte>(sizeInBytes, Allocator.Temp);

            var commandPtr = (InputDeviceCommand*)NativeArrayUnsafeUtility.GetUnsafePtr(buffer);
            commandPtr->type = type;
            commandPtr->sizeInBytes = sizeInBytes;

            return buffer;
        }

        public FourCC typeStatic
        {
            get { return new FourCC(); }
        }
    }
}