namespace TagFighter.ExtensionMethods
{
    public static class TypeExtensions
    {

        public static System.Type GetSubclassOfRawGeneric(this System.Type baseType, System.Type rawGenericType) {
            var curType = baseType;

            while (curType != null && !(curType.IsGenericType && curType.GetGenericTypeDefinition() == rawGenericType)) {
                curType = curType.BaseType;
            }

            return curType;
        }
    }
}


namespace TagFighter.UI.Editor
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using TagFighter.Effects.Steps;
    using TagFighter.ExtensionMethods;
    using UnityEditor.Experimental.GraphView;
    using UnityEngine;
    using UnityEditor;
    using System.Linq;

    public class EffectStepNodeView : UnityEditor.Experimental.GraphView.Node
    {
        public Action<EffectStepNodeView> OnNodeSelected;
        public EffectStepNode Node;
        const string InvalidClassName = "InvalidStep";
        const Orientation DefaultOrientation = Orientation.Horizontal;
        public EffectStepNodeView(EffectStepNode node) {
            Node = node;
            title = Node.ToString();
            viewDataKey = Node.Guid;

            style.left = Node.Position.x;
            style.top = Node.Position.y;

            CreateInputPorts();
            CreateOutputPorts();
            SetClasses();
        }

        public void RefreshDisplay(EffectStepValidatedEventArgs e) {
            title = e.DisplayName;
            if (e.IsValid) {
                RemoveFromClassList(InvalidClassName);
            }
            else {
                AddToClassList(InvalidClassName);
            }

        }

        void SetClasses() {
            var stepType = Node.GetType().GetTypeInfo().GetCustomAttribute<StepTypeAttribute>();
            if (stepType != null) {
                AddToClassList($"{stepType.Name}Step");
            }

            if (!Node.IsValid) {
                AddToClassList(InvalidClassName);
            }

        }

        string GetPortDisplayName(System.Type portType) {
            (var subType, var arrayMarker) = portType.IsGenericType && portType.GetGenericTypeDefinition() == typeof(IEnumerable<>) ?
                                            (portType.GenericTypeArguments[0], "[]") : (portType, "");
            return $"({subType.Name}{arrayMarker})";
        }

        void CreateInputPorts() {
            var direction = Direction.Input;
            const string InPortDisplayName = "In";

            foreach (var port in Node.Inputs.Where(port => port != null)) {
                var portView = port.Capacity switch {
                    PortCapacity.Single => InstantiatePort(DefaultOrientation, direction, Port.Capacity.Single, port.GetType().GenericTypeArguments[0]),
                    _ => InstantiatePort(DefaultOrientation, direction, Port.Capacity.Multi, port.GetType().GenericTypeArguments[0])
                };
                portView.portName = port.DisplayName != null && port.DisplayName != "" ? port.DisplayName : InPortDisplayName;
                portView.portName += GetPortDisplayName(portView.portType);
                portView.userData = (port, Node);
                inputContainer.Add(portView);
            }
        }

        void CreateOutputPorts() {
            var direction = Direction.Output;
            const string OutPortDisplayName = "Out";

            var genericStepType = Node.GetType().GetSubclassOfRawGeneric(typeof(OutputStep<>));
            if (genericStepType != null) {
                var portView = InstantiatePort(DefaultOrientation, direction, Port.Capacity.Single, genericStepType.GenericTypeArguments[0]);
                portView.portName = OutPortDisplayName + GetPortDisplayName(portView.portType);
                outputContainer.Add(portView);
            }
        }

        public override void SetPosition(Rect newPos) {
            base.SetPosition(newPos);
            Undo.RecordObject(Node, "Update Node Position");
            Node.Position.x = newPos.xMin;
            Node.Position.y = newPos.yMin;
            EditorUtility.SetDirty(Node);
        }
        public override void OnSelected() {
            base.OnSelected();
            OnNodeSelected?.Invoke(this);
        }
    }
}