using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Configuration;
using System;
using System.Numerics;

namespace Ryujinx.Motion
{
    public class MotionDevice
    {
        public Vector3 Gyroscope     { get; private set; }
        public Vector3 Accelerometer { get; private set; }
        public Vector3 Rotation      { get; private set; }
        public float[] Orientation   { get; private set; }

        private Client _motionSource;

        public MotionDevice(Client motionSource)
        {
            _motionSource = motionSource;
        }

        public void RegisterController(PlayerIndex player)
        {
            InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(x => x.PlayerIndex == player);

            if (config != null && config.EnableMotion)
            {
                string host = config.DsuServerHost;
                int    port = config.DsuServerPort;

                _motionSource.RegisterClient((int)player, host, port);
                _motionSource.RequestData((int)player, config.Slot);

                if (config.ControllerType == ControllerType.JoyconPair && !config.MirrorInput)
                {
                    _motionSource.RequestData((int)player, config.AltSlot);
                }
            }
        }

        public void Poll(InputConfig config, int slot)
        {
            Orientation = new float[9];

            if (!config.EnableMotion || !_motionSource.TryGetData((int)config.PlayerIndex, slot, out MotionInput input))
            {
                Accelerometer = new Vector3();
                Gyroscope     = new Vector3();

                return;
            }

            Gyroscope     = Truncate(input.Gyroscrope * 0.0027f, 3);
            Accelerometer = Truncate(input.Accelerometer, 3);
            Rotation      = Truncate(input.Rotation * 0.0027f, 3);

            Matrix4x4 orientation = input.GetOrientation();

            Orientation[0] = Math.Clamp(orientation.M11, -1f, 1f);
            Orientation[1] = Math.Clamp(orientation.M12, -1f, 1f);
            Orientation[2] = Math.Clamp(orientation.M13, -1f, 1f);
            Orientation[3] = Math.Clamp(orientation.M21, -1f, 1f);
            Orientation[4] = Math.Clamp(orientation.M22, -1f, 1f);
            Orientation[5] = Math.Clamp(orientation.M23, -1f, 1f);
            Orientation[6] = Math.Clamp(orientation.M31, -1f, 1f);
            Orientation[7] = Math.Clamp(orientation.M32, -1f, 1f);
            Orientation[8] = Math.Clamp(orientation.M33, -1f, 1f);
        }

        private static Vector3 Truncate(Vector3 value, int decimals)
        {
            float power = MathF.Pow(10, decimals);

            value.X = float.IsNegative(value.X) ? MathF.Ceiling(value.X * power) / power : MathF.Floor(value.X * power) / power;
            value.Y = float.IsNegative(value.Y) ? MathF.Ceiling(value.Y * power) / power : MathF.Floor(value.Y * power) / power;
            value.Z = float.IsNegative(value.Z) ? MathF.Ceiling(value.Z * power) / power : MathF.Floor(value.Z * power) / power;

            return value;
        }
    }
}
