using Unity.Collections;
using Unity.Mathematics;
using UnityEngine.Jobs;
namespace UnityEngine.Rendering.Universal
{
    /// 
    /// Contains  cached properties needed for rendering.
    /// 
    internal class DecalCachedChunk : DecalChunk
    {
        public MaterialPropertyBlock propertyBlock;
        public int passIndexDBuffer;
        public int passIndexEmissive;
        public int passIndexScreenSpace;
        public int passIndexGBuffer;
        public int drawOrder;
        public bool isCreated;
        public NativeArray decalToWorlds;
        public NativeArray normalToWorlds;
        public NativeArray sizeOffsets;
        public NativeArray drawDistances;
        public NativeArray angleFades;
        public NativeArray uvScaleBias;
        public NativeArray layerMasks;
        public NativeArray sceneLayerMasks;
        public NativeArray fadeFactors;
        public NativeArray boundingSpheres;
        public NativeArray scaleModes;
        public NativeArray renderingLayerMasks;
        public NativeArray positions;
        public NativeArray rotation;
        public NativeArray scales;
        public NativeArray dirty;
        public BoundingSphere[] boundingSphereArray;
        public override void RemoveAtSwapBack(int entityIndex)
        {
            RemoveAtSwapBack(ref decalToWorlds, entityIndex, count);
            RemoveAtSwapBack(ref normalToWorlds, entityIndex, count);
            RemoveAtSwapBack(ref sizeOffsets, entityIndex, count);
            RemoveAtSwapBack(ref drawDistances, entityIndex, count);
            RemoveAtSwapBack(ref angleFades, entityIndex, count);
            RemoveAtSwapBack(ref uvScaleBias, entityIndex, count);
            RemoveAtSwapBack(ref layerMasks, entityIndex, count);
            RemoveAtSwapBack(ref sceneLayerMasks, entityIndex, count);
            RemoveAtSwapBack(ref fadeFactors, entityIndex, count);
            RemoveAtSwapBack(ref boundingSphereArray, entityIndex, count);
            RemoveAtSwapBack(ref boundingSpheres, entityIndex, count);
            RemoveAtSwapBack(ref scaleModes, entityIndex, count);
            RemoveAtSwapBack(ref renderingLayerMasks, entityIndex, count);
            RemoveAtSwapBack(ref positions, entityIndex, count);
            RemoveAtSwapBack(ref rotation, entityIndex, count);
            RemoveAtSwapBack(ref scales, entityIndex, count);
            RemoveAtSwapBack(ref dirty, entityIndex, count);
            count--;
        }
        public override void SetCapacity(int newCapacity)
        {
            decalToWorlds.ResizeArray(newCapacity);
            normalToWorlds.ResizeArray(newCapacity);
            sizeOffsets.ResizeArray(newCapacity);
            drawDistances.ResizeArray(newCapacity);
            angleFades.ResizeArray(newCapacity);
            uvScaleBias.ResizeArray(newCapacity);
            layerMasks.ResizeArray(newCapacity);
            sceneLayerMasks.ResizeArray(newCapacity);
            fadeFactors.ResizeArray(newCapacity);
            boundingSpheres.ResizeArray(newCapacity);
            scaleModes.ResizeArray(newCapacity);
            renderingLayerMasks.ResizeArray(newCapacity);
            positions.ResizeArray(newCapacity);
            rotation.ResizeArray(newCapacity);
            scales.ResizeArray(newCapacity);
            dirty.ResizeArray(newCapacity);
            ArrayExtensions.ResizeArray(ref boundingSphereArray, newCapacity);
            capacity = newCapacity;
        }
        public override void Dispose()
        {
            if (capacity == 0)
                return;
            decalToWorlds.Dispose();
            normalToWorlds.Dispose();
            sizeOffsets.Dispose();
            drawDistances.Dispose();
            angleFades.Dispose();
            uvScaleBias.Dispose();
            layerMasks.Dispose();
            sceneLayerMasks.Dispose();
            fadeFactors.Dispose();
            boundingSpheres.Dispose();
            scaleModes.Dispose();
            renderingLayerMasks.Dispose();
            positions.Dispose();
            rotation.Dispose();
            scales.Dispose();
            dirty.Dispose();
            count = 0;
            capacity = 0;
        }
    }
    /// 
    /// Caches  properties into .
    /// Uses jobs with .
    /// 
    internal class DecalUpdateCachedSystem
    {
        private DecalEntityManager m_EntityManager;
        private ProfilingSampler m_Sampler;
        private ProfilingSampler m_SamplerJob;
        public DecalUpdateCachedSystem(DecalEntityManager entityManager)
        {
            m_EntityManager = entityManager;
            m_Sampler = new ProfilingSampler("DecalUpdateCachedSystem.Execute");
            m_SamplerJob = new ProfilingSampler("DecalUpdateCachedSystem.ExecuteJob");
        }
        public void Execute()
        {
            using (new ProfilingScope(m_Sampler))
            {
                for (int i = 0; i < m_EntityManager.chunkCount; ++i)
                    Execute(m_EntityManager.entityChunks[i], m_EntityManager.cachedChunks[i], m_EntityManager.entityChunks[i].count);
            }
        }
        private void Execute(DecalEntityChunk entityChunk, DecalCachedChunk cachedChunk, int count)
        {
            if (count == 0)
                return;
            cachedChunk.currentJobHandle.Complete();
            // Make sure draw order is up to date
            var material = entityChunk.material;
            if (material.HasProperty("_DrawOrder"))
                cachedChunk.drawOrder = material.GetInt("_DrawOrder");
            // Shader can change any time in editor, so we have to update passes each time
#if !UNITY_EDITOR
            if (!cachedChunk.isCreated)
#endif
            {
                int passIndexDBuffer = material.FindPass(DecalShaderPassNames.DBufferProjector);
                cachedChunk.passIndexDBuffer = passIndexDBuffer;
                int passIndexEmissive = material.FindPass(DecalShaderPassNames.DecalProjectorForwardEmissive);
                cachedChunk.passIndexEmissive = passIndexEmissive;
                int passIndexScreenSpace = material.FindPass(DecalShaderPassNames.DecalScreenSpaceProjector);
                cachedChunk.passIndexScreenSpace = passIndexScreenSpace;
                int passIndexGBuffer = material.FindPass(DecalShaderPassNames.DecalGBufferProjector);
                cachedChunk.passIndexGBuffer = passIndexGBuffer;
                cachedChunk.isCreated = true;
            }
            using (new ProfilingScope(m_SamplerJob))
            {
                UpdateTransformsJob updateTransformJob = new UpdateTransformsJob()
                {
                    positions = cachedChunk.positions,
                    rotations = cachedChunk.rotation,
                    scales = cachedChunk.scales,
                    dirty = cachedChunk.dirty,
                    scaleModes = cachedChunk.scaleModes,
                    sizeOffsets = cachedChunk.sizeOffsets,
                    decalToWorlds = cachedChunk.decalToWorlds,
                    normalToWorlds = cachedChunk.normalToWorlds,
                    boundingSpheres = cachedChunk.boundingSpheres,
                    minDistance = System.Single.Epsilon,
                };
                var handle = updateTransformJob.Schedule(entityChunk.transformAccessArray);
                cachedChunk.currentJobHandle = handle;
            }
        }
#if ENABLE_BURST_1_0_0_OR_NEWER
        [Unity.Burst.BurstCompile]
#endif
        public unsafe struct UpdateTransformsJob : IJobParallelForTransform
        {
            private static readonly quaternion k_MinusYtoZRotation = quaternion.EulerXYZ(-math.PI / 2.0f, 0, 0);
            public NativeArray positions;
            public NativeArray rotations;
            public NativeArray scales;
            public NativeArray dirty;
            [ReadOnly] public NativeArray scaleModes;
            [ReadOnly] public NativeArray sizeOffsets;
            [WriteOnly] public NativeArray decalToWorlds;
            [WriteOnly] public NativeArray normalToWorlds;
            [WriteOnly] public NativeArray boundingSpheres;
            public float minDistance;
            private float DistanceBetweenQuaternions(quaternion a, quaternion b)
            {
                return math.distancesq(a.value, b.value);
            }
            public void Execute(int index, TransformAccess transform)
            {
                // Check if transform changed
                bool positionChanged = math.distancesq(transform.position, positions[index]) > minDistance;
                if (positionChanged)
                    positions[index] = transform.position;
                bool rotationChanged = DistanceBetweenQuaternions(transform.rotation, rotations[index]) > minDistance;
                if (rotationChanged)
                    rotations[index] = transform.rotation;
                bool scaleChanged = math.distancesq(transform.localScale, scales[index]) > minDistance;
                if (scaleChanged)
                    scales[index] = transform.localScale;
                // Early out if transform did not changed
                if (!positionChanged && !rotationChanged && !scaleChanged && !dirty[index])
                    return;
                float4x4 localToWorld;
                if (scaleModes[index] == DecalScaleMode.InheritFromHierarchy)
                {
                    localToWorld = transform.localToWorldMatrix;
                    localToWorld = math.mul(localToWorld, new float4x4(k_MinusYtoZRotation, float3.zero));
                }
                else
                {
                    quaternion rotation = math.mul(transform.rotation, k_MinusYtoZRotation);
                    localToWorld = float4x4.TRS(positions[index], rotation, new float3(1, 1, 1));
                }
                float4x4 decalRotation = localToWorld;
                // z/y axis swap for normal to decal space, Unity is column major
                float4 temp = decalRotation.c1;
                decalRotation.c1 = decalRotation.c2;
                decalRotation.c2 = temp;
                normalToWorlds[index] = decalRotation;
                float4x4 sizeOffset = sizeOffsets[index];
                float4x4 decalToWorld = math.mul(localToWorld, sizeOffset);
                decalToWorlds[index] = decalToWorld;
                boundingSpheres[index] = GetDecalProjectBoundingSphere(decalToWorld);
                dirty[index] = false;
            }
            private BoundingSphere GetDecalProjectBoundingSphere(Matrix4x4 decalToWorld)
            {
                float4 min = new float4(-0.5f, -0.5f, -0.5f, 1.0f);
                float4 max = new float4(0.5f, 0.5f, 0.5f, 1.0f);
                min = math.mul(decalToWorld, min);
                max = math.mul(decalToWorld, max);
                float3 position = ((max + min) / 2f).xyz;
                float radius = math.length(max - min) / 2f;
                BoundingSphere res = new BoundingSphere();
                res.position = position;
                res.radius = radius;
                return res;
            }
        }
    }
}