using System;
using System.Collections.Generic;
using System.Reflection;
using AwesomeTechnologies.Utility.Culling;
using UnityEngine;
using UnityEngine.Profiling;
using UnityEngine.Rendering;

namespace AwesomeTechnologies.VegetationSystem
{
    public static class GeometryUtilityAllocFree
    {
        public static Plane[] FrustrumPlanes = new Plane[6];

        private static readonly Action<Plane[], Matrix4x4> InternalExtractPlanes =
            (Action<Plane[], Matrix4x4>)Delegate.CreateDelegate(
                typeof(Action<Plane[], Matrix4x4>),
                // ReSharper disable once AssignNullToNotNullAttribute
                typeof(GeometryUtility).GetMethod("Internal_ExtractPlanes",
                    BindingFlags.Static | BindingFlags.NonPublic));

        public static void CalculateFrustrumPlanes(Camera camera)
        {
            InternalExtractPlanes(FrustrumPlanes, camera.projectionMatrix * camera.worldToCameraMatrix);
        }
    }

    public partial class VegetationSystemPro
    {
        private void SetFrustumCullingPlanes(Camera selectedCamera)
        {
            GeometryUtilityAllocFree.CalculateFrustrumPlanes(selectedCamera);

            Vector4 cameraFrustumPlane0 = new Vector4(GeometryUtilityAllocFree.FrustrumPlanes[0].normal.x, GeometryUtilityAllocFree.FrustrumPlanes[0].normal.y, GeometryUtilityAllocFree.FrustrumPlanes[0].normal.z,
                GeometryUtilityAllocFree.FrustrumPlanes[0].distance);
            Vector4 cameraFrustumPlane1 = new Vector4(GeometryUtilityAllocFree.FrustrumPlanes[1].normal.x, GeometryUtilityAllocFree.FrustrumPlanes[1].normal.y, GeometryUtilityAllocFree.FrustrumPlanes[1].normal.z,
                GeometryUtilityAllocFree.FrustrumPlanes[1].distance);
            Vector4 cameraFrustumPlane2 = new Vector4(GeometryUtilityAllocFree.FrustrumPlanes[2].normal.x, GeometryUtilityAllocFree.FrustrumPlanes[2].normal.y, GeometryUtilityAllocFree.FrustrumPlanes[2].normal.z,
                GeometryUtilityAllocFree.FrustrumPlanes[2].distance);
            Vector4 cameraFrustumPlane3 = new Vector4(GeometryUtilityAllocFree.FrustrumPlanes[3].normal.x, GeometryUtilityAllocFree.FrustrumPlanes[3].normal.y, GeometryUtilityAllocFree.FrustrumPlanes[3].normal.z,
                GeometryUtilityAllocFree.FrustrumPlanes[3].distance);
            Vector4 cameraFrustumPlane4 = new Vector4(GeometryUtilityAllocFree.FrustrumPlanes[4].normal.x, GeometryUtilityAllocFree.FrustrumPlanes[4].normal.y, GeometryUtilityAllocFree.FrustrumPlanes[4].normal.z,
                GeometryUtilityAllocFree.FrustrumPlanes[4].distance);
            Vector4 cameraFrustumPlane5 = new Vector4(GeometryUtilityAllocFree.FrustrumPlanes[5].normal.x, GeometryUtilityAllocFree.FrustrumPlanes[5].normal.y, GeometryUtilityAllocFree.FrustrumPlanes[5].normal.z,
                GeometryUtilityAllocFree.FrustrumPlanes[5].distance);

            FrusumMatrixShader.SetVector(_cameraFrustumPlan0, cameraFrustumPlane0);
            FrusumMatrixShader.SetVector(_cameraFrustumPlan1, cameraFrustumPlane1);
            FrusumMatrixShader.SetVector(_cameraFrustumPlan2, cameraFrustumPlane2);
            FrusumMatrixShader.SetVector(_cameraFrustumPlan3, cameraFrustumPlane3);
            FrusumMatrixShader.SetVector(_cameraFrustumPlan4, cameraFrustumPlane4);
            FrusumMatrixShader.SetVector(_cameraFrustumPlan5, cameraFrustumPlane5);

            Vector3 worldSpaceCameraPosition = selectedCamera.transform.position;
            Vector4 worldSpaceCameraPos = new Vector4(worldSpaceCameraPosition.x, worldSpaceCameraPosition.y, worldSpaceCameraPosition.z, 1);
            //TODO change to ID on setVector
            FrusumMatrixShader.SetVector("_WorldSpaceCameraPos", worldSpaceCameraPos);
        }

