using System;

namespace UnityEngine.Rendering.Universal
{
    // A dynamic array of bits backed by a managed array of floats,
    // since that's what Unity Shader constant API offers.
    //
    // Example:
    // ShaderBitArray bits;
    // bits.Resize(8);
    // bits[0] = true;
    // cmd.SetGlobalFloatArray("_BitArray", bits.data);
    // bits.Clear();
    internal struct ShaderBitArray
    {
        const int k_BitsPerElement = 32;
        const int k_ElementShift = 5;
        const int k_ElementMask = (1 << k_ElementShift) - 1;

        private float[] m_Data;

        public int elemLength => m_Data == null ? 0 : m_Data.Length;
        public int bitCapacity => elemLength * k_BitsPerElement;
        public float[] data => m_Data;

        public void Resize(int bitCount)
        {
            if (bitCapacity > bitCount)
                return;

            int newElemCount = ((bitCount + (k_BitsPerElement - 1)) / k_BitsPerElement);
            if (newElemCount == m_Data?.Length)
                return;

            var newData = new float[newElemCount];
            if (m_Data != null)
            {
                for (int i = 0; i < m_Data.Length; i++)
                    newData[i] = m_Data[i];
            }
            m_Data = newData;
        }

        public void Clear()
        {
            for (int i = 0; i < m_Data.Length; i++)
                m_Data[i] = 0;
        }

        private void GetElementIndexAndBitOffset(int index, out int elemIndex, out int bitOffset)
        {
            elemIndex = index >> k_ElementShift;
            bitOffset = index & k_ElementMask;
        }

        public bool this[int index]
        {
            get
            {
                GetElementIndexAndBitOffset(index, out var elemIndex, out var bitOffset);

                unsafe
                {
                    fixed (float* floatData = m_Data)
                    {
                        uint* uintElem = (uint*)&floatData[elemIndex];
                        bool val = ((*uintElem) & (1u << bitOffset)) != 0u;
                        return val;
                    }
                }
            }
            set
            {
                GetElementIndexAndBitOffset(index, out var elemIndex, out var bitOffset);
                unsafe
                {
                    fixed (float* floatData = m_Data)
                    {
                        uint* uintElem = (uint*)&floatData[elemIndex];
                        if (value == true)
                            *uintElem = (*uintElem) | (1u << bitOffset);
                        else
                            *uintElem = (*uintElem) & ~(1u << bitOffset);
                    }
                }
            }
        }

        public override string ToString()
        {
            unsafe
            {
                Debug.Assert(bitCapacity < 4096, "Bit string too long! It was truncated!");
                int len = Math.Min(bitCapacity, 4096);
                byte* buf = stackalloc byte[len];
                for (int i = 0; i < len; i++)
                {
                    buf[i] = (byte)(this[i] ? '1' : '0');
                }

                return new string((sbyte*)buf, 0, len, System.Text.Encoding.UTF8);
            }
        }
    }
}