#region ================== Namespaces

using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.GZBuilder.Data;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Types;

#endregion

namespace CodeImp.DoomBuilder.Controls
{
	public partial class ArgumentsControl : UserControl
	{
		#region ================== Native stuff

		[DllImport("user32.dll")]
		private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
		
		private const int WM_SETREDRAW = 11;

		#endregion

		#region ================== Enums

		private enum ArgZeroMode
		{
			DEFAULT,
			SCRIPT_NUMBER,
			SCRIPT_NAME,
		}

		#endregion

		#region ================== Variables

		private string arg0str;
		private bool havearg0str;
		private int action;
		private ArgumentInfo[] arginfo;
		private ArgZeroMode argzeromode;

		#endregion

		#region ================== Constructor

		public ArgumentsControl()
		{
			InitializeComponent();

			// Only when running (this.DesignMode won't do when not this, but one of parent controls is in design mode)
			if(LicenseManager.UsageMode != LicenseUsageMode.Designtime)
			{
				//mxd. Setup script numbers
				scriptnumbers.Location = new Point(arg0.Location.X, arg0.Location.Y + 2);
				foreach(ScriptItem si in General.Map.NumberedScripts.Values)
					scriptnumbers.Items.Add(new ColoredComboBoxItem(si, si.IsInclude ? SystemColors.HotTrack : SystemColors.WindowText));
				scriptnumbers.DropDownWidth = Tools.GetDropDownWidth(scriptnumbers);

				//mxd. Setup script names
				if(General.Map.UDMF)
				{
					scriptnames.Location = scriptnumbers.Location;
					foreach(ScriptItem nsi in General.Map.NamedScripts.Values)
						scriptnames.Items.Add(new ColoredComboBoxItem(nsi, nsi.IsInclude ? SystemColors.HotTrack : SystemColors.WindowText));
					scriptnames.DropDownWidth = Tools.GetDropDownWidth(scriptnames);
				}
				else
				{
					scriptnames.Visible = false;
					cbuseargstr.Visible = false;
				}
			}
		}

		#endregion

		#region ================== Setup

		public void SetValue(Linedef l, bool first)
		{
			SetValue(l.Fields, l.Args, first);
		}

		public void SetValue(Thing t, bool first)
		{
			SetValue(t.Fields, t.Args, first);
		}

		private void SetValue(UniFields fields, int[] args, bool first)
		{
			if(first)
			{
				if(General.Map.UDMF)
				{
					arg0str = fields.GetValue("arg0str", string.Empty);
					havearg0str = !string.IsNullOrEmpty(arg0str);
				}

				// Update arguments
				arg0.SetValue(args[0]);
				arg1.SetValue(args[1]);
				arg2.SetValue(args[2]);
				arg3.SetValue(args[3]);
				arg4.SetValue(args[4]);
			}
			else
			{
				if(General.Map.UDMF)
				{
					if(arg0str != fields.GetValue("arg0str", string.Empty))
					{
						havearg0str = true;
						arg0str = string.Empty;
					}
				}

				// Update arguments
				if(!string.IsNullOrEmpty(arg0.Text) && args[0] != arg0.GetResult(int.MinValue)) arg0.ClearValue();
				if(!string.IsNullOrEmpty(arg1.Text) && args[1] != arg1.GetResult(int.MinValue)) arg1.ClearValue();
				if(!string.IsNullOrEmpty(arg2.Text) && args[2] != arg2.GetResult(int.MinValue)) arg2.ClearValue();
				if(!string.IsNullOrEmpty(arg3.Text) && args[3] != arg3.GetResult(int.MinValue)) arg3.ClearValue();
				if(!string.IsNullOrEmpty(arg4.Text) && args[4] != arg4.GetResult(int.MinValue)) arg4.ClearValue();
			}
		}

		#endregion

		#region ================== Apply

