// Amplify Shader Editor - Visual Shader Editing Tool
// Copyright (c) Amplify Creations, Lda <info@amplify.pt>

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace AmplifyShaderEditor
{
	public enum PreviewLocation
	{
		Auto,
		TopCenter,
		BottomCenter,
		Left,
		Right
	}

	public enum NodeMessageType
	{
		Error,
		Warning,
		Info
	}

	[Serializable]
	public class ParentNode : UndoParentNode, ISerializationCallbackReceiver
	{

		protected readonly string[] PrecisionLabels = { "Float", "Half" };
		protected readonly string[] PrecisionLabelsExtra = { "Float", "Half", "Inherit" };

		private const double NodeClickTime = 0.2;
		protected GUIContent PrecisionContent = new GUIContent( "Precision", "Changes the precision of internal calculations, using lower types saves some performance\nDefault: Float" );
		private const int MoveCountBuffer = 3;// When testing for stopped movement we need to take Layout and Repaint into account for them not to interfere with tests
		private const float MinInsideBoxWidth = 20;
		private const float MinInsideBoxHeight = 10;

		private const string WikiLinkStr = "online reference";

		public delegate void OnNodeEvent( ParentNode node, bool testOnlySelected, InteractionMode interactionMode );
		public delegate void OnNodeGenericEvent( ParentNode node );
		public delegate void OnNodeReOrder( ParentNode node, int index );
		public delegate void DrawPropertySection();
		public delegate void OnSRPAction( int outputId, ref MasterNodeDataCollector dataCollector );

		[SerializeField]
		protected PrecisionType m_currentPrecisionType = PrecisionType.Inherit;

		[SerializeField]
		protected bool m_customPrecision = false;

		[SerializeField]
		protected InteractionMode m_defaultInteractionMode = InteractionMode.Other;

		public event OnNodeEvent OnNodeStoppedMovingEvent;
		public OnNodeGenericEvent OnNodeChangeSizeEvent;
		public OnNodeGenericEvent OnNodeDestroyedEvent;
		public event OnNodeReOrder OnNodeReOrderEvent;
		public OnSRPAction OnLightweightAction;
		public OnSRPAction OnHDAction;

		[SerializeField]
		private int m_uniqueId;

		[SerializeField]
		protected Rect m_position;

		[SerializeField]
		protected Rect m_unpreviewedPosition;

		[SerializeField]
		protected GUIContent m_content;

		[SerializeField]
		protected GUIContent m_additionalContent;

		[SerializeField]
		protected bool m_initialized;

		[SerializeField]
		protected NodeConnectionStatus m_connStatus;
		protected bool m_selfPowered = false;

		[SerializeField]
		protected int m_activeConnections;

		[SerializeField]
		protected System.Type m_activeType;

		[SerializeField]
		protected int m_activePort;

		[SerializeField]
		protected int m_activeNode;

		protected NodeRestrictions m_restrictions;

		[SerializeField]
		protected Color m_statusColor;

		[SerializeField]
		protected Rect m_propertyDrawPos;

		// Ports
		[SerializeField]
		protected List<InputPort> m_inputPorts = new List<InputPort>();

		protected Dictionary<int, InputPort> m_inputPortsDict = new Dictionary<int, InputPort>();

		[SerializeField]
		protected List<OutputPort> m_outputPorts = new List<OutputPort>();

		protected Dictionary<int, OutputPort> m_outputPortsDict = new Dictionary<int, OutputPort>();

		[SerializeField]
		protected Rect m_globalPosition;

		[SerializeField]
		protected Rect m_headerPosition;

		//private Vector2 m_tooltipOffset;

		[SerializeField]
		protected bool m_sizeIsDirty = false;

		[SerializeField]
		protected Vector2 m_extraSize;

		[SerializeField]
		protected Vector2 m_insideSize;

		[SerializeField]
		protected float m_fontHeight;

		// Editor State save on Play Button
		[SerializeField]
		protected bool m_isDirty;

		[SerializeField]
		private int m_isMoving = 0;
		[SerializeField]
		private Rect m_lastPosition;

		// Live Shader Gen
		[SerializeField]
		private bool m_saveIsDirty;

		[SerializeField]
		protected bool m_requireMaterialUpdate = false;

		[SerializeField]
		protected int m_commentaryParent = -1;

		[SerializeField]
		protected int m_depth = -1;

		[SerializeField]
		protected bool m_materialMode = false;

		[SerializeField]
		protected bool m_showPreview = false;

		[SerializeField]
		protected int m_previewMaterialPassId = -1;
		
		protected bool m_useSquareNodeTitle = false;

		[SerializeField]
		protected bool m_continuousPreviewRefresh = false;
		private bool m_previewIsDirty = true;

		// Error Box Messages
		private Rect m_errorBox;
		private bool m_previousErrorMessage = false;
		protected bool m_showErrorMessage = false;
		protected NodeMessageType m_errorMessageTypeIsError = NodeMessageType.Error;
		protected string m_errorMessageTooltip = string.Empty;

		private GUIContent m_errorIcon = new GUIContent();
		private GUIContent m_errorMessage = new GUIContent();
		private GUIStyle m_errorCurrentStyle;

		private const string ErrorTitle = "ERROR";
		private const string WarningTitle = "WARNING";
		private const string InfoTitle = "INFO";

		// Drawing Node
		protected PreviewLocation m_selectedLocation = PreviewLocation.Auto;
		private int m_extraHeaderHeight = 0;
		protected bool m_isVisible;
		protected bool m_selected = false;
		protected bool m_rmbIgnore;
		protected GUIContent m_sizeContentAux;

		protected uint m_currentReadParamIdx = 1;
		protected bool m_reorderLocked = false;

		protected Rect m_cachedPos;
		protected Vector2 m_accumDelta = Vector2.zero;

		private bool m_isOnGrid = false;
		protected bool m_useInternalPortData = false;
		protected bool m_autoDrawInternalPortData = true;
		protected DrawOrder m_drawOrder = DrawOrder.Default;

		protected bool m_movingInFrame = false;
		protected float m_anchorAdjust = -1;

		protected Color m_headerColor;

		[SerializeField] // needs to be serialized because of Undo
		protected Color m_headerColorModifier = Color.white;

		protected bool m_infiniteLoopDetected = false;
		protected int m_textLabelWidth = -1;

		private bool m_linkVisibility = false;
		[SerializeField]
		protected bool m_hasTooltipLink = true;

		protected int m_category = 0;

		protected double m_lastTimeSelected;
		private double m_tooltipTimestamp;
		protected string m_tooltipText;

		protected Rect m_unscaledRemainingBox;
		protected Rect m_remainingBox;

		private int m_visibleInputs = 0;
		private int m_visibleOutputs = 0;

		private double m_doubleClickTimestamp;
		private const double DoubleClickTime = 0.25;

		protected bool m_canExpand = true;

		protected bool m_firstDraw = true;

		protected int m_matrixId = -1;

		private float m_paddingTitleLeft = 0;
		private float m_paddingTitleRight = 0;

		// Preview Fields
		private Material m_previewMaterial = null;
		private Shader m_previewShader = null;
		protected string m_previewShaderGUID = string.Empty;
		protected float m_marginPreviewLeft = 0;
		protected bool m_globalShowPreview = false;
		protected Rect m_unscaledPreviewRect;
		protected Rect m_previewRect;
		protected bool m_drawPreviewMaskButtons = true;
		private int m_channelNumber = 0;
		protected bool m_firstPreviewDraw = true;
		[SerializeField]
		protected bool m_drawPreview = true;
		protected bool m_drawPreviewExpander = true;
		private bool m_spherePreview = false;
		protected bool m_drawPreviewAsSphere = false;
		protected bool m_forceDrawPreviewAsPlane = false;
		private bool m_finishPreviewRender = false;

		private int m_cachedMainTexId = -1;
		private int m_cachedMaskTexId = -1;
		private int m_cachedPortsId = -1;
		private int m_cachedPortId = -1;

		private int m_cachedDrawSphereId = -1;
		private int m_cachedInvertedZoomId = -1;
		//private int m_cachedIsLinearId = -1;

		private bool[] m_previewChannels = { true, true, true, false };

		// Others
		protected bool m_hasSubtitle = false;
		protected bool m_showSubtitle = true;
		protected bool m_hasLeftDropdown = false;
		protected bool m_autoWrapProperties = false;
		protected bool m_internalDataFoldout = true;
		protected bool m_propertiesFoldout = true;
		protected bool m_repopulateDictionaries = true;

		protected Vector2 m_lastInputBottomRight = Vector2.zero;
		protected Vector2 m_lastOutputBottomLeft = Vector2.zero;

		private Vector4 m_portMask = Vector4.zero;

		private Vector2 m_auxVector2 = Vector4.zero;
		protected Rect m_auxRect;

		protected PreviewLocation m_autoLocation;
		protected Rect m_titlePos;
		protected Rect m_addTitlePos;
		protected Rect m_expandRect;
		protected Rect m_dropdownRect;
		protected Rect m_currInputPortPos;
		protected Rect m_currOutputPortPos;
		protected Color m_colorBuffer;

		[SerializeField]
		protected bool m_docking = false;

		[SerializeField]
		protected int m_visiblePorts = 0;

		protected int m_graphDepth = 0;

		protected int m_oldInputCount = -1;

		protected bool m_dropdownEditing = false;

		protected bool m_isNodeBeingCopied = false;

		protected string m_previousTitle = string.Empty;

		protected string m_previousAdditonalTitle = string.Empty;

		private bool m_alive = true;

		private bool m_wasDeprecated = false;

		private double m_timedUpdateInitialValue;
		private double m_timedUpdateInterval;
		private bool m_fireTimedUpdateRequest = false;

		public ParentNode()
		{
			m_position = new Rect( 0, 0, 0, 0 );
			m_content = new GUIContent( GUIContent.none );
			m_additionalContent = new GUIContent( GUIContent.none );
			CommonInit( -1 );
		}

		public ParentNode( int uniqueId, float x, float y, float width, float height )
		{
			m_position = new Rect( x, y, width, height );
			m_content = new GUIContent( GUIContent.none );
			m_additionalContent = new GUIContent( GUIContent.none );
			CommonInit( uniqueId );
		}

		public virtual void OnEnable()
		{
			hideFlags = HideFlags.HideAndDontSave;

			RefreshUIHeaderColor();

			m_tooltipTimestamp = Time.realtimeSinceStartup;
			hideFlags = HideFlags.DontSave;
		}		

		public void RefreshUIHeaderColor()
		{
			// @diogo: handle special shader function case; this will have to do for now...
			var functionNode = this as FunctionNode;
			if ( functionNode != null && functionNode.Function != null )
			{				
				if ( functionNode.Function.Header == AmplifyShaderFunction.HeaderStyle.Custom )
				{
					m_headerColor = functionNode.Function.HeaderColor;
				}
				else if ( functionNode.Function.Header == AmplifyShaderFunction.HeaderStyle.Category )
				{
					m_headerColor = UIUtils.GetColorFromCategory( functionNode.Function.CustomNodeCategory );
				}
				else
				{
					m_headerColor = UIUtils.GetColorFromCategory( "Functions" );
				}
			}
			else if ( m_nodeAttribs != null )
			{
				if( UIUtils.HasColorCategory( m_nodeAttribs.Category ) )
				{
					m_headerColor = UIUtils.GetColorFromCategory( m_nodeAttribs.Category );
				}
				else
				{
					if( !string.IsNullOrEmpty( m_nodeAttribs.CustomCategoryColor ) )
					{
						m_headerColor = UIUtils.AddColorCategory( m_nodeAttribs.Category, m_nodeAttribs.CustomCategoryColor );
					}
				}
			}
		}

		protected virtual void CommonInit( int uniqueId )
		{
			m_uniqueId = uniqueId;

			m_isOnGrid = false;
			ConnStatus = NodeConnectionStatus.Not_Connected;
			m_inputPorts = new List<InputPort>();
			m_inputPortsDict = new Dictionary<int, InputPort>();

			m_outputPorts = new List<OutputPort>();
			m_outputPortsDict = new Dictionary<int, OutputPort>();

			System.Reflection.MemberInfo info = this.GetType();
			m_nodeAttribs = info.GetCustomAttributes( true )[ 0 ] as NodeAttributes;
			if( m_nodeAttribs != null )
			{
				m_content.text = m_nodeAttribs.Name;
				//m_content.tooltip = m_nodeAttribs.Description;
				m_tooltipText = m_nodeAttribs.Description;
				m_selected = false;
			}

			m_sizeContentAux = new GUIContent();
			m_extraSize = new Vector2( 0, 0 );
			m_insideSize = new Vector2( 0, 0 );
			m_sizeIsDirty = true;
			m_initialized = true;
			m_restrictions = new NodeRestrictions();

			m_propertyDrawPos = new Rect();
		}

		public virtual void AfterCommonInit()
		{
			if( PreviewShader && !HasPreviewShader )
			{
				m_drawPreview = false;
				m_drawPreviewExpander = false;
				m_canExpand = false;
			}

			if( m_drawPreviewExpander || m_hasLeftDropdown )
			{
				m_paddingTitleRight += Constants.PreviewExpanderWidth + Constants.IconsLeftRightMargin;
				m_paddingTitleLeft = Constants.PreviewExpanderWidth + Constants.IconsLeftRightMargin;
			}
		}

		public virtual void Destroy()
		{
			m_alive = false;
			if( OnNodeDestroyedEvent != null )
			{
				OnNodeDestroyedEvent( this );
				OnNodeDestroyedEvent = null;
			}

			OnLightweightAction = null;
			OnHDAction = null;

			OnNodeStoppedMovingEvent = null;
			OnNodeChangeSizeEvent = null;
			OnNodeReOrderEvent = null;
			if( m_restrictions != null )
				m_restrictions.Destroy();
			m_restrictions = null;

			if( m_inputPorts != null )
			{
				int inputCount = m_inputPorts.Count;
				for( int i = 0; i < inputCount; i++ )
				{
					m_inputPorts[ i ].Destroy();
				}
				m_inputPorts.Clear();
				m_inputPorts = null;
			}

			if( m_outputPorts != null )
			{
				int outputCount = m_outputPorts.Count;
				for( int i = 0; i < outputCount; i++ )
				{
					m_outputPorts[ i ].Destroy();
				}
				m_outputPorts.Clear();
				m_outputPorts = null;
			}

			if( m_inputPortsDict != null )
				m_inputPortsDict.Clear();

			m_inputPortsDict = null;

			if( m_outputPortsDict != null )
				m_outputPortsDict.Clear();

			m_outputPortsDict = null;

			if( m_previewMaterial != null )
				DestroyImmediate( m_previewMaterial );
			m_previewMaterial = null;

			m_previewShader = null;
			//m_containerGraph = null;
		}

		public virtual void Move( Vector2 delta )
		{
			if( m_docking )
				return;

			Move( delta, false );
		}

		public virtual void Move( Vector2 delta, bool snap )
		{
			if( m_docking )
				return;

			if( m_isMoving == 0 )
			{
				m_cachedPos = m_position;
				m_accumDelta = Vector2.zero;
			}

			m_isMoving = MoveCountBuffer;
			m_accumDelta += delta;

			if( snap )
			{
				m_position.x = Mathf.Round( ( m_cachedPos.x + m_accumDelta.x ) / 16 ) * 16;
				m_position.y = Mathf.Round( ( m_cachedPos.y + m_accumDelta.y ) / 16 ) * 16;
			}
			else
			{
				m_position.x += delta.x;
				m_position.y += delta.y;
			}
			//if(Event.current.type == EventType.Layout)
			m_movingInFrame = true;
		}

		public virtual void UpdateMaterial( Material mat )
		{
			m_requireMaterialUpdate = false;
		}

		public virtual void SetMaterialMode( Material mat, bool fetchMaterialValues )
		{
			m_materialMode = ( mat != null );
		}

		public virtual bool UpdateShaderDefaults( ref Shader shader, ref TextureDefaultsDataColector defaultCol ) { return false; }
		public virtual void ForceUpdateFromMaterial( Material material ) { }
		public void SetSaveIsDirty()
		{
			if( m_connStatus == NodeConnectionStatus.Connected )
			{
				SaveIsDirty = true;
			}
		}

		public void ActivateNodeReordering( int index )
		{
			if( OnNodeReOrderEvent != null )
				OnNodeReOrderEvent( this, index );
		}

		void RecalculateInputPortIdx()
		{
			m_inputPortsDict.Clear();
			int count = m_inputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( m_inputPorts[ i ].IsConnected )
				{
					int nodeId = m_inputPorts[ i ].ExternalReferences[ 0 ].NodeId;
					int portId = m_inputPorts[ i ].ExternalReferences[ 0 ].PortId;
					ParentNode node = UIUtils.GetNode( nodeId );
					if( node != null )
					{
						OutputPort outputPort = node.GetOutputPortByUniqueId( portId );
						int outputCount = outputPort.ExternalReferences.Count;
						for( int j = 0; j < outputCount; j++ )
						{
							if( outputPort.ExternalReferences[ j ].NodeId == m_uniqueId &&
								outputPort.ExternalReferences[ j ].PortId == m_inputPorts[ i ].PortId )
							{
								outputPort.ExternalReferences[ j ].PortId = i;
							}
						}
					}
				}
				m_inputPorts[ i ].PortId = i;
				m_inputPortsDict.Add( i, m_inputPorts[ i ] );
			}
		}

		public void SwapInputPorts( int fromIdx, int toIdx )
		{
			InputPort port = m_inputPorts[ fromIdx ];
			//if( toIdx > fromIdx )
			//	toIdx--;
			m_inputPorts.Remove( port );
			m_inputPorts.Insert( toIdx, port );
			RecalculateInputPortIdx();
			SetSaveIsDirty();
		}

		public void RemoveInputPort( int idx )
		{
			if( idx < m_inputPorts.Count )
			{
				m_inputPortsDict.Remove( m_inputPorts[ idx ].PortId );
				m_inputPorts.RemoveAt( idx );
				SetSaveIsDirty();
				m_sizeIsDirty = true;
			}
		}

		public void RemoveOutputPort( string name )
		{
			int count = m_outputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( m_outputPorts[ i ].Name.Equals( name ) )
				{
					if( m_outputPorts[ i ].IsConnected )
					{
						m_containerGraph.DeleteConnection( false, m_uniqueId, m_outputPorts[ i ].PortId, false, true );
						m_outputPortsDict.Remove( m_outputPorts[ i ].PortId );
						m_outputPorts.RemoveAt( i );
						SetSaveIsDirty();
						m_sizeIsDirty = true;
					}
				}
			}
		}

		public void RemoveOutputPort( int idx, bool isArrayIndex = true )
		{
			if( isArrayIndex )
			{
				// idx represents a position on the output port array
				if( idx < m_outputPorts.Count )
				{
					if( m_outputPorts[ idx ].IsConnected )
					{
						m_containerGraph.DeleteConnection( false, m_uniqueId, m_outputPorts[ idx ].PortId, false, true );
					}

					m_outputPortsDict.Remove( m_outputPorts[ idx ].PortId );
					m_outputPorts.RemoveAt( idx );
					SetSaveIsDirty();
					m_sizeIsDirty = true;
				}
			}
			else
			{
				// idx represents a port unique id
				int count = m_outputPorts.Count;
				int arrIdx = -1;
				for( int i = 0; i < count; i++ )
				{
					if( m_outputPorts[ i ].PortId == idx )
					{
						arrIdx = i;
						break;
					}
				}

				if( arrIdx >= 0 )
				{
					if( m_outputPorts[ arrIdx ].IsConnected )
					{
						m_containerGraph.DeleteConnection( false, m_uniqueId, idx, false, true );
					}

					m_outputPortsDict.Remove( idx );
					m_outputPorts.RemoveAt( arrIdx );
					SetSaveIsDirty();
					m_sizeIsDirty = true;
				}
			}
		}

		// Manually add Ports 
		public InputPort AddInputPort( WirePortDataType type, bool typeLocked, string name, int orderId = -1, MasterNodePortCategory category = MasterNodePortCategory.Fragment, int uniquePortId = -1 )
		{
			InputPort port = new InputPort( m_uniqueId, ( uniquePortId < 0 ? m_inputPorts.Count : uniquePortId ), type, name, typeLocked, ( orderId >= 0 ? orderId : m_inputPorts.Count ), category );
			m_inputPorts.Add( port );
			m_inputPortsDict.Add( port.PortId, port );
			SetSaveIsDirty();
			m_sizeIsDirty = true;
			return port;
		}

		public InputPort AddInputPort( WirePortDataType type, bool typeLocked, string name, string dataName, int orderId = -1, MasterNodePortCategory category = MasterNodePortCategory.Fragment, int uniquePortId = -1 )
		{
			InputPort port = new InputPort( m_uniqueId, ( uniquePortId < 0 ? m_inputPorts.Count : uniquePortId ), type, name, dataName, typeLocked, ( orderId >= 0 ? orderId : m_inputPorts.Count ), category );
			m_inputPorts.Add( port );
			m_inputPortsDict.Add( port.PortId, port );
			SetSaveIsDirty();
			m_sizeIsDirty = true;
			return port;
		}

		public InputPort AddInputPortAt( int idx, WirePortDataType type, bool typeLocked, string name, int orderId = -1, MasterNodePortCategory category = MasterNodePortCategory.Fragment, int uniquePortId = -1 )
		{
			InputPort port = new InputPort( m_uniqueId, ( uniquePortId < 0 ? m_inputPorts.Count : uniquePortId ), type, name, typeLocked, ( orderId >= 0 ? orderId : m_inputPorts.Count ), category );
			m_inputPorts.Insert( idx, port );
			m_inputPortsDict.Add( port.PortId, port );
			SetSaveIsDirty();
			m_sizeIsDirty = true;
			RecalculateInputPortIdx();
			return port;
		}

		public void AddOutputPort( WirePortDataType type, string name, int uniquePortId = -1 )
		{
			m_outputPorts.Add( new OutputPort( this, m_uniqueId, ( uniquePortId < 0 ? m_outputPorts.Count : uniquePortId ), type, name ) );
			m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
			SetSaveIsDirty();
			m_sizeIsDirty = true;
		}

		public void AddOutputPortAt( int idx, WirePortDataType type, string name, int uniquePortId = -1 )
		{
			OutputPort port = new OutputPort( this, m_uniqueId, ( uniquePortId < 0 ? m_outputPorts.Count : uniquePortId ), type, name );
			m_outputPorts.Insert( idx, port );
			m_outputPortsDict.Add( port.PortId, port );
			SetSaveIsDirty();
			m_sizeIsDirty = true;
		}

		public void AddOutputVectorPorts( WirePortDataType type, string name )
		{
			m_sizeIsDirty = true;
			m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, type, name ) );
			m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );

			switch( type )
			{
				case WirePortDataType.FLOAT2:
				{
					m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "X" ) );
					m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
					m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "Y" ) );
					m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
				}
				break;
				case WirePortDataType.FLOAT3:
				{
					m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "X" ) );
					m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
					m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "Y" ) );
					m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
					m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "Z" ) );
					m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
				}
				break;
				case WirePortDataType.FLOAT4:
				{
					m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "X" ) );
					m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
					m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "Y" ) );
					m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
					m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "Z" ) );
					m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
					m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "W" ) );
					m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
				}
				break;
			}
			SetSaveIsDirty();
		}

		public void SetPreviewDirtyFromOutputs()
		{
			PreviewIsDirty = true;
		}

		public string GetOutputVectorItem( int vectorPortId, int currentPortId, string result )
		{
			if( m_outputPorts[ 0 ].DataType == WirePortDataType.COLOR )
			{
				switch( currentPortId - vectorPortId )
				{
					case 1: result += ".r"; break;
					case 2: result += ".g"; break;
					case 3: result += ".b"; break;
					case 4: result += ".a"; break;
				}
			}
			else
			{
				switch( currentPortId - vectorPortId )
				{
					case 1: result += ".x"; break;
					case 2: result += ".y"; break;
					case 3: result += ".z"; break;
					case 4: result += ".w"; break;
				}
			}
			return result;
		}

		public void AddOutputColorPorts( string name, bool addAlpha = true )
		{
			m_sizeIsDirty = true;
			//Main port
			m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, addAlpha ? WirePortDataType.COLOR : WirePortDataType.FLOAT3, name ) );
			m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );

			//Color components port
			m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "R" ) );
			m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
			m_outputPorts[ m_outputPorts.Count - 1 ].CustomColor = Color.red;

			m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "G" ) );
			m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
			m_outputPorts[ m_outputPorts.Count - 1 ].CustomColor = Color.green;

			m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "B" ) );
			m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
			m_outputPorts[ m_outputPorts.Count - 1 ].CustomColor = Color.blue;

			if( addAlpha )
			{
				m_outputPorts.Add( new OutputPort( this, m_uniqueId, m_outputPorts.Count, WirePortDataType.FLOAT, "A" ) );
				m_outputPortsDict.Add( m_outputPorts[ m_outputPorts.Count - 1 ].PortId, m_outputPorts[ m_outputPorts.Count - 1 ] );
				m_outputPorts[ m_outputPorts.Count - 1 ].CustomColor = Color.white;
			}
		}

		public void ConvertFromVectorToColorPorts()
		{
			m_outputPorts[ 0 ].ChangeType( WirePortDataType.COLOR, false );

			m_outputPorts[ 1 ].Name = "R";
			m_outputPorts[ 1 ].CustomColor = Color.red;

			m_outputPorts[ 2 ].Name = "G";
			m_outputPorts[ 2 ].CustomColor = Color.green;

			m_outputPorts[ 3 ].Name = "B";
			m_outputPorts[ 3 ].CustomColor = Color.blue;

			m_outputPorts[ 4 ].Name = "A";
			m_outputPorts[ 4 ].CustomColor = Color.white;
		}


		public string GetOutputColorItem( int vectorPortId, int currentPortId, string result )
		{
			switch( currentPortId - vectorPortId )
			{
				case 1: result += ".r"; break;
				case 2: result += ".g"; break;
				case 3: result += ".b"; break;
				case 4: result += ".a"; break;
			}
			return result;
		}

		public void ChangeOutputType( WirePortDataType type, bool invalidateConnections )
		{
			int outputCount = m_outputPorts.Count;
			for( int i = 0; i < outputCount; i++ )
			{
				m_outputPorts[ i ].ChangeType( type, invalidateConnections );
			}
		}

		public void ChangeInputType( WirePortDataType type, bool invalidateConnections )
		{
			int inputCount = m_inputPorts.Count;
			for( int i = 0; i < inputCount; i++ )
			{
				m_inputPorts[ i ].ChangeType( type, invalidateConnections );
			}
		}

		public void ChangeOutputProperties( int outputID, string newName, WirePortDataType newType, bool invalidateConnections = true )
		{
			if( outputID < m_outputPorts.Count )
			{
				m_outputPorts[ outputID ].ChangeProperties( newName, newType, invalidateConnections );
				IsDirty = true;
				m_sizeIsDirty = true;
				SetSaveIsDirty();
			}
		}

		public void ChangeOutputName( int outputArrayIdx, string newName )
		{
			if( outputArrayIdx < m_outputPorts.Count )
			{
				m_outputPorts[ outputArrayIdx ].Name = newName;
				IsDirty = true;
				m_sizeIsDirty = true;
			}
		}

		public InputPort CheckInputPortAt( Vector3 pos )
		{
			int count = m_inputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( m_inputPorts[ i ].InsideActiveArea( pos ) )
					return m_inputPorts[ i ];
			}
			return null;
		}

		public InputPort GetFirstInputPortOfType( WirePortDataType dataType, bool countObjectTypeAsValid )
		{
			int count = m_inputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( ( m_inputPorts[ i ].CheckValidType( dataType ) ) || ( countObjectTypeAsValid && m_inputPorts[ i ].DataType == WirePortDataType.OBJECT ) )
					return m_inputPorts[ i ];
			}
			return null;
		}

		public OutputPort CheckOutputPortAt( Vector3 pos )
		{
			int count = m_outputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( m_outputPorts[ i ].InsideActiveArea( pos ) )
					return m_outputPorts[ i ];
			}
			return null;
		}

		public OutputPort GetFirstOutputPortOfType( WirePortDataType dataType, bool checkForCasts )
		{
			int count = m_outputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( ( m_outputPorts[ i ].CheckValidType( dataType ) ) || ( checkForCasts && UIUtils.CanCast( dataType, m_outputPorts[ i ].DataType ) ) )
					return m_outputPorts[ i ];
			}
			return null;
		}

		virtual protected void ChangeSizeFinished() { m_firstPreviewDraw = true; /*MarkForPreviewUpdate();*/ }
		protected void ChangeSize()
		{
			m_cachedPos = m_position;
			//UIUtils.ResetMainSkin();

			Vector2 inSize = Vector2.zero;
			int inputCount = 0;
			int inputSize = m_inputPorts.Count;
			for( int i = 0; i < inputSize; i++ )
			{
				if( m_inputPorts[ i ].Visible )
				{
					if( m_inputPorts[ i ].DirtyLabelSize || m_inputPorts[ i ].LabelSize == Vector2.zero )
					{
						m_inputPorts[ i ].DirtyLabelSize = false;
						m_sizeContentAux.text = m_inputPorts[ i ].Name;
						m_inputPorts[ i ].UnscaledLabelSize = UIUtils.UnZoomedInputPortStyle.CalcSize( m_sizeContentAux );
					}

					inSize.x = Mathf.Max( inSize.x, m_inputPorts[ i ].UnscaledLabelSize.x );
					inSize.y = Mathf.Max( inSize.y, m_inputPorts[ i ].UnscaledLabelSize.y );
					inputCount += 1;
				}
			}
			if( inSize.x > 0 )
				inSize.x += UIUtils.PortsSize.x + Constants.PORT_TO_LABEL_SPACE_X * 2;
			inSize.x += m_marginPreviewLeft;
			inSize.y = Mathf.Max( inSize.y, UIUtils.PortsSize.y );


			Vector2 outSize = Vector2.zero;
			int outputCount = 0;
			int outputSize = m_outputPorts.Count;
			for( int i = 0; i < outputSize; i++ )
			{
				if( m_outputPorts[ i ].Visible )
				{
					if( m_outputPorts[ i ].DirtyLabelSize || m_outputPorts[ i ].LabelSize == Vector2.zero )
					{
						m_outputPorts[ i ].DirtyLabelSize = false;
						m_sizeContentAux.text = m_outputPorts[ i ].Name;
						m_outputPorts[ i ].UnscaledLabelSize = UIUtils.UnZoomedOutputPortPortStyle.CalcSize( m_sizeContentAux );
					}

					outSize.x = Mathf.Max( outSize.x, m_outputPorts[ i ].UnscaledLabelSize.x );
					outSize.y = Mathf.Max( outSize.y, m_outputPorts[ i ].UnscaledLabelSize.y );
					outputCount += 1;
				}
			}
			if( outSize.x > 0 )
				outSize.x += UIUtils.PortsSize.x + Constants.PORT_TO_LABEL_SPACE_X * 2;
			outSize.y = Mathf.Max( outSize.y, UIUtils.PortsSize.y );

			if( m_additionalContent.text.Length > 0 )
			{
				m_extraHeaderHeight = (int)Constants.NODE_HEADER_EXTRA_HEIGHT;
				m_hasSubtitle = true && m_showSubtitle;
			}
			else
			{
				m_extraHeaderHeight = 0;
				m_hasSubtitle = false;
			}

			float headerWidth = Mathf.Max( UIUtils.UnZoomedNodeTitleStyle.CalcSize( m_content ).x + m_paddingTitleLeft + m_paddingTitleRight, UIUtils.UnZoomedPropertyValuesTitleStyle.CalcSize( m_additionalContent ).x + m_paddingTitleLeft + m_paddingTitleRight );
			m_position.width = Mathf.Max(headerWidth, Mathf.Max(MinInsideBoxWidth, m_insideSize.x) + inSize.x + outSize.x);/* + Constants.NODE_HEADER_LEFTRIGHT_MARGIN * 2;*/
			m_position.width = Mathf.Round(m_position.width / 32) * 32 + 32;
            //m_position.width += m_extraSize.x;

            m_fontHeight = Mathf.Max( inSize.y, outSize.y );

			m_position.height = Mathf.Max( inputCount, outputCount ) * ( m_fontHeight + Constants.INPUT_PORT_DELTA_Y );// + Constants.INPUT_PORT_DELTA_Y;
			m_position.height = Mathf.Max( m_position.height, Mathf.Max( MinInsideBoxHeight, m_insideSize.y ) );
			m_position.height += UIUtils.HeaderMaxHeight + /*m_extraHeaderHeight +*/ Constants.INPUT_PORT_DELTA_Y;// + m_extraSize.y;
			if( m_showErrorMessage )
				m_position.height += 24;

			m_unpreviewedPosition = m_position;
			//UIUtils.CurrentWindow.CameraDrawInfo.InvertedZoom = cachedZoom;
			if( OnNodeChangeSizeEvent != null )
			{
				OnNodeChangeSizeEvent( this );
			}
			ChangeSizeFinished();
		}

		public virtual void Reset() { }
		public virtual void OnOutputPortConnected( int portId, int otherNodeId, int otherPortId ) { }

		public virtual void OnInputPortConnected( int portId, int otherNodeId, int otherPortId, bool activateNode = true )
		{
			InputPort port = GetInputPortByUniqueId( portId );
			if( activateNode && m_connStatus == NodeConnectionStatus.Connected )
			{
				port.GetOutputNode().ActivateNode( m_activeNode, m_activePort, m_activeType );
			}

			PreviewIsDirty = true;
			OnNodeChange();
			SetSaveIsDirty();
		}

		public virtual void OnInputPortDisconnected( int portId ) { PreviewIsDirty = true; OnNodeChange(); }
		public virtual void OnOutputPortDisconnected( int portId ) { }

		public virtual void OnNodeChange()
		{
			CheckSpherePreview();
			int count = m_outputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( m_outputPorts[ i ].IsConnected )
				{
					for( int f = 0; f < m_outputPorts[ i ].ExternalReferences.Count; f++ )
					{
						ContainerGraph.GetNode( m_outputPorts[ i ].ExternalReferences[ f ].NodeId ).OnNodeChange();
					}
				}
			}
		}

		public virtual void ActivateNode( int signalGenNodeId, int signalGenPortId, System.Type signalGenNodeType )
		{
			if( m_selfPowered )
				return;

			ConnStatus = m_restrictions.GetRestiction( signalGenNodeType, signalGenPortId ) ? NodeConnectionStatus.Error : NodeConnectionStatus.Connected;
			m_activeConnections += 1;

			if( m_activeConnections == 1 )
			{
				m_activeType = signalGenNodeType;
				m_activeNode = signalGenNodeId;
				m_activePort = signalGenPortId;
				for( int i = 0; i < m_inputPorts.Count; i++ )
				{
					if( m_inputPorts[ i ].IsConnected )
					{
						m_inputPorts[ i ].GetOutputNode().ActivateNode( signalGenNodeId, signalGenPortId, signalGenNodeType );
					}
				}
			}
			// saveisdirty might be needed, gonna leave this here for now
			// SetSaveIsDirty();
		}

		public virtual void DeactivateInputPortNode( int deactivatedPort, bool forceComplete )
		{
			GetInputPortByUniqueId( deactivatedPort ).GetOutputNode().DeactivateNode( deactivatedPort, false );
		}

		public virtual void DeactivateNode( int deactivatedPort, bool forceComplete )
		{
			if( m_selfPowered )
				return;

			// saveisdirty might be needed, gonna leave this here for now
			// SetSaveIsDirty();
			m_activeConnections -= 1;
			if( forceComplete || m_activeConnections <= 0 )
			{
				m_activeConnections = 0;
				ConnStatus = NodeConnectionStatus.Not_Connected;
				for( int i = 0; i < m_inputPorts.Count; i++ )
				{
					if( m_inputPorts[ i ].IsConnected )
					{
						ParentNode node = m_inputPorts[ i ].GetOutputNode();
						if( node != null )
							node.DeactivateNode( deactivatedPort == -1 ? m_inputPorts[ i ].PortId : deactivatedPort, false );
					}
				}
			}
		}

		public Rect GlobalToLocalPosition( DrawInfo drawInfo )
		{
			float width = m_globalPosition.width / drawInfo.InvertedZoom;
			float height = m_globalPosition.height / drawInfo.InvertedZoom;

			float x = m_globalPosition.x / drawInfo.InvertedZoom - drawInfo.CameraOffset.x;
			float y = m_globalPosition.y / drawInfo.InvertedZoom - drawInfo.CameraOffset.y;
			return new Rect( x, y, width, height );
		}

		protected void CalculatePositionAndVisibility( DrawInfo drawInfo )
		{
			//m_movingInFrame = false;
			m_globalPosition = m_position;
			m_globalPosition.x = drawInfo.InvertedZoom * ( m_globalPosition.x + drawInfo.CameraOffset.x );
			m_globalPosition.y = drawInfo.InvertedZoom * ( m_globalPosition.y + drawInfo.CameraOffset.y );
			m_globalPosition.width *= drawInfo.InvertedZoom;
			m_globalPosition.height *= drawInfo.InvertedZoom;

			m_isVisible = ( m_globalPosition.x + m_globalPosition.width > 0 ) &&
							( m_globalPosition.x < drawInfo.CameraArea.width ) &&
							( m_globalPosition.y + m_globalPosition.height > 0 ) &&
							( m_globalPosition.y < drawInfo.CameraArea.height );

			if( m_isMoving > 0 && drawInfo.CurrentEventType != EventType.MouseDrag )
			{
				float deltaX = Mathf.Abs( m_lastPosition.x - m_position.x );
				float deltaY = Mathf.Abs( m_lastPosition.y - m_position.y );
				if( deltaX < 0.01f && deltaY < 0.01f )
				{
					m_isMoving -= 1;
					if( m_isMoving == 0 )
					{
						OnSelfStoppedMovingEvent();
					}
				}
				else
				{
					m_isMoving = MoveCountBuffer;
				}
				m_lastPosition = m_position;
			}
		}

		public void FireStoppedMovingEvent( bool testOnlySelected, InteractionMode interactionMode )
		{
			if( OnNodeStoppedMovingEvent != null )
				OnNodeStoppedMovingEvent( this, testOnlySelected, interactionMode );
		}

		public virtual void OnSelfStoppedMovingEvent()
		{
			FireStoppedMovingEvent( true, m_defaultInteractionMode );
		}

		protected void DrawPrecisionProperty( bool withInherit = true )
		{
			if( withInherit )
				m_currentPrecisionType = (PrecisionType)EditorGUILayoutPopup( PrecisionContent.text, (int)m_currentPrecisionType, PrecisionLabelsExtra );
			else
				m_currentPrecisionType = (PrecisionType)EditorGUILayoutPopup( PrecisionContent.text, (int)m_currentPrecisionType, PrecisionLabels );
		}

		public virtual void DrawTitle( Rect titlePos )
		{
			if( ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD3 )
			{
				GUI.Label( titlePos, m_content, UIUtils.GetCustomStyle( CustomStyle.NodeTitle ) );
			}
		}

		public virtual void DrawPreview( DrawInfo drawInfo, Rect rect )
		{
			//if ( !m_drawPreview )
			//	return;

			if( m_cachedDrawSphereId == -1 )
				m_cachedDrawSphereId = Shader.PropertyToID( "_DrawSphere" );

			if( m_cachedInvertedZoomId == -1 )
				m_cachedInvertedZoomId = Shader.PropertyToID( "_InvertedZoom" );

			m_channelNumber = 0;
			Vector4 mask = Vector4.one;
			if( m_outputPorts.Count > 0 )
			{
				switch( m_outputPorts[ 0 ].DataType )
				{
					case WirePortDataType.FLOAT:
					m_channelNumber = 1;
					mask.Set( 1, 1, 1, 0 );
					break;
					case WirePortDataType.FLOAT2:
					m_channelNumber = 2;
					mask.Set( m_previewChannels[ 0 ] ? 1 : 0, m_previewChannels[ 1 ] ? 1 : 0, 1, 0 );
					break;
					case WirePortDataType.COLOR:
					case WirePortDataType.FLOAT4:
					case WirePortDataType.SAMPLER1D:
					case WirePortDataType.SAMPLER2D:
					case WirePortDataType.SAMPLER3D:
					case WirePortDataType.SAMPLERCUBE:
					case WirePortDataType.SAMPLER2DARRAY:
					m_channelNumber = 4;
					mask.Set( m_previewChannels[ 0 ] ? 1 : 0, m_previewChannels[ 1 ] ? 1 : 0, m_previewChannels[ 2 ] ? 1 : 0, m_previewChannels[ 3 ] ? 1 : 0 );
					break;
					default:
					m_channelNumber = 3;
					mask.Set( m_previewChannels[ 0 ] ? 1 : 0, m_previewChannels[ 1 ] ? 1 : 0, m_previewChannels[ 2 ] ? 1 : 0, 0 );
					break;
				}
			}

			UIUtils.LinearMaterial.SetFloat( m_cachedDrawSphereId, ( SpherePreview ? 1 : 0 ) );
			UIUtils.LinearMaterial.SetFloat( m_cachedInvertedZoomId, drawInfo.InvertedZoom );
			UIUtils.LinearMaterial.SetVector( "_Mask", mask );

			bool cached = GL.sRGBWrite;
			GL.sRGBWrite = true;
			//EditorGUI.DrawPreviewTexture( rect, PreviewTexture, UIUtils.LinearMaterial );
			int pass = 0;
			if( SpherePreview )
			{
				if( mask.w == 1 )
					pass = 3;
				else
					pass = 1;
			}
			else if( mask.w == 1 )
				pass = 2;

			Graphics.DrawTexture( rect, PreviewTexture, UIUtils.LinearMaterial, pass );
			GL.sRGBWrite = cached;
			//Preview buttons
			if( m_drawPreviewMaskButtons )
				DrawPreviewMaskButtonsRepaint( drawInfo, rect );
		}

		protected void DrawPreviewMaskButtonsLayout( DrawInfo drawInfo, Rect rect )
		{
			if( rect.Contains( drawInfo.MousePosition ) && m_channelNumber > 1 && ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD2 )
			{
				Rect buttonRect = rect;
				buttonRect.height = 14 * drawInfo.InvertedZoom;
				buttonRect.y = rect.yMax - buttonRect.height;
				buttonRect.width = 14 * drawInfo.InvertedZoom;

				if( m_channelNumber == 2 )
				{
					m_previewChannels[ 0 ] = GUI.Toggle( buttonRect, m_previewChannels[ 0 ], string.Empty, GUIStyle.none );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					m_previewChannels[ 1 ] = GUI.Toggle( buttonRect, m_previewChannels[ 1 ], string.Empty, GUIStyle.none );
				}
				else if( m_channelNumber == 3 )
				{
					m_previewChannels[ 0 ] = GUI.Toggle( buttonRect, m_previewChannels[ 0 ], string.Empty, GUIStyle.none );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					m_previewChannels[ 1 ] = GUI.Toggle( buttonRect, m_previewChannels[ 1 ], string.Empty, GUIStyle.none );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					m_previewChannels[ 2 ] = GUI.Toggle( buttonRect, m_previewChannels[ 2 ], string.Empty, GUIStyle.none );
				}
				else if( m_channelNumber == 4 )
				{
					m_previewChannels[ 0 ] = GUI.Toggle( buttonRect, m_previewChannels[ 0 ], string.Empty, GUIStyle.none );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					m_previewChannels[ 1 ] = GUI.Toggle( buttonRect, m_previewChannels[ 1 ], string.Empty, GUIStyle.none );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					m_previewChannels[ 2 ] = GUI.Toggle( buttonRect, m_previewChannels[ 2 ], string.Empty, GUIStyle.none );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					m_previewChannels[ 3 ] = GUI.Toggle( buttonRect, m_previewChannels[ 3 ], string.Empty, GUIStyle.none );
				}
			}
		}

		protected void DrawPreviewMaskButtonsRepaint( DrawInfo drawInfo, Rect rect )
		{
			if( drawInfo.CurrentEventType == EventType.Repaint && ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD2 && rect.Contains( drawInfo.MousePosition ) && m_channelNumber > 1 )
			{
				Rect buttonRect = rect;
				buttonRect.height = 14 * drawInfo.InvertedZoom;
				buttonRect.y = rect.yMax - buttonRect.height;
				buttonRect.width = 14 * drawInfo.InvertedZoom;

				if( m_channelNumber == 2 )
				{
					UIUtils.MiniButtonTopMid.Draw( buttonRect, "R", false, false, m_previewChannels[ 0 ], false );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					UIUtils.MiniButtonTopRight.Draw( buttonRect, "G", false, false, m_previewChannels[ 1 ], false );
				}
				else if( m_channelNumber == 3 )
				{
					UIUtils.MiniButtonTopMid.Draw( buttonRect, "R", false, false, m_previewChannels[ 0 ], false );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					UIUtils.MiniButtonTopMid.Draw( buttonRect, "G", false, false, m_previewChannels[ 1 ], false );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					UIUtils.MiniButtonTopRight.Draw( buttonRect, "B", false, false, m_previewChannels[ 2 ], false );
				}
				else if( m_channelNumber == 4 )
				{
					UIUtils.MiniButtonTopMid.Draw( buttonRect, "R", false, false, m_previewChannels[ 0 ], false );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					UIUtils.MiniButtonTopMid.Draw( buttonRect, "G", false, false, m_previewChannels[ 1 ], false );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					UIUtils.MiniButtonTopMid.Draw( buttonRect, "B", false, false, m_previewChannels[ 2 ], false );
					buttonRect.x += 14 * drawInfo.InvertedZoom;
					UIUtils.MiniButtonTopRight.Draw( buttonRect, "A", false, false, m_previewChannels[ 3 ], false );
				}
			}
		}

		public void SetTimedUpdate( double timerInterval )
		{
			m_timedUpdateInitialValue = EditorApplication.timeSinceStartup;
			m_timedUpdateInterval = timerInterval;
			m_fireTimedUpdateRequest = true;
		}

		public virtual void FireTimedUpdate() { }
		/// <summary>
		/// 
		/// </summary>
		/// <param name="drawInfo"></param>
		public virtual void OnNodeLogicUpdate( DrawInfo drawInfo )
		{
			if( m_fireTimedUpdateRequest && ( EditorApplication.timeSinceStartup - m_timedUpdateInitialValue ) > m_timedUpdateInterval )
			{
				m_fireTimedUpdateRequest = false;
				FireTimedUpdate();
			}

			if( m_repopulateDictionaries )
			{
				m_repopulateDictionaries = false;

				m_inputPortsDict.Clear();
				int inputCount = m_inputPorts.Count;
				for( int i = 0; i < inputCount; i++ )
				{
					m_inputPortsDict.Add( m_inputPorts[ i ].PortId, m_inputPorts[ i ] );
				}

				m_outputPortsDict.Clear();
				int outputCount = m_outputPorts.Count;
				for( int i = 0; i < outputCount; i++ )
				{
					m_outputPortsDict.Add( m_outputPorts[ i ].PortId, m_outputPorts[ i ] );
				}
			}
		}

		/// <summary>
		/// This method should only be called to calculate layouts of elements to be draw later, only runs once per frame and before wires are drawn
		/// </summary>
		/// <param name="drawInfo"></param>
		public virtual void OnNodeLayout( DrawInfo drawInfo )
		{

			if( ContainerGraph.ChangedLightingModel )
			{
				m_sizeIsDirty = true;
				m_firstPreviewDraw = true;
			}

			if( m_firstDraw )
			{
				m_firstDraw = false;
				AfterCommonInit();
				OnNodeChange();
			}

			if( m_previousErrorMessage != m_showErrorMessage )
			{
				m_sizeIsDirty = true;
			}

			if( m_sizeIsDirty )
			{
				m_sizeIsDirty = false;
				ChangeSize();
			}

			CalculatePositionAndVisibility( drawInfo );

			m_unscaledRemainingBox = m_position;
			m_remainingBox = m_globalPosition;

			m_lastInputBottomRight = m_position.position;
			m_lastOutputBottomLeft = m_position.position;
			m_lastOutputBottomLeft.x += m_position.width;

			m_visibleInputs = 0;
			m_visibleOutputs = 0;

			if( m_hasSubtitle )
				m_extraHeaderHeight = (int)Constants.NODE_HEADER_EXTRA_HEIGHT;
			else
				m_extraHeaderHeight = 0;

			m_lastInputBottomRight.y += UIUtils.HeaderMaxHeight + m_extraHeaderHeight;
			m_lastOutputBottomLeft.y += UIUtils.HeaderMaxHeight + m_extraHeaderHeight;
			m_unscaledRemainingBox.y += UIUtils.HeaderMaxHeight + m_extraHeaderHeight;

			if( m_isVisible )
			{
				// Header
				m_headerPosition = m_globalPosition;
				m_headerPosition.height = UIUtils.CurrentHeaderHeight + m_extraHeaderHeight * drawInfo.InvertedZoom;

				// Title
				m_titlePos = m_globalPosition;
				m_titlePos.height = m_headerPosition.height;
				if( m_hasSubtitle )
					m_titlePos.yMin += ( 2 * drawInfo.InvertedZoom );
				else
					m_titlePos.yMin += ( 8 * drawInfo.InvertedZoom );
				m_titlePos.width -= ( m_paddingTitleLeft + m_paddingTitleRight ) * drawInfo.InvertedZoom;
				m_titlePos.x += m_paddingTitleLeft * drawInfo.InvertedZoom;

				// Additional Title
				if( m_hasSubtitle )
				{
					m_addTitlePos = m_titlePos;
					m_addTitlePos.y = m_globalPosition.y;
					m_addTitlePos.yMin += ( 15 * drawInfo.InvertedZoom );
				}

				// Left Dropdown
				if( m_hasLeftDropdown && ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD4 )
				{
					m_dropdownRect = m_headerPosition;
					m_dropdownRect.width = Constants.NodeButtonSizeX * drawInfo.InvertedZoom;
					m_dropdownRect.x = m_globalPosition.x + ( Constants.IconsLeftRightMargin + 1 ) * drawInfo.InvertedZoom;
					m_dropdownRect.height = Constants.NodeButtonSizeY * drawInfo.InvertedZoom;
					m_dropdownRect.y = m_globalPosition.y + m_headerPosition.height * 0.5f - 14 * drawInfo.InvertedZoom * 0.5f;
				}

				// Expander
				if( m_drawPreviewExpander && ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD4 )
				{
					m_expandRect = m_globalPosition;
					m_expandRect.width = Constants.PreviewExpanderWidth * drawInfo.InvertedZoom;
					m_expandRect.x = m_globalPosition.x + m_globalPosition.width - ( Constants.IconsLeftRightMargin + Constants.PreviewExpanderWidth ) * drawInfo.InvertedZoom; //titlePos.x + titlePos.width;
					m_expandRect.height = Constants.PreviewExpanderHeight * drawInfo.InvertedZoom;
					m_expandRect.y = m_globalPosition.y + m_headerPosition.height * 0.5f - Constants.PreviewExpanderHeight * drawInfo.InvertedZoom * 0.5f;
				}
			}

			if( m_anchorAdjust < 0 )
			{
				m_anchorAdjust = UIUtils.GetCustomStyle( CustomStyle.PortEmptyIcon ).normal.background.width;
			}

			m_unscaledRemainingBox.y += Constants.INPUT_PORT_DELTA_Y;
			m_lastOutputBottomLeft.y += Constants.INPUT_PORT_DELTA_Y;
			m_lastInputBottomRight.y += Constants.INPUT_PORT_DELTA_Y;

			// Input Ports
			{
				m_currInputPortPos = m_globalPosition;
				m_currInputPortPos.width = drawInfo.InvertedZoom * UIUtils.PortsSize.x;
				m_currInputPortPos.height = drawInfo.InvertedZoom * UIUtils.PortsSize.y;

				m_currInputPortPos.x += drawInfo.InvertedZoom * Constants.PORT_INITIAL_X;
				m_currInputPortPos.y += drawInfo.InvertedZoom * Constants.PORT_INITIAL_Y + m_extraHeaderHeight * drawInfo.InvertedZoom;
				int inputCount = m_inputPorts.Count;

				float initialX = m_lastInputBottomRight.x;

				for( int i = 0; i < inputCount; i++ )
				{
					if( m_inputPorts[ i ].Visible )
					{
						m_visibleInputs++;
						// Button
						m_inputPorts[ i ].Position = m_currInputPortPos;

						// Label
						m_inputPorts[ i ].LabelPosition = m_currInputPortPos;
						float deltaX = 1f * drawInfo.InvertedZoom * ( UIUtils.PortsSize.x + Constants.PORT_TO_LABEL_SPACE_X );
						m_auxRect = m_inputPorts[ i ].LabelPosition;
						m_auxRect.x += deltaX;
						m_inputPorts[ i ].LabelPosition = m_auxRect;

						//if( m_inputPorts[ i ].DirtyLabelSize || m_inputPorts[ i ].LabelSize == Vector2.zero )
						//{
						//	m_inputPorts[ i ].DirtyLabelSize = false;
						//	m_sizeContentAux.text = m_inputPorts[ i ].Name;
						//	m_inputPorts[ i ].UnscaledLabelSize = UIUtils.UnZoomedInputPortStyle.CalcSize( m_sizeContentAux );
						//}

						m_inputPorts[ i ].LabelSize = m_inputPorts[ i ].UnscaledLabelSize * drawInfo.InvertedZoom;

						m_lastInputBottomRight.x = Mathf.Max( m_lastInputBottomRight.x, initialX + m_inputPorts[ i ].UnscaledLabelSize.x + Constants.PORT_INITIAL_X + Constants.PORT_TO_LABEL_SPACE_X + UIUtils.PortsSize.x );

						if( !m_inputPorts[ i ].Locked )
						{
							float overflow = 2;
							float scaledOverflow = 4 * drawInfo.InvertedZoom;
							m_auxRect = m_currInputPortPos;
							m_auxRect.yMin -= scaledOverflow + overflow;
							m_auxRect.yMax += scaledOverflow + overflow;
							m_auxRect.xMin -= Constants.PORT_INITIAL_X * drawInfo.InvertedZoom + scaledOverflow + overflow;
							if( m_containerGraph.ParentWindow.WireReferenceUtils.OutputPortReference.IsValid )
								m_auxRect.xMax += m_inputPorts[ i ].LabelSize.x + Constants.PORT_TO_LABEL_SPACE_X * drawInfo.InvertedZoom + scaledOverflow + overflow;
							else
								m_auxRect.xMax += Constants.PORT_TO_LABEL_SPACE_X * drawInfo.InvertedZoom + scaledOverflow + overflow;
							m_inputPorts[ i ].ActivePortArea = m_auxRect;
						}
						m_currInputPortPos.y += drawInfo.InvertedZoom * ( m_fontHeight + Constants.INPUT_PORT_DELTA_Y );
						//GUI.Label( m_inputPorts[ i ].ActivePortArea, string.Empty, UIUtils.Box );
					}
				}
				if( m_visibleInputs > 0 )
					m_lastInputBottomRight.y += m_fontHeight * m_visibleInputs + Constants.INPUT_PORT_DELTA_Y * ( m_visibleInputs - 1 );
			}

			// Output Ports
			{
				m_currOutputPortPos = m_globalPosition;
				m_currOutputPortPos.width = drawInfo.InvertedZoom * UIUtils.PortsSize.x;
				m_currOutputPortPos.height = drawInfo.InvertedZoom * UIUtils.PortsSize.y;

				m_currOutputPortPos.x += ( m_globalPosition.width - drawInfo.InvertedZoom * ( Constants.PORT_INITIAL_X + m_anchorAdjust ) );
				m_currOutputPortPos.y += drawInfo.InvertedZoom * Constants.PORT_INITIAL_Y + m_extraHeaderHeight * drawInfo.InvertedZoom;
				int outputCount = m_outputPorts.Count;

				float initialX = m_lastOutputBottomLeft.x;

				for( int i = 0; i < outputCount; i++ )
				{
					if( m_outputPorts[ i ].Visible )
					{
						m_visibleOutputs++;
						//Button
						m_outputPorts[ i ].Position = m_currOutputPortPos;

						// Label
						m_outputPorts[ i ].LabelPosition = m_currOutputPortPos;
						float deltaX = 1f * drawInfo.InvertedZoom * ( UIUtils.PortsSize.x + Constants.PORT_TO_LABEL_SPACE_X );
						m_auxRect = m_outputPorts[ i ].LabelPosition;
						m_auxRect.x -= deltaX;
						m_outputPorts[ i ].LabelPosition = m_auxRect;

						m_outputPorts[ i ].LabelSize = m_outputPorts[ i ].UnscaledLabelSize * drawInfo.InvertedZoom;

						m_lastOutputBottomLeft.x = Mathf.Min( m_lastOutputBottomLeft.x, initialX - m_outputPorts[ i ].UnscaledLabelSize.x - Constants.PORT_INITIAL_X - Constants.PORT_TO_LABEL_SPACE_X - UIUtils.PortsSize.x );

						if( !m_outputPorts[ i ].Locked )
						{
							float overflow = 2;
							float scaledOverflow = 4 * drawInfo.InvertedZoom;
							m_auxRect = m_currOutputPortPos;
							m_auxRect.yMin -= scaledOverflow + overflow;
							m_auxRect.yMax += scaledOverflow + overflow;
							if( m_containerGraph.ParentWindow.WireReferenceUtils.InputPortReference.IsValid )
								m_auxRect.xMin -= m_outputPorts[ i ].LabelSize.x + Constants.PORT_TO_LABEL_SPACE_X * drawInfo.InvertedZoom + scaledOverflow + overflow;
							else
								m_auxRect.xMin -= Constants.PORT_TO_LABEL_SPACE_X * drawInfo.InvertedZoom + scaledOverflow + overflow;
							m_auxRect.xMax += Constants.PORT_INITIAL_X * drawInfo.InvertedZoom + scaledOverflow + overflow;
							m_outputPorts[ i ].ActivePortArea = m_auxRect;
						}
						m_currOutputPortPos.y += drawInfo.InvertedZoom * ( m_fontHeight + Constants.INPUT_PORT_DELTA_Y );
						//GUI.Label( m_outputPorts[ i ].ActivePortArea, string.Empty, UIUtils.Box );
					}
				}
				if( m_visibleOutputs > 0 )
					m_lastOutputBottomLeft.y += m_fontHeight * m_visibleOutputs + Constants.INPUT_PORT_DELTA_Y * ( m_visibleOutputs - 1 );
			}

			m_lastInputBottomRight.x += m_marginPreviewLeft;

			//Vector2 scaledLastOutputBottomLeft = ( m_lastOutputBottomLeft + drawInfo.CameraOffset ) * drawInfo.InvertedZoom;
			//GUI.Label( new Rect( scaledLastOutputBottomLeft, Vector2.one * 2 ), string.Empty, UIUtils.CurrentWindow.CustomStylesInstance.Box );

			m_unscaledRemainingBox.xMin = m_lastInputBottomRight.x;
			//m_unscaledRemainingBox.yMin = m_lastInputBottomRight.y;
			m_unscaledRemainingBox.xMax = m_lastOutputBottomLeft.x;
			m_unscaledRemainingBox.yMax = Mathf.Max( m_lastOutputBottomLeft.y, m_lastInputBottomRight.y );

			m_remainingBox.position = ( m_unscaledRemainingBox.position + drawInfo.CameraOffset ) * drawInfo.InvertedZoom;
			m_remainingBox.size = m_unscaledRemainingBox.size * drawInfo.InvertedZoom;

			//GUI.Label( m_remainingBox, string.Empty, UIUtils.Box );

			if( m_visibleInputs == 0 )
			{
				m_remainingBox.x += Constants.PORT_INITIAL_X * drawInfo.InvertedZoom;
				m_remainingBox.width -= Constants.PORT_INITIAL_X * drawInfo.InvertedZoom;
			}

			if( m_visibleOutputs == 0 )
			{
				m_remainingBox.width -= Constants.PORT_INITIAL_X * drawInfo.InvertedZoom;
			}

			if( ContainerGraph.ParentWindow.GlobalPreview != m_globalShowPreview )
			{
				m_globalShowPreview = ContainerGraph.ParentWindow.GlobalPreview;
				m_sizeIsDirty = true;
				ContainerGraph.ParentWindow.RequestRepaint();
			}

			// Generate Proper Preview Rect
			float marginAround = 10;
			float scaledMarginAround = marginAround * drawInfo.InvertedZoom;
			float previewSize = 128;
			PreviewLocation m_autoLocation = m_selectedLocation;
			if( m_selectedLocation == PreviewLocation.Auto )
			{
				if( m_visibleOutputs > m_visibleInputs )
				{
					m_autoLocation = PreviewLocation.Left;
				}
				else if( m_visibleOutputs < m_visibleInputs )
				{
					m_autoLocation = PreviewLocation.Right;
				}
				else if( m_unscaledRemainingBox.width > previewSize )
				{
					m_autoLocation = PreviewLocation.TopCenter;
				}
				else
				{
					m_autoLocation = PreviewLocation.BottomCenter;
				}
			}

			if( m_canExpand && ( m_showPreview || m_globalShowPreview ) )
			{
				if( m_autoLocation == PreviewLocation.TopCenter )
				{
					m_unscaledPreviewRect.y = m_unscaledRemainingBox.y;
					m_unscaledPreviewRect.x = m_unscaledRemainingBox.center.x - 0.5f * ( previewSize + 2 * marginAround );
				}
				else if( m_autoLocation == PreviewLocation.BottomCenter )
				{
					m_unscaledPreviewRect.y = Mathf.Max( m_lastOutputBottomLeft.y, m_lastInputBottomRight.y );
					m_unscaledPreviewRect.x = m_position.x + 0.5f * m_position.width - 0.5f * ( previewSize + 2 * marginAround );
				}
				else if( m_autoLocation == PreviewLocation.Left )
				{
					m_unscaledPreviewRect.y = m_lastInputBottomRight.y;
					m_unscaledPreviewRect.x = m_position.x;
				}
				else if( m_autoLocation == PreviewLocation.Right )
				{
					m_unscaledPreviewRect.y = m_lastOutputBottomLeft.y;
					m_unscaledPreviewRect.x = m_lastInputBottomRight.x;
				}
				if( m_autoLocation == PreviewLocation.BottomCenter )
					m_unscaledPreviewRect.height = previewSize + 2 * marginAround;
				else if( m_autoLocation == PreviewLocation.TopCenter )
					m_unscaledPreviewRect.height = previewSize + marginAround;
				else
					m_unscaledPreviewRect.height = previewSize + ( m_visibleInputs > 0 && m_visibleOutputs > 0 ? 2 * marginAround : marginAround );
				m_unscaledPreviewRect.width = previewSize + 2 * marginAround;

				m_previewRect = m_unscaledPreviewRect;
				m_previewRect.position = ( m_previewRect.position + drawInfo.CameraOffset ) * drawInfo.InvertedZoom;
				m_auxVector2.Set( previewSize * drawInfo.InvertedZoom, previewSize * drawInfo.InvertedZoom );
				m_previewRect.size = m_auxVector2;

				if( m_autoLocation == PreviewLocation.BottomCenter )
				{
					m_auxVector2.Set( m_previewRect.position.x + scaledMarginAround, m_previewRect.position.y + scaledMarginAround );
					m_previewRect.position = m_auxVector2;
				}
				else if( m_autoLocation == PreviewLocation.TopCenter )
				{
					m_auxVector2.Set( m_previewRect.position.x + scaledMarginAround, m_previewRect.position.y );
					m_previewRect.position = m_auxVector2;
				}
				else
				{
					m_previewRect.position += new Vector2( scaledMarginAround, ( m_visibleInputs > 0 && m_visibleOutputs > 0 ? scaledMarginAround : 0 ) );
				}
			}

			// Adjust node rect after preview
			if( m_firstPreviewDraw )
			{
				m_firstPreviewDraw = false;
				ContainerGraph.ParentWindow.RequestRepaint();
				if( m_canExpand && ( m_showPreview || m_globalShowPreview ) )
				{
					if( m_autoLocation == PreviewLocation.TopCenter )
					{
						float fillWidth = m_unscaledRemainingBox.width - m_unscaledPreviewRect.width;
						m_extraSize.x = Mathf.Max( -fillWidth, 0 );
						float fillHeight = m_position.yMax - m_unscaledPreviewRect.yMax;
						m_extraSize.y = Mathf.Max( -fillHeight, 0 );
					}
					if( m_autoLocation == PreviewLocation.BottomCenter )
					{
						float fillWidth = m_position.width - m_unscaledPreviewRect.width;
						m_extraSize.x = Mathf.Max( -fillWidth, 0 );
						float fillHeight = m_position.yMax - m_unscaledPreviewRect.yMax;
						m_extraSize.y = Mathf.Max( -fillHeight, 0 );
					}
					else if( m_autoLocation == PreviewLocation.Left )
					{
						float fillWidth = m_lastOutputBottomLeft.x - m_unscaledPreviewRect.xMax;
						m_extraSize.x = Mathf.Max( -fillWidth, 0 );
						float fillHeight = m_position.yMax - m_unscaledPreviewRect.yMax;
						m_extraSize.y = Mathf.Max( -fillHeight, 0 );
					}
					else if( m_autoLocation == PreviewLocation.Right )
					{
						float fillWidth = m_position.xMax - m_unscaledPreviewRect.xMax;
						m_extraSize.x = Mathf.Max( -fillWidth, 0 );
						float fillHeight = m_position.yMax - m_unscaledPreviewRect.yMax;
						m_extraSize.y = Mathf.Max( -fillHeight, 0 );
					}

					if( m_showErrorMessage )
						m_extraSize.y += 24;
				}
				else if( m_canExpand )
				{
					m_extraSize.y = 0;
					m_extraSize.x = 0;
				}

				m_position.width = m_unpreviewedPosition.width + m_extraSize.x;
				m_position.height = m_unpreviewedPosition.height + m_extraSize.y;
			}


			if( m_showErrorMessage )
			{
				m_errorBox = m_globalPosition;
				m_errorBox.y = ( m_globalPosition.yMax - 28 * drawInfo.InvertedZoom ) + 3 * drawInfo.InvertedZoom;
				m_errorBox.height = 25 * drawInfo.InvertedZoom;
			}

			m_previousErrorMessage = m_showErrorMessage;
		}

		/// <summary>
		/// This method should only be called to draw elements, runs once per frame and after wires are drawn
		/// </summary>
		/// <param name="drawInfo"></param>
		public virtual void OnNodeRepaint( DrawInfo drawInfo )
		{
			if( !m_isVisible )
				return;

			m_colorBuffer = GUI.color;
			// Background
			GUI.color = m_infiniteLoopDetected ? Constants.InfiniteLoopColor : Constants.NodeBodyColor;
			if( m_useSquareNodeTitle || ContainerGraph.LodLevel >= ParentGraph.NodeLOD.LOD2 )
				GUI.Label( m_globalPosition, string.Empty, UIUtils.NodeWindowOffSquare );
			else
				GUI.Label( m_globalPosition, string.Empty, UIUtils.GetCustomStyle( CustomStyle.NodeWindowOff ) );

			// Header
			//GUI
			GUI.color = m_headerColor * m_headerColorModifier;
			if( m_useSquareNodeTitle || ContainerGraph.LodLevel >= ParentGraph.NodeLOD.LOD2 )
				GUI.Label( m_headerPosition, string.Empty, UIUtils.NodeHeaderSquare );
			else
				GUI.Label( m_headerPosition, string.Empty, UIUtils.GetCustomStyle( CustomStyle.NodeHeader ) );
			GUI.color = m_colorBuffer;

			// @diogo: shader function icon
			var functionNode = this as FunctionNode;
			if ( functionNode != null )
			{
				float scaledSize = FunctionNode.HeaderIconSize * drawInfo.InvertedZoom;
				GUI.DrawTexture( new Rect( m_globalPosition.x, m_globalPosition.y, scaledSize, scaledSize ), functionNode.HeaderIcon, ScaleMode.ScaleAndCrop, true );
			}			

			// Title
			DrawTitle( m_titlePos );

			// Additional Tile
			if( m_hasSubtitle && ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD3 )
				GUI.Label( m_addTitlePos, m_additionalContent, UIUtils.GetCustomStyle( CustomStyle.PropertyValuesTitle ) );

			// Dropdown
			if( m_hasLeftDropdown && !m_dropdownEditing && ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD4 )
				GUI.Label( m_dropdownRect, string.Empty, UIUtils.PropertyPopUp );

			// Expander
			if( m_drawPreviewExpander && ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD4 )
				GUI.Label( m_expandRect, string.Empty, ( m_showPreview ? UIUtils.PreviewCollapser : UIUtils.PreviewExpander ) );

			// Input Ports
			int inputCount = m_inputPorts.Count;

			for( int i = 0; i < inputCount; i++ )
			{
				if( m_inputPorts[ i ].Visible )
				{
					// Input Port Icon
					if( ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD4 )
					{
						if( m_inputPorts[ i ].Locked )
							GUI.color = Constants.LockedPortColor;
						else if( ContainerGraph.ParentWindow.Options.ColoredPorts )
							GUI.color = UIUtils.GetColorForDataType( m_inputPorts[ i ].DataType, false, true );
						else
							GUI.color = m_inputPorts[ i ].HasCustomColor ? m_inputPorts[ i ].CustomColor : UIUtils.GetColorForDataType( m_inputPorts[ i ].DataType, true, true );

						GUIStyle style = m_inputPorts[ i ].IsConnected ? UIUtils.GetCustomStyle( CustomStyle.PortFullIcon ) : UIUtils.GetCustomStyle( CustomStyle.PortEmptyIcon );
						GUI.Label( m_inputPorts[ i ].Position, string.Empty, style );

						GUI.color = m_colorBuffer;
					}

					// Input Port Label
					if( ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD3 )
					{
						if( m_inputPorts[ i ].Locked )
						{
							GUI.color = Constants.PortLockedTextColor;
							GUI.Label( m_inputPorts[ i ].LabelPosition, m_inputPorts[ i ].Name, UIUtils.InputPortLabel );
							GUI.color = m_colorBuffer;
						}
						else
						{
							if( m_containerGraph.ParentWindow.GlobalShowInternalData && !m_inputPorts[ i ].IsConnected && UIUtils.InternalDataOnPort.fontSize > 1f && ( m_inputPorts[ i ].AutoDrawInternalData || ( m_autoDrawInternalPortData && m_useInternalPortData ) ) && m_inputPorts[ i ].DisplayInternalData.Length > 4 && ( ( m_inputPorts[ i ].DataType >= WirePortDataType.FLOAT && m_inputPorts[ i ].DataType <= WirePortDataType.INT ) || m_inputPorts[ i ].DataType == WirePortDataType.UINT ) )
							{
								GUI.color = Constants.NodeBodyColor/* * new Color( 1f, 1f, 1f, 0.75f )*/;
								Rect internalBox = m_inputPorts[ i ].LabelPosition;
								m_sizeContentAux.text = m_inputPorts[ i ].DisplayInternalData;
								Vector2 portText = UIUtils.InternalDataOnPort.CalcSize( m_sizeContentAux );
								internalBox.width = portText.x;
								internalBox.height = portText.y;
								internalBox.y = m_inputPorts[ i ].LabelPosition.center.y - internalBox.height * 0.5f;
								internalBox.x = GlobalPosition.x - internalBox.width - 4 * drawInfo.InvertedZoom - 1;
								Rect backBox = new Rect( internalBox );
								backBox.xMin -= 4 * drawInfo.InvertedZoom;
								backBox.xMax += 4 * drawInfo.InvertedZoom;
								backBox.yMin -= 2 * drawInfo.InvertedZoom;
								backBox.yMax += 2 * drawInfo.InvertedZoom;
								GUI.Label( backBox, string.Empty, UIUtils.InternalDataBackground );
								GUI.color *= new Color( 1f, 1f, 1f, 0.5f );
								GUI.Label( internalBox, m_sizeContentAux, UIUtils.InternalDataOnPort );
								GUI.color = m_colorBuffer;
							}
							GUI.Label( m_inputPorts[ i ].LabelPosition, m_inputPorts[ i ].Name, UIUtils.InputPortLabel );
						}
					}
				}
			}

			// Output Ports
			int outputCount = m_outputPorts.Count;
			for( int i = 0; i < outputCount; i++ )
			{
				if( m_outputPorts[ i ].Visible )
				{
					// Output Port Icon
					if( ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD4 )
					{
						if( m_outputPorts[ i ].Locked )
							GUI.color = Constants.LockedPortColor;
						else if( ContainerGraph.ParentWindow.Options.ColoredPorts )
							GUI.color = UIUtils.GetColorForDataType( m_outputPorts[ i ].DataType, false, false );
						else
							GUI.color = m_outputPorts[ i ].HasCustomColor ? m_outputPorts[ i ].CustomColor : UIUtils.GetColorForDataType( m_outputPorts[ i ].DataType, true, false );

						GUIStyle style = m_outputPorts[ i ].IsConnected ? UIUtils.GetCustomStyle( CustomStyle.PortFullIcon ) : UIUtils.GetCustomStyle( CustomStyle.PortEmptyIcon );
						GUI.Label( m_outputPorts[ i ].Position, string.Empty, style );

						GUI.color = m_colorBuffer;
					}

					// Output Port Label
					if( ContainerGraph.LodLevel <= ParentGraph.NodeLOD.LOD3 )
					{
						if( m_outputPorts[ i ].Locked )
						{
							GUI.color = Constants.PortLockedTextColor;
							GUI.Label( m_outputPorts[ i ].LabelPosition, m_outputPorts[ i ].Name, UIUtils.OutputPortLabel );
							GUI.color = m_colorBuffer;
						}
						else
						{
							GUI.Label( m_outputPorts[ i ].LabelPosition, m_outputPorts[ i ].Name, UIUtils.OutputPortLabel );
						}
					}
				}
			}

			// Preview
			if( ( m_showPreview || m_globalShowPreview ) && m_drawPreview )
				DrawPreview( drawInfo, m_previewRect );

			// Error and Warning bottom message
			if( m_showErrorMessage )
			{
				GUI.color = new Color( 0.0f, 0.0f, 0.0f, 0.5f );
				GUI.Label( m_errorBox, string.Empty, UIUtils.Separator );
				GUI.color = m_colorBuffer;

				switch( m_errorMessageTypeIsError )
				{
					default:
					case NodeMessageType.Error:
					{
						m_errorMessage.text = ErrorTitle;
						m_errorIcon.image = UIUtils.SmallErrorIcon;
						m_errorCurrentStyle = UIUtils.BoldErrorStyle;
					}
					break;
					case NodeMessageType.Warning:
					{
						m_errorMessage.text = WarningTitle;
						m_errorIcon.image = UIUtils.SmallWarningIcon;
						m_errorCurrentStyle = UIUtils.BoldWarningStyle;
					}
					break;
					case NodeMessageType.Info:
					{
						m_errorMessage.text = InfoTitle;
						m_errorIcon.image = UIUtils.SmallInfoIcon;
						m_errorCurrentStyle = UIUtils.BoldInfoStyle;
					}
					break;
				}

				Rect textBox = m_errorBox;
				textBox.y += 1 * drawInfo.InvertedZoom;
				textBox.height = 24 * drawInfo.InvertedZoom;

				float textWidth = m_errorCurrentStyle.CalcSize( m_errorMessage ).x;

				GUI.Label( textBox, m_errorMessage, m_errorCurrentStyle );
				textBox.x -= textWidth * 0.5f + 12 * drawInfo.InvertedZoom;
				GUI.Label( textBox, m_errorIcon, m_errorCurrentStyle );
				textBox.x += textWidth + 24 * drawInfo.InvertedZoom;
				GUI.Label( textBox, m_errorIcon, m_errorCurrentStyle );
			}

			// Selection Box
			if( m_selected )
			{
				GUI.color = Constants.NodeSelectedColor;
				if( m_useSquareNodeTitle || ContainerGraph.LodLevel >= ParentGraph.NodeLOD.LOD2 )
					GUI.Label( m_globalPosition, string.Empty, UIUtils.NodeWindowOnSquare );
				else
					GUI.Label( m_globalPosition, string.Empty, UIUtils.GetCustomStyle( CustomStyle.NodeWindowOn ) );
				GUI.color = m_colorBuffer;
			}

			// Debug Visualizers
			//if( FinishPreviewRender || m_continuousPreviewRefresh )
			//{
			//	GUI.color = new Color( 0, 1, 0.5f, 1f );
			//	Rect r = m_globalPosition;
			//	r.width = 8;
			//	r.height = 8;
			//	r.x -= 5 * drawInfo.InvertedZoom;
			//	r.y -= 5 * drawInfo.InvertedZoom;
			//	GUI.Label( r, string.Empty, UIUtils.GetCustomStyle( CustomStyle.PortFullIcon ) );
			//	GUI.color = m_colorBuffer;
			//	FinishPreviewRender = false;
			//}
			//GUI.Label( m_remainingBox, string.Empty, UIUtils.Box );
		}

		public bool DropdownEditing { get { return m_dropdownEditing; } set { m_dropdownEditing = value; PreviewIsDirty = true; } }
		public void DisablePreview()
		{
			m_showPreview = false;
			m_sizeIsDirty = true;
		}
		/// <summary>
		/// Handles gui controls, runs before node layout
		/// </summary>
		/// <param name="drawInfo"></param>
		public virtual void DrawGUIControls( DrawInfo drawInfo )
		{
			if( !m_initialized )
				return;

			if( !m_isVisible )
				return;

			if( drawInfo.CurrentEventType == EventType.MouseDown && drawInfo.LeftMouseButtonPressed )
			{
				if( m_expandRect.Contains( drawInfo.MousePosition ) )
				{
					m_showPreview = !m_showPreview;
					m_sizeIsDirty = true;
					ContainerGraph.ParentWindow.MouseInteracted = true;
				}

				if( m_hasLeftDropdown && m_dropdownRect.Contains( drawInfo.MousePosition ) )
				{
					m_dropdownEditing = true;
				}
				else if( m_dropdownEditing )
				{
					DropdownEditing = false;
				}
			}

			DrawGuiPorts( drawInfo );
		}

		//public static bool MyRepeatButton( DrawInfo drawInfo, Rect position, string text, GUIStyle style )
		//{
		//	if(/* drawInfo.CurrentEventType == EventType.MouseDown &&*/ position.Contains( drawInfo.MousePosition ) )
		//	{
		//		UIUtils.CurrentWindow.MouseInteracted = true;
		//		return true;
		//	}
		//	return false;
		//}

		public void DrawGuiPorts( DrawInfo drawInfo )
		{
			if( !m_initialized )
				return;

			if( !m_isVisible )
				return;

			if( drawInfo.CurrentEventType == EventType.MouseDown )
			{
				int inputCount = m_inputPorts.Count;
				int outputCount = m_outputPorts.Count;

				for( int i = 0; i < inputCount; i++ )
				{
					if( m_inputPorts[ i ].Visible && !m_inputPorts[ i ].Locked && m_isVisible && m_inputPorts[ i ].ActivePortArea.Contains( drawInfo.MousePosition ) && drawInfo.LeftMouseButtonPressed )
					{
						UIUtils.CurrentWindow.MouseInteracted = true;
						m_inputPorts[ i ].Click();
						// need to put the mouse button on a hot state so it will detect the Mouse Up event correctly on the Editor Window
						int controlID = GUIUtility.GetControlID( FocusType.Passive );
						//int controlID = GUIUtility.GetControlID( "repeatButton".GetHashCode(), FocusType.Passive, m_inputPorts[ i ].ActivePortArea );
						GUIUtility.hotControl = controlID;

						bool saveReference = true;
						if( m_inputPorts[ i ].IsConnected )
						{
							double doubleTapTime = EditorApplication.timeSinceStartup;
							bool doubleTap = ( doubleTapTime - m_doubleClickTimestamp ) < DoubleClickTime;
							m_doubleClickTimestamp = doubleTapTime;

							if( doubleTap )
							{
								m_containerGraph.DeleteConnection( true, UniqueId, m_inputPorts[ i ].PortId, true, true );
								Event.current.Use();
							}
							else
							//if ( AppyModifierToPort( _inputPorts[ i ], true ) )
							//{
							//saveReference = false;
							//}
							if( !ApplyModifierToPort( m_inputPorts[ i ], true ) )
							{
								UIUtils.ShowContextOnPick = false;
								PickInput( m_inputPorts[ i ] );
							}
							saveReference = false;
						}

						if( saveReference && !m_containerGraph.ParentWindow.WireReferenceUtils.InputPortReference.IsValid )
						//if ( !modifierApplied && !UIUtils.InputPortReference.IsValid )
						{
							m_containerGraph.ParentWindow.WireReferenceUtils.SetInputReference( m_uniqueId, m_inputPorts[ i ].PortId, m_inputPorts[ i ].DataType, m_inputPorts[ i ].TypeLocked );
						}

						IsDirty = true;
						inputCount = m_inputPorts.Count;
					}
				}

				for( int i = 0; i < outputCount; i++ )
				{
					if( m_outputPorts[ i ].Visible && m_outputPorts[ i ].ActivePortArea.Contains( drawInfo.MousePosition ) && drawInfo.LeftMouseButtonPressed )
					{
						UIUtils.CurrentWindow.MouseInteracted = true;
						m_outputPorts[ i ].Click();
						// need to put the mouse button on a hot state so it will detect the Mouse Up event correctly on the Editor Window
						int controlID = GUIUtility.GetControlID( FocusType.Passive );
						//int controlID = GUIUtility.GetControlID( "aseRepeatButton".GetHashCode(), FocusType.Passive, m_outputPorts[ i ].ActivePortArea );
						GUIUtility.hotControl = controlID;

						bool saveReference = true;
						if( m_outputPorts[ i ].IsConnected )
						{
							if( ApplyModifierToPort( m_outputPorts[ i ], false ) )
							{
								saveReference = false;
							}
						}

						if( saveReference && !m_containerGraph.ParentWindow.WireReferenceUtils.OutputPortReference.IsValid )
						{
							m_containerGraph.ParentWindow.WireReferenceUtils.SetOutputReference( m_uniqueId, m_outputPorts[ i ].PortId, m_outputPorts[ i ].DataType, false );
						}

						IsDirty = true;
						outputCount = m_outputPorts.Count;
					}
				}
			}

			//Preview buttons
			if( m_drawPreviewMaskButtons && ( drawInfo.CurrentEventType == EventType.MouseDown || drawInfo.CurrentEventType == EventType.MouseUp ) )
				DrawPreviewMaskButtonsLayout( drawInfo, m_previewRect );
		}

		/// <summary>
		/// Can be used to draw an entire node, runs after wires
		/// </summary>
		/// <param name="drawInfo"></param>
		public virtual void Draw( DrawInfo drawInfo )
		{
			if( !m_initialized )
				return;

			if( drawInfo.CurrentEventType == EventType.Repaint )
				OnNodeRepaint( drawInfo );
		}

		public virtual void SetPreviewInputs()
		{
			if( !HasPreviewShader || !m_initialized )
				return;

			int count = m_inputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( m_inputPorts[ i ].IsConnected && m_inputPorts[ i ].InputNodeHasPreview( ContainerGraph ) )
				{
					m_inputPorts[ i ].SetPreviewInputTexture( ContainerGraph );
				}
				else
				{
					m_inputPorts[ i ].SetPreviewInputValue( ContainerGraph );
				}
			}
		}


		public bool SafeDraw( DrawInfo drawInfo )
		{
			EditorGUI.BeginChangeCheck();
			Draw( drawInfo );
			if( EditorGUI.EndChangeCheck() )
			{
				SaveIsDirty = true;
				return true;
			}
			return false;
		}

		public bool ShowTooltip( DrawInfo drawInfo )
		{
			if( string.IsNullOrEmpty( m_tooltipText ) )
				return false;

			if( m_globalPosition.Contains( drawInfo.MousePosition ) || m_linkVisibility )
			{
				if( m_tooltipTimestamp + 0.6f < Time.realtimeSinceStartup || m_linkVisibility )
				{
					bool errorTooltip = false;
					if( m_showErrorMessage && m_errorBox.Contains( drawInfo.MousePosition ) && !string.IsNullOrEmpty( m_errorMessageTooltip ) )
						errorTooltip = true;

					Rect globalTooltipPos = m_globalPosition;
					GUIContent temp = new GUIContent( errorTooltip ? m_errorMessageTooltip : m_tooltipText );
					UIUtils.TooltipBox.wordWrap = false;
					Vector2 optimal = UIUtils.TooltipBox.CalcSize( temp );
					if( optimal.x > 300f )
					{
						UIUtils.TooltipBox.wordWrap = true;
						optimal.x = 300f;
						optimal.y = UIUtils.TooltipBox.CalcHeight( temp, 300f );
					}

					globalTooltipPos.width = Mathf.Max( 120, optimal.x );
					globalTooltipPos.height = optimal.y;
					globalTooltipPos.center = m_globalPosition.center;

					if( !errorTooltip && m_hasTooltipLink )
						globalTooltipPos.height += 16;

					if( errorTooltip )
						globalTooltipPos.y = 10 + m_globalPosition.yMax;
					else
						globalTooltipPos.y = m_globalPosition.yMin - 10 - globalTooltipPos.height;

					if ( globalTooltipPos.x < 10 )
						globalTooltipPos.x = 10;

					if( globalTooltipPos.x + globalTooltipPos.width > Screen.width - 10 )
						globalTooltipPos.x = Screen.width - globalTooltipPos.width - 10;

					//UNCOMMENT this for auto adjust tooltip to the top window box
					//if( globalTooltipPos.y < 40 )
					//	globalTooltipPos.y = 40;

					if( errorTooltip && globalTooltipPos.y + globalTooltipPos.height > Screen.height - 32 )
						globalTooltipPos.y = Screen.height - 32 - globalTooltipPos.height;

					GUI.Label( globalTooltipPos, temp, UIUtils.TooltipBox );

					if( !errorTooltip && m_hasTooltipLink )
					{
						Rect link = globalTooltipPos;
						link.y = globalTooltipPos.yMax - 16;
						link.height = 16;
						link.width = 86;
						link.x = globalTooltipPos.center.x - 43;
						Rect hover = globalTooltipPos;
						hover.yMax += 15;// m_globalPosition.yMax;
						m_linkVisibility = hover.Contains( drawInfo.MousePosition );
						if( link.Contains( drawInfo.MousePosition ) )
						{
							if( drawInfo.CurrentEventType == EventType.MouseDown )
							{
								if( m_tooltipTimestamp + 1.25f < Time.realtimeSinceStartup )
								{
									Application.OpenURL( Attributes.NodeUrl );
								}
							}
							else
							{
								UIUtils.MainSkin.customStyles[ 52 ].Draw( link, WikiLinkStr, true, false, false, false );
							}
						}
						else
						{
							GUI.Label( link, WikiLinkStr, UIUtils.MainSkin.customStyles[ 52 ] );
						}
					}
					ContainerGraph.ParentWindow.RequestRepaint();
					return true;
				}
			}
			else
			{
				if( !m_linkVisibility )
					m_tooltipTimestamp = Time.realtimeSinceStartup;
			}

			return false;
		}

		public virtual bool SafeDrawProperties()
		{
			EditorGUI.BeginChangeCheck();
			PreDrawProperties();
			if( m_autoWrapProperties )
			{
				NodeUtils.DrawPropertyGroup( ref m_propertiesFoldout, Constants.ParameterLabelStr, DrawProperties );
			}
			else
			{
				DrawProperties();
			}
			if( EditorGUI.EndChangeCheck() )
			{
				PreviewIsDirty = true;
				//UIUtils.RecordObject(this);
				//MarkForPreviewUpdate();
				return true;
			}
			return false;
		}


		public void PreDrawProperties()
		{
			if( m_useInternalPortData && m_autoDrawInternalPortData )
			{
				DrawInternalDataGroup();
			}
		}

		virtual public void DrawProperties() { }

		protected void DrawInternalDataGroup()
		{
			bool drawInternalDataUI = false;
			int inputCount = m_inputPorts.Count;
			if( inputCount > 0 )
			{
				for( int i = 0; i < inputCount; i++ )
				{
					if( m_inputPorts[ i ].Available && m_inputPorts[ i ].ValidInternalData && !m_inputPorts[ i ].IsConnected /*&& ( m_inputPorts[ i ].AutoDrawInternalData || ( m_autoDrawInternalPortData && m_useInternalPortData ) )*/  /*&& m_inputPorts[ i ].AutoDrawInternalData*/ )
					{
						drawInternalDataUI = true;
						break;
					}
				}
			}

			if( drawInternalDataUI )
				NodeUtils.DrawPropertyGroup( ref m_internalDataFoldout, Constants.InternalDataLabelStr, () =>
				{
					for( int i = 0; i < m_inputPorts.Count; i++ )
					{
						if( m_inputPorts[ i ].ValidInternalData && !m_inputPorts[ i ].IsConnected && m_inputPorts[ i ].Visible /*&& m_inputPorts[ i ].AutoDrawInternalData*/ )
						{
							m_inputPorts[ i ].ShowInternalData( this );
						}
					}
				} );
		}

		protected void PickInput( InputPort port )
		{
			WireReference connection = port.GetConnection( 0 );
			OutputPort from = port.GetOutputConnection( 0 );

			m_containerGraph.ParentWindow.WireReferenceUtils.OutputPortReference.SetReference( from.NodeId, from.PortId, from.DataType, connection.TypeLocked );
			m_containerGraph.DeleteConnection( true, UniqueId, port.PortId, true, true );
			//TODO: check if not necessary
			Event.current.Use();
			IsDirty = true;
			SetSaveIsDirty();
		}

		protected bool ApplyModifierToPort( WirePort port, bool isInput )
		{
			bool modifierApplied = false;
			switch( Event.current.modifiers )
			{
				case EventModifiers.Alt:
				{
					m_containerGraph.DeleteConnection( isInput, UniqueId, port.PortId, true, true );
					modifierApplied = true;
					m_containerGraph.ParentWindow.InvalidateAlt();
				}
				break;
				case EventModifiers.Control:
				{
					//WireReference connection = port.GetConnection( 0 );
					//if ( isInput )
					//{
					//	UIUtils.OutputPortReference.SetReference( connection.NodeId, connection.PortId, connection.DataType, connection.TypeLocked );
					//}
					//else
					//{
					//	UIUtils.InputPortReference.SetReference( connection.NodeId, connection.PortId, connection.DataType, connection.TypeLocked );
					//}

					//UIUtils.DeleteConnection( isInput, UniqueId, port.PortId, true );
					//modifierApplied = true;

					if( !isInput )
					{
						WireReference connection = port.GetConnection( 0 );
						m_containerGraph.ParentWindow.WireReferenceUtils.InputPortReference.SetReference( connection.NodeId, connection.PortId, connection.DataType, connection.TypeLocked );
						m_containerGraph.DeleteConnection( isInput, UniqueId, port.PortId, true, true );
						modifierApplied = true;
					}
				}
				break;
			}

			if( isInput )
				m_containerGraph.ParentWindow.WireReferenceUtils.SwitchPortReference.SetReference( port.NodeId, port.PortId, port.DataType, false ); //always save last connection
			else
				m_containerGraph.ParentWindow.WireReferenceUtils.SwitchPortReference.SetReference( -1, -1, WirePortDataType.OBJECT, false ); //invalidate connection

			if( modifierApplied )
			{
				Event.current.Use();
				IsDirty = true;
				SetSaveIsDirty();
			}
			return modifierApplied;
		}

		public void DeleteAllInputConnections( bool alsoDeletePorts , bool inhibitWireNodeAutoDel = false )
		{
			int count = m_inputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( m_inputPorts[ i ].IsConnected )
				{
					ParentNode connNode = null;
					if( inhibitWireNodeAutoDel )
					{
						connNode = m_inputPorts[ i ].GetOutputNode();
						connNode.Alive = false;
					}
					m_containerGraph.DeleteConnection( true, UniqueId, m_inputPorts[ i ].PortId, false, true );
					if( inhibitWireNodeAutoDel )
					{
						connNode.Alive = true;
					}
				}

			}
			if( alsoDeletePorts )
			{
				m_inputPorts.Clear();
				m_inputPortsDict.Clear();
			}
			SetSaveIsDirty();
		}

		public void DeleteAllOutputConnections( bool alsoDeletePorts )
		{
			int count = m_outputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( m_outputPorts[ i ].IsConnected )
					m_containerGraph.DeleteConnection( false, UniqueId, m_outputPorts[ i ].PortId, false, true );
			}

			if( alsoDeletePorts )
			{
				m_outputPorts.Clear();
				m_outputPortsDict.Clear();
			}
			SetSaveIsDirty();
		}

		public void DeleteInputPortByArrayIdx( int arrayIdx )
		{
			if( arrayIdx >= m_inputPorts.Count )
				return;

			m_containerGraph.DeleteConnection( true, UniqueId, m_inputPorts[ arrayIdx ].PortId, false, true );
			m_inputPortsDict.Remove( m_inputPorts[ arrayIdx ].PortId );
			m_inputPorts.RemoveAt( arrayIdx );

			m_sizeIsDirty = true;
			SetSaveIsDirty();
			RecalculateInputPortIdx();
		}

		public void DeleteOutputPortByArrayIdx( int portIdx )
		{
			if( portIdx >= m_outputPorts.Count )
				return;

			m_containerGraph.DeleteConnection( false, UniqueId, m_outputPorts[ portIdx ].PortId, false, true );
			m_outputPortsDict.Remove( m_outputPorts[ portIdx ].PortId );
			m_outputPorts.RemoveAt( portIdx );
			m_sizeIsDirty = true;
		}

		public InputPort GetInputPortByArrayId( int id )
		{
			if( id < m_inputPorts.Count )
				return m_inputPorts[ id ];

			return null;
		}

		public OutputPort GetOutputPortByArrayId( int id )
		{
			if( id < m_outputPorts.Count )
				return m_outputPorts[ id ];

			return null;
		}

		public InputPort GetInputPortByUniqueId( int id )
		{
			if( m_inputPortsDict.ContainsKey( id ) )
				return m_inputPortsDict[ id ];

			if( m_inputPortsDict.Count != m_inputPorts.Count )
				m_repopulateDictionaries = true;

			int inputCount = m_inputPorts.Count;
			for( int i = 0; i < inputCount; i++ )
			{
				if( m_inputPorts[ i ].PortId == id )
				{
					return m_inputPorts[ i ];
				}
			}
			return null;
		}

		public OutputPort GetOutputPortByUniqueId( int id )
		{
			if( m_outputPortsDict.ContainsKey( id ) )
				return m_outputPortsDict[ id ];

			if( m_outputPortsDict.Count != m_outputPorts.Count )
				m_repopulateDictionaries = true;

			int outputCount = m_outputPorts.Count;
			for( int i = 0; i < outputCount; i++ )
			{
				if( m_outputPorts[ i ].PortId == id )
					return m_outputPorts[ i ];
			}
			return null;
		}

		public virtual void AfterDuplication(){}

		public override string ToString()
		{
			string dump = "";
			dump += ( "Type: " + GetType() );
			dump += ( " Unique Id: " + UniqueId + "\n" );
			dump += ( " Inputs: \n" );

			int inputCount = m_inputPorts.Count;
			int outputCount = m_outputPorts.Count;

			for( int inputIdx = 0; inputIdx < inputCount; inputIdx++ )
			{
				dump += ( m_inputPorts[ inputIdx ] + "\n" );
			}
			dump += ( "Outputs: \n" );
			for( int outputIdx = 0; outputIdx < outputCount; outputIdx++ )
			{
				dump += ( m_outputPorts[ outputIdx ] + "\n" );
			}
			return dump;
		}

		public string GetValueFromOutputStr( int outputId, WirePortDataType inputPortType, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar )
		{
			if( ignoreLocalvar )
			{
				return GenerateShaderForOutput( outputId, ref dataCollector, ignoreLocalvar );
			}
			OutputPort outPort = GetOutputPortByUniqueId( outputId );
			if( outPort.IsLocalValue( dataCollector.PortCategory ) )
			{
				if( outPort.DataType != WirePortDataType.OBJECT && outPort.DataType != inputPortType )
				{
					return UIUtils.CastPortType( ref dataCollector, CurrentPrecisionType, new NodeCastInfo( m_uniqueId, outputId ), null, outPort.DataType, inputPortType, outPort.LocalValue( dataCollector.PortCategory ) );
				}
				else
				{
					return outPort.LocalValue( dataCollector.PortCategory );
				}
			}

			string result = GenerateShaderForOutput( outputId, ref dataCollector, ignoreLocalvar );
			result = CreateOutputLocalVariable( outputId, result, ref dataCollector );

			if( outPort.DataType != WirePortDataType.OBJECT && outPort.DataType != inputPortType )
			{
				result = UIUtils.CastPortType( ref dataCollector, CurrentPrecisionType, new NodeCastInfo( m_uniqueId, outputId ), null, outPort.DataType, inputPortType, result );
			}
			return result;
		}

		public virtual string GenerateShaderForOutput( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar )
		{
			if( dataCollector.IsSRP )
			{
				switch( dataCollector.CurrentSRPType )
				{
					case TemplateSRPType.HDRP: if(OnHDAction!=null) OnHDAction( outputId, ref dataCollector ); break;
					case TemplateSRPType.URP:if(OnLightweightAction != null) OnLightweightAction( outputId, ref dataCollector ); break;	
				}
			}
			return string.Empty;
		}

		public string GenerateValueInVertex( ref MasterNodeDataCollector dataCollector, WirePortDataType dataType, string dataValue, string dataName, bool createInterpolator )
		{

			if( !dataCollector.IsFragmentCategory )
				return dataValue;

			//TEMPLATES
			if( dataCollector.IsTemplate )
			{
				if( createInterpolator && dataCollector.TemplateDataCollectorInstance.HasCustomInterpolatedData( dataName ) )
					return dataName;

				MasterNodePortCategory category = dataCollector.PortCategory;
				dataCollector.PortCategory = MasterNodePortCategory.Vertex;

				dataCollector.PortCategory = category;

				if( createInterpolator )
				{
					dataCollector.TemplateDataCollectorInstance.RegisterCustomInterpolatedData( dataName, dataType, CurrentPrecisionType, dataValue );
				}
				else
				{
					dataCollector.AddToVertexLocalVariables( -1, CurrentPrecisionType, dataType, dataName, dataValue );
				}

				return dataName;
			}

			//SURFACE 
			{
				if( dataCollector.TesselationActive )
				{
					UIUtils.ShowMessage( UniqueId, "Unable to use Vertex to Frag when Tessellation is active" );
					return m_outputPorts[ 0 ].ErrorValue;
				}

				if( createInterpolator )
					dataCollector.AddToInput( UniqueId, dataName, dataType, CurrentPrecisionType );

				MasterNodePortCategory portCategory = dataCollector.PortCategory;
				dataCollector.PortCategory = MasterNodePortCategory.Vertex;
				if( createInterpolator )
				{
					dataCollector.AddLocalVariable( UniqueId, Constants.VertexShaderOutputStr + "." + dataName, dataValue + ";" );
				}
				else
				{
					dataCollector.AddLocalVariable( UniqueId, CurrentPrecisionType, dataType, dataName, dataValue );
				}
				dataCollector.PortCategory = portCategory;
				return createInterpolator ? Constants.InputVarStr + "." + dataName : dataName;
			}
		}

		public string GenerateInputInVertex( ref MasterNodeDataCollector dataCollector , int inputPortUniqueId , string varName , bool createInterpolator , bool noInterpolationFlag = false , bool sampleFlag = false )
		{
			InputPort inputPort = GetInputPortByUniqueId( inputPortUniqueId );
			if( !dataCollector.IsFragmentCategory )
			{
				string value = inputPort.GeneratePortInstructions( ref dataCollector );
				dataCollector.AddLocalVariable( -1 , CurrentPrecisionType , inputPort.DataType , varName , value );
				return varName;
			}

			//TEMPLATES
			if( dataCollector.IsTemplate )
			{
				if( createInterpolator && dataCollector.TemplateDataCollectorInstance.HasCustomInterpolatedData( varName ) )
					return varName;

				MasterNodePortCategory category = dataCollector.PortCategory;
				dataCollector.PortCategory = MasterNodePortCategory.Vertex;
				//bool dirtyVertexVarsBefore = dataCollector.DirtyVertexVariables;
				//ContainerGraph.ResetNodesLocalVariablesIfNot( this, MasterNodePortCategory.Vertex );

				string data = inputPort.GeneratePortInstructions( ref dataCollector );

				dataCollector.PortCategory = category;
				//if( !dirtyVertexVarsBefore && dataCollector.DirtyVertexVariables )
				//{
				//	dataCollector.AddVertexInstruction( dataCollector.VertexLocalVariablesFromList, UniqueId, false );
				//	dataCollector.ClearVertexLocalVariables();
				//	ContainerGraph.ResetNodesLocalVariablesIfNot( this, MasterNodePortCategory.Vertex );
				//}

				//ContainerGraph.ResetNodesLocalVariablesIfNot( this, MasterNodePortCategory.Fragment );

				if( createInterpolator )
				{
					dataCollector.TemplateDataCollectorInstance.RegisterCustomInterpolatedData( varName, inputPort.DataType, CurrentPrecisionType, data,true,MasterNodePortCategory.Fragment,noInterpolationFlag,sampleFlag );
				}
				else
				{
					dataCollector.AddToVertexLocalVariables( -1, CurrentPrecisionType, inputPort.DataType, varName, data );
				}

				return varName;
			}

			//SURFACE 
			{
				if( dataCollector.TesselationActive )
				{
					UIUtils.ShowMessage( UniqueId, "Unable to use Vertex to Frag when Tessellation is active" );
					return m_outputPorts[ 0 ].ErrorValue;
				}

				if( createInterpolator )
					dataCollector.AddToInput( UniqueId, varName, inputPort.DataType, CurrentPrecisionType );

				MasterNodePortCategory portCategory = dataCollector.PortCategory;
				dataCollector.PortCategory = MasterNodePortCategory.Vertex;

				//bool dirtyVertexVarsBefore = dataCollector.DirtyVertexVariables;

				//ContainerGraph.ResetNodesLocalVariablesIfNot( this, MasterNodePortCategory.Vertex );

				string vertexVarValue = inputPort.GeneratePortInstructions( ref dataCollector );
				if( createInterpolator )
				{
					dataCollector.AddLocalVariable( UniqueId, Constants.VertexShaderOutputStr + "." + varName, vertexVarValue + ";" );
				}
				else
				{
					dataCollector.AddLocalVariable( UniqueId, CurrentPrecisionType, inputPort.DataType, varName, vertexVarValue );
				}

				dataCollector.PortCategory = portCategory;

				//if( !dirtyVertexVarsBefore && dataCollector.DirtyVertexVariables )
				//{
				//	dataCollector.AddVertexInstruction( dataCollector.VertexLocalVariables, UniqueId, false );
				//	dataCollector.ClearVertexLocalVariables();
				//	ContainerGraph.ResetNodesLocalVariablesIfNot( this, MasterNodePortCategory.Vertex );
				//}

				//ContainerGraph.ResetNodesLocalVariablesIfNot( this, MasterNodePortCategory.Fragment );

				return createInterpolator ? Constants.InputVarStr + "." + varName : varName;
			}
		}


		protected virtual void OnUniqueIDAssigned() { }

		public string CreateOutputLocalVariable( int outputArrayId, string value, ref MasterNodeDataCollector dataCollector )
		{
			OutputPort port = GetOutputPortByUniqueId( outputArrayId );

			if( port.IsLocalValue( dataCollector.PortCategory ) )
				return port.LocalValue( dataCollector.PortCategory );

			if( port.ConnectionCount > 1 )
			{
				RegisterLocalVariable( outputArrayId, value, ref dataCollector );
				return port.LocalValue( dataCollector.PortCategory );
			}
			else
			{
				// revisit later (break to components case)
				port.SetLocalValue( value, dataCollector.PortCategory );
			}

			return value;
		}

		public void RegisterLocalVariable( int outputArrayId, string value, ref MasterNodeDataCollector dataCollector, string customName = null )
		{
			OutputPort port = GetOutputPortByUniqueId( outputArrayId );
			if( (int)port.DataType >= (int)( 1 << 10 ) || port.DataType == WirePortDataType.OBJECT ) //10 is the flag start of sampler types
			{
				port.SetLocalValue( value, dataCollector.PortCategory );
				return;
			}

			bool vertexMode = dataCollector.PortCategory == MasterNodePortCategory.Vertex || dataCollector.PortCategory == MasterNodePortCategory.Tessellation;
			string localVar = port.ConfigOutputLocalValue( CurrentPrecisionType, value, customName, dataCollector.PortCategory );

			if( vertexMode )
			{
				dataCollector.AddToVertexLocalVariables( m_uniqueId, localVar );
			}
			else
			{
				dataCollector.AddToFragmentLocalVariables( m_uniqueId, localVar );
			}
		}

		public void InvalidateConnections()
		{
			int inputCount = m_inputPorts.Count;
			int outputCount = m_outputPorts.Count;

			for( int i = 0; i < inputCount; i++ )
			{
				m_inputPorts[ i ].InvalidateAllConnections();
			}

			for( int i = 0; i < outputCount; i++ )
			{
				m_outputPorts[ i ].InvalidateAllConnections();
			}
		}

		public virtual bool OnClick( Vector2 currentMousePos2D )
		{
			bool singleClick = true;
			if( ( EditorApplication.timeSinceStartup - m_lastTimeSelected ) < NodeClickTime )
			{
				OnNodeDoubleClicked( currentMousePos2D );
				singleClick = false;
			}

			m_lastTimeSelected = EditorApplication.timeSinceStartup;
			return singleClick;
		}

		public virtual void OnNodeDoubleClicked( Vector2 currentMousePos2D )
		{
			ContainerGraph.ParentWindow.ParametersWindow.IsMaximized = !ContainerGraph.ParentWindow.ParametersWindow.IsMaximized;
		}

		public virtual void OnNodeSelected( bool value )
		{
			if( !value )
			{
				if( m_inputPorts != null )
				{
					int count = m_inputPorts.Count;
					for( int i = 0; i < count; i++ )
					{
						m_inputPorts[ i ].ResetEditing();
					}
				}

				if( m_outputPorts != null )
				{
					int count = m_outputPorts.Count;
					for( int i = 0; i < count; i++ )
					{
						m_outputPorts[ i ].ResetEditing();
					}
				}
			}
		}

		public void ResetOutputLocals()
		{
			int outputCount = m_outputPorts.Count;
			for( int i = 0; i < outputCount; i++ )
			{
				m_outputPorts[ i ].ResetLocalValue();
			}
		}


		public void ResetOutputLocalsIfNot( MasterNodePortCategory category )
		{
			int outputCount = m_outputPorts.Count;
			for( int i = 0; i < outputCount; i++ )
			{
				//if( !m_outputPorts[ i ].IsLocalOnCategory( category ) )
				//	m_outputPorts[ i ].ResetLocalValue();
				m_outputPorts[ i ].ResetLocalValueIfNot( category );
			}
		}

		public virtual void Rewire() { }

		//public virtual List<int> NodeReferences { get { return null; } }

		public int UniqueId
		{
			get { return m_uniqueId; }

			set
			{
				m_uniqueId = value;

				int inputCount = m_inputPorts.Count;
				int outputCount = m_outputPorts.Count;

				for( int inputIdx = 0; inputIdx < inputCount; inputIdx++ )
				{
					m_inputPorts[ inputIdx ].NodeId = value;
				}

				for( int outputIdx = 0; outputIdx < outputCount; outputIdx++ )
				{
					m_outputPorts[ outputIdx ].NodeId = value;
				}
				OnUniqueIDAssigned();
			}
		}
		public void SetBaseUniqueId( int uniqueId, bool setOnPorts = false )
		{
			m_uniqueId = uniqueId;
			if( setOnPorts )
			{
				int inputCount = m_inputPorts.Count;
				int outputCount = m_outputPorts.Count;

				for( int inputIdx = 0; inputIdx < inputCount; inputIdx++ )
				{
					m_inputPorts[ inputIdx ].NodeId = uniqueId;
				}

				for( int outputIdx = 0; outputIdx < outputCount; outputIdx++ )
				{
					m_outputPorts[ outputIdx ].NodeId = uniqueId;
				}
			}
		}

		public string OutputId
		{
			get
			{
				if( ContainerGraph.GraphId > 0 )
					return UniqueId + "_g" + ContainerGraph.GraphId;
				else
					return UniqueId.ToString();
			}
		}


		public virtual Rect Position { get { return m_position; } }
		public Rect TruePosition { get { return m_position; } }

		public Vector2 CenterPosition { get { return new Vector2( m_position.x + m_position.width * 0.5f, m_position.y + m_position.height * 0.5f ); ; } }

		public Rect GlobalPosition { get { return m_globalPosition; } }

		public Vector2 Corner { get { return new Vector2( m_position.x + m_position.width, m_position.y + m_position.height ); } }
		public Vector2 Vec2Position
		{
			get { return new Vector2( m_position.x, m_position.y ); }

			set
			{
				m_position.x = value.x;
				m_position.y = value.y;
			}
		}

		public Vector3 Vec3Position
		{
			get { return new Vector3( m_position.x, m_position.y, 0f ); }

			set
			{
				m_position.x = value.x;
				m_position.y = value.y;
			}
		}


		public bool Selected
		{
			get { return m_selected; }
			set
			{
				m_infiniteLoopDetected = false;
				m_selected = value;
				OnNodeSelected( value );
			}
		}

		public List<InputPort> InputPorts { get { return m_inputPorts; } }

		public List<OutputPort> OutputPorts
		{
			get { return m_outputPorts; }
		}

		public bool IsConnected { get { return m_connStatus == NodeConnectionStatus.Connected; } }
		public NodeConnectionStatus ConnStatus
		{
			get { return m_connStatus; }
			set
			{
				if( m_selfPowered )
				{
					m_connStatus = NodeConnectionStatus.Connected;
				}
				else
				{
					m_connStatus = value;
				}

				switch( m_connStatus )
				{
					case NodeConnectionStatus.Island:
					case NodeConnectionStatus.Not_Connected: m_statusColor = Constants.NodeDefaultColor; break;
					case NodeConnectionStatus.Connected: m_statusColor = Constants.NodeConnectedColor; break;
					case NodeConnectionStatus.Error: m_statusColor = Constants.NodeErrorColor; break;
				}

			}
		}

		public bool SelfPowered
		{
			set
			{
				m_selfPowered = value;
				if( value )
				{
					ConnStatus = NodeConnectionStatus.Connected;
				}
			}
		}

		// This is also called when recording on Undo
		public virtual void OnBeforeSerialize() { }
		public virtual void OnAfterDeserialize()
		{
			m_selected = false;
			m_isOnGrid = false;
			for( int i = 0; i < m_inputPorts.Count; i++ )
			{
				m_inputPorts[ i ].ResetWireReferenceStatus();
			}
			m_repopulateDictionaries = true;
			m_sizeIsDirty = true;
			m_previewIsDirty = true;
		}

		public virtual int InputIdFromDeprecated( int oldInputId ) { return oldInputId; }

		public virtual int OutputIdFromDeprecated( int oldOutputId ) { return oldOutputId; }

		public virtual void ReadFromDeprecated( ref string[] nodeParams, Type oldType = null ) { }

		//Inherited classes must call this base method in order to setup id and position
		public virtual void ReadFromString( ref string[] nodeParams )
		{
			ParentReadFromString( ref nodeParams );
		}

		public void ParentReadFromString( ref string[] nodeParams )
		{
			m_currentReadParamIdx = IOUtils.NodeTypeId + 1;

			UniqueId = Convert.ToInt32( nodeParams[ m_currentReadParamIdx++ ] );

			string[] posCoordinates = nodeParams[ m_currentReadParamIdx++ ].Split( IOUtils.VECTOR_SEPARATOR );

			m_position.x = Convert.ToSingle( posCoordinates[ 0 ] );
			m_position.y = Convert.ToSingle( posCoordinates[ 1 ] );

			if( UIUtils.CurrentShaderVersion() > 22 )
			{
				string val = GetCurrentParam( ref nodeParams );
				if( m_customPrecision )
				{
					if( val.Equals("Fixed") )
						m_currentPrecisionType = PrecisionType.Half;
					else
					m_currentPrecisionType = (PrecisionType)Enum.Parse( typeof( PrecisionType ), val );
				}
				else
				{
					m_currentPrecisionType = PrecisionType.Inherit;
				}
			}

			if( UIUtils.CurrentShaderVersion() > 5004 )
				m_showPreview = Convert.ToBoolean( GetCurrentParam( ref nodeParams ) );

		}

		//should be called after ReadFromString
		public virtual void ReadInputDataFromString( ref string[] nodeParams )
		{
			int count = 0;
			if( UIUtils.CurrentShaderVersion() > 7003 )
			{
				try
				{
					count = Convert.ToInt32( nodeParams[ m_currentReadParamIdx++ ] );
				}
				catch( Exception e )
				{
					Debug.LogException( e );
				}
			}
			else
			{
				count = ( m_oldInputCount < 0 ) ? m_inputPorts.Count : m_oldInputCount;
			}

			for( int i = 0; i < count && i < nodeParams.Length && m_currentReadParamIdx < nodeParams.Length; i++ )
			{
				if( UIUtils.CurrentShaderVersion() < 5003 )
				{
					int newId = VersionConvertInputPortId( i );
					if( UIUtils.CurrentShaderVersion() > 23 )
					{
						m_inputPorts[ newId ].DataType = (WirePortDataType)Enum.Parse( typeof( WirePortDataType ), nodeParams[ m_currentReadParamIdx++ ] );
					}

					m_inputPorts[ newId ].InternalData = nodeParams[ m_currentReadParamIdx++ ];
					if( m_inputPorts[ newId ].IsEditable && UIUtils.CurrentShaderVersion() >= 3100 && m_currentReadParamIdx < nodeParams.Length )
					{
						m_inputPorts[ newId ].Name = nodeParams[ m_currentReadParamIdx++ ];
					}
					m_inputPorts[ newId ].UpdatePreviewInternalData();
				}
				else
				{
					string portIdStr = nodeParams[ m_currentReadParamIdx++ ];
					int portId = -1;
					try
					{
						portId = Convert.ToInt32( portIdStr );
					}
					catch( Exception e )
					{
						Debug.LogException( e );
					}

					WirePortDataType DataType = (WirePortDataType)Enum.Parse( typeof( WirePortDataType ), nodeParams[ m_currentReadParamIdx++ ] );
					string InternalData = nodeParams[ m_currentReadParamIdx++ ];
					bool isEditable = Convert.ToBoolean( nodeParams[ m_currentReadParamIdx++ ] );
					string Name = string.Empty;
					if( isEditable && m_currentReadParamIdx < nodeParams.Length )
					{
						Name = nodeParams[ m_currentReadParamIdx++ ];
					}

					InputPort inputPort = GetInputPortByUniqueId( portId );
					if( inputPort != null )
					{
						if( UIUtils.IsValidType( DataType ) )
							inputPort.DataType = DataType;

						inputPort.InternalData = InternalData;
						if( !string.IsNullOrEmpty( Name ) )
						{
							inputPort.Name = Name;
						}
						inputPort.UpdatePreviewInternalData();
					}
				}
			}
		}

		public virtual void ReadOutputDataFromString( ref string[] nodeParams )
		{
			int count = 0;
			if( UIUtils.CurrentShaderVersion() > 7003 )
			{
				count = Convert.ToInt32( nodeParams[ m_currentReadParamIdx++ ] );
			}
			else
			{
				count = m_outputPorts.Count;
			}

			for( int i = 0; i < count && i < nodeParams.Length && m_currentReadParamIdx < nodeParams.Length; i++ )
			{
				try
				{
					WirePortDataType dataType = (WirePortDataType)Enum.Parse( typeof( WirePortDataType ), nodeParams[ m_currentReadParamIdx++ ] );
					int portId = -1;
					if( UIUtils.CurrentShaderVersion() > 13903 )
					{
						portId = Convert.ToInt32( nodeParams[ m_currentReadParamIdx++ ] ); ;
					}
					else
					{
						portId = i;
					}

					OutputPort port = GetOutputPortByUniqueId( portId );
					if( port != null && UIUtils.IsValidType( dataType ) )
					{
						port.DataType = dataType;
					}
					
				}
				catch( Exception e )
				{
					Debug.LogException( e );
				}
			}
		}

		public virtual void ReadAdditionalClipboardData( ref string[] nodeParams ) { }

		protected string GetCurrentParam( ref string[] nodeParams )
		{
			if( m_currentReadParamIdx < nodeParams.Length )
			{
				return nodeParams[ m_currentReadParamIdx++ ];
			}

			UIUtils.ShowMessage( UniqueId, "Invalid params number in node " + m_uniqueId + " of type " + GetType(), MessageSeverity.Error );
			return string.Empty;
		}

		protected string GetCurrentParam( int index, ref string[] nodeParams )
		{
			if( m_currentReadParamIdx < nodeParams.Length )
			{
				return nodeParams[ index ];
			}

			UIUtils.ShowMessage( UniqueId, "Invalid params number in node " + m_uniqueId + " of type " + GetType(), MessageSeverity.Error );
			return string.Empty;
		}


		public virtual void WriteToString( ref string nodeInfo, ref string connectionsInfo )
		{
			IOUtils.AddTypeToString( ref nodeInfo, IOUtils.NodeParam );
			IOUtils.AddFieldValueToString( ref nodeInfo, GetType() );
			IOUtils.AddFieldValueToString( ref nodeInfo, m_uniqueId );
			IOUtils.AddFieldValueToString( ref nodeInfo, ( m_position.x.ToString() + IOUtils.VECTOR_SEPARATOR + m_position.y.ToString() ) );
			IOUtils.AddFieldValueToString( ref nodeInfo, m_currentPrecisionType );
			IOUtils.AddFieldValueToString( ref nodeInfo, m_showPreview );
			for( int i = 0; i < m_inputPorts.Count; i++ )
			{
				m_inputPorts[ i ].WriteToString( ref connectionsInfo );
			}
		}

		public virtual void WriteInputDataToString( ref string nodeInfo )
		{
			IOUtils.AddFieldValueToString( ref nodeInfo, m_inputPorts.Count );
			for( int i = 0; i < m_inputPorts.Count; i++ )
			{
				IOUtils.AddFieldValueToString( ref nodeInfo, m_inputPorts[ i ].PortId );
				IOUtils.AddFieldValueToString( ref nodeInfo, m_inputPorts[ i ].DataType );
				IOUtils.AddFieldValueToString( ref nodeInfo, m_inputPorts[ i ].InternalData );
				IOUtils.AddFieldValueToString( ref nodeInfo, m_inputPorts[ i ].IsEditable );
				if( m_inputPorts[ i ].IsEditable )
				{
					IOUtils.AddFieldValueToString( ref nodeInfo, m_inputPorts[ i ].Name );
				}
			}
		}

		public void WriteOutputDataToString( ref string nodeInfo )
		{
			IOUtils.AddFieldValueToString( ref nodeInfo, m_outputPorts.Count );
			for( int i = 0; i < m_outputPorts.Count; i++ )
			{
				IOUtils.AddFieldValueToString( ref nodeInfo, m_outputPorts[ i ].DataType );
				IOUtils.AddFieldValueToString( ref nodeInfo, m_outputPorts[ i ].PortId );
			}
		}

		public virtual void WriteAdditionalClipboardData( ref string nodeInfo ) { }

		public virtual string GetIncludes() { return string.Empty; }
		public virtual void OnObjectDropped( UnityEngine.Object obj ) { }
		public virtual void SetupFromCastObject( UnityEngine.Object obj ) { }
		public virtual bool OnNodeInteraction( ParentNode node ) { return false; }
		public virtual void OnConnectedOutputNodeChanges( int portId, int otherNodeId, int otherPortId, string name, WirePortDataType type ) { }
		public virtual void OnConnectedInputNodeChanges( int portId, int otherNodeId, int otherPortId, string name, WirePortDataType type ) { }

		public Rect CachedPos { get { return m_cachedPos; } }

		public bool IsOnGrid
		{
			set { m_isOnGrid = value; }
			get { return m_isOnGrid; }
		}

		public uint CurrentReadParamIdx
		{
			get { return m_currentReadParamIdx++; }
			set { m_currentReadParamIdx = value; }
		}

		public Dictionary<string, InputPort> InputPortsDict
		{
			get
			{
				Dictionary<string, InputPort> dict = new Dictionary<string, InputPort>();
				for( int i = 0; i < m_inputPorts.Count; i++ )
				{
					dict.Add( m_inputPorts[ i ].Name, m_inputPorts[ i ] );
				}
				return dict;
			}
		}

		public bool IsDirty
		{
			set { m_isDirty = value && UIUtils.DirtyMask; }
			get
			{
				bool value = m_isDirty;
				m_isDirty = false;
				return value;
			}
		}

		public virtual void ResetNodeData()
		{
			m_category = 0;
			m_graphDepth = 0;
		}

		public virtual void PropagateNodeData( NodeData nodeData, ref MasterNodeDataCollector dataCollector )
		{
			UIUtils.SetCategoryInBitArray( ref m_category, nodeData.Category );
			nodeData.GraphDepth += 1;
			if( nodeData.GraphDepth > m_graphDepth )
			{
				m_graphDepth = nodeData.GraphDepth;
			}
			int count = m_inputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				if( m_inputPorts[ i ].IsConnected )
				{
					m_inputPorts[ i ].GetOutputNode().PropagateNodeData( nodeData, ref dataCollector );
				}
			}
		}
		
		public void SetTitleTextOnCallback( string compareTitle, Action<ParentNode, string> callback )
		{
			if( !m_previousTitle.Equals( compareTitle ) )
			{
				m_previousTitle = compareTitle;
				m_sizeIsDirty = true;
				callback( this, compareTitle );
			}
		}

		public void SetAdditonalTitleTextOnCallback( string compareTitle, Action<ParentNode, string> callback )
		{
			if( !m_previousAdditonalTitle.Equals( compareTitle ) )
			{
				m_previousAdditonalTitle = compareTitle;
				m_sizeIsDirty = true;
				callback( this, compareTitle );
			}
		}

		public virtual void SetClippedTitle( string newText, int maxSize = 170, string endString = "..." )
		{
			m_content.text = GenerateClippedTitle( newText,maxSize,endString );
			m_sizeIsDirty = true;
		}
		
		public virtual void SetClippedAdditionalTitle( string newText, int maxSize = 170, string endString = "..." )
		{
			m_additionalContent.text = GenerateClippedTitle( newText, maxSize, endString );
			m_sizeIsDirty = true;
		}


		public void SetTitleText( string newText )
		{
			if( !newText.Equals( m_content.text ) )
			{
				m_content.text = newText;
				m_sizeIsDirty = true;
			}
		}

		public void SetAdditonalTitleText( string newText )
		{
			if( !newText.Equals( m_additionalContent.text ) )
			{
				m_additionalContent.text = newText;
				m_sizeIsDirty = true;
			}
		}

		public string GenerateErrorValue( int outputIdx = 0 )
		{
			switch( m_outputPorts[ outputIdx ].DataType )
			{
				case WirePortDataType.FLOAT2:
				{
					return "(0).xx";
				}
				case WirePortDataType.FLOAT3:
				{
					return "(0).xxx";
				}
				case WirePortDataType.FLOAT4:
				case WirePortDataType.COLOR:
				{
					return "(0).xxxx";
				}
			}
			return "0";
		}

		//Methods created to take into account new ports added on nodes newer versions
		//This way we can convert connections from previous versions to newer ones and not brake shader graph
		public virtual int VersionConvertInputPortId( int portId ) { return portId; }
		public virtual int VersionConvertOutputPortId( int portId ) { return portId; }

		public virtual string DataToArray { get { return string.Empty; } }

		public bool SaveIsDirty
		{
			set { m_saveIsDirty = value && UIUtils.DirtyMask; }
			get
			{
				bool value = m_saveIsDirty;
				m_saveIsDirty = false;
				return value;
			}
		}

		public GUIContent TitleContent { get { return m_content; } }
		public GUIContent AdditonalTitleContent { get { return m_additionalContent; } }
		public bool IsVisible { get { return m_isVisible; } }
		public NodeAttributes Attributes { get { return m_nodeAttribs; } }
		public bool ReorderLocked { get { return m_reorderLocked; } }
		public bool RequireMaterialUpdate { get { return m_requireMaterialUpdate; } }
		public bool RMBIgnore { get { return m_rmbIgnore; } }
		public float TextLabelWidth { get { return m_textLabelWidth; } }
		public bool IsMoving { get { return m_isMoving > 0; } }
		public bool MovingInFrame { get { return m_movingInFrame; } set { m_movingInFrame = value; } }
		public bool SizeIsDirty { get { return m_sizeIsDirty; } set { m_sizeIsDirty = value; } }
		public int Category { get { return m_category; } }
		public int CommentaryParent
		{
			get { return m_commentaryParent; }
			set { m_commentaryParent = value; }
		}

		public int Depth
		{
			get { return m_depth; }
			set { m_depth = value; }
		}

		public int MatrixId
		{
			get { return m_matrixId; }
			set { m_matrixId = value; }
		}

		public float PaddingTitleRight
		{
			get { return m_paddingTitleRight; }
			set { m_paddingTitleRight += value; }
		}

		public float PaddingTitleLeft
		{
			get { return m_paddingTitleLeft; }
			set { m_paddingTitleLeft += value; }
		}

		public int CachedPortsId
		{
			get
			{
				return m_cachedPortsId;
			}
		}

		public virtual bool RecursivePreviewUpdate( Dictionary<string,bool> duplicatesDict = null )
		{
			if( duplicatesDict == null )
			{
				duplicatesDict = ContainerGraph.ParentWindow.VisitedChanged;
			}

			for( int i = 0; i < InputPorts.Count; i++ )
			{
				ParentNode outNode = null;
				if( InputPorts[ i ].ExternalReferences.Count > 0 )
				{
					outNode = ContainerGraph.GetNode( InputPorts[ i ].ExternalReferences[ 0 ].NodeId );
				}
				if( outNode != null )
				{
					if( !duplicatesDict.ContainsKey( outNode.OutputId ) )
					{
						bool result = outNode.RecursivePreviewUpdate();
						if( result )
							PreviewIsDirty = true;
					} else if( duplicatesDict[ outNode.OutputId ] )
					{
						PreviewIsDirty = true;
					}
				}
			}
			
			bool needsUpdate = PreviewIsDirty;
			RenderNodePreview();
			if( !duplicatesDict.ContainsKey( OutputId ) )
				duplicatesDict.Add( OutputId, needsUpdate );
			return needsUpdate;
		}

		public virtual void RenderNodePreview()
		{
			//Runs at least one time
			if( !HasPreviewShader || !m_initialized )
			{
				// nodes with no preview don't update at all
				PreviewIsDirty = false;
				return;
			}

			if( !PreviewIsDirty && !m_continuousPreviewRefresh )
				return;

			//Debug.Log( "PREVIEW " + this );

			SetPreviewInputs();

			if( m_cachedMainTexId == -1 )
				m_cachedMainTexId = Shader.PropertyToID( "_MainTex" );

			if( m_cachedMaskTexId == -1 )
				m_cachedMaskTexId = Shader.PropertyToID( "_MaskTex" );

			if( m_cachedPortsId == -1 )
				m_cachedPortsId = Shader.PropertyToID( "_Ports" );

			if( m_cachedPortId == -1 )
				m_cachedPortId = Shader.PropertyToID( "_Port" );

			if (!Preferences.GlobalDisablePreviews)
			{
				RenderTexture temp = RenderTexture.active;
				RenderTexture beforeMask = RenderTexture.GetTemporary(Constants.PreviewSize, Constants.PreviewSize, 0, Constants.PreviewFormat, RenderTextureReadWrite.Linear);

				int count = m_outputPorts.Count;
				for( int i = 0; i < count; i++ )
				{
					if( i == 0 )
					{
						RenderTexture.active = beforeMask;
						Graphics.Blit( null, beforeMask, PreviewMaterial, m_previewMaterialPassId );

						m_portMask.Set( 0, 0, 0, 0 );

						switch( m_outputPorts[ i ].DataType )
						{
							case WirePortDataType.INT:
							case WirePortDataType.FLOAT:
							m_portMask.Set( 1, 1, 1, 1 );
							break;
							case WirePortDataType.FLOAT2:
							m_portMask.Set( 1, 1, 0, 0 );
							break;
							case WirePortDataType.FLOAT3:
							m_portMask.Set( 1, 1, 1, 0 );
							break;
							case WirePortDataType.COLOR:
							case WirePortDataType.FLOAT4:
							m_portMask.Set( 1, 1, 1, 1 );
							break;
							default:
							m_portMask.Set( 1, 1, 1, 1 );
							break;
						}

						if( m_outputPorts[ i ].DataType == WirePortDataType.FLOAT3x3 || m_outputPorts[ i ].DataType == WirePortDataType.FLOAT4x4 )
						{
							m_outputPorts[ i ].MaskingMaterial.SetTexture( m_cachedMainTexId, EditorGUIUtility.whiteTexture );
						}
						else
						{
							m_outputPorts[ i ].MaskingMaterial.SetTexture( m_cachedMainTexId, beforeMask );
						}
						m_outputPorts[ i ].MaskingMaterial.SetVector( m_cachedPortsId, m_portMask );

						RenderTexture.active = m_outputPorts[ i ].OutputPreviewTexture;
						Graphics.Blit( null , m_outputPorts[ i ].OutputPreviewTexture , m_outputPorts[ i ].MaskingMaterial , 0 );
					}
					else
					{
						m_outputPorts[ i ].MaskingMaterial.SetTexture( m_cachedMaskTexId , PreviewTexture );
						m_outputPorts[ i ].MaskingMaterial.SetFloat( m_cachedPortId , i );

						RenderTexture.active = m_outputPorts[ i ].OutputPreviewTexture;
						Graphics.Blit( null , m_outputPorts[ i ].OutputPreviewTexture , m_outputPorts[ i ].MaskingMaterial , 1 );					
					}
				}

				RenderTexture.ReleaseTemporary(beforeMask);
				RenderTexture.active = temp;
			}

			PreviewIsDirty = m_continuousPreviewRefresh;

			FinishPreviewRender = true;
		}

		protected void ShowTab( NodeMessageType type, string tooltip )
		{
			m_showErrorMessage = true;
			m_errorMessageTypeIsError = type;
			m_errorMessageTooltip = tooltip;
		}

		protected void ShowTab()
		{
			m_showErrorMessage = true;
		}

		protected void HideTab()
		{
			m_showErrorMessage = false;
		}

		public virtual RenderTexture PreviewTexture
		{
			get
			{
				if( m_outputPorts.Count > 0 )
					return m_outputPorts[ 0 ].OutputPreviewTexture;
				else
					return null;
			}
		}

		public void FullWriteToString( ref string nodesInfo, ref string connectionsInfo )
		{
			WriteToString( ref nodesInfo, ref connectionsInfo );
			WriteInputDataToString( ref nodesInfo );
			WriteOutputDataToString( ref nodesInfo );
		}

		public void ClipboardFullWriteToString( ref string nodesInfo, ref string connectionsInfo )
		{
			FullWriteToString( ref nodesInfo, ref connectionsInfo );
			WriteAdditionalClipboardData( ref nodesInfo );
		}

		public void FullReadFromString( ref string[] parameters )
		{
			try
			{
				ReadFromString( ref parameters );
				ReadInputDataFromString( ref parameters );
				ReadOutputDataFromString( ref parameters );
			}
			catch( Exception e )
			{
				Debug.LogException( e );
			}
		}

		public void ClipboardFullReadFromString( ref string[] parameters )
		{
			try
			{
				FullReadFromString( ref parameters );
				ReadAdditionalClipboardData( ref parameters );
			}
			catch( Exception e )
			{
				Debug.LogException( e );
			}
		}

		public string GenerateClippedTitle( string original , int maxSize = 170, string endString = "..." )
		{
			if( UIUtils.UnZoomedNodeTitleStyle == null )
				return original;

			GUIContent content = new GUIContent( original );

			string finalTitle = string.Empty;
			bool addEllipsis = false;
			for( int i = 1; i <= original.Length; i++ )
			{
				content.text = original.Substring( 0, i );
				Vector2 titleSize = UIUtils.UnZoomedNodeTitleStyle.CalcSize( content );
				if( titleSize.x > maxSize )
				{
					addEllipsis = true;
					break;
				}
				else
				{
					finalTitle = content.text;
				}
			}
			if( addEllipsis )
				finalTitle += endString;

			return finalTitle;
		}

		public virtual void RefreshOnUndo() { }
		public virtual void CalculateCustomGraphDepth() { }
		public int GraphDepth { get { return m_graphDepth; } }

		public PrecisionType CurrentPrecisionType { get { return m_currentPrecisionType == PrecisionType.Inherit ? ContainerGraph.CurrentPrecision : m_currentPrecisionType; } }


		public Material PreviewMaterial
		{
			get
			{
				if( m_previewMaterial == null )
				{
					m_previewMaterial = new Material( PreviewShader );
				}
				return m_previewMaterial;
			}
		}

		public Shader PreviewShader
		{
			get
			{
				if( m_previewShader == null )
				{
					m_previewShader = AssetDatabase.LoadAssetAtPath<Shader>( AssetDatabase.GUIDToAssetPath( m_previewShaderGUID ) );
				}

				if( m_previewShader == null )
				{
					m_previewShader = AssetDatabase.LoadAssetAtPath<Shader>( AssetDatabase.GUIDToAssetPath( "d9ca47581ac157145bff6f72ac5dd73e" ) ); //ranged float guid
				}

				if( m_previewShader == null )
					m_previewShader = Shader.Find( "Unlit/Colored Transparent" );

				return m_previewShader;
			}
		}

		public bool HasPreviewShader
		{
			get { return !string.IsNullOrEmpty( m_previewShaderGUID ); }
		}

		public void CheckSpherePreview()
		{
			bool oneIsSphere = false;

			if( m_drawPreviewAsSphere )
				oneIsSphere = true;
			int count = m_inputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				ParentNode node = null;
				if( m_inputPorts[ i ].ExternalReferences.Count > 0)
				{
					node = ContainerGraph.GetNode( m_inputPorts[ i ].ExternalReferences[ 0 ].NodeId );
				}
				if( node != null && node.SpherePreview )
					oneIsSphere = true;
			}

			if( m_forceDrawPreviewAsPlane )
				oneIsSphere = false;

			SpherePreview = oneIsSphere;
		}

		public bool SpherePreview
		{
			get { return m_spherePreview; }
			set { m_spherePreview = value; }
		}

		public bool ShowPreview
		{
			get { return m_showPreview; }
			set { m_showPreview = value; }
		}

		public int VisiblePorts
		{
			get { return m_visiblePorts; }
			set { m_visiblePorts = value; }
		}

		public bool Docking
		{
			get { return m_docking; }
			set { m_docking = value; }
		}

		public bool UseSquareNodeTitle
		{
			get { return m_useSquareNodeTitle; }
			set { m_useSquareNodeTitle = value; }
		}

		public bool InsideShaderFunction
		{
			get { return ContainerGraph != ContainerGraph.ParentWindow.CurrentGraph; }
		}

		public virtual void SetContainerGraph( ParentGraph newgraph )
		{
			m_containerGraph = newgraph;
		}
		public virtual void OnMasterNodeReplaced( MasterNode newMasterNode ) { PreviewIsDirty = true;  }
		public virtual void RefreshExternalReferences() { }

		public Rect DropdownRect { get { return m_dropdownRect; } }

		public virtual bool Contains( Vector2 pos ) { return m_globalPosition.Contains( pos ); }
		public virtual bool Contains( Vector3 pos ) { return m_globalPosition.Contains( pos ); }
		public bool IsNodeBeingCopied { get { return m_isNodeBeingCopied; } set { m_isNodeBeingCopied = value; } }

		public virtual WirePortDataType GetInputPortVisualDataTypeByArrayIdx( int portArrayIdx )
		{
			return m_inputPorts[ portArrayIdx ].DataType;
		}

		public virtual WirePortDataType GetOutputPortVisualDataTypeById( int portId )
		{
			return GetOutputPortByUniqueId( portId ).DataType;
		}


		public virtual bool CheckFindText( string text )
		{
			return TitleContent.text.IndexOf( text, StringComparison.CurrentCultureIgnoreCase ) >= 0;
		}

		public virtual ParentNode ExecuteStubCode(){ return this; }
		public virtual float HeightEstimate
		{
			get
			{
				float heightEstimate = 0;
				heightEstimate = 32 + Constants.INPUT_PORT_DELTA_Y;
				for( int i = 0; i < InputPorts.Count; i++ )
				{
					if( InputPorts[ i ].Visible )
						heightEstimate += 18 + Constants.INPUT_PORT_DELTA_Y;
				}

				return heightEstimate;
				// Magic number 18 represents m_fontHeight that might not be set yet
				//return Constants.NODE_HEADER_EXTRA_HEIGHT + Mathf.Max( 18 + m_inputPorts.Count, m_outputPorts.Count ) * Constants.INPUT_PORT_DELTA_Y;
			}
		}
		public bool WasDeprecated { get { return m_wasDeprecated; } set { m_wasDeprecated = value; } }
		public bool Alive { get { return m_alive;} set { m_alive = value; } }
		public string TypeName { get { if( m_nodeAttribs != null ) return m_nodeAttribs.Name;return GetType().ToString(); } }
		public bool PreviewIsDirty { set { m_previewIsDirty = value; } get { return m_previewIsDirty; } }
		protected bool FinishPreviewRender { get { return m_finishPreviewRender; } set { m_finishPreviewRender = value; } }
		public virtual bool IsStubNode { get { return false; } }
	}
}
