using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UNEB.Utility;
namespace UNEB
{
    /// 
    /// The visual representation of a logic unit such as an object or function.
    /// 
    public abstract class Node : ScriptableObject
    {
        public static readonly Vector2 kDefaultSize = new Vector2(140f, 110f);
        /// 
        /// The space reserved between knobs.
        /// 
        public const float kKnobOffset = 4f;
        /// 
        /// The space reserved for the header (title) of the node.
        /// 
        public const float kHeaderHeight = 15f;
        /// 
        /// The max label width for a field in the body.
        /// 
        public const float kBodyLabelWidth = 100f;
        /// 
        /// The rect of the node in canvas space.
        /// 
        [HideInInspector]
        public Rect bodyRect;
        /// 
        /// How much additional offset to apply when resizing.
        /// 
        public const float resizePaddingX = 20f;
        [SerializeField, HideInInspector]
        private List _outputs = new List();
        [SerializeField, HideInInspector]
        private List _inputs = new List();
        // Hides the node asset.
        // Sets up the name via type information.
        void OnEnable()
        {
            hideFlags = HideFlags.HideInHierarchy;
            name = GetType().Name;
#if UNITY_EDITOR
            name = ObjectNames.NicifyVariableName(name);
#endif
        }
        /// 
        /// Always call the base OnDisable() to cleanup the connection objects.
        /// 
        protected virtual void OnDestroy()
        {
            _inputs.RemoveAll(
                (input) =>
                {
                    ScriptableObject.DestroyImmediate(input, true);
                    return true;
                });
            _outputs.RemoveAll(
                (output) =>
                {
                    ScriptableObject.DestroyImmediate(output, true);
                    return true;
                });
        }
        /// 
        /// Use this for initialization.
        /// 
        public virtual void Init() {
            bodyRect.size = kDefaultSize;
        }
        public virtual void OnNodeGUI()
        {
            OnNodeHeaderGUI();
            OnConnectionsGUI();
            onBodyGuiInternal();
        }
        /// 
        /// Renders the node connections. By default, after the header.
        /// 
        public virtual void OnConnectionsGUI()
        {
            int inputCount = _inputs.Count;
            int outputCount = _outputs.Count;
            int maxCount = (int)Mathf.Max(inputCount, outputCount);
            // The entire knob section is stacked rows of inputs and outputs.
            for (int i = 0; i < maxCount; ++i) {
                GUILayout.BeginHorizontal();
                // Render the knob layout horizontally.
                if (i < inputCount) _inputs[i].OnConnectionGUI(i);
                if (i < outputCount) _outputs[i].OnConnectionGUI(i);
                GUILayout.EndHorizontal();
            }
        }
        /// 
        /// Render the title/header of the node. By default, renders on top of the node.
        /// 
        public virtual void OnNodeHeaderGUI()
        {
            // Draw header
            GUILayout.Box(name, HeaderStyle);
        }
        /// 
        /// Draws the body of the node. By default, after the connections.
        /// 
        public virtual void OnBodyGUI() { }
        // Handles the coloring and layout of the body.
        // This is for convenience so the user does not need to worry about this boiler plate code.
        protected virtual void onBodyGuiInternal()
        {
            float oldLabelWidth = EditorGUIUtility.labelWidth;
            EditorGUIUtility.labelWidth = kBodyLabelWidth;
            // Cache the old label style.
            // Do this first before changing the EditorStyles.label style.
            // So the original values are kept.
            var oldLabelStyle = UnityLabelStyle;
            // Setup new values for the label style.
            EditorStyles.label.normal = DefaultStyle.normal;
            EditorStyles.label.active = DefaultStyle.active;
            EditorStyles.label.focused = DefaultStyle.focused;
            EditorGUILayout.BeginVertical();
            GUILayout.Space(kKnobOffset);
            OnBodyGUI();
            // Revert back to old label style.
            EditorStyles.label.normal = oldLabelStyle.normal;
            EditorStyles.label.active = oldLabelStyle.active;
            EditorStyles.label.focused = oldLabelStyle.focused;
            EditorGUIUtility.labelWidth = oldLabelWidth;
            EditorGUILayout.EndVertical();
        }
        public NodeInput AddInput(string name = "input", bool multipleConnections = false)
        {
            var input = NodeInput.Create(this, multipleConnections);
            input.name = name;
            _inputs.Add(input);
            return input;
        }
        public NodeOutput AddOutput(string name = "output", bool multipleConnections = false)
        {
            var output = NodeOutput.Create(this, multipleConnections);
            output.name = name;
            _outputs.Add(output);
            return output;
        }
        /// 
        /// Called when the output knob had an input connection removed.
        /// 
        /// 
        public virtual void OnInputConnectionRemoved(NodeInput removedInput) { }
        /// 
        /// Called when the output knob made a connection to an input knob.
        /// 
        /// 
        public virtual void OnNewInputConnection(NodeInput addedInput) { }
        public IEnumerable Outputs
        {
            get { return _outputs; }
        }
        public IEnumerable Inputs
        {
            get { return _inputs; }
        }
        public int InputCount
        {
            get { return _inputs.Count; }
        }
        public int OutputCount
        {
            get { return _outputs.Count; }
        }
        public NodeInput GetInput(int index)
        {
            return _inputs[index];
        }
        public NodeOutput GetOutput(int index)
        {
            return _outputs[index];
        }
        /// 
        /// Get the Y value of the top header.
        /// 
        public float HeaderTop
        {
            get { return bodyRect.yMin + kHeaderHeight; }
        }
        /// 
        /// Resize the node to fit the knobs.
        /// 
        public void FitKnobs()
        {
            int maxCount = (int)Mathf.Max(_inputs.Count, _outputs.Count);
            float totalKnobsHeight = maxCount * NodeConnection.kMinSize.y;
            float totalOffsetHeight = (maxCount - 1) * kKnobOffset;
            float heightRequired = totalKnobsHeight + totalOffsetHeight + kHeaderHeight;
            // Add some extra height at the end.
            bodyRect.height = heightRequired + kHeaderHeight / 2f;
        }
        #region Styles and Contents
        private static GUIStyle _unityLabelStyle;
        /// 
        /// Caches the default EditorStyle.
        /// There is a strange bug with it being overriden when opening an Animation window.
        /// 
        public static GUIStyle UnityLabelStyle
        {
            get
            {
                if (_unityLabelStyle == null) {
                    _unityLabelStyle = new GUIStyle(EditorStyles.label);
                }
                return _unityLabelStyle;
            }
        }
        private static GUIStyle _defStyle;
        public static GUIStyle DefaultStyle
        {
            get
            {
                if (_defStyle == null) {
                    _defStyle = new GUIStyle(EditorStyles.label);
                    _defStyle.normal.textColor = Color.white * 0.9f;
                    _defStyle.active.textColor = ColorExtensions.From255(126, 186, 255) * 0.9f;
                    _defStyle.focused.textColor = ColorExtensions.From255(126, 186, 255);
                }
                return _defStyle;
            }
        }
        private static GUIStyle _headerStyle;
        public GUIStyle HeaderStyle
        {
            get
            {
                if (_headerStyle == null) {
                    _headerStyle = new GUIStyle();
                    _headerStyle.stretchWidth = true;
                    _headerStyle.alignment = TextAnchor.MiddleLeft;
                    _headerStyle.padding.left = 5;
                    _headerStyle.normal.textColor = Color.white * 0.9f;
                    _headerStyle.fixedHeight = kHeaderHeight;
                }
                return _headerStyle;
            }
        }
        #endregion
    }
}