		public void Apply(Linedef l, int step)
		{
			//mxd. Script name/number handling
			// We can't rely on control visibility here, because all controlls will be invisible if ArgumentsControl is invisible
			// (for example, when a different tab is selected)
			switch(argzeromode)
			{
				// Apply arg0str
				case ArgZeroMode.SCRIPT_NAME:
					if(!string.IsNullOrEmpty(scriptnames.Text))
						l.Fields["arg0str"] = new UniValue(UniversalType.String, scriptnames.Text);
					break;

				// Apply script number
				case ArgZeroMode.SCRIPT_NUMBER:
					if(!string.IsNullOrEmpty(scriptnumbers.Text))
					{
						if(scriptnumbers.SelectedItem != null)
							l.Args[0] = ((ScriptItem)((ColoredComboBoxItem)scriptnumbers.SelectedItem).Value).Index;
						else if(!int.TryParse(scriptnumbers.Text.Trim(), out l.Args[0]))
							l.Args[0] = 0;

						if(l.Fields.ContainsKey("arg0str")) l.Fields.Remove("arg0str");
					}
					break;

				// Apply classic arg
				case ArgZeroMode.DEFAULT:
					l.Args[0] = arg0.GetResult(l.Args[0], step);
					if(l.Fields.ContainsKey("arg0str")) l.Fields.Remove("arg0str");
					break;

				default: throw new NotImplementedException("Unknown ArgZeroMode");
			}

			// Apply the rest of args
			l.Args[1] = arg1.GetResult(l.Args[1], step);
			l.Args[2] = arg2.GetResult(l.Args[2], step);
			l.Args[3] = arg3.GetResult(l.Args[3], step);
			l.Args[4] = arg4.GetResult(l.Args[4], step);
		}

		public void Apply(Thing t, int step)
		{
			//mxd. Script name/number handling
			// We can't rely on control visibility here, because all controlls will be invisible if ArgumentsControl is invisible
			// (for example, when a different tab is selected)
			switch(argzeromode)
			{
				// Apply arg0str
				case ArgZeroMode.SCRIPT_NAME:
					if(!string.IsNullOrEmpty(scriptnames.Text))
						t.Fields["arg0str"] = new UniValue(UniversalType.String, scriptnames.Text);
					break;

				// Apply script number
				case ArgZeroMode.SCRIPT_NUMBER:
					if(!string.IsNullOrEmpty(scriptnumbers.Text))
					{
						if(scriptnumbers.SelectedItem != null)
							t.Args[0] = ((ScriptItem)((ColoredComboBoxItem)scriptnumbers.SelectedItem).Value).Index;
						else if(!int.TryParse(scriptnumbers.Text.Trim(), out t.Args[0]))
							t.Args[0] = 0;

						if(t.Fields.ContainsKey("arg0str")) t.Fields.Remove("arg0str");
					}
					break;

				// Apply classic arg
				case ArgZeroMode.DEFAULT:
					t.Args[0] = arg0.GetResult(t.Args[0], step);
					if(t.Fields.ContainsKey("arg0str")) t.Fields.Remove("arg0str");
					break;

				default: throw new NotImplementedException("Unknown ArgZeroMode");
			}

			// Apply the rest of args
			t.Args[1] = arg1.GetResult(t.Args[1], step);
			t.Args[2] = arg2.GetResult(t.Args[2], step);
			t.Args[3] = arg3.GetResult(t.Args[3], step);
			t.Args[4] = arg4.GetResult(t.Args[4], step);
		}

		#endregion

		#region ================== Update

		public void UpdateAction(int action, bool setuponly)
		{
			UpdateAction(action, setuponly, null);
		}

