using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering.RenderGraphModule.NativeRenderPassCompiler;

namespace UnityEngine.Rendering.RenderGraphModule
{
    public partial class RenderGraph
    {
        internal static class RenderGraphExceptionMessages
        {
            internal static bool enableCaller = true;

            internal const string k_RenderGraphExecutionError = "Render Graph Execution error";

            static readonly Dictionary<RenderGraphState, string> m_RenderGraphStateMessages = new()
            {
                {
                    RenderGraphState.RecordingPass,
                    "This API cannot be called when Render Graph records a pass. Call the API within SetRenderFunc() or outside of AddUnsafePass()/AddComputePass()/AddRasterRenderPass()."
                },
                {
                    RenderGraphState.RecordingGraph,
                    "This API cannot be called during the Render Graph high-level recording step. Call the API within AddUnsafePass()/AddComputePass()/AddRasterRenderPass() or outside of RecordRenderGraph()."
                },
                {
                    RenderGraphState.RecordingPass | RenderGraphState.Executing,
                    "This API cannot be called when Render Graph records a pass or executes it. Call the API outside of AddUnsafePass()/AddComputePass()/AddRasterRenderPass()."
                },
                {
                    RenderGraphState.Executing,
                    "This API cannot be called during the Render Graph execution. Call the API outside of SetRenderFunc()."
                },
                {
                    RenderGraphState.Active,
                    "This API cannot be called when Render Graph is active. Call the API outside of RecordRenderGraph()."
                }
            };

            // General Errors
            const string k_ErrorDefaultMessage = "Invalid render graph state, impossible to log the exception.";

            internal const string k_NonTextureAsAttachmentError = "Only textures can be used as a fragment attachment.";

            // Compiler Context Data
            internal const string k_OneResourceTwoVersionsError =
                "A pass is using SetAttachment or UseTexture on two versions of the same resource. Make sure you only access the latest version.";

            internal const string k_UseTextureRandWriteTwoVersionsError =
                "A pass is using UseTextureRandomWrite on two versions of the same resource.  Make sure you only access the latest version.";

            // Passes Data
            internal const string k_InvalidGetRenderTargetInfoResultsError =
                "GetRenderTargetInfo returned invalid results. Check that the width, height, and number of MSAA samples is not 0.";

            internal const string k_CannotDetermineSubPassFlagNoDepth =
                "SubPassFlag for merging cannot be determined if native pass doesn't have a depth attachment. Make sure your pass has a depth attachment.";

            internal const string k_AddingOlderAttachmentVersion = "The pass adds an older version while a higher version is already registered with the pass. Make sure you only access the latest version.";

            // Users shouldn't be seeing these, they are a sort of catch for internal RG mistakes.
            internal const string k_NonIncrementalCreationCall =
                "Something went wrong when compiling the graph. The Creation lists must be set-up incrementally for all passes, but AddFirstUse is called in an arbitrary non-incremental way.";

            internal const string k_NonIncrementalDestructionCall =
                "Something went wrong when compiling the graph. The Destruction lists must be set-up incrementally for all passes, AddLastUse is called in an arbitrary non-incremental way.";

            internal static string MismatchInDimensions(string name, int fragWidth, int fragHeight, int fragVolumeDepth,
                ResourceUnversionedData resInfo)
                =>
                    $"Mismatch in fragment dimensions when using resource '{name}'. Expected {fragWidth} x {fragHeight} x {fragVolumeDepth} " +
                    $"but got {resInfo.width} x {resInfo.height} x {resInfo.volumeDepth} instead.";

            internal static string MismatchInMSAASamlpes(string name, int expectedSamples, int actualSamples)
            {
                var expectedSamplesString = expectedSamples == 1 ? "None" : expectedSamples.ToString();
                var actualSamplesString = actualSamples == 1 ? "None" : actualSamples.ToString();
                return $"Mismatch in number of MSAA samples when using resource '{name}'. Expected {expectedSamplesString} but got {actualSamplesString} instead.";
            }

            // RenderGraphBuilders
            internal const string k_UndisposedBuilderPreviousPass =
                "Finish building the previous pass first by disposing of the pass builder object before adding a new pass. You can manually dispose of the builder with 'builder.Dispose()'.";

            internal const string k_WriteToVersionedResource =
                "The pass writes to a versioned resource handle. You can only write to unversioned resource handles to avoid branches in the resource history.";

            internal const string k_WriteToResourceTwice = "The pass writes to a resource twice. You can only write the same resource once within a pass.";

            internal const string k_TextureAlreadyBeingUsedThroughSetAttachment =
                "UseTexture is called on a texture that is already used through SetRenderAttachment. Check your code and make sure the texture is only used once.";

            internal const string k_SetRenderAttachmentTextureAlreadyUsed =
                "SetRenderAttachment is called on a texture that is already used through UseTexture/SetRenderAttachment. Check your code and make sure the texture is only used once.";

            internal const string k_SetRenderAttachmentOnDepthTexture =
                "SetRenderAttachment is called on a texture that has a depth format. Use a texture with a color format instead, or call SetRenderDepthAttachment.";

            internal const string k_SetRenderAttachmentOnGlobalTexture =
                "SetRenderAttachment is called on a texture that is currently bound to a global texture slot. Shaders might be using the texture using samplers. Make sure textures are not set as globals when using them as fragment attachments.";

            internal const string k_InvalidResource = "Using an invalid resource. Invalid resources can be resources leftover from a previous execution.";

            internal const string k_ReadWriteTransient =
                "This pass is reading or writing a transient resource. Transient resources are always assumed to be both read and written using 'AccessFlags.ReadWrite'.";

            internal static string NoGlobalTextureAtPropertyID(int propertyId) =>
                $"This pass is trying to read the global texture property {propertyId} but no previous pass in the graph bound a value to this global.";

