// Amplify Shader Editor - Visual Shader Editing Tool
// Copyright (c) Amplify Creations, Lda <info@amplify.pt>
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;

namespace AmplifyShaderEditor
{

	public struct AppendData
	{
		public WirePortDataType PortType;
		public int OldPortId;
		public int NewPortId;
		public AppendData( WirePortDataType portType, int oldPortId, int newPortId )
		{
			PortType = portType;
			OldPortId = oldPortId;
			NewPortId = newPortId;
		}
	}

	[Serializable]
	[NodeAttributes( "Append", "Vector Operators", "Append channels to create a new component", null, KeyCode.V, tags: "combine" )]
	public sealed class DynamicAppendNode : ParentNode
	{
		private const string OutputTypeStr = "Output type";
		private const string OutputFormatStr = "({0}({1}))";

		[SerializeField]
		private WirePortDataType m_selectedOutputType = WirePortDataType.FLOAT4;

		[SerializeField]
		private int m_selectedOutputTypeInt = 2;

		private readonly string[] m_outputValueTypes ={ "Vector2",
														"Vector3",
														"Vector4",
														"Color"};

		[SerializeField]
		private int[] m_occupiedChannels = { -1, -1, -1, -1 };

		[SerializeField]
		private int m_maskId;

		[SerializeField]
		private Vector4 m_maskValue = Vector4.one;

		protected override void CommonInit( int uniqueId )
		{
			base.CommonInit( uniqueId );
			AddInputPort( WirePortDataType.FLOAT, false, Constants.ChannelNamesVector[ 0 ] );
			AddInputPort( WirePortDataType.FLOAT, false, Constants.ChannelNamesVector[ 1 ] );
			AddInputPort( WirePortDataType.FLOAT, false, Constants.ChannelNamesVector[ 2 ] );
			AddInputPort( WirePortDataType.FLOAT, false, Constants.ChannelNamesVector[ 3 ] );
			AddOutputPort( m_selectedOutputType, Constants.EmptyPortValue );
			m_textLabelWidth = 90;
			m_autoWrapProperties = true;
			m_useInternalPortData = true;
			m_hasLeftDropdown = true;
			m_previewShaderGUID = "bfcd2919fe75bbf428fbbe583f463a9e";
		}

		public override void OnEnable()
		{
			base.OnEnable();
			m_maskId = Shader.PropertyToID( "_Mask" );
		}

		void NewUpdateBehaviorConn( int portId, bool onLoading )
		{
			InputPort inputPort = GetInputPortByUniqueId( portId );
			int channelsRequired = UIUtils.GetChannelsAmount( onLoading ? inputPort.DataType : inputPort.ConnectionType( 0 ) );
			int availableChannels = UIUtils.GetChannelsAmount( m_selectedOutputType );

			// Invalidate previously used channels
			for( int i = 0; i < availableChannels; i++ )
			{
				if( m_occupiedChannels[ i ] == portId )
				{
					m_occupiedChannels[ i ] = -1;
					m_inputPorts[ i ].Visible = true;
				}
			}
			// Lock available channels to port
			int len = Mathf.Min( portId + channelsRequired, availableChannels );

			int channelsUsed = 0;
			for( int i = portId; i < len; i++ )
			{
				if( m_occupiedChannels[ i ] == -1 )
				{
					m_occupiedChannels[ i ] = portId;
					channelsUsed += 1;
				}
				else
				{
					break;
				}
			}

			if( !onLoading )
				inputPort.ChangeType( UIUtils.GetWireTypeForChannelAmount( channelsUsed ), false );

			if( channelsUsed > 1 && portId < availableChannels - 1 )
			{
				channelsUsed -= 1;
				int i = portId + 1;
				for( ; channelsUsed > 0; i++, --channelsUsed )
				{
					m_inputPorts[ i ].Visible = false;
				}

			}
			m_sizeIsDirty = true;
		}

		void NewUpdateBehaviorDisconn( int portId )
		{
			int availableChannels = UIUtils.GetChannelsAmount( m_selectedOutputType );
			// Invalidate previously used channels
			for( int i = 0; i < availableChannels; i++ )
			{
				if( m_occupiedChannels[ i ] == portId )
				{
					m_occupiedChannels[ i ] = -1;
					m_inputPorts[ i ].Visible = true;
					m_inputPorts[ i ].ChangeType( WirePortDataType.FLOAT, false );
				}
			}
			m_sizeIsDirty = true;
		}