		public void UpdateAction(int action, bool setuponly, ThingTypeInfo info)
		{
			// Update arguments
			int showaction = 0;
			ArgumentInfo[] oldarginfo = (arginfo != null ? (ArgumentInfo[])arginfo.Clone() : null); //mxd

			// Only when action type is known
			if(General.Map.Config.LinedefActions.ContainsKey(action)) showaction = action;
			
			// Update argument infos
			if((showaction == 0) && (info != null)) arginfo = info.Args;
			else arginfo = General.Map.Config.LinedefActions[showaction].Args;

			// Don't update action args when thing type is changed
			if(info != null && showaction != 0 && this.action == showaction) return;

			//mxd. Don't update action args when old and new argument infos match
			if(arginfo != null && oldarginfo != null && ArgumentInfosMatch(arginfo, oldarginfo)) return;

			// Change the argument descriptions
			this.BeginUpdate();

			UpdateArgument(arg0, arg0label, arginfo[0]);
			UpdateArgument(arg1, arg1label, arginfo[1]);
			UpdateArgument(arg2, arg2label, arginfo[2]);
			UpdateArgument(arg3, arg3label, arginfo[3]);
			UpdateArgument(arg4, arg4label, arginfo[4]);

			if(!setuponly)
			{
				// Apply action's or thing's default arguments
				if(showaction != 0 || info != null)
				{
					arg0.SetDefaultValue();
					arg1.SetDefaultValue();
					arg2.SetDefaultValue();
					arg3.SetDefaultValue();
					arg4.SetDefaultValue();
				}
				else //or set them to 0
				{
					arg0.SetValue(0);
					arg1.SetValue(0);
					arg2.SetValue(0);
					arg3.SetValue(0);
					arg4.SetValue(0);
				}
			}

			// Store current action
			this.action = showaction;

			this.EndUpdate();
		}

		public void UpdateScriptControls()
		{
			// Update script-specific stuff
			if(Array.IndexOf(GZBuilder.GZGeneral.ACS_SPECIALS, action) != -1)
			{
				// Update script controls visibility
				bool shownamedscripts = (General.Map.UDMF && havearg0str);
				cbuseargstr.Visible = General.Map.UDMF;
				cbuseargstr.Checked = shownamedscripts;
				scriptnames.Visible = shownamedscripts;
				scriptnumbers.Visible = !shownamedscripts;

				// Update named script name
				if(shownamedscripts)
				{
					argzeromode = ArgZeroMode.SCRIPT_NAME;
					if(General.Map.NamedScripts.ContainsKey(arg0str))
					{
						int i = 0;
						foreach(ScriptItem item in General.Map.NamedScripts.Values)
						{
							if(item.Name == arg0str)
							{
								scriptnames.SelectedIndex = i;
								UpdateScriptArguments(item);
								break;
							}
							i++;
						}
					}
					else
					{
						// Unknown script name
						scriptnames.Text = arg0str;
					}
				}
				else
				{
					// Update numbered script name
					argzeromode = ArgZeroMode.SCRIPT_NUMBER;
					int a0 = arg0.GetResult(0);
					if(General.Map.NumberedScripts.ContainsKey(a0))
					{
						int i = 0;
						foreach(ScriptItem item in General.Map.NumberedScripts.Values)
						{
							if(item.Index == a0)
							{
								scriptnumbers.SelectedIndex = i;
								UpdateScriptArguments(item);
								break;
							}

							i++;
						}
					}
					else
					{
						// Unknown script number...
						scriptnumbers.Text = a0.ToString();
					}
				}
			}
			else
			{
				cbuseargstr.Visible = false;
				scriptnames.Visible = false;
				scriptnumbers.Visible = false;
				cbuseargstr.Checked = false;
				argzeromode = ArgZeroMode.DEFAULT;
			}

			arg0.Visible = (!scriptnames.Visible && !scriptnumbers.Visible);
		}

		private void UpdateArgument(ArgumentBox arg, Label label, ArgumentInfo info)
		{
			// Update labels
			label.Text = info.Title + ":";
			label.Enabled = info.Used;
			arg.ForeColor = (label.Enabled ? SystemColors.WindowText : SystemColors.GrayText);
			arg.Setup(info);

			// Update tooltip
			UpdateToolTip(label, info);
		}

		private void UpdateToolTip(Label label, ArgumentInfo info)
		{
			if(info.Used && !string.IsNullOrEmpty(info.ToolTip))
			{
				tooltip.SetToolTip(label, info.ToolTip);
				label.Font = new Font(label.Font, FontStyle.Underline);
				label.ForeColor = SystemColors.HotTrack;
			}
			else
			{
				tooltip.SetToolTip(label, null);
				label.Font = new Font(label.Font, FontStyle.Regular);
				label.ForeColor = SystemColors.WindowText;
			}
		}

