using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor.Graphing;
using UnityEngine;

namespace UnityEditor.ShaderGraph
{
    sealed partial class GraphData : ISerializationCallbackReceiver
    {
        public static class GraphValidation
        {
            static Dictionary<Type, FieldInfo> s_ActiveSubTarget = new();
            static readonly PropertyInfo s_JsonData = typeof(Serialization.JsonData<SubTarget>).GetProperty("value");

            static SubTarget GetActiveSubTarget(Target target)
            {
                if (target.activeSubTarget != null && target.activeSubTarget.IsActive())
                    return target.activeSubTarget;

                var type = target.GetType();
                if (!s_ActiveSubTarget.TryGetValue(type, out var activeSubTarget))
                {
                    activeSubTarget = type.GetField("m_ActiveSubTarget", BindingFlags.Instance | BindingFlags.NonPublic);
                    s_ActiveSubTarget.Add(type, activeSubTarget);
                }
                if (activeSubTarget != null)
                {
                    var jsonData = activeSubTarget.GetValue(target);
                    if (jsonData != null)
                    {
                        return s_JsonData.GetValue(jsonData) as SubTarget;
                    }
                }
                return null;
            }

            public static void ValidateNode(AbstractMaterialNode node)
            {
                Type t = node.GetType();
                node.ValidateNode();
                if (!(node is BlockNode))
                {
                    bool disallowedByAnyTargets = false;
                    bool disallowedByAllTargets = true;
                    bool disallowedByAnySubTarget = false;
                    IEnumerable<Target> targets = node.owner.activeTargets;

                    foreach (var target in targets)
                    {
                        var subtarget = GetActiveSubTarget(target);
                        if (subtarget != null && (!subtarget.IsNodeAllowedBySubTarget(t) || !node.IsSubTargetCompatible(subtarget.GetType())))
                        {
                            disallowedByAnySubTarget = true;
                            node.isValid = false;
                            node.owner.AddValidationError(node.objectId, $"{node.name} Node is not allowed by {subtarget.displayName} implementation", Rendering.ShaderCompilerMessageSeverity.Error);
                        }
                        //if at least one target doesn't allow a node, it is considered invalid
                        else if (!target.IsNodeAllowedByTarget(t))
                        {
                            disallowedByAnyTargets = true;
                            node.isValid = false;
                            node.owner.AddValidationError(node.objectId, $"{node.name} Node is not allowed by {target.displayName} implementation", Rendering.ShaderCompilerMessageSeverity.Warning);
                            node.owner.m_UnsupportedTargets.Add(target);
                        }
                        //at least one target does allow node, not going to be explicitly set inactive
                        else
                        {
                            disallowedByAllTargets = false;
                        }

                        if (subtarget != null && subtarget.ValidateNodeCompatibility(node, out string warningMessage, out Rendering.ShaderCompilerMessageSeverity severity))
                        {
                            if (severity == Rendering.ShaderCompilerMessageSeverity.Error)
                            {
                                disallowedByAnySubTarget = true;
                                node.isValid = false;
                            }
                            node.owner.AddValidationError(node.objectId, warningMessage, severity);
                        }
                    }

                    // Subgraphs have no allegiance to a Target/SubTarget workflow,
                    // but we can infer incompatibilities when SRPFilter and SubTargetFilter are
                    // similarly incompatible across nodes in a subgraph.
                    // Unfortunately, there isn't a good way to promote these compatibilities to
                    // the main graph without quite a bit more work.
                    if (node.owner.isSubGraph)
                    {
                        disallowedByAllTargets = false;
                        System.Text.StringBuilder sb = new();
                        if (!node.IsCompatibleWithSRPs(srpSet, out var badSrps))
                        {
                            disallowedByAnyTargets = true;
                            sb.Append("Nodes in subgraph have conflicting SRP restrictions; ");
                            foreach (var srp in badSrps)
                                sb.Append($"{srp.Name}, ");
                            sb.Remove(sb.Length - 2, 2);
                            sb.AppendLine();
                        }
                        if (!node.IsCompatibleWithSubTargetFilters(subTargetSet, out var badSubTargets))
                        {
                            disallowedByAnySubTarget = true;
                            sb.Append("Nodes in subgraph have conflicting Material restrictions; ");
                            foreach (var subTarget in badSubTargets)
                                sb.Append($"{subTarget.Name}, ");
                            sb.Remove(sb.Length - 2, 2);
                        }
                        if (disallowedByAnySubTarget || disallowedByAnyTargets)
                        {
                            node.isValid = false;
                            node.owner.AddValidationError(node.objectId, sb.ToString(), Rendering.ShaderCompilerMessageSeverity.Warning);
                        }
                    }

                    if (!disallowedByAnyTargets && !disallowedByAnySubTarget)
                    {
                        node.isValid = true;
                    }

                    //Set ActiveState based on if all targets disallow this node
                    if (disallowedByAllTargets)
                    {
                        node.SetOverrideActiveState(AbstractMaterialNode.ActiveState.ExplicitInactive);
                        node.owner.AddValidationError(node.objectId, $"{node.name} Node is not allowed by any active targets, and will not be used in generation", Rendering.ShaderCompilerMessageSeverity.Warning);
                    }
                    else
                    {
                        node.SetOverrideActiveState(AbstractMaterialNode.ActiveState.Implicit);
                    }
                }
            }

            static HashSet<Type> srpSet;
            static HashSet<Type> subTargetSet;
            public static void ValidateGraph(GraphData graph)
            {
                graph.m_UnsupportedTargets.Clear();

                srpSet = new();
                subTargetSet = new();

                if (graph.isSubGraph)
                {
                    foreach(var node in graph.GetNodes<AbstractMaterialNode>())
                    {
                        node.GatherSRPCompatibility(ref srpSet);
                        node.GatherSubTargetCompatibility(ref subTargetSet);
                    }
                }
                GraphDataUtils.ApplyActionLeafFirst(graph, ValidateNode);
            }
        }
    }
}