		void RenamePorts()
		{
			int channel = 0;
			for( int i = 0; i < 4; i++ )
			{
				if( m_inputPorts[ i ].Visible )
				{
					string name = string.Empty;
					int usedChannels = UIUtils.GetChannelsAmount( m_inputPorts[ i ].DataType );
					bool isColor = ( m_selectedOutputType == WirePortDataType.COLOR );
					for( int j = 0; j < usedChannels; j++ )
					{
						if( channel < Constants.ChannelNamesVector.Length )
							name += isColor ? Constants.ChannelNamesColor[ channel++ ] : Constants.ChannelNamesVector[ channel++ ];
					}
					m_inputPorts[ i ].Name = name;
				}
			}

			CalculatePreviewData();
		}

		void UpdatePortTypes()
		{
			ChangeOutputType( m_selectedOutputType, false );
			int availableChannels = UIUtils.GetChannelsAmount( m_selectedOutputType );
			int usedChannels = 0;
			while( usedChannels < availableChannels )
			{
				int channelsRequired = m_inputPorts[ usedChannels ].IsConnected ? UIUtils.GetChannelsAmount( m_inputPorts[ usedChannels ].DataType ) : 0;
				if( channelsRequired > 0 )
				{

					if( ( usedChannels + channelsRequired ) < availableChannels )
					{
						usedChannels += channelsRequired;
					}
					else
					{
						m_inputPorts[ usedChannels ].Visible = true;
						WirePortDataType newType = UIUtils.GetWireTypeForChannelAmount( availableChannels - usedChannels );
						m_inputPorts[ usedChannels ].ChangeType( newType, false );
						usedChannels = availableChannels;
						break;
					}
				}
				else
				{
					m_occupiedChannels[ usedChannels ] = -1;
					m_inputPorts[ usedChannels ].Visible = true;
					m_inputPorts[ usedChannels ].ChangeType( WirePortDataType.FLOAT, false );
					usedChannels += 1;
				}
			}

			for( int i = usedChannels; i < availableChannels; i++ )
			{
				m_occupiedChannels[ i ] = -1;
				m_inputPorts[ i ].Visible = true;
				m_inputPorts[ i ].ChangeType( WirePortDataType.FLOAT, false );
			}

			for( int i = availableChannels; i < 4; i++ )
			{
				m_occupiedChannels[ i ] = -1;
				m_inputPorts[ i ].Visible = false;
				m_inputPorts[ i ].ChangeType( WirePortDataType.FLOAT, false );
			}
			m_sizeIsDirty = true;
		}

		public override void OnInputPortConnected( int portId, int otherNodeId, int otherPortId, bool activateNode = true )
		{
			base.OnInputPortConnected( portId, otherNodeId, otherPortId, activateNode );

			if( ( m_containerGraph.IsLoading || m_isNodeBeingCopied ) && UIUtils.CurrentShaderVersion() < 13206 )
				return;

			NewUpdateBehaviorConn( portId, ( UIUtils.IsLoading|| m_isNodeBeingCopied ) );
			RenamePorts();

		}

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

			if( ( UIUtils.IsLoading || m_isNodeBeingCopied ) && UIUtils.CurrentShaderVersion() < 13206 )
				return;

			NewUpdateBehaviorDisconn( portId );
			RenamePorts();
		}

		public override void OnConnectedOutputNodeChanges( int portId, int otherNodeId, int otherPortId, string name, WirePortDataType type )
		{
			base.OnConnectedOutputNodeChanges( portId, otherNodeId, otherPortId, name, type );

			if( ( UIUtils.IsLoading || m_isNodeBeingCopied ) && UIUtils.CurrentShaderVersion() < 13206 )
				return;

			NewUpdateBehaviorConn( portId, ( UIUtils.IsLoading || m_isNodeBeingCopied ) );
			RenamePorts();
		}

		void SetupPorts()
		{
			switch( m_selectedOutputTypeInt )
			{
				case 0: m_selectedOutputType = WirePortDataType.FLOAT2; break;
				case 1: m_selectedOutputType = WirePortDataType.FLOAT3; break;
				case 2: m_selectedOutputType = WirePortDataType.FLOAT4; break;
				case 3: m_selectedOutputType = WirePortDataType.COLOR; break;
			}
			UpdatePortTypes();
			RenamePorts();
		}

