using ARMeilleure.IntermediateRepresentation;
using System.Numerics;

namespace ARMeilleure.CodeGen.Arm64
{
    static class CodeGenCommon
    {
        public const int TcAddressRegister = 8;
        public const int ReservedRegister = 17;

        public static bool ConstFitsOnSImm7(int value, int scale)
        {
            return (((value >> scale) << 25) >> (25 - scale)) == value;
        }

        public static bool ConstFitsOnSImm9(int value)
        {
            return ((value << 23) >> 23) == value;
        }

        public static bool ConstFitsOnUImm12(int value)
        {
            return (value & 0xfff) == value;
        }

        public static bool ConstFitsOnUImm12(int value, OperandType type)
        {
            int scale = Assembler.GetScaleForType(type);
            return (((value >> scale) & 0xfff) << scale) == value;
        }

        public static bool TryEncodeBitMask(Operand operand, out int immN, out int immS, out int immR)
        {
            return TryEncodeBitMask(operand.Type, operand.Value, out immN, out immS, out immR);
        }

        public static bool TryEncodeBitMask(OperandType type, ulong value, out int immN, out int immS, out int immR)
        {
            if (type == OperandType.I32)
            {
                value |= value << 32;
            }

            return TryEncodeBitMask(value, out immN, out immS, out immR);
        }

        public static bool TryEncodeBitMask(ulong value, out int immN, out int immS, out int immR)
        {
            // Some special values also can't be encoded:
            // 0 can't be encoded because we need to subtract 1 from onesCount (which would became negative if 0).
            // A value with all bits set can't be encoded because it is reserved according to the spec, because:
            // Any value AND all ones will be equal itself, so it's effectively a no-op.
            // Any value OR all ones will be equal all ones, so one can just use MOV.
            // Any value XOR all ones will be equal its inverse, so one can just use MVN.
            if (value is 0 or ulong.MaxValue)
            {
                immN = 0;
                immS = 0;
                immR = 0;

                return false;
            }

            // Normalize value, rotating it such that the LSB is 1: Ensures we get a complete element that has not
            // been cut-in-half across the word boundary.
            int rotation = BitOperations.TrailingZeroCount(value & (value + 1));
            ulong rotatedValue = ulong.RotateRight(value, rotation);

            // Now that we have a complete element in the LSB with the LSB = 1, determine size and number of ones
            // in element.
            int elementSize = BitOperations.TrailingZeroCount(rotatedValue & (rotatedValue + 1));
            int onesInElement = BitOperations.TrailingZeroCount(~rotatedValue);

            // Check the value is repeating; also ensures element size is a power of two.
            if (ulong.RotateRight(value, elementSize) != value)
            {
                immN = 0;
                immS = 0;
                immR = 0;

                return false;
            }

            immN = (elementSize >> 6) & 1;
            immS = (((~elementSize + 1) << 1) | (onesInElement - 1)) & 0x3f;
            immR = (elementSize - rotation) & (elementSize - 1);

            return true;
        }
    }
}