        void SetupComputeShaders()
        {
            //_dummyMaskTexture = new Texture2D(2, 2);
            _dummyComputeBuffer = new ComputeBuffer(1, (16 * 4) + 16, ComputeBufferType.Default); // *2

            MergeBufferShader = (ComputeShader)Resources.Load("MergeInstancedIndirectBuffers");
            MergeBufferKernelHandle = MergeBufferShader.FindKernel("MergeInstancedIndirectBuffers");

            FrusumMatrixShader = (ComputeShader)Resources.Load("GPUFrustumCulling");
            FrustumKernelHandle = FrusumMatrixShader.FindKernel("GPUFrustumCulling");
            _mergeBufferID = Shader.PropertyToID("MergeBuffer");

            _floatingOriginOffsetID = Shader.PropertyToID("_FloatingOriginOffset");

            _mergeSourceBuffer0ID = Shader.PropertyToID("MergeSourceBuffer0");
            _mergeSourceBuffer1ID = Shader.PropertyToID("MergeSourceBuffer1");
            _mergeSourceBuffer2ID = Shader.PropertyToID("MergeSourceBuffer2");
            _mergeSourceBuffer3ID = Shader.PropertyToID("MergeSourceBuffer3");
            _mergeSourceBuffer4ID = Shader.PropertyToID("MergeSourceBuffer4");
            _mergeSourceBuffer5ID = Shader.PropertyToID("MergeSourceBuffer5");
            _mergeSourceBuffer6ID = Shader.PropertyToID("MergeSourceBuffer6");
            _mergeSourceBuffer7ID = Shader.PropertyToID("MergeSourceBuffer7");
            _mergeSourceBuffer8ID = Shader.PropertyToID("MergeSourceBuffer8");
            _mergeSourceBuffer9ID = Shader.PropertyToID("MergeSourceBuffer9");
            _mergeSourceBuffer10ID = Shader.PropertyToID("MergeSourceBuffer10");
            _mergeSourceBuffer11ID = Shader.PropertyToID("MergeSourceBuffer11");
            _mergeSourceBuffer12ID = Shader.PropertyToID("MergeSourceBuffer12");
            _mergeSourceBuffer13ID = Shader.PropertyToID("MergeSourceBuffer13");
            _mergeSourceBuffer14ID = Shader.PropertyToID("MergeSourceBuffer14");

            _mergeInstanceCount0ID = Shader.PropertyToID("MergeSourceBufferCount0");
            _mergeInstanceCount1ID = Shader.PropertyToID("MergeSourceBufferCount1");
            _mergeInstanceCount2ID = Shader.PropertyToID("MergeSourceBufferCount2");
            _mergeInstanceCount3ID = Shader.PropertyToID("MergeSourceBufferCount3");
            _mergeInstanceCount4ID = Shader.PropertyToID("MergeSourceBufferCount4");
            _mergeInstanceCount5ID = Shader.PropertyToID("MergeSourceBufferCount5");
            _mergeInstanceCount6ID = Shader.PropertyToID("MergeSourceBufferCount6");
            _mergeInstanceCount7ID = Shader.PropertyToID("MergeSourceBufferCount7");
            _mergeInstanceCount8ID = Shader.PropertyToID("MergeSourceBufferCount8");
            _mergeInstanceCount9ID = Shader.PropertyToID("MergeSourceBufferCount9");
            _mergeInstanceCount10ID = Shader.PropertyToID("MergeSourceBufferCount10");
            _mergeInstanceCount11ID = Shader.PropertyToID("MergeSourceBufferCount11");
            _mergeInstanceCount12ID = Shader.PropertyToID("MergeSourceBufferCount12");
            _mergeInstanceCount13ID = Shader.PropertyToID("MergeSourceBufferCount13");
            _mergeInstanceCount14ID = Shader.PropertyToID("MergeSourceBufferCount14");

            _cameraFrustumPlan0 = Shader.PropertyToID("_VS_CameraFrustumPlane0");
            _cameraFrustumPlan1 = Shader.PropertyToID("_VS_CameraFrustumPlane1");
            _cameraFrustumPlan2 = Shader.PropertyToID("_VS_CameraFrustumPlane2");
            _cameraFrustumPlan3 = Shader.PropertyToID("_VS_CameraFrustumPlane3");
            _cameraFrustumPlan4 = Shader.PropertyToID("_VS_CameraFrustumPlane4");
            _cameraFrustumPlan5 = Shader.PropertyToID("_VS_CameraFrustumPlane5");

            _instanceCountID = Shader.PropertyToID("_InstanceCount");
            _sourceBufferID = Shader.PropertyToID("SourceShaderDataBuffer");
            _visibleBufferLod0ID = Shader.PropertyToID("VisibleBufferLOD0");
            _visibleBufferLod1ID = Shader.PropertyToID("VisibleBufferLOD1");
            _visibleBufferLod2ID = Shader.PropertyToID("VisibleBufferLOD2");
            _visibleBufferLod3ID = Shader.PropertyToID("VisibleBufferLOD3");
            
            _shadowBufferLod0ID = Shader.PropertyToID("ShadowBufferLOD0");
            _shadowBufferLod1ID = Shader.PropertyToID("ShadowBufferLOD1");
            _shadowBufferLod2ID = Shader.PropertyToID("ShadowBufferLOD2");
            _shadowBufferLod3ID = Shader.PropertyToID("ShadowBufferLOD3");
            
            _lightDirection = Shader.PropertyToID("_LightDirection");
            _planeOrigin = Shader.PropertyToID("_PlaneOrigin");
            _boundsSize = Shader.PropertyToID("_BoundsSize");
            
            _cullFarStartID = Shader.PropertyToID("_CullFarStart");
            _visibleShaderDataBufferID = Shader.PropertyToID("VisibleShaderDataBuffer");
            _indirectShaderDataBufferID = Shader.PropertyToID("IndirectShaderDataBuffer");

            _useLodsID = Shader.PropertyToID("UseLODs");
            _noFrustumCullingID = Shader.PropertyToID("NoFrustumCulling");           
            _shadowCullingID = Shader.PropertyToID("ShadowCulling");
            
            _boundingSphereRadiusID = Shader.PropertyToID("_BoundingSphereRadius");

            _lod1Distance = Shader.PropertyToID("_LOD1Distance");
            _lod2Distance = Shader.PropertyToID("_LOD2Distance");
            _lod3Distance = Shader.PropertyToID("_LOD3Distance");

            _lodFactor = Shader.PropertyToID("_LODFactor");
            _lodBias = Shader.PropertyToID("_LODBias");
            _lodFadeDistance = Shader.PropertyToID("_LODFadeDistance");
            _lodCount = Shader.PropertyToID("_LODCount");
        }

