using System;
using System.Collections.Generic;
using UnityEngine;
namespace XNode
{
    /// 
    /// Base class for all nodes
    /// 
    /// 
    /// Classes extending this class will be considered as valid nodes by xNode.
    /// 
    /// [System.Serializable]
    /// public class Adder : Node {
    ///     [Input] public float a;
    ///     [Input] public float b;
    ///     [Output] public float result;
    ///
    ///     // GetValue should be overridden to return a value for any specified output port
    ///     public override object GetValue(NodePort port) {
    ///         return a + b;
    ///     }
    /// }
    /// 
    /// 
    [Serializable]
    public abstract class Node : ScriptableObject
    {
        ///  Used by  and  to determine when to display the field value associated with a  
        public enum ShowBackingValue
        {
            ///  Never show the backing value 
            Never,
            ///  Show the backing value only when the port does not have any active connections 
            Unconnected,
            ///  Always show the backing value 
            Always
        }
        public enum ConnectionType
        {
            ///  Allow multiple connections
            Multiple,
            ///  always override the current connection 
            Override,
        }
        ///  Tells which types of input to allow 
        public enum TypeConstraint
        {
            ///  Allow all types of input
            None,
            ///  Allow connections where input value type is assignable from output value type (eg. ScriptableObject --> Object)
            Inherited,
            ///  Allow only similar types 
            Strict,
            ///  Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)
            InheritedInverse,
        }
        public virtual bool IsRemove()
        {
            return true;
        }
        #region Obsolete
        [Obsolete("Use DynamicPorts instead")]
        public IEnumerable InstancePorts
        {
            get { return DynamicPorts; }
        }
        [Obsolete("Use DynamicOutputs instead")]
        public IEnumerable InstanceOutputs
        {
            get { return DynamicOutputs; }
        }
        [Obsolete("Use DynamicInputs instead")]
        public IEnumerable InstanceInputs
        {
            get { return DynamicInputs; }
        }
        [Obsolete("Use AddDynamicInput instead")]
        public NodePort AddInstanceInput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
            string fieldName = null)
        {
            return AddDynamicInput(type, connectionType, typeConstraint, fieldName);
        }
        [Obsolete("Use AddDynamicOutput instead")]
        public NodePort AddInstanceOutput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
            string fieldName = null)
        {
            return AddDynamicOutput(type, connectionType, typeConstraint, fieldName);
        }
        [Obsolete("Use AddDynamicPort instead")]
        private NodePort AddInstancePort(Type type, NodePort.IO direction, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
            string fieldName = null)
        {
            return AddDynamicPort(type, direction, connectionType, typeConstraint, fieldName);
        }
        [Obsolete("Use RemoveDynamicPort instead")]
        public void RemoveInstancePort(string fieldName)
        {
            RemoveDynamicPort(fieldName);
        }
        [Obsolete("Use RemoveDynamicPort instead")]
        public void RemoveInstancePort(NodePort port)
        {
            RemoveDynamicPort(port);
        }
        [Obsolete("Use ClearDynamicPorts instead")]
        public void ClearInstancePorts()
        {
            ClearDynamicPorts();
        }
        #endregion
        ///  Iterate over all ports on this node. 
        public IEnumerable Ports
        {
            get
            {
                foreach (NodePort port in ports.Values) yield return port;
            }
        }
        ///  Iterate over all outputs on this node. 
        public IEnumerable Outputs
        {
            get
            {
                foreach (NodePort port in Ports)
                {
                    if (port.IsOutput) yield return port;
                }
            }
        }
        ///  Iterate over all inputs on this node. 
        public IEnumerable Inputs
        {
            get
            {
                foreach (NodePort port in Ports)
                {
                    if (port.IsInput) yield return port;
                }
            }
        }
        ///  Iterate over all dynamic ports on this node. 
        public IEnumerable DynamicPorts
        {
            get
            {
                foreach (NodePort port in Ports)
                {
                    if (port.IsDynamic) yield return port;
                }
            }
        }
        ///  Iterate over all dynamic outputs on this node. 
        public IEnumerable DynamicOutputs
        {
            get
            {
                foreach (NodePort port in Ports)
                {
                    if (port.IsDynamic && port.IsOutput) yield return port;
                }
            }
        }
        ///  Iterate over all dynamic inputs on this node. 
        public IEnumerable DynamicInputs
        {
            get
            {
                foreach (NodePort port in Ports)
                {
                    if (port.IsDynamic && port.IsInput) yield return port;
                }
            }
        }
        ///  Parent  
        [SerializeField] public NodeGraph graph;
        ///  Position on the  
        [SerializeField] public Vector2 position;
        [NodeSerialize("GUID")] [SerializeField]
        public int guid;
        [HideInInspector] [SerializeField] private int uniqueID;
        /// 
        /// 唯一ID,首次创建的时候赋值
        /// 
        public int UniqueID
        {
            get
            {
                if (uniqueID == 0)
                {
                    uniqueID = GetInstanceID();
                }
                return uniqueID;
            }
            set
            {
                if (UniqueID == 0)
                {
                    uniqueID = value;
                }
            }
        }
        /// 
        /// 复制组件的时候调用
        /// 
        public void ResetUniqueID()
        {
            uniqueID = GetInstanceID();
        }
        ///  It is recommended not to modify these at hand. Instead, see  and  
        [SerializeField] private NodePortDictionary ports = new NodePortDictionary();
        ///  Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable 
        public static NodeGraph graphHotfix;
        protected void OnEnable()
        {
            if (graphHotfix != null) graph = graphHotfix;
            graphHotfix = null;
            UpdatePorts();
            Init();
        }
        ///  Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. This happens automatically on enable or on redrawing a dynamic port list. 
        public void UpdatePorts()
        {
            NodeDataCache.UpdatePorts(this, ports);
        }
        ///  Initialize node. Called on enable. 
        public virtual void Init()
        {
        }
        ///  Checks all connections for invalid references, and removes them. 
        public void VerifyConnections()
        {
            foreach (NodePort port in Ports) port.VerifyConnections();
        }
        #region Dynamic Ports
        ///  Convenience function. 
        /// 
        /// 
        public NodePort AddDynamicInput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null)
        {
            return AddDynamicPort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName);
        }
        ///  Convenience function. 
        /// 
        /// 
        public NodePort AddDynamicOutput(Type type, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
            string fieldName = null)
        {
            return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
        }
        ///  Add a dynamic, serialized port to this node. 
        /// 
        /// 
        private NodePort AddDynamicPort(Type type, NodePort.IO direction, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
            string fieldName = null)
        {
            if (fieldName == null)
            {
                fieldName = "dynamicInput_0";
                int i = 0;
                while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i);
            }
            else if (HasPort(fieldName))
            {
                Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
                return ports[fieldName];
            }
            NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
            ports.Add(fieldName, port);
            return port;
        }
        ///  Remove an dynamic port from the node 
        public void RemoveDynamicPort(string fieldName)
        {
            NodePort dynamicPort = GetPort(fieldName);
            if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist");
            RemoveDynamicPort(GetPort(fieldName));
        }
        ///  Remove an dynamic port from the node 
        public void RemoveDynamicPort(NodePort port)
        {
            if (port == null) throw new ArgumentNullException("port");
            else if (port.IsStatic) throw new ArgumentException("cannot remove static port");
            port.ClearConnections();
            ports.Remove(port.fieldName);
        }
        ///  Removes all dynamic ports from the node 
        [ContextMenu("Clear Dynamic Ports")]
        public void ClearDynamicPorts()
        {
            List dynamicPorts = new List(DynamicPorts);
            foreach (NodePort port in dynamicPorts)
            {
                RemoveDynamicPort(port);
            }
        }
        #endregion
        #region Ports
        ///  Returns output port which matches fieldName 
        public NodePort GetOutputPort(string fieldName)
        {
            NodePort port = GetPort(fieldName);
            if (port == null || port.direction != NodePort.IO.Output) return null;
            else return port;
        }
        ///  Returns input port which matches fieldName 
        public NodePort GetInputPort(string fieldName)
        {
            NodePort port = GetPort(fieldName);
            if (port == null || port.direction != NodePort.IO.Input) return null;
            else return port;
        }
        ///  Returns port which matches fieldName 
        public NodePort GetPort(string fieldName)
        {
            NodePort port;
            if (ports.TryGetValue(fieldName, out port)) return port;
            else return null;
        }
        public bool HasPort(string fieldName)
        {
            return ports.ContainsKey(fieldName);
        }
        #endregion
        #region Inputs/Outputs
        ///  Return input value for a specified port. Returns fallback value if no ports are connected 
        /// Field name of requested input port
        /// If no ports are connected, this value will be returned
        public T GetInputValue(string fieldName, T fallback = default(T))
        {
            NodePort port = GetPort(fieldName);
            if (port != null && port.IsConnected) return port.GetInputValue();
            else return fallback;
        }
        ///  Return all input values for a specified port. Returns fallback value if no ports are connected 
        /// Field name of requested input port
        /// If no ports are connected, this value will be returned
        public T[] GetInputValues(string fieldName, params T[] fallback)
        {
            NodePort port = GetPort(fieldName);
            if (port != null && port.IsConnected) return port.GetInputValues();
            else return fallback;
        }
        ///  Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. 
        /// The requested port.
        public virtual object GetValue(NodePort port)
        {
            Debug.LogWarning("No GetValue(NodePort port) override defined for " + GetType());
            return null;
        }
        #endregion
        ///  Called after a connection between two s is created 
        /// Output Input
        public virtual void OnCreateConnection(NodePort from, NodePort to)
        {
        }
        ///  Called after a connection is removed from this port 
        /// Output or Input
        public virtual void OnRemoveConnection(NodePort port)
        {
        }
        ///  Disconnect everything from this node 
        public void ClearConnections()
        {
            foreach (NodePort port in Ports) port.ClearConnections();
        }
        #region Attributes
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
        public class NodeSerializeAttribute : Attribute
        {
            public string showName;
            public string tooltip;
            public int showType;
            public bool isDisable;
            public NodeSerializeAttribute(string showName, string tooltip = "", int showType = 0, bool isDisable = false)
            {
                this.showName = showName;
                this.tooltip = tooltip;
                this.showType = showType;
                this.isDisable = isDisable;
            }
        }
        /// 
        /// 这里暂时用来处理RandomNode的
        /// 
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
        public class NodeSerializeAttributeSpecial : Attribute
        {
        }
        // [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
        // public class DisabledAttribute : Attribute
        // {
        // }
        ///  Mark a serializable field as an input port. You can access this through  
        [AttributeUsage(AttributeTargets.Field)]
        public class InputAttribute : Attribute
        {
            public ShowBackingValue backingValue;
            public ConnectionType connectionType;
            [Obsolete("Use dynamicPortList instead")]
            public bool instancePortList
            {
                get { return dynamicPortList; }
                set { dynamicPortList = value; }
            }
            public bool dynamicPortList;
            public TypeConstraint typeConstraint;
            ///  Mark a serializable field as an input port. You can access this through  
            /// Should we display the backing value for this port as an editor field? 
            /// Should we allow multiple connections? 
            /// Constrains which input connections can be made to this port 
            /// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays 
            public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple,
                TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false)
            {
                this.backingValue = backingValue;
                this.connectionType = connectionType;
                this.dynamicPortList = dynamicPortList;
                this.typeConstraint = typeConstraint;
            }
        }
        ///  Mark a serializable field as an output port. You can access this through  
        [AttributeUsage(AttributeTargets.Field)]
        public class OutputAttribute : Attribute
        {
            public ShowBackingValue backingValue;
            public ConnectionType connectionType;
            [Obsolete("Use dynamicPortList instead")]
            public bool instancePortList
            {
                get { return dynamicPortList; }
                set { dynamicPortList = value; }
            }
            public bool dynamicPortList;
            public TypeConstraint typeConstraint;
            ///  Mark a serializable field as an output port. You can access this through  
            /// Should we display the backing value for this port as an editor field? 
            /// Should we allow multiple connections? 
            /// Constrains which input connections can be made from this port 
            /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays 
            public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,
                bool dynamicPortList = false)
            {
                this.backingValue = backingValue;
                this.connectionType = connectionType;
                this.dynamicPortList = dynamicPortList;
                this.typeConstraint = typeConstraint;
            }
            ///  Mark a serializable field as an output port. You can access this through  
            /// Should we display the backing value for this port as an editor field? 
            /// Should we allow multiple connections? 
            /// If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays 
            [Obsolete("Use constructor with TypeConstraint")]
            public OutputAttribute(ShowBackingValue backingValue, ConnectionType connectionType, bool dynamicPortList) : this(backingValue, connectionType, TypeConstraint.None, dynamicPortList)
            {
            }
        }
        ///  Manually supply node class with a context menu path 
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true)]
        public class CreateNodeMenuAttribute : Attribute
        {
            public string menuName;
            public int order;
            public bool isServerRecord;
            public string tooltip = "帮助文本默认在[CreateNodeMenuAttribute]中配置,暂时由程序维护,有更好的意见可以及时反馈";
            public string showName;
            ///  Manually supply node class with a context menu path 
            ///  Path to this node in the context menu. Null or empty hides it. 
            public CreateNodeMenuAttribute(string menuName)
            {
                this.menuName = menuName;
                this.order = 0;
            }
            ///  Manually supply node class with a context menu path 
            ///  Path to this node in the context menu. Null or empty hides it. 
            ///  The order by which the menu items are displayed. 
            public CreateNodeMenuAttribute(string menuName, int order)
            {
                this.menuName = menuName;
                this.order = order;
            }
            public CreateNodeMenuAttribute(string menuName, int order, bool isServerRecord, string tooltip, string showName)
            {
                this.menuName = menuName;
                this.order = order;
                this.isServerRecord = isServerRecord;
                this.tooltip = tooltip;
                this.showName = showName;
            }
        }
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field, AllowMultiple = true)]
        public class CreateDialogueNodeMenuAttribute : Attribute
        {
            public string menuName;
            public int order;
            public bool isServerRecord;
            public string tooltip;
            public string showName;
            ///  Manually supply node class with a context menu path 
            ///  Path to this node in the context menu. Null or empty hides it. 
            public CreateDialogueNodeMenuAttribute(string menuName)
            {
                this.menuName = menuName;
                this.order = 0;
            }
            ///  Manually supply node class with a context menu path 
            ///  Path to this node in the context menu. Null or empty hides it. 
            ///  The order by which the menu items are displayed. 
            public CreateDialogueNodeMenuAttribute(string menuName, int order)
            {
                this.menuName = menuName;
                this.order = order;
            }
            public CreateDialogueNodeMenuAttribute(string menuName, int order, bool isServerRecord, string tooltip, string showName)
            {
                this.menuName = menuName;
                this.order = order;
                this.isServerRecord = isServerRecord;
                this.tooltip = tooltip;
                this.showName = showName;
            }
        }
        ///  Prevents Node of the same type to be added more than once (configurable) to a NodeGraph 
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
        public class DisallowMultipleNodesAttribute : Attribute
        {
            // TODO: Make inheritance work in such a way that applying [DisallowMultipleNodes(1)] to type NodeBar : Node
            //       while type NodeFoo : NodeBar exists, will let you add *either one* of these nodes, but not both.
            public int max;
            ///  Prevents Node of the same type to be added more than once (configurable) to a NodeGraph 
            ///  How many nodes to allow. Defaults to 1. 
            public DisallowMultipleNodesAttribute(int max = 1)
            {
                this.max = max;
            }
        }
        ///  Specify a color for this node type 
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
        public class NodeTintAttribute : Attribute
        {
            public Color color;
            ///  Specify a color for this node type 
            ///  Red [0.0f .. 1.0f] 
            ///  Green [0.0f .. 1.0f] 
            ///  Blue [0.0f .. 1.0f] 
            public NodeTintAttribute(float r, float g, float b)
            {
                color = new Color(r, g, b);
            }
            ///  Specify a color for this node type 
            ///  HEX color value 
            public NodeTintAttribute(string hex)
            {
                ColorUtility.TryParseHtmlString(hex, out color);
            }
            ///  Specify a color for this node type 
            ///  Red [0 .. 255] 
            ///  Green [0 .. 255] 
            ///  Blue [0 .. 255] 
            public NodeTintAttribute(byte r, byte g, byte b)
            {
                color = new Color32(r, g, b, byte.MaxValue);
            }
        }
        ///  Specify a width for this node type 
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
        public class NodeWidthAttribute : Attribute
        {
            public int width;
            ///  Specify a width for this node type 
            ///  Width 
            public NodeWidthAttribute(int width)
            {
                this.width = width;
            }
        }
        #endregion
        [Serializable]
        private class NodePortDictionary : Dictionary, ISerializationCallbackReceiver
        {
            [SerializeField] private List keys = new List();
            [SerializeField] private List values = new List();
            public void OnBeforeSerialize()
            {
                keys.Clear();
                values.Clear();
                foreach (KeyValuePair pair in this)
                {
                    keys.Add(pair.Key);
                    values.Add(pair.Value);
                }
            }
            public void OnAfterDeserialize()
            {
                this.Clear();
                if (keys.Count != values.Count)
                    throw new Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable.");
                for (int i = 0; i < keys.Count; i++)
                    this.Add(keys[i], values[i]);
            }
        }
    }
}