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

//#define ADD_SHADER_FUNCTION_HEADERS

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System;
namespace AmplifyShaderEditor
{
	[Serializable]
	[NodeAttributes( "Function Node", "Functions", "Function Node", KeyCode.None, false, 0, int.MaxValue, typeof( AmplifyShaderFunction ) )]
	public class FunctionNode : ParentNode
	{
		[SerializeField]
		private AmplifyShaderFunction m_function;

		[SerializeField]
		private ParentGraph m_functionGraph;

		[SerializeField]
		private int m_functionGraphId = -1;

		[SerializeField]
		private List<FunctionInput> m_allFunctionInputs;
		private Dictionary<int, FunctionInput> m_allFunctionInputsDict = new Dictionary<int, FunctionInput>();

		[SerializeField]
		private List<FunctionOutput> m_allFunctionOutputs;
		private Dictionary<int, FunctionOutput> m_allFunctionOutputsDict = new Dictionary<int, FunctionOutput>();

		[SerializeField]
		private List<FunctionSwitch> m_allFunctionSwitches;
		private Dictionary<int, FunctionSwitch> m_allFunctionSwitchesDict = new Dictionary<int, FunctionSwitch>();

		[SerializeField]
		private ReordenatorNode m_reordenator;

		[SerializeField]
		private string m_filename;

		[SerializeField]
		private string m_headerTitle = string.Empty;

		[SerializeField]
		private int m_orderIndex;

		[SerializeField]
		private string m_functionCheckSum;

		[SerializeField]
		private string m_functionGUID = string.Empty;

		//[SerializeField]
		//private List<string> m_includes = new List<string>();

		//[SerializeField]
		//private List<string> m_pragmas = new List<string>();

		[SerializeField]
		private List<AdditionalDirectiveContainer> m_directives = new List<AdditionalDirectiveContainer>();

		private bool m_parametersFoldout = true;
		[SerializeField]
		private ParentGraph m_outsideGraph = null;

		[SerializeField]
		private FunctionOutput m_mainPreviewNode;

		bool m_portsChanged = false;
		//[SerializeField]
		bool m_initialGraphDraw = false;

		private bool m_refreshIdsRequired = false;

		public string[] ReadOptionsHelper = new string[] { };

		private bool m_lateRefresh = false;

		private Dictionary<int, bool> m_duplicatesBuffer = new Dictionary<int, bool>();
		string LastLine( string text )
		{
			string[] lines = text.Replace( "\r", "" ).Split( '\n' );
			return lines[ lines.Length - 1 ];
		}