		public override void Draw( DrawInfo drawInfo )
		{
			base.Draw( drawInfo );

			if( m_dropdownEditing )
			{
				EditorGUI.BeginChangeCheck();
				m_selectedOutputTypeInt = EditorGUIPopup( m_dropdownRect, m_selectedOutputTypeInt, m_outputValueTypes, UIUtils.PropertyPopUp );
				if( EditorGUI.EndChangeCheck() )
				{
					SetupPorts();
					DropdownEditing = false;
				}
			}
		}

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

			EditorGUI.BeginChangeCheck();
			m_selectedOutputTypeInt = EditorGUILayoutPopup( OutputTypeStr, m_selectedOutputTypeInt, m_outputValueTypes );
			if( EditorGUI.EndChangeCheck() )
			{
				SetupPorts();
			}

			EditorGUILayout.EndVertical();
		}

		public override string GenerateShaderForOutput( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalVar )
		{
			if( m_outputPorts[ 0 ].IsLocalValue( dataCollector.PortCategory ) )
				return m_outputPorts[ 0 ].LocalValue( dataCollector.PortCategory );
			string result = string.Empty;
			for( int i = 0; i < 4; i++ )
			{
				if( m_inputPorts[ i ].Visible )
				{
					if( i > 0 )
					{
						result += " , ";
					}
					result += m_inputPorts[ i ].GeneratePortInstructions( ref dataCollector );
				}
			}

			result = string.Format( OutputFormatStr,
									UIUtils.PrecisionWirePortToCgType( CurrentPrecisionType, m_selectedOutputType ),
									result );

			RegisterLocalVariable( 0, result, ref dataCollector, "appendResult" + OutputId );
			return m_outputPorts[ 0 ].LocalValue( dataCollector.PortCategory );
		}

		public override void ReadFromString( ref string[] nodeParams )
		{
			base.ReadFromString( ref nodeParams );
			m_selectedOutputType = (WirePortDataType)Enum.Parse( typeof( WirePortDataType ), GetCurrentParam( ref nodeParams ) );
			switch( m_selectedOutputType )
			{
				case WirePortDataType.FLOAT2: m_selectedOutputTypeInt = 0; break;
				case WirePortDataType.FLOAT3: m_selectedOutputTypeInt = 1; break;
				case WirePortDataType.FLOAT4: m_selectedOutputTypeInt = 2; break;
				case WirePortDataType.COLOR: m_selectedOutputTypeInt = 3; break;
			}
		}

		public override void ReadFromDeprecated( ref string[] nodeParams, Type oldType = null )
		{
			m_selectedOutputType = (WirePortDataType)Enum.Parse( typeof( WirePortDataType ), GetCurrentParam( ref nodeParams ) );
			switch( m_selectedOutputType )
			{
				case WirePortDataType.FLOAT2: m_selectedOutputTypeInt = 0; break;
				case WirePortDataType.FLOAT3: m_selectedOutputTypeInt = 1; break;
				case WirePortDataType.FLOAT4: m_selectedOutputTypeInt = 2; break;
				case WirePortDataType.COLOR: m_selectedOutputTypeInt = 3; break;
			}
            for( int i = 0; i < 4; i++ )
            {
                m_inputPorts[i].FloatInternalData = Convert.ToSingle( GetCurrentParam( ref nodeParams ) );
            }
        }

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

			if( UIUtils.CurrentShaderVersion() < 13206 )
			{
				//TODO: MAKE THIS LESS BRUTE FORCE
				List<AppendData> reroutes = new List<AppendData>();
				int availableChannel = 0;
				for( int i = 0; i < 4 && availableChannel < 4; i++ )
				{
					int channelsAmount = UIUtils.GetChannelsAmount( m_inputPorts[ i ].DataType );
					if( m_inputPorts[ i ].IsConnected /*&& availableChannel != i*/ )
					{
						reroutes.Add( new AppendData( m_inputPorts[ i ].DataType, i, availableChannel ) );
					}

					availableChannel += channelsAmount;
				}

				if( reroutes.Count > 0 )
				{
					for( int i = reroutes.Count - 1; i > -1; i-- )
					{
						int nodeId = m_inputPorts[ reroutes[ i ].OldPortId ].ExternalReferences[ 0 ].NodeId;
						int portId = m_inputPorts[ reroutes[ i ].OldPortId ].ExternalReferences[ 0 ].PortId;

						m_containerGraph.DeleteConnection( true, UniqueId, reroutes[ i ].OldPortId, false, false, false );
						m_containerGraph.CreateConnection( UniqueId, reroutes[ i ].NewPortId, nodeId, portId, false );
						NewUpdateBehaviorConn( reroutes[ i ].NewPortId, true );
					}
				}

				availableChannel = UIUtils.GetChannelsAmount( m_selectedOutputType );
				int currChannelIdx = 0;
				for( ; currChannelIdx < availableChannel; currChannelIdx++ )
				{
					if( m_inputPorts[ currChannelIdx ].Visible )
					{
						int channelsAmount = UIUtils.GetChannelsAmount( m_inputPorts[ currChannelIdx ].DataType );
						for( int j = currChannelIdx + 1; j < currChannelIdx + channelsAmount; j++ )
						{
							m_inputPorts[ j ].Visible = false;
						}
					}
				}

				for( ; currChannelIdx < 4; currChannelIdx++ )
				{
					m_inputPorts[ currChannelIdx ].Visible = false;
				}
			}
			SetupPorts();
			m_sizeIsDirty = true;
		}


		void CalculatePreviewData()
		{
			switch( m_outputPorts[ 0 ].DataType )
			{
				default: m_maskValue = Vector4.zero; break;
				case WirePortDataType.INT:
				case WirePortDataType.FLOAT: m_maskValue = new Vector4( 1, 0, 0, 0 ); break;
				case WirePortDataType.FLOAT2: m_maskValue = new Vector4( 1, 1, 0, 0 ); break;
				case WirePortDataType.FLOAT3: m_maskValue = new Vector4( 1, 1, 1, 0 ); break;
				case WirePortDataType.FLOAT4:
				case WirePortDataType.COLOR: m_maskValue = Vector4.one; break;
			}

			m_previewMaterialPassId = -1;
			switch( m_inputPorts[ 0 ].DataType )
			{
				case WirePortDataType.INT:
				case WirePortDataType.FLOAT:
				{
					switch( m_inputPorts[ 1 ].DataType )
					{
						case WirePortDataType.FLOAT:
						case WirePortDataType.INT:
						{
							if( m_inputPorts[ 2 ].DataType == WirePortDataType.FLOAT ||
								m_inputPorts[ 2 ].DataType == WirePortDataType.INT )
							{
								m_previewMaterialPassId = 0;
							}
							else if( m_inputPorts[ 2 ].DataType == WirePortDataType.FLOAT2 )
							{
								m_previewMaterialPassId = 1;
							}
						}
						break;
						case WirePortDataType.FLOAT2: m_previewMaterialPassId = 2; break;
						case WirePortDataType.FLOAT3: m_previewMaterialPassId = 3; break;
					}
					
				}; break;
				case WirePortDataType.FLOAT2:
				{
					if( m_inputPorts[ 2 ].DataType == WirePortDataType.FLOAT ||
						m_inputPorts[ 2 ].DataType == WirePortDataType.INT )
					{
						m_previewMaterialPassId = 4;
					}
					else if( m_inputPorts[ 2 ].DataType == WirePortDataType.FLOAT2 )
					{
						m_previewMaterialPassId = 5;
					}
				}; break;
				case WirePortDataType.FLOAT3: m_previewMaterialPassId = 6; break;
				case WirePortDataType.FLOAT4:
				case WirePortDataType.COLOR: m_previewMaterialPassId = 7; break;
			}

			if( m_previewMaterialPassId == -1 )
			{
				m_previewMaterialPassId = 0;
				if( DebugConsoleWindow.DeveloperMode )
				{
					UIUtils.ShowMessage( UniqueId, "Could not find pass ID for append" , MessageSeverity.Error );
				}
			}
		}

		public override void SetPreviewInputs()
		{
			base.SetPreviewInputs();
			PreviewMaterial.SetVector( m_maskId, m_maskValue );
		}

		public override void WriteToString( ref string nodeInfo, ref string connectionsInfo )
		{
			base.WriteToString( ref nodeInfo, ref connectionsInfo );
			IOUtils.AddFieldValueToString( ref nodeInfo, m_selectedOutputType );
		}
	}
}