// Copyright (C) 2019-2021 Alexander Bogarsukov. All rights reserved.
// See the LICENSE.md file in the project root for more information.

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Rendering.Universal;
using UnityEngine.XR;

namespace UnityFx.Outline.URP
{
	internal class OutlinePass : ScriptableRenderPass
	{
		//private const string _profilerTag = "OutlinePass";
		//private static readonly ProfilingSampler _profilingSampler = new ProfilingSampler(_profilerTag);

		private readonly OutlineFeature _feature;
		private readonly List<OutlineRenderObject> _renderObjects = new List<OutlineRenderObject>();
		private readonly List<ShaderTagId> _shaderTagIdList = new List<ShaderTagId>();

		private ScriptableRenderer _renderer;

		public OutlinePass(OutlineFeature feature, string[] shaderTags)
		{
			_feature = feature;

			if (shaderTags != null && shaderTags.Length > 0)
			{
				foreach (var passName in shaderTags)
				{
					_shaderTagIdList.Add(new ShaderTagId(passName));
				}
			}
			else
			{
				_shaderTagIdList.Add(new ShaderTagId("UniversalForward"));
				_shaderTagIdList.Add(new ShaderTagId("LightweightForward"));
				_shaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
			}
		}

		public void Setup(ScriptableRenderer renderer)
		{
			_renderer = renderer;
		}

		public class PassData {
			internal RendererListHandle rendererListHandle;
			internal IOutlineSettings outlineSettings;
			internal OutlineResources outlineResources;
			internal OutlineLayerCollection outlineLayers;
			internal int outlineLayerMask;
			internal uint outlineRenderingLayerMask;

			internal ScriptableRenderer renderer;
			internal List<OutlineRenderObject> renderObjects;

			internal TextureHandle cameraColorTarget;
			internal RenderTextureDescriptor targetDescriptor;
		}

		static void ExecuteDrawPass(PassData data, UnsafeGraphContext context)
        {
			if (data.outlineLayerMask != 0)
			{
				var depthTexture = new RenderTargetIdentifier("_CameraDepthTexture");

				if (data.outlineSettings.IsAlphaTestingEnabled())
				{
					context.cmd.SetGlobalFloat(data.outlineResources.AlphaCutoffId, data.outlineSettings.OutlineAlphaCutoff);
				}

				using (var renderer = new OutlineRenderer(CommandBufferHelpers.GetNativeCommandBuffer(context.cmd), data.outlineResources, data.cameraColorTarget, depthTexture, data.targetDescriptor))
				{
					renderer.RenderObjectClear(data.outlineSettings.OutlineRenderMode);
					context.cmd.DrawRendererList(data.rendererListHandle);
					renderer.RenderOutline(data.outlineSettings);
				}
			}

			if (data.outlineLayers)
			{
				var depthTexture = new RenderTargetIdentifier("_CameraDepthTexture");

				using (var renderer = new OutlineRenderer(CommandBufferHelpers.GetNativeCommandBuffer(context.cmd), data.outlineResources, data.cameraColorTarget, depthTexture, data.targetDescriptor))
				{
					data.renderObjects.Clear();
					data.outlineLayers.GetRenderObjects(data.renderObjects);
					renderer.Render(data.renderObjects);
				}
			}

			//context.cmd.DrawRendererList(data.rendererListHandle);
		}

		private void InitRendererLists(ContextContainer frameData, ref PassData passData, RenderGraph renderGraph)
        {
			UniversalRenderingData universalRenderingData = frameData.Get<UniversalRenderingData>();
			UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
			UniversalLightData lightData = frameData.Get<UniversalLightData>();
			
			SortingCriteria sortFlags = cameraData.defaultOpaqueSortFlags;
			RenderQueueRange renderQueueRange = RenderQueueRange.opaque;
			/* // or for transparents :
			sortFlags = SortingCriteria.CommonTransparent;
			renderQueueRange = RenderQueueRange.transparent;
			*/
			FilteringSettings filterSettings = new FilteringSettings(renderQueueRange, _feature.OutlineLayerMask);
			DrawingSettings drawSettings = RenderingUtils.CreateDrawingSettings(_shaderTagIdList, universalRenderingData, cameraData, lightData, sortFlags);
			drawSettings.overrideMaterialPassIndex = _feature.OutlineSettings.IsAlphaTestingEnabled()? 
				OutlineResources.RenderShaderAlphaTestPassId :
				OutlineResources.RenderShaderDefaultPassId;
			
			var param = new RendererListParams(universalRenderingData.cullResults, drawSettings, filterSettings);
			passData.rendererListHandle = renderGraph.CreateRendererList(param);
		}
	
		public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
			UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
			UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();

			// Draw Renderers
			using (var builder = renderGraph.AddUnsafePass<PassData>("Custom DrawRendererList Pass", out var passData))
			{
				// Init PassData
				InitRendererLists(frameData, ref passData, renderGraph);
				if (!passData.rendererListHandle.IsValid()) return;

				passData.outlineSettings = _feature.OutlineSettings;
				passData.outlineResources = _feature.OutlineResources;
				passData.outlineLayers = _feature.OutlineLayers;
				passData.outlineLayerMask = _feature.OutlineLayerMask;
				passData.outlineRenderingLayerMask = _feature.OutlineRenderingLayerMask;
				passData.cameraColorTarget = resourceData.cameraColor;
				passData.targetDescriptor = cameraData.cameraTargetDescriptor;
				passData.renderer = _renderer;
				
				passData.renderObjects = _renderObjects;

				// Declare Inputs
				builder.UseRendererList(passData.rendererListHandle);

				// Set Render Target
				//builder.SetRenderAttachment(resourceData.activeColorTexture, 0);
				//builder.SetRenderAttachmentDepth(resourceData.activeDepthTexture);

				// Assign ExecutePass
				builder.SetRenderFunc((PassData data, UnsafeGraphContext context) => ExecuteDrawPass(data, context));
			}
        }
	}
}