		public void CommonInit( AmplifyShaderFunction function, int uniqueId )
		{
			SetBaseUniqueId( uniqueId );

			if( function == null )
				return;

			m_refreshIdsRequired = UIUtils.IsLoading && ( UIUtils.CurrentShaderVersion() < 14004 );

			m_function = function;

			if( Function.FunctionName.Length > 1 )
			{
				SetTitleText( GraphContextMenu.AddSpacesToSentence( Function.FunctionName ) );
			}
			else
			{
				SetTitleText( Function.FunctionName );
			}
			m_tooltipText = Function.Description;
			m_hasTooltipLink = false;
			if( m_functionGraph == null )
			{
				//m_functionGraph = new ParentGraph();
				m_functionGraph = CreateInstance<ParentGraph>();
				m_functionGraph.Init();
				m_functionGraph.ParentWindow = ContainerGraph.ParentWindow;
			}

			if( string.IsNullOrEmpty( m_functionGUID ) )
			{
				m_functionGUID = AssetDatabase.AssetPathToGUID( AssetDatabase.GetAssetPath( m_function ) );
			}

			m_functionGraphId = Mathf.Max( m_functionGraphId, ContainerGraph.ParentWindow.GraphCount );
			ContainerGraph.ParentWindow.GraphCount = m_functionGraphId + 1;
			m_functionGraph.SetGraphId( m_functionGraphId );

			ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
			ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;

			AmplifyShaderEditorWindow.LoadFromMeta( ref m_functionGraph, ContainerGraph.ParentWindow.ContextMenuInstance, Function.FunctionInfo );
			//m_functionCheckSum = LastLine( m_function.FunctionInfo );
			m_functionCheckSum = AssetDatabase.GetAssetDependencyHash( AssetDatabase.GetAssetPath( m_function ) ).ToString();
			List<PropertyNode> propertyList = UIUtils.PropertyNodesList();
			m_allFunctionInputs = UIUtils.FunctionInputList();
			m_allFunctionOutputs = UIUtils.FunctionOutputList();
			m_allFunctionSwitches = UIUtils.FunctionSwitchList();

			ContainerGraph.ParentWindow.CustomGraph = cachedGraph;

			m_allFunctionInputs.Sort( ( x, y ) => { return x.OrderIndex.CompareTo( y.OrderIndex ); } );
			m_allFunctionOutputs.Sort( ( x, y ) => { return x.OrderIndex.CompareTo( y.OrderIndex ); } );
			m_allFunctionSwitches.Sort( ( x, y ) => { return x.OrderIndex.CompareTo( y.OrderIndex ); } );

			int inputCount = m_allFunctionInputs.Count;
			for( int i = 0; i < inputCount; i++ )
			{
				if( m_refreshIdsRequired )
				{
					AddInputPort( m_allFunctionInputs[ i ].SelectedInputType, false, m_allFunctionInputs[ i ].InputName );
				}
				else
				{
					AddInputPort( m_allFunctionInputs[ i ].SelectedInputType, false, m_allFunctionInputs[ i ].InputName, -1, MasterNodePortCategory.Fragment, m_allFunctionInputs[ i ].UniqueId );
				}
				InputPortSwitchRestriction( m_inputPorts[ i ] );

				if( !m_allFunctionInputs[ i ].InputPorts[ 0 ].IsConnected )
				{
					m_inputPorts[ i ].AutoDrawInternalData = true;
					m_inputPorts[ i ].InternalData = m_allFunctionInputs[ i ].InputPorts[ 0 ].InternalData;
				}
				m_allFunctionInputs[ i ].Fnode = this;
			}

			int outputCount = m_allFunctionOutputs.Count;
			FunctionOutput first = null;
			for( int i = 0; i < outputCount; i++ )
			{
				if( i == 0 )
					first = m_allFunctionOutputs[ i ];

				if( m_allFunctionOutputs[ i ].PreviewNode )
				{
					m_mainPreviewNode = m_allFunctionOutputs[ i ];
				}

				if( m_refreshIdsRequired )
				{
					AddOutputPort( m_allFunctionOutputs[ i ].AutoOutputType, m_allFunctionOutputs[ i ].OutputName );
				}
				else
				{
					AddOutputPort( m_allFunctionOutputs[ i ].AutoOutputType, m_allFunctionOutputs[ i ].OutputName, m_allFunctionOutputs[ i ].UniqueId );
				}
				OutputPortSwitchRestriction( m_outputPorts[ i ] );
			}

			// make sure to hide the ports properly
			CheckPortVisibility();

			if( m_mainPreviewNode == null )
				m_mainPreviewNode = first;

			//create reordenator to main graph
			bool inside = false;
			if( ContainerGraph.ParentWindow.CustomGraph != null )
				inside = true;

			if( /*hasConnectedProperties*/propertyList.Count > 0 )
			{
				m_reordenator = ScriptableObject.CreateInstance<ReordenatorNode>();
				m_reordenator.Init( "_" + Function.FunctionName, Function.FunctionName, propertyList, false );
				m_reordenator.OrderIndex = m_orderIndex;
				m_reordenator.HeaderTitle = Function.FunctionName;
				m_reordenator.IsInside = inside;
			}

			if( m_reordenator != null )
			{
				cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
				ContainerGraph.ParentWindow.CustomGraph = null;
				UIUtils.RegisterPropertyNode( m_reordenator );
				ContainerGraph.ParentWindow.CustomGraph = cachedGraph;

				if( inside )
				{
					UIUtils.RegisterPropertyNode( m_reordenator );
				}
			}

			m_textLabelWidth = 120;

			UIUtils.RegisterFunctionNode( this );

			m_previewShaderGUID = "aca70c900c50c004e8ef0b47c4fac4d4";
			m_useInternalPortData = false;
			m_selectedLocation = function.PreviewPosition;
			UIUtils.CurrentWindow.OutsideGraph.OnLODMasterNodesAddedEvent += OnLODMasterNodesAddedEvent;
		}

		public InputPort GetInput( FunctionInput input )
		{
			int index = m_allFunctionInputs.FindIndex( ( x ) => { return x.Equals( input ); } );
			if( index >= 0 )
				return InputPorts[ index ];
			else
				return null;
		}

		private void OnLODMasterNodesAddedEvent( int lod )
		{
			AddShaderFunctionDirectivesInternal( lod );
		}

		public void SetPreviewInput( InputPort input )
		{
			if( !HasPreviewShader || !m_initialized )
				return;

			if( input.IsConnected && input.InputNodeHasPreview( ContainerGraph ) )
			{
				input.SetPreviewInputTexture( ContainerGraph );
			}
			else
			{
				input.SetPreviewInputValue( ContainerGraph );
			}
		}

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

			if( m_allFunctionOutputs == null || m_allFunctionOutputs.Count == 0 )
				return false;