            internal static string UseDepthWithColorFormat(GraphicsFormat colorFormat) =>
                $"SetRenderAttachmentDepth is called on a texture that has a color format {colorFormat}. Use a texture with a depth format instead, or call SetRenderAttachment.";

            internal static string UseTransientTextureInWrongPass(int transientIndex) =>
                $"This pass is using a transient resource from a different pass (pass index {transientIndex}). A transient resource should only be used in a single pass.";

            internal static string IncompatibleTextureUVOrigin(TextureUVOriginSelection origin, string attachmentType, string attachmentName, RenderGraphResourceType attachmentResourceType, int attachmentResourceIndex, TextureUVOriginSelection attachmentOrigin) =>
                $"TextureUVOrigin `{origin}` is not compatible with existing {attachmentType} attachment `{attachmentType}` of type `{attachmentResourceType}` at index `{attachmentResourceIndex}` with TextureUVOrigin `{attachmentOrigin}`";

            internal static string IncompatibleTextureUVOriginUseTexture(TextureUVOriginSelection origin) =>
                $"UseTexture() of a resource with `{origin}` is not compatible with Unity's standard UV origin for texture reading {TextureUVOrigin.BottomLeft}. Are you trying to UseTexture() on a backbuffer?";

            // RenderGraphPass
            internal const string k_MoreThanOneResourceForMRTIndex =
                "You can only bind a single texture to a single index in a multiple render texture (MRT). Verify your indexes are correct.";

            internal const string k_MoreThanOneTextureForFragInputIndex =
                "You can only bind a single texture to a fragment input index. Verify your indexes are correct.";

            internal const string k_MoreThanOneTextureRandomWriteInputIndex =
                "You can only bind a single texture to a random write input index. Verify your indexes are correct.";

            internal const string k_MultipleDepthTextures = "You can only set a single depth texture per pass.";

            //NativePassCompiler
            internal const string k_LoadingMemorylessResource = "This pass is loading a resource marked as memoryless.";

            internal const string k_ResolvignMemorylessResource =
                "This pass is storing or resolving a resource marked as memoryless";

            internal const string k_RenderPassIsEmpty = "Empty render pass";

            internal const string k_RenderPassHasInvalidProperties =
                "Invalid render pass properties. One or more properties are zero.";

            internal const string k_ShadingRateImageAttachmentDoesNotMatch =
                "Low level rendergraph error: Shading rate image attachment in renderpass does not match.";

            internal const string k_AttachmentsDoNotMatch =
                "Low level rendergraph error: Attachments in renderpass do not match.";

            internal const string k_MultisampledShaderResolveInvalidAttachmentSetup =
                "Low level rendergraph error: last subpass with shader resolve must have one color attachment.";

            internal const string k_MultisampledShaderResolveInputAttachmentNotMemoryless =
                "Low level rendergraph error: last subpass with shader resolve must have all input attachments as memoryless attachments.";

            internal const string k_InvalidMRTSetup = "Multiple render texture (MRT) setup is invalid. Some indices are not used.";

            internal const string k_NoDepthBufferMRT = "Setting multiple render textures (MRTs) without a depth buffer is not supported.";

            internal const string k_InvalidDepthAndColorTargets = "Neither depth nor color render targets are correctly set up.";

            internal const string k_InvalidResourceType = "Invalid resource type, expected texture or buffer";

            internal const string k_NoRenderFunction = "RenderPass was not provided with an execute function.";

            internal const string k_BeginNoActivePass =
                "Compiler error: Pass is marked as beginning a native sub pass but no pass is currently active.";

            internal const string k_NoActivePassForSubpass =
                "Compiler error: Generated a subpass pass but no pass is currently active.";
            internal static string UsingLegacyRenderGraph(string passName) =>
                "Pass '" + passName + "' is using the legacy rendergraph API." +
                " You cannot use legacy passes with the Native Render Pass Compiler." +
                " The APIs that are compatible with the Native Render Pass Compiler are AddUnsafePass, AddComputePass and AddRasterRenderPass.";

            internal static string IncompatibleTextureUVOriginStore(string firstAttachmentName, TextureUVOriginSelection firstAttachmentOrigin, string secondAttachmentName, TextureUVOriginSelection secondAttachmentOrigin) =>
                $"Texture attachment {firstAttachmentName} with uv origin {firstAttachmentOrigin} does not match with texture attachment {secondAttachmentName} with uv origin {secondAttachmentOrigin}. Storing both would result in contents being flipped.";

            internal static string GetExceptionMessage(RenderGraphState state)
            {
                string caller = GetHigherCaller();
                if (!m_RenderGraphStateMessages.TryGetValue(state, out var messageException))
                {
                    return enableCaller ? $"[{caller}] {k_ErrorDefaultMessage}" : k_ErrorDefaultMessage;
                }

                return enableCaller ? $"[{caller}] {messageException}" : messageException;
            }

            static string GetHigherCaller()
            {
                // k_CurrentStackCaller is used here to skip three levels in the call stack:
                // Level 0: GetHigherCaller() itself.
                // Level 1: GetExceptionMessage() or any other wrapper method that calls GetHigherCaller().
                // Level 2: The function that directly calls GetMessage() (e.g CheckNotUsedWhenExecute).
                // Level 3: The actual function we are interested in, our public API.
                const int k_CurrentStackCaller = 3;

                var stackTrace = new StackTrace(k_CurrentStackCaller, false);

                if (stackTrace.FrameCount > 0)
                {
                    var frame = stackTrace.GetFrame(0);
                    return frame?.GetMethod()?.Name ?? "UnknownCaller";
                }

                return "UnknownCaller";
            }
        }
    }
}