        void DisposeComputeShaders()
        {
            _dummyComputeBuffer?.Dispose();
        }

        void DrawCellsIndirectComputeShader()
        {
            Profiler.BeginSample("Draw instanced indirect vegetation");

            float lodBias = QualitySettings.lodBias * VegetationSettings.LODDistanceFactor;

            Vector4 floatingOriginOffsetVector4 = new Vector4(FloatingOriginOffset.x,FloatingOriginOffset.y,FloatingOriginOffset.z,0);

            Vector3 sunLightDirection = SunDirectionalLight ? SunDirectionalLight.transform.forward : new Vector3(0, 0, 0);
            float minBoundsHeight = VegetationSystemBounds.center.y - VegetationSystemBounds.extents.y;
            Vector3 planeOrigin = new Vector3(0, minBoundsHeight, 0);
            bool shadowCulling = (SunDirectionalLight != null);
            
            for (int i = 0; i <= VegetationStudioCameraList.Count - 1; i++)
            {
                if (!VegetationStudioCameraList[i].Enabled) continue;
                if (VegetationStudioCameraList[i].RenderBillboardsOnly) continue;
                
                SetFrustumCullingPlanes(VegetationStudioCameraList[i].SelectedCamera);

                var targetCamera = VegetationStudioCameraList[i].RenderDirectToCamera
                    ? VegetationStudioCameraList[i].SelectedCamera
                    : null;

                for (int j = 0; j <= VegetationPackageProList.Count - 1; j++)
                {
                    for (int k = 0; k <= VegetationPackageProList[j].VegetationInfoList.Count - 1; k++)
                    {
                        VegetationItemInfoPro vegetationItemInfo = VegetationPackageProList[j].VegetationInfoList[k];
                        if (vegetationItemInfo.VegetationRenderMode != VegetationRenderMode.InstancedIndirect) continue;

                        VegetationItemModelInfo vegetationItemModelInfo =
                            VegetationPackageProModelsList[j].VegetationItemModelList[k];

                      
                        
                        float vegetationItemCullDistance;

                        float renderDistanceFactor =vegetationItemModelInfo.VegetationItemInfo.RenderDistanceFactor;
                        if (VegetationSettings.DisableRenderDistanceFactor) renderDistanceFactor = 1;

                        //if (vegetationItemModelInfo.DistanceBand == 0)
                        if (vegetationItemModelInfo.VegetationItemInfo.VegetationType == VegetationType.Tree || vegetationItemModelInfo.VegetationItemInfo.VegetationType == VegetationType.LargeObjects)
                        {
                            vegetationItemCullDistance = VegetationSettings.GetTreeDistance() * renderDistanceFactor;                            
                        }
                        else
                        {
                            vegetationItemCullDistance = VegetationSettings.GetVegetationDistance() * renderDistanceFactor;
                        }                    

                        ShadowCastingMode shadowCastingMode =
                            VegetationSettings.GetShadowCastingMode(vegetationItemInfo.VegetationType);

                        if (vegetationItemInfo.DisableShadows)
                        {
                            shadowCastingMode = ShadowCastingMode.Off;
                        }
                        
                        bool useShadowCulling = shadowCulling && shadowCastingMode == ShadowCastingMode.On;
                        useShadowCulling = useShadowCulling && vegetationItemModelInfo.DistanceBand == 1;

                        LayerMask layer = VegetationSettings.GetLayer(vegetationItemInfo.VegetationType);

                        int totalInstanceCount = 0;
                        _hasBufferList.Clear();
                        for (int l = 0;
                            l <= VegetationStudioCameraList[i].JobCullingGroup.VisibleCellIndexList.Length - 1;
                            l++)
                        {
                            int potentialVisibleCellIndex =
                                VegetationStudioCameraList[i].JobCullingGroup.VisibleCellIndexList[l];
                            VegetationCell vegetationCell = VegetationStudioCameraList[i]
                                .PotentialVisibleVegetationCellList[potentialVisibleCellIndex];

                            BoundingSphereInfo boundingSphereInfo = VegetationStudioCameraList[i]
                                .GetBoundingSphereInfo(potentialVisibleCellIndex);

                            int vegetationItemDistanceBand = vegetationItemModelInfo.DistanceBand;
                            
                            if (boundingSphereInfo.CurrentDistanceBand > vegetationItemDistanceBand)
                            {
                                continue;
                            }

                            if (vegetationCell.VegetationPackageInstancesList[j].VegetationItemMatrixList[k].Length == 0)
                            {
                                continue;
                            }
                            
                            ComputeBufferInfo computeBufferInfo = vegetationCell.VegetationPackageInstancesList[j]
                                .VegetationItemComputeBufferList[k];
                            if (!computeBufferInfo.Created)
                            {
                                continue;
                            }

                            _hasBufferList.Add(vegetationCell);
                        }

                        if (_hasBufferList.Count == 0) continue;

                        int buffercount = 15;
                        for (int m = 0; m <= _hasBufferList.Count - 1; m++)
                        {
                            totalInstanceCount += _hasBufferList[m].VegetationPackageInstancesList[j]
                                .VegetationItemMatrixList[k].Length;
                        }

                        if (totalInstanceCount == 0) continue;

                        CameraComputeBuffers cameraComputeBuffers = vegetationItemModelInfo.CameraComputeBufferList[i];

                        if (VegetationRenderSettings.EnableSinglePassInstancedVR)
                        {
                            if (totalInstanceCount * 2 > cameraComputeBuffers.MergeBuffer.count)
                            {
                                cameraComputeBuffers.UpdateComputeBufferSize(totalInstanceCount * 2+ 5000);
                            }
                        }
                        else
                        {
                            if (totalInstanceCount > cameraComputeBuffers.MergeBuffer.count)
                            {
                                cameraComputeBuffers.UpdateComputeBufferSize(totalInstanceCount + 5000);
                            }
                        }
                        

                        cameraComputeBuffers.MergeBuffer.SetCounterValue(0);
                        MergeBufferShader.SetBuffer(MergeBufferKernelHandle, _mergeBufferID,
                            cameraComputeBuffers.MergeBuffer);

                        for (int m = 0; m <= _hasBufferList.Count - 1; m += buffercount)
                        {
                            int instanceCount0 = _hasBufferList[m].VegetationPackageInstancesList[j]
                                .VegetationItemMatrixList[k].Length;

                            for (int n = 1; n <= buffercount - 1; n++)
                            {
                                if (m + n < _hasBufferList.Count)
                                {
                                    int tempInstanceCount = _hasBufferList[m + n].VegetationPackageInstancesList[j]
                                        .VegetationItemMatrixList[k].Length;
                                    if (tempInstanceCount > instanceCount0) instanceCount0 = tempInstanceCount;
                                }
                            }

                            // ReSharper disable once RedundantCast
                            int threadGroups = Mathf.CeilToInt((float)instanceCount0 / 32f);
                            if (threadGroups == 0) continue;

                            SetComputeShaderBuffer(_mergeSourceBuffer0ID, _mergeInstanceCount0ID, m, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer1ID, _mergeInstanceCount1ID, m + 1, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer2ID, _mergeInstanceCount2ID, m + 2, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer3ID, _mergeInstanceCount3ID, m + 3, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer4ID, _mergeInstanceCount4ID, m + 4, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer5ID, _mergeInstanceCount5ID, m + 5, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer6ID, _mergeInstanceCount6ID, m + 6, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer7ID, _mergeInstanceCount7ID, m + 7, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer8ID, _mergeInstanceCount8ID, m + 8, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer9ID, _mergeInstanceCount9ID, m + 9, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer10ID, _mergeInstanceCount10ID, m + 10, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer11ID, _mergeInstanceCount11ID, m + 11, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer12ID, _mergeInstanceCount12ID, m + 12, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer13ID, _mergeInstanceCount13ID, m + 13, j, k);
                            SetComputeShaderBuffer(_mergeSourceBuffer14ID, _mergeInstanceCount14ID, m + 14, j, k);

                            MergeBufferShader.Dispatch(MergeBufferKernelHandle, threadGroups, 1, 1);
                        }

                        for (int n = 0; n <= vegetationItemModelInfo.VegetationMeshLod0.subMeshCount - 1; n++)
                        {
                            ComputeBuffer.CopyCount(cameraComputeBuffers.MergeBuffer,
                                cameraComputeBuffers.ArgsBufferMergedLOD0List[n], sizeof(uint) * 1);
                        }

                        int threadGroupsFrustum = Mathf.CeilToInt(totalInstanceCount / 32f);
                        if (threadGroupsFrustum == 0) continue;

                        cameraComputeBuffers.VisibleBufferLOD0.SetCounterValue(0);
                        cameraComputeBuffers.VisibleBufferLOD1.SetCounterValue(0);
                        cameraComputeBuffers.VisibleBufferLOD2.SetCounterValue(0);
                        cameraComputeBuffers.VisibleBufferLOD3.SetCounterValue(0);
                        
                        cameraComputeBuffers.ShadowBufferLOD0.SetCounterValue(0);
                        cameraComputeBuffers.ShadowBufferLOD1.SetCounterValue(0);
                        cameraComputeBuffers.ShadowBufferLOD2.SetCounterValue(0);
                        cameraComputeBuffers.ShadowBufferLOD3.SetCounterValue(0);

                        bool useLODs = true;

                        FrusumMatrixShader.SetFloat(_cullFarStartID, vegetationItemCullDistance);

                        FrusumMatrixShader.SetVector(_floatingOriginOffsetID, floatingOriginOffsetVector4);

                        FrusumMatrixShader.SetBuffer(FrustumKernelHandle, _sourceBufferID,
                            cameraComputeBuffers.MergeBuffer);
                        FrusumMatrixShader.SetBuffer(FrustumKernelHandle, _visibleBufferLod0ID,
                            cameraComputeBuffers.VisibleBufferLOD0);
                        FrusumMatrixShader.SetBuffer(FrustumKernelHandle, _visibleBufferLod1ID,
                            cameraComputeBuffers.VisibleBufferLOD1);
                        FrusumMatrixShader.SetBuffer(FrustumKernelHandle, _visibleBufferLod2ID,
                            cameraComputeBuffers.VisibleBufferLOD2);
                        FrusumMatrixShader.SetBuffer(FrustumKernelHandle, _visibleBufferLod3ID,
                            cameraComputeBuffers.VisibleBufferLOD3);
                        
                        FrusumMatrixShader.SetBuffer(FrustumKernelHandle, _shadowBufferLod0ID,
                            cameraComputeBuffers.ShadowBufferLOD0);
                        FrusumMatrixShader.SetBuffer(FrustumKernelHandle, _shadowBufferLod1ID,
                            cameraComputeBuffers.ShadowBufferLOD1);
                        FrusumMatrixShader.SetBuffer(FrustumKernelHandle, _shadowBufferLod2ID,
                            cameraComputeBuffers.ShadowBufferLOD2);
                        FrusumMatrixShader.SetBuffer(FrustumKernelHandle, _shadowBufferLod3ID,
                            cameraComputeBuffers.ShadowBufferLOD3);
                        
                        FrusumMatrixShader.SetInt(_instanceCountID, totalInstanceCount);
                        FrusumMatrixShader.SetBool(_useLodsID, useLODs);
                        FrusumMatrixShader.SetBool(_noFrustumCullingID, VegetationStudioCameraList[i].CameraCullingMode == CameraCullingMode.Complete360);
                        FrusumMatrixShader.SetBool(_shadowCullingID, useShadowCulling);
                        
                        FrusumMatrixShader.SetFloat(_boundingSphereRadiusID,
                            vegetationItemModelInfo.BoundingSphereRadius);

                        FrusumMatrixShader.SetFloat(_lod1Distance, vegetationItemModelInfo.LOD1Distance);
                        FrusumMatrixShader.SetFloat(_lod2Distance, vegetationItemModelInfo.LOD2Distance);
                        FrusumMatrixShader.SetFloat(_lod3Distance, vegetationItemModelInfo.LOD3Distance);
                        
                        FrusumMatrixShader.SetVector(_lightDirection, sunLightDirection);
                        FrusumMatrixShader.SetVector(_planeOrigin, planeOrigin);
                        FrusumMatrixShader.SetVector(_boundsSize, vegetationItemModelInfo.VegetationItemInfo.Bounds.size);                                                                       

                        FrusumMatrixShader.SetFloat(_lodFactor, vegetationItemInfo.LODFactor);
                        FrusumMatrixShader.SetFloat(_lodBias, lodBias);
                        
                        float crossFadeDistance = 0;

                        if (vegetationItemModelInfo.VegetationItemInfo.EnableCrossFade)
                        {
                            crossFadeDistance = VegetationRenderSettings.CrossFadeDistance;
                        }

#if USING_HDRP
                        if (vegetationItemModelInfo.VegetationItemInfo.VegetationRenderMode ==
                            VegetationRenderMode.Instanced)
                        {
                            crossFadeDistance = 0;
                        }
#endif
                        
                        FrusumMatrixShader.SetFloat(_lodFadeDistance, crossFadeDistance);
                        FrusumMatrixShader.SetInt(_lodCount, vegetationItemModelInfo.LODCount);

                        FrusumMatrixShader.Dispatch(FrustumKernelHandle, threadGroupsFrustum, 1, 1);
                        if (VegetationRenderSettings.EnableSinglePassInstancedVR)
                        {
                            FrusumMatrixShader.Dispatch(FrustumKernelHandle, threadGroupsFrustum, 1, 1);
                        }

                        for (int n = 0; n <= vegetationItemModelInfo.VegetationMeshLod0.subMeshCount - 1; n++)
                        {
                            ComputeBuffer.CopyCount(cameraComputeBuffers.VisibleBufferLOD0,
                                cameraComputeBuffers.ArgsBufferMergedLOD0List[n], sizeof(uint) * 1);
                            
                            ComputeBuffer.CopyCount(cameraComputeBuffers.ShadowBufferLOD0,
                                cameraComputeBuffers.ShadowArgsBufferMergedLOD0List[n], sizeof(uint) * 1);
                        }

                        if (useLODs)
                        {
                            for (int n = 0; n <= vegetationItemModelInfo.VegetationMeshLod1.subMeshCount - 1; n++)
                            {
                                ComputeBuffer.CopyCount(cameraComputeBuffers.VisibleBufferLOD1,
                                    cameraComputeBuffers.ArgsBufferMergedLOD1List[n], sizeof(uint) * 1);
                                
                                ComputeBuffer.CopyCount(cameraComputeBuffers.ShadowBufferLOD1,
                                    cameraComputeBuffers.ShadowArgsBufferMergedLOD1List[n], sizeof(uint) * 1);
                            }

                            for (int n = 0; n <= vegetationItemModelInfo.VegetationMeshLod2.subMeshCount - 1; n++)
                            {                                                            
                                ComputeBuffer.CopyCount(cameraComputeBuffers.VisibleBufferLOD2,
                                    cameraComputeBuffers.ArgsBufferMergedLOD2List[n], sizeof(uint) * 1);
                                
                                ComputeBuffer.CopyCount(cameraComputeBuffers.ShadowBufferLOD2,
                                    cameraComputeBuffers.ShadowArgsBufferMergedLOD2List[n], sizeof(uint) * 1);
                            }

                            for (int n = 0; n <= vegetationItemModelInfo.VegetationMeshLod3.subMeshCount - 1; n++)
                            {
                                ComputeBuffer.CopyCount(cameraComputeBuffers.VisibleBufferLOD3,
                                    cameraComputeBuffers.ArgsBufferMergedLOD3List[n], sizeof(uint) * 1);
                                
                                ComputeBuffer.CopyCount(cameraComputeBuffers.ShadowBufferLOD3,
                                    cameraComputeBuffers.ShadowArgsBufferMergedLOD3List[n], sizeof(uint) * 1);                            
                            }
                        }

                        //TODO calculate bounds one for each LOD
                        float boundsDistance = vegetationItemCullDistance * 2 +
                                               vegetationItemModelInfo.BoundingSphereRadius;
                        Bounds cellBounds = new Bounds(VegetationStudioCameraList[i].SelectedCamera.transform.position,
                            new Vector3(boundsDistance, boundsDistance, boundsDistance));

                        RenderVegetationItemLODIndirect(vegetationItemModelInfo, cellBounds, 0, i,
                            targetCamera, shadowCastingMode, layer,false);

                        if (shadowCastingMode == ShadowCastingMode.On)
                        {
                            RenderVegetationItemLODIndirect(vegetationItemModelInfo, cellBounds, 0, i,
                                targetCamera, ShadowCastingMode.ShadowsOnly, layer,true);
                        }                                          
  
                        if (useLODs)
                        {
                            if (vegetationItemModelInfo.LODCount > 1)
                            {
                                RenderVegetationItemLODIndirect(vegetationItemModelInfo, cellBounds, 1, i,
                                    targetCamera, shadowCastingMode, layer,false);
                                
                                if (shadowCastingMode == ShadowCastingMode.On)
                                {
                                    RenderVegetationItemLODIndirect(vegetationItemModelInfo, cellBounds, 1, i,
                                        targetCamera, ShadowCastingMode.ShadowsOnly, layer,true);
                                }
                            }

                            if (vegetationItemModelInfo.LODCount > 2)
                            {
                                RenderVegetationItemLODIndirect(vegetationItemModelInfo, cellBounds, 2, i,
                                    targetCamera, shadowCastingMode, layer,false);

                                if (shadowCastingMode == ShadowCastingMode.On)
                                {
                                    RenderVegetationItemLODIndirect(vegetationItemModelInfo, cellBounds, 2, i,
                                        targetCamera, ShadowCastingMode.ShadowsOnly, layer,true);
                                }
                            }

                            if (vegetationItemModelInfo.LODCount > 3)
                            {
                                RenderVegetationItemLODIndirect(vegetationItemModelInfo, cellBounds, 3, i,
                                   targetCamera, shadowCastingMode, layer,false);
                                if (shadowCastingMode == ShadowCastingMode.On)
                                {
                                    RenderVegetationItemLODIndirect(vegetationItemModelInfo, cellBounds, 3, i,
                                        targetCamera, ShadowCastingMode.ShadowsOnly, layer,true);
                                }
                            }
                        }
                    }
                }
            }
            Profiler.EndSample();
        }