			for( int i = 0; i < m_allFunctionOutputs.Count; i++ )
			{
				ParentNode outNode = m_allFunctionOutputs[ i ];
				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 override void RenderNodePreview()
		{
			if( m_outputPorts == null )
				return;

			if( !PreviewIsDirty && !m_continuousPreviewRefresh )
				return;

			// this is in the wrong place??
			if( m_drawPreviewAsSphere != m_mainPreviewNode.SpherePreview )
			{
				m_drawPreviewAsSphere = m_mainPreviewNode.SpherePreview;
				OnNodeChange();
			}

			int count = m_outputPorts.Count;
			for( int i = 0; i < count; i++ )
			{
				m_outputPorts[ i ].OutputPreviewTexture = m_allFunctionOutputs[ i ].PreviewTexture;
			}

			if( PreviewIsDirty )
				FinishPreviewRender = true;

			PreviewIsDirty = false;
		}

		public override RenderTexture PreviewTexture
		{
			get
			{
				if( m_mainPreviewNode != null )
					return m_mainPreviewNode.PreviewTexture;
				else
					return base.PreviewTexture;
			}
		}

		private void AddShaderFunctionDirectivesInternal( int lod )
		{
			List<TemplateMultiPassMasterNode> nodes = ContainerGraph.ParentWindow.OutsideGraph.GetMultiPassMasterNodes( lod );
			int count = nodes.Count;
			for( int i = 0; i < count; i++ )
			{
				nodes[ i ].PassModule.AdditionalDirectives.AddShaderFunctionItems( OutputId, Function.AdditionalDirectives.DirectivesList );
			}
		}

		public override void RefreshExternalReferences()
		{
			base.RefreshExternalReferences();
			if( Function == null )
				return;

			//Debug.Log( "RefreshExternalReferences " + m_function.FunctionName + " " + UIUtils.CurrentWindow.IsShaderFunctionWindow );

			Function.UpdateDirectivesList();

			MasterNode masterNode = UIUtils.CurrentWindow.OutsideGraph.CurrentMasterNode;
			StandardSurfaceOutputNode surface = masterNode as StandardSurfaceOutputNode;



			if( surface != null )
			{
				//for( int i = 0; i < Function.AdditionalIncludes.IncludeList.Count; i++ )
				//{
				//	//ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalIncludes.OutsideList.Add( Function.AdditionalIncludes.IncludeList[ i ] );
				//	ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalDirectives.AddShaderFunctionItem( AdditionalLineType.Include, Function.AdditionalIncludes.IncludeList[ i ] );
				//	m_includes.Add( Function.AdditionalIncludes.IncludeList[ i ] );
				//}

				//for( int i = 0; i < Function.AdditionalPragmas.PragmaList.Count; i++ )
				//{
				//	//ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalPragmas.OutsideList.Add( Function.AdditionalPragmas.PragmaList[ i ] );
				//	ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalDirectives.AddShaderFunctionItem(AdditionalLineType.Pragma, Function.AdditionalPragmas.PragmaList[ i ] );
				//	m_pragmas.Add( Function.AdditionalPragmas.PragmaList[ i ] );
				//}
				surface.AdditionalDirectives.AddShaderFunctionItems( OutputId, Function.AdditionalDirectives.DirectivesList );
			}
			else
			{
				if( ContainerGraph.ParentWindow.OutsideGraph.MultiPassMasterNodes.Count > 0 )
				{
					for( int lod = -1; lod < ContainerGraph.ParentWindow.OutsideGraph.LodMultiPassMasternodes.Count; lod++ )
					{
						AddShaderFunctionDirectivesInternal( lod );
					}
				}
				else
				{
					// Assuring that we're not editing a Shader Function, as directives setup is not needed there
					if( !UIUtils.CurrentWindow.IsShaderFunctionWindow )
					{
						// This function is nested inside a shader function itself and this method
						// was called before the main output node was created. 
						// This is possible since all nodes RefreshExternalReferences(...) are called at the end 
						// of a LoadFromMeta
						// Need to delay this setup to after all nodes are loaded to then setup the directives
						m_lateRefresh = true;
						return;
					}
				}

			}
			m_directives.AddRange( Function.AdditionalDirectives.DirectivesList );

			if( m_refreshIdsRequired )
			{
				m_refreshIdsRequired = false;
				int inputCount = m_inputPorts.Count;
				for( int i = 0; i < inputCount; i++ )
				{
					m_inputPorts[ i ].ChangePortId( m_allFunctionInputs[ i ].UniqueId );
				}

				int outputCount = m_outputPorts.Count;
				for( int i = 0; i < outputCount; i++ )
				{
					m_outputPorts[ i ].ChangePortId( m_allFunctionOutputs[ i ].UniqueId );
				}
			}

			if( ContainerGraph.ParentWindow.CurrentGraph != m_functionGraph )
				ContainerGraph.ParentWindow.CurrentGraph.InstancePropertyCount += m_functionGraph.InstancePropertyCount;

			ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
			ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;

			if( ReadOptionsHelper.Length > 2 )
			{
				for( int i = 1; i < ReadOptionsHelper.Length; i += 2 )
				{
					int optionId = Convert.ToInt32( ReadOptionsHelper[ i ] );
					int optionValue = Convert.ToInt32( ReadOptionsHelper[ i + 1 ] );
					for( int j = 0; j < m_allFunctionSwitches.Count; j++ )
					{
						if( m_allFunctionSwitches[ j ].UniqueId == optionId )
						{
							m_allFunctionSwitches[ j ].SetCurrentSelectedInput( optionValue, m_allFunctionSwitches[ j ].GetCurrentSelectedInput() );
							break;
						}
					}
				}
			}

			ContainerGraph.ParentWindow.CustomGraph = cachedGraph;

			m_portsChanged = true;
		}

		void InputPortSwitchRestriction( WirePort port )
		{
			switch( port.DataType )
			{
				case WirePortDataType.OBJECT:
				break;
				case WirePortDataType.FLOAT:
				case WirePortDataType.FLOAT2:
				case WirePortDataType.FLOAT3:
				case WirePortDataType.FLOAT4:
				case WirePortDataType.COLOR:
				case WirePortDataType.INT:
				{
					port.CreatePortRestrictions( WirePortDataType.FLOAT, WirePortDataType.FLOAT2, WirePortDataType.FLOAT3, WirePortDataType.FLOAT4, WirePortDataType.COLOR, WirePortDataType.INT, WirePortDataType.OBJECT );
				}
				break;
				case WirePortDataType.FLOAT3x3:
				case WirePortDataType.FLOAT4x4:
				{
					port.CreatePortRestrictions( WirePortDataType.FLOAT3x3, WirePortDataType.FLOAT4x4, WirePortDataType.OBJECT );
				}
				break;
				case WirePortDataType.SAMPLER1D:
				case WirePortDataType.SAMPLER2D:
				case WirePortDataType.SAMPLER3D:
				case WirePortDataType.SAMPLERCUBE:
				{
					port.CreatePortRestrictions( WirePortDataType.SAMPLER1D, WirePortDataType.SAMPLER2D, WirePortDataType.SAMPLER3D, WirePortDataType.SAMPLERCUBE, WirePortDataType.OBJECT );
				}
				break;
				default:
				break;
			}
		}

		void OutputPortSwitchRestriction( WirePort port )
		{
			switch( port.DataType )
			{
				case WirePortDataType.OBJECT:
				break;
				case WirePortDataType.FLOAT:
				case WirePortDataType.FLOAT2:
				case WirePortDataType.FLOAT3:
				case WirePortDataType.FLOAT4:
				case WirePortDataType.COLOR:
				case WirePortDataType.INT:
				case WirePortDataType.FLOAT3x3:
				case WirePortDataType.FLOAT4x4:
				{
					port.AddPortForbiddenTypes( WirePortDataType.SAMPLER1D, WirePortDataType.SAMPLER2D, WirePortDataType.SAMPLER3D, WirePortDataType.SAMPLERCUBE );
				}
				break;
				case WirePortDataType.SAMPLER1D:
				case WirePortDataType.SAMPLER2D:
				case WirePortDataType.SAMPLER3D:
				case WirePortDataType.SAMPLERCUBE:
				{
					port.CreatePortRestrictions( WirePortDataType.SAMPLER1D, WirePortDataType.SAMPLER2D, WirePortDataType.SAMPLER3D, WirePortDataType.SAMPLERCUBE, WirePortDataType.OBJECT );
				}
				break;
				default:
				break;
			}
		}

		public override void PropagateNodeData( NodeData nodeData, ref MasterNodeDataCollector dataCollector )
		{
			ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
			m_outsideGraph = cachedGraph;
			ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;

			for( int i = 0; i < m_allFunctionOutputs.Count; i++ )
			{
				m_allFunctionOutputs[ i ].PropagateNodeData( nodeData, ref dataCollector );
			}

			ContainerGraph.ParentWindow.CustomGraph = cachedGraph;

			base.PropagateNodeData( nodeData, ref dataCollector );
		}

		protected override void OnUniqueIDAssigned()
		{
			base.OnUniqueIDAssigned();
			UIUtils.RegisterFunctionNode( this );
		}

		public override void SetupFromCastObject( UnityEngine.Object obj )
		{
			base.SetupFromCastObject( obj );
			AmplifyShaderFunction function = obj as AmplifyShaderFunction;
			CommonInit( function, UniqueId );
			RefreshExternalReferences();
		}

		public override void OnInputPortConnected( int portId, int otherNodeId, int otherPortId, bool activateNode = true )
		{
			base.OnInputPortConnected( portId, otherNodeId, otherPortId, activateNode );
			FunctionInput functionInput = m_refreshIdsRequired ? m_allFunctionInputs[ portId ] : GetFunctionInputByUniqueId( portId );
			functionInput.PreviewIsDirty = true;
			if( functionInput.AutoCast )
			{
				InputPort inputPort = m_refreshIdsRequired ? m_inputPorts[ portId ] : GetInputPortByUniqueId( portId );
				inputPort.MatchPortToConnection();

				ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
				ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;
				functionInput.ChangeOutputType( inputPort.DataType, false );
				ContainerGraph.ParentWindow.CustomGraph = cachedGraph;
			}

			for( int i = 0; i < m_allFunctionOutputs.Count; i++ )
			{
				m_outputPorts[ i ].ChangeType( m_allFunctionOutputs[ i ].InputPorts[ 0 ].DataType, false );
			}
		}

		public override void OnInputPortDisconnected( int portId )
		{
			base.OnInputPortDisconnected( portId );

			FunctionInput functionInput = m_refreshIdsRequired ? m_allFunctionInputs[ portId ] : GetFunctionInputByUniqueId( portId );
			functionInput.PreviewIsDirty = true;
		}

		public override void OnConnectedOutputNodeChanges( int inputPortId, int otherNodeId, int otherPortId, string name, WirePortDataType type )
		{
			base.OnConnectedOutputNodeChanges( inputPortId, otherNodeId, otherPortId, name, type );
			FunctionInput functionInput = m_refreshIdsRequired ? m_allFunctionInputs[ inputPortId ] : GetFunctionInputByUniqueId( inputPortId );
			functionInput.PreviewIsDirty = true;
			if( functionInput.AutoCast )
			{
				InputPort inputPort = m_refreshIdsRequired ? m_inputPorts[ inputPortId ] : GetInputPortByUniqueId( inputPortId );
				inputPort.MatchPortToConnection();

				ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
				ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;
				functionInput.ChangeOutputType( inputPort.DataType, false );
				ContainerGraph.ParentWindow.CustomGraph = cachedGraph;
			}

			for( int i = 0; i < m_allFunctionOutputs.Count; i++ )
			{
				m_outputPorts[ i ].ChangeType( m_allFunctionOutputs[ i ].InputPorts[ 0 ].DataType, false );
			}
		}

		public override void DrawProperties()
		{
			base.DrawProperties();

			if( Function == null )
				return;
			
			if( Function.Description.Length > 0 || m_allFunctionSwitches.Count > 0 )
				NodeUtils.DrawPropertyGroup( ref m_parametersFoldout, "Parameters", DrawDescription );

			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_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 )
						{
							EditorGUI.BeginChangeCheck();
							m_inputPorts[ i ].ShowInternalData( this );
							if( EditorGUI.EndChangeCheck() )
							{
								m_allFunctionInputs[ i ].PreviewIsDirty = true;
							}
						}
					}
				} );
		}

		private void DrawDescription()
		{
			if( Function.Description.Length > 0 )
				EditorGUILayout.HelpBox( Function.Description, MessageType.Info );

			ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
			ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;
			for( int i = 0; i < m_allFunctionSwitches.Count; i++ )
			{
				m_allFunctionSwitches[ i ].AsDrawn = false;
			}

			for( int i = 0; i < m_allFunctionSwitches.Count; i++ )
			{
				if( m_allFunctionSwitches[ i ].DrawOption( this ) )
				{
					m_portsChanged = true;
				}
			}
			ContainerGraph.ParentWindow.CustomGraph = cachedGraph;
		}

		private void RemoveShaderFunctionDirectivesInternal( int lod )
		{
			List<TemplateMultiPassMasterNode> nodes = ContainerGraph.ParentWindow.OutsideGraph.GetMultiPassMasterNodes( lod );
			int count = nodes.Count;
			for( int i = 0; i < count; i++ )
			{
				nodes[ i ].PassModule.AdditionalDirectives.RemoveShaderFunctionItems( OutputId );
			}
		}

		public override void Destroy()
		{
			m_mainPreviewNode = null;
			base.Destroy();

			m_duplicatesBuffer.Clear();
			m_duplicatesBuffer = null;

			if( m_functionGraph != null && ContainerGraph.ParentWindow.CurrentGraph != m_functionGraph )
				ContainerGraph.ParentWindow.CurrentGraph.InstancePropertyCount -= m_functionGraph.InstancePropertyCount;

			if( ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface != null )
			{
				//for( int i = 0; i < m_includes.Count; i++ )
				//{
				//	//if( ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalIncludes.OutsideList.Contains( m_includes[ i ] ) )
				//	//{
				//	//	ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalIncludes.OutsideList.Remove( m_includes[ i ] );
				//	//}
				//	ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalDirectives.RemoveShaderFunctionItem( AdditionalLineType.Include, m_includes[ i ] );
				//}

				//for( int i = 0; i < m_pragmas.Count; i++ )
				//{
				//	//if( ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalPragmas.OutsideList.Contains( m_pragmas[ i ] ) )
				//	//{
				//	//	ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalPragmas.OutsideList.Remove( m_pragmas[ i ] );
				//	//}
				//	ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalDirectives.RemoveShaderFunctionItem( AdditionalLineType.Pragma, m_pragmas[ i ] );
				//}
				ContainerGraph.ParentWindow.OutsideGraph.CurrentStandardSurface.AdditionalDirectives.RemoveShaderFunctionItems( OutputId/*, m_directives */);
			}
			else
			{
				if( ContainerGraph.ParentWindow.OutsideGraph.MultiPassMasterNodes.Count > 0 )
				{
					for( int lod = -1; lod < ContainerGraph.ParentWindow.OutsideGraph.LodMultiPassMasternodes.Count; lod++ )
					{
						RemoveShaderFunctionDirectivesInternal( lod );
					}
				}
			}




			// Cannot GameObject.Destroy(m_directives[i]) since we would be removing them from 
			// the shader function asset itself

			m_directives.Clear();
			m_directives = null;

			if( m_reordenator != null )
			{
				ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
				ContainerGraph.ParentWindow.CustomGraph = null;
				UIUtils.UnregisterPropertyNode( m_reordenator );
				ContainerGraph.ParentWindow.CustomGraph = cachedGraph;

				m_reordenator.Destroy();
				m_reordenator = null;
			}

			UIUtils.UnregisterFunctionNode( this );

			ParentGraph cachedGraph2 = ContainerGraph.ParentWindow.CustomGraph;
			ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;

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

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

			if( m_functionGraph != null )
				m_functionGraph.SoftDestroy();
			m_functionGraph = null;

			ContainerGraph.ParentWindow.CustomGraph = cachedGraph2;
			m_function = null;

			m_allFunctionOutputsDict.Clear();
			m_allFunctionOutputsDict = null;

			m_allFunctionSwitchesDict.Clear();
			m_allFunctionSwitchesDict = null;

			m_allFunctionInputsDict.Clear();
			m_allFunctionInputsDict = null;

			UIUtils.CurrentWindow.OutsideGraph.OnLODMasterNodesAddedEvent -= OnLODMasterNodesAddedEvent;
		}

		public override void OnNodeLogicUpdate( DrawInfo drawInfo )
		{
			if( m_lateRefresh )
			{
				m_lateRefresh = false;
				RefreshExternalReferences();
			}

			CheckForChangesRecursively();

			base.OnNodeLogicUpdate( drawInfo );
			ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
			ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;

			if( m_functionGraph != null )
			{
				int nodeCount = m_functionGraph.AllNodes.Count;
				for( int i = 0; i < nodeCount; i++ )
				{
					m_functionGraph.AllNodes[ i ].OnNodeLogicUpdate( drawInfo );
				}

				if( !string.IsNullOrEmpty( FunctionGraph.CurrentFunctionOutput.SubTitle ) )
				{
					SetAdditonalTitleText( FunctionGraph.CurrentFunctionOutput.SubTitle );
				}
			}

			ContainerGraph.ParentWindow.CustomGraph = cachedGraph;
			if( m_portsChanged )
			{
				m_portsChanged = false;
				for( int i = 0; i < m_allFunctionOutputs.Count; i++ )
				{
					m_outputPorts[ i ].ChangeType( m_allFunctionOutputs[ i ].InputPorts[ 0 ].DataType, false );
				}

				CheckPortVisibility();
			}
		}

		public override void Draw( DrawInfo drawInfo )
		{
			//CheckForChangesRecursively();

			if( !m_initialGraphDraw && drawInfo.CurrentEventType == EventType.Repaint )
			{
				m_initialGraphDraw = true;
				ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
				ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;
				if( m_functionGraph != null )
				{
					for( int i = 0; i < m_functionGraph.AllNodes.Count; i++ )
					{
						ParentNode node = m_functionGraph.AllNodes[ i ];
						if( node != null )
						{
							node.OnNodeLayout( drawInfo );
						}
					}
				}
				ContainerGraph.ParentWindow.CustomGraph = cachedGraph;
			}

			base.Draw( drawInfo );
		}

		public bool CheckForChanges( bool forceCheck = false, bool forceChange = false )
		{
			if( ( ContainerGraph.ParentWindow.CheckFunctions || forceCheck || forceChange ) && m_function != null )
			{
				//string newCheckSum = LastLine( m_function.FunctionInfo );
				string newCheckSum = AssetDatabase.GetAssetDependencyHash( AssetDatabase.GetAssetPath( m_function ) ).ToString();
				if( !m_functionCheckSum.Equals( newCheckSum ) || forceChange )
				{
					m_functionCheckSum = newCheckSum;
					ContainerGraph.OnDuplicateEvent += DuplicateMe;
					return true;
				}
			}
			return false;
		}

		public bool CheckForChangesRecursively()
		{
			if( m_functionGraph == null )
				return false;

			bool result = false;
			for( int i = 0; i < m_functionGraph.FunctionNodes.NodesList.Count; i++ )
			{
				if( m_functionGraph.FunctionNodes.NodesList[ i ].CheckForChangesRecursively() )
					result = true;
			}
			if( CheckForChanges( false, result ) )
				result = true;

			return result;
		}

		public void DuplicateMe()
		{
			bool previewOpen = m_showPreview;

			string allOptions = m_allFunctionSwitches.Count.ToString();
			for( int i = 0; i < m_allFunctionSwitches.Count; i++ )
			{
				allOptions += "," + m_allFunctionSwitches[ i ].UniqueId + "," + m_allFunctionSwitches[ i ].GetCurrentSelectedInput();
			}

			ReadOptionsHelper = allOptions.Split( ',' );

			ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
			ContainerGraph.ParentWindow.CustomGraph = null;
			MasterNode masterNode = ContainerGraph.ParentWindow.CurrentGraph.CurrentMasterNode;
			if( masterNode != null )
				masterNode.InvalidateMaterialPropertyCount();

			ContainerGraph.ParentWindow.CustomGraph = cachedGraph;

			ParentNode newNode = ContainerGraph.CreateNode( m_function, false, Vec2Position );
			newNode.ShowPreview = previewOpen;
			( newNode as FunctionNode ).ReadOptionsHelper = ReadOptionsHelper;
			newNode.RefreshExternalReferences();
			if( ( newNode as FunctionNode ).m_reordenator && m_reordenator )
				( newNode as FunctionNode ).m_reordenator.OrderIndex = m_reordenator.OrderIndex;

			for( int i = 0; i < m_outputPorts.Count; i++ )
			{
				if( m_outputPorts[ i ].IsConnected )
				{
					OutputPort newOutputPort = newNode.GetOutputPortByUniqueId( m_outputPorts[ i ].PortId );
					if( newNode.OutputPorts != null && newOutputPort != null )
					{
						for( int j = m_outputPorts[ i ].ExternalReferences.Count - 1; j >= 0; j-- )
						{
							ContainerGraph.CreateConnection( m_outputPorts[ i ].ExternalReferences[ j ].NodeId, m_outputPorts[ i ].ExternalReferences[ j ].PortId, newOutputPort.NodeId, newOutputPort.PortId );
						}
					}
				}
				//else
				//{
				//if( newNode.OutputPorts != null && newNode.OutputPorts[ i ] != null )
				//{
				//    ContainerGraph.DeleteConnection( false, newNode.UniqueId, newNode.OutputPorts[ i ].PortId, false, false, false );
				//}
				//}
			}

			for( int i = 0; i < m_inputPorts.Count; i++ )
			{
				if( m_inputPorts[ i ].IsConnected )
				{
					InputPort newInputPort = newNode.GetInputPortByUniqueId( m_inputPorts[ i ].PortId );
					if( newNode.InputPorts != null && newInputPort != null )
					{
						ContainerGraph.CreateConnection( newInputPort.NodeId, newInputPort.PortId, m_inputPorts[ i ].ExternalReferences[ 0 ].NodeId, m_inputPorts[ i ].ExternalReferences[ 0 ].PortId );
					}
				}
			}

			ContainerGraph.OnDuplicateEvent -= DuplicateMe;

			if( Selected )
			{
				ContainerGraph.DeselectNode( this );
				ContainerGraph.SelectNode( newNode, true, false );
			}

			ContainerGraph.DestroyNode( this, false );
		}

		private FunctionOutput GetFunctionOutputByUniqueId( int uniqueId )
		{
			int listCount = m_allFunctionOutputs.Count;
			if( m_allFunctionOutputsDict.Count != m_allFunctionOutputs.Count )
			{
				m_allFunctionOutputsDict.Clear();
				for( int i = 0; i < listCount; i++ )
				{
					m_allFunctionOutputsDict.Add( m_allFunctionOutputs[ i ].UniqueId, m_allFunctionOutputs[ i ] );
				}
			}

			if( m_allFunctionOutputsDict.ContainsKey( uniqueId ) )
				return m_allFunctionOutputsDict[ uniqueId ];

			return null;
		}

		private FunctionInput GetFunctionInputByUniqueId( int uniqueId )
		{
			int listCount = m_allFunctionInputs.Count;
			if( m_allFunctionInputsDict.Count != m_allFunctionInputs.Count )
			{
				m_allFunctionInputsDict.Clear();
				for( int i = 0; i < listCount; i++ )
				{
					m_allFunctionInputsDict.Add( m_allFunctionInputs[ i ].UniqueId, m_allFunctionInputs[ i ] );
				}
			}

			if( m_allFunctionInputsDict.ContainsKey( uniqueId ) )
				return m_allFunctionInputsDict[ uniqueId ];

			return null;
		}

		public override string GenerateShaderForOutput( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar )
		{
			OutputPort outputPort = GetOutputPortByUniqueId( outputId );
			FunctionOutput functionOutput = GetFunctionOutputByUniqueId( outputId );

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

			m_functionGraph.CurrentPrecision = ContainerGraph.ParentWindow.CurrentGraph.CurrentPrecision;
			ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
			m_outsideGraph = cachedGraph;
			ContainerGraph.ParentWindow.CustomGraph = m_functionGraph;
#if ADD_SHADER_FUNCTION_HEADERS
			if( m_reordenator != null && m_reordenator.RecursiveCount() > 0 && m_reordenator.HasTitle )
			{
				dataCollector.AddToProperties( UniqueId, "[Header(" + m_reordenator.HeaderTitle.Replace( "-", " " ) + ")]", m_reordenator.OrderIndex );
			}
#endif
			string result = string.Empty;
			for( int i = 0; i < m_allFunctionInputs.Count; i++ )
			{
				if( !m_allFunctionInputs[ i ].InputPorts[ 0 ].IsConnected || m_inputPorts[ i ].IsConnected )
					m_allFunctionInputs[ i ].OnPortGeneration += FunctionNodeOnPortGeneration;
			}

			result += functionOutput.GenerateShaderForOutput( outputId, ref dataCollector, ignoreLocalvar );

			for( int i = 0; i < m_allFunctionInputs.Count; i++ )
			{
				if( !m_allFunctionInputs[ i ].InputPorts[ 0 ].IsConnected || m_inputPorts[ i ].IsConnected )
					m_allFunctionInputs[ i ].OnPortGeneration -= FunctionNodeOnPortGeneration;
			}

			ContainerGraph.ParentWindow.CustomGraph = cachedGraph;

			if( outputPort.ConnectionCount > 1 )
				RegisterLocalVariable( outputId, result, ref dataCollector );
			else
				outputPort.SetLocalValue( result, dataCollector.PortCategory );

			return outputPort.LocalValue( dataCollector.PortCategory );
		}

		private string FunctionNodeOnPortGeneration( ref MasterNodeDataCollector dataCollector, int index, ParentGraph graph )
		{
			ParentGraph cachedGraph = ContainerGraph.ParentWindow.CustomGraph;
			ContainerGraph.ParentWindow.CustomGraph = m_outsideGraph;
			string result = m_inputPorts[ index ].GeneratePortInstructions( ref dataCollector );
			ContainerGraph.ParentWindow.CustomGraph = cachedGraph;
			return result;
		}

		public override void WriteToString( ref string nodeInfo, ref string connectionsInfo )
		{
			base.WriteToString( ref nodeInfo, ref connectionsInfo );

			if( Function != null )
				IOUtils.AddFieldValueToString( ref nodeInfo, m_function.name );
			else
				IOUtils.AddFieldValueToString( ref nodeInfo, m_filename );
			IOUtils.AddFieldValueToString( ref nodeInfo, m_reordenator != null ? m_reordenator.RawOrderIndex : -1 );
			IOUtils.AddFieldValueToString( ref nodeInfo, m_headerTitle );
			IOUtils.AddFieldValueToString( ref nodeInfo, m_functionGraphId );
			IOUtils.AddFieldValueToString( ref nodeInfo, m_functionGUID );

			int functionSwitchCount = m_allFunctionSwitches != null ? m_allFunctionSwitches.Count : 0;
			string allOptions = functionSwitchCount.ToString();
			for( int i = 0; i < functionSwitchCount; i++ )
			{
				allOptions += "," + m_allFunctionSwitches[ i ].UniqueId + "," + m_allFunctionSwitches[ i ].GetCurrentSelectedInput();
			}
			IOUtils.AddFieldValueToString( ref nodeInfo, allOptions );
		}

		public override void ReadFromString( ref string[] nodeParams )
		{
			base.ReadFromString( ref nodeParams );
			m_filename = GetCurrentParam( ref nodeParams );
			m_orderIndex = Convert.ToInt32( GetCurrentParam( ref nodeParams ) );
			m_headerTitle = GetCurrentParam( ref nodeParams );

			if( UIUtils.CurrentShaderVersion() > 7203 )
			{
				m_functionGraphId = Convert.ToInt32( GetCurrentParam( ref nodeParams ) );
			}

			if( UIUtils.CurrentShaderVersion() > 13704 )
			{
				m_functionGUID = GetCurrentParam( ref nodeParams );
			}

			AmplifyShaderFunction loaded = AssetDatabase.LoadAssetAtPath<AmplifyShaderFunction>( AssetDatabase.GUIDToAssetPath( m_functionGUID ) );
			if( loaded != null )
			{
				CommonInit( loaded, UniqueId );
			}
			else
			{
				string[] guids = AssetDatabase.FindAssets( "t:AmplifyShaderFunction " + m_filename );
				if( guids.Length > 0 )
				{
					string sfGuid = null;

					foreach( string guid in guids )
					{
						string assetPath = AssetDatabase.GUIDToAssetPath( guid );
						string name = System.IO.Path.GetFileNameWithoutExtension( assetPath );
						if( name.Equals( m_filename, StringComparison.OrdinalIgnoreCase ) )
						{
							sfGuid = guid;
							break;
						}
					}
					loaded = AssetDatabase.LoadAssetAtPath<AmplifyShaderFunction>( AssetDatabase.GUIDToAssetPath( sfGuid ) );

					if( loaded != null )
					{
						CommonInit( loaded, UniqueId );
					}
					else
					{
						SetTitleText( "ERROR" );
						UIUtils.ShowMessage( UniqueId, string.Format( "Error loading {0} shader function from project folder", m_filename ), MessageSeverity.Error );
					}
				}
				else
				{
					SetTitleText( "Missing Function" );
					UIUtils.ShowMessage( UniqueId, string.Format( "Missing {0} shader function on project folder", m_filename ), MessageSeverity.Error );
				}
			}
			if( UIUtils.CurrentShaderVersion() > 14203 )
			{
				ReadOptionsHelper = GetCurrentParam( ref nodeParams ).Split( ',' );
			}
		}

		public override void ReadOutputDataFromString( ref string[] nodeParams )
		{
			if( Function == null )
				return;

			base.ReadOutputDataFromString( ref nodeParams );

			ConfigureInputportsAfterRead();
		}

		public override void OnNodeDoubleClicked( Vector2 currentMousePos2D )
		{
			if( Function == null )
				return;

			ContainerGraph.DeSelectAll();
			this.Selected = true;

			ContainerGraph.ParentWindow.OnLeftMouseUp();
			AmplifyShaderEditorWindow.LoadShaderFunctionToASE( Function, true );
			this.Selected = false;
		}

		private void ConfigureInputportsAfterRead()
		{
			if( InputPorts != null )
			{
				int inputCount = InputPorts.Count;
				for( int i = 0; i < inputCount; i++ )
				{
					InputPorts[ i ].ChangeProperties( m_allFunctionInputs[ i ].InputName, m_allFunctionInputs[ i ].SelectedInputType, false );
				}
			}

			if( OutputPorts != null )
			{
				int outputCount = OutputPorts.Count;
				for( int i = 0; i < outputCount; i++ )
				{
					OutputPorts[ i ].ChangeProperties( m_allFunctionOutputs[ i ].OutputName, m_allFunctionOutputs[ i ].AutoOutputType, false );
				}
			}
		}

		private void CheckPortVisibility()
		{
			bool changes = false;
			if( InputPorts != null )
			{
				for( int i = 0; i < m_allFunctionInputs.Count; i++ )
				{
					if( m_inputPorts[ i ].Visible != m_allFunctionInputs[ i ].IsConnected )
					{
						m_inputPorts[ i ].Visible = m_allFunctionInputs[ i ].IsConnected;
						changes = true;
					}
				}
			}

			if( changes )
				m_sizeIsDirty = true;
		}

		public bool HasProperties { get { return m_reordenator != null; } }

		public ParentGraph FunctionGraph
		{
			get { return m_functionGraph; }
			set { m_functionGraph = value; }
		}

		public AmplifyShaderFunction Function
		{
			get { return m_function; }
			set { m_function = value; }
		}

		public override void RecordObjectOnDestroy( string Id )
		{
			base.RecordObjectOnDestroy( Id );
			if( m_reordenator != null )
				m_reordenator.RecordObject( Id );

			if( m_functionGraph != null )
			{
				Undo.RegisterCompleteObjectUndo( m_functionGraph, Id );
				for( int i = 0; i < m_functionGraph.AllNodes.Count; i++ )
				{
					m_functionGraph.AllNodes[ i ].RecordObject( Id );
				}
			}
		}

		public override void SetContainerGraph( ParentGraph newgraph )
		{
			base.SetContainerGraph( newgraph );
			if( m_functionGraph == null )
				return;
			for( int i = 0; i < m_functionGraph.AllNodes.Count; i++ )
			{
				m_functionGraph.AllNodes[ i ].SetContainerGraph( m_functionGraph );
			}
		}
		
		public override void OnMasterNodeReplaced( MasterNode newMasterNode )
		{
			base.OnMasterNodeReplaced( newMasterNode );
			if( m_functionGraph == null )
				return;

			m_functionGraph.FireMasterNodeReplacedEvent( newMasterNode );

			StandardSurfaceOutputNode surface = newMasterNode as StandardSurfaceOutputNode;
			if( surface != null )
			{
				surface.AdditionalDirectives.AddShaderFunctionItems( OutputId, Function.AdditionalDirectives.DirectivesList );
			}
			else
			{
				if( ContainerGraph.ParentWindow.OutsideGraph.MultiPassMasterNodes.Count > 0 )
				{
					for( int lod = -1; lod < ContainerGraph.ParentWindow.OutsideGraph.LodMultiPassMasternodes.Count; lod++ )
					{
						AddShaderFunctionDirectivesInternal( lod );
					}
				}
			}
		}
	}
}