		private void UpdateScriptArguments(ScriptItem item)
		{
			Label[] labels = { arg0label, arg1label, arg2label, arg3label, arg4label };
			ArgumentBox[] args = { arg0, arg1, arg2, arg3, arg4 };
			if(item != null)
			{
				string[] argnames = item.GetArgumentsDescriptions(action);
				for(int i = 0; i < labels.Length; i++)
				{
					if(!string.IsNullOrEmpty(argnames[i]))
					{
						labels[i].Text = argnames[i] + ":";
						labels[i].Enabled = true;
						labels[i].Font = new Font(labels[i].Font, FontStyle.Regular);
						labels[i].ForeColor = SystemColors.WindowText;
					}
					else
					{
						labels[i].Text = arginfo[i].Title;
						labels[i].Enabled = arginfo[i].Used;
						UpdateToolTip(labels[i], arginfo[i]);
					}

					args[i].ForeColor = (labels[i].Enabled ? SystemColors.WindowText : SystemColors.GrayText);
				}
			}
			else
			{
				for(int i = 0; i < labels.Length; i++)
				{
					labels[i].Text = arginfo[i].Title;
					labels[i].Enabled = arginfo[i].Used;
					UpdateToolTip(labels[i], arginfo[i]);
					args[i].ForeColor = (labels[i].Enabled ? SystemColors.WindowText : SystemColors.GrayText);
				}
			}
		}

		//mxd
		private static bool ArgumentInfosMatch(ArgumentInfo[] info1, ArgumentInfo[] info2)
		{
			if(info1.Length != info2.Length) return false;
			bool haveusedargs = false; // Arguments should still be reset if all arguments are unused
			
			for(int i = 0; i < info1.Length; i++)
			{
				if(info1[i].Used != info1[2].Used || info1[i].Type != info1[2].Type 
					|| info1[i].Title.ToUpperInvariant() != info2[i].Title.ToUpperInvariant())
					return false;

				haveusedargs |= (info1[i].Used || info1[2].Used);
			}

			return haveusedargs;
		}

		#endregion

		#region ================== Redraw control

		private void BeginUpdate()
		{
			SendMessage(this.Parent.Handle, WM_SETREDRAW, false, 0);
		}

		private void EndUpdate()
		{
			SendMessage(this.Parent.Handle, WM_SETREDRAW, true, 0);
			this.Parent.Refresh();
		}

		#endregion

		#region ================== Events

		private void cbuseargstr_CheckedChanged(object sender, EventArgs e)
		{
			if(!cbuseargstr.Visible) return;
			scriptnames.Visible = cbuseargstr.Checked;
			scriptnumbers.Visible = !cbuseargstr.Checked;
			arg0label.Text = (cbuseargstr.Checked ? "Script Name:" : "Script Number:");
			argzeromode = (cbuseargstr.Checked ? ArgZeroMode.SCRIPT_NAME : ArgZeroMode.SCRIPT_NUMBER);
		}

		private void scriptnumbers_TextChanged(object sender, EventArgs e)
		{
			if(string.IsNullOrEmpty(scriptnumbers.Text)) return;
			ScriptItem item = null;
			if(scriptnumbers.SelectedIndex != -1)
			{
				item = ((ScriptItem)((ColoredComboBoxItem)scriptnumbers.SelectedItem).Value);
			}
			else
			{
				int scriptindex;
				if(int.TryParse(scriptnumbers.Text, out scriptindex) && General.Map.NumberedScripts.ContainsKey(scriptindex))
					item = General.Map.NumberedScripts[scriptindex];
			}

			UpdateScriptArguments(item);
		}

		private void scriptnames_TextChanged(object sender, EventArgs e)
		{
			if(string.IsNullOrEmpty(scriptnames.Text)) return;
			ScriptItem item = null;
			if(scriptnames.SelectedIndex != -1)
			{
				item = ((ScriptItem)((ColoredComboBoxItem)scriptnames.SelectedItem).Value);
			}
			else
			{
				string scriptname = scriptnames.Text.Trim().ToLowerInvariant();
				if(General.Map.NamedScripts.ContainsKey(scriptname))
					item = General.Map.NamedScripts[scriptname];
			}

			UpdateScriptArguments(item);
		}

		#endregion
	}
}