        void RenderVegetationItemLODIndirect(VegetationItemModelInfo vegetationItemModelInfo, Bounds cellBounds, int lodIndex, int cameraIndex, Camera selectedCamera, ShadowCastingMode shadowCastingMode, int layer, bool shadows)
        {
            MaterialPropertyBlock materialPropertyBlock = vegetationItemModelInfo.GetLODMaterialPropertyBlock(lodIndex);
            materialPropertyBlock.Clear();

            ComputeBuffer visibleBuffer = vegetationItemModelInfo.GetLODVisibleBuffer(lodIndex, cameraIndex,shadows);

            Mesh mesh = vegetationItemModelInfo.GetLODMesh(lodIndex);
            Material[] materials = vegetationItemModelInfo.GetLODMaterials(lodIndex);

            if (vegetationItemModelInfo.ShaderControler != null &&
                vegetationItemModelInfo.ShaderControler.Settings.SampleWind)
            {
                MeshRenderer meshRenderer = vegetationItemModelInfo.WindSamplerMeshRendererList[cameraIndex];
                if (meshRenderer)
                {
                    meshRenderer.GetPropertyBlock(materialPropertyBlock);
                }
            }

            materialPropertyBlock.SetBuffer(_visibleShaderDataBufferID, visibleBuffer);
            materialPropertyBlock.SetBuffer(_indirectShaderDataBufferID, visibleBuffer);

            List<ComputeBuffer> argsBufferList = vegetationItemModelInfo.GetLODArgsBufferList(lodIndex, cameraIndex,shadows);

            //int[] data = new int[5];
            //argsBufferList[0].GetData(data);
            //Debug.Log("Indirect - LOD" + lodIndex + ":" + data[1]);
            int submeshesToRender = Mathf.Min(mesh.subMeshCount, materials.Length);      
            for (int i = 0; i <= submeshesToRender - 1; i++)
            {
                Graphics.DrawMeshInstancedIndirect(mesh, i, materials[i], cellBounds,
                    argsBufferList[i], 0, materialPropertyBlock, shadowCastingMode, true, layer,
                    selectedCamera,LightProbeUsage.Off);
            }
        }

        void SetComputeShaderBuffer(int bufferID, int bufferCountID, int cellIndex, int vegetationPackageIndex, int vegetationItemIndex)
        {
            if (cellIndex < _hasBufferList.Count)
            {
                VegetationCell vegetationCell = _hasBufferList[cellIndex];
                int instanceCount = _hasBufferList[cellIndex].VegetationPackageInstancesList[vegetationPackageIndex].VegetationItemMatrixList[vegetationItemIndex].Length;
                MergeBufferShader.SetBuffer(MergeBufferKernelHandle, bufferID, vegetationCell.VegetationPackageInstancesList[vegetationPackageIndex].VegetationItemComputeBufferList[vegetationItemIndex].ComputeBuffer);
                MergeBufferShader.SetInt(bufferCountID, instanceCount);
            }
            else
            {
                MergeBufferShader.SetBuffer(MergeBufferKernelHandle, bufferID, _dummyComputeBuffer);
                MergeBufferShader.SetInt(bufferCountID, 0);
            }
        }
    }
}