
#region ================== Copyright (c) 2007 Pascal vd Heiden

/*
 * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
 * This program is released under GNU General Public License
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 */

#endregion

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

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Types;

#endregion

namespace CodeImp.DoomBuilder.Windows
{
	/// <summary>
	/// Dialog window that allows viewing and editing of Thing properties.
	/// </summary>
	internal partial class ThingEditForm : DelayedForm
	{
		#region ================== Events

		public event EventHandler OnValuesChanged; //mxd

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

		private ICollection<Thing> things;
		private ThingTypeInfo thinginfo;
		private bool preventchanges;
		private bool preventmapchange; //mxd
		private bool undocreated; //mxd
		private static bool useabsoluteheight; //mxd
		private List<ThingProperties> thingprops; //mxd
        private bool flagsvalue_ignore = false;
        private bool flags_ignore = false;

		private struct ThingProperties //mxd
		{
			//public readonly int Type;
			public readonly int AngleDoom;
			public readonly float X;
			public readonly float Y;
			public readonly float Z;

			public ThingProperties(Thing t) 
			{
				X = t.Position.x;
				Y = t.Position.y;
				Z = t.Position.z;
				//Type = t.Type;
				AngleDoom = t.AngleDoom;
			}
		}

		#endregion

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

		// Constructor
		public ThingEditForm()
		{
			// Initialize
			InitializeComponent();
			
			// Fill flags list
			foreach(KeyValuePair<string, string> tf in General.Map.Config.ThingFlags)
				flags.Add(tf.Value, tf.Key);

			// Fill actions list
			action.GeneralizedCategories = General.Map.Config.GenActionCategories;
			action.AddInfo(General.Map.Config.SortedLinedefActions.ToArray());

			// Tag/Effects?
			actiongroup.Visible = General.Map.FormatInterface.HasThingAction;
			idgroup.Visible = General.Map.FormatInterface.HasThingTag;
			
			// Thing height?
			posZ.Visible = General.Map.FormatInterface.HasThingHeight;
			zlabel.Visible = General.Map.FormatInterface.HasThingHeight;
			cbAbsoluteHeight.Visible = General.Map.FormatInterface.HasThingHeight; //mxd

			//mxd. Decimals allowed?
			if(General.Map.FormatInterface.VertexDecimals > 0) 
			{
				posX.AllowDecimal = true;
				posY.AllowDecimal = true;
				posZ.AllowDecimal = true;
			}

			//mxd. Use doom angle clamping?
			anglecontrol.DoomAngleClamping = General.Map.Config.DoomThingRotationAngles;

			//mxd. Arrange inteface
			int targetheight;
			if(General.Map.FormatInterface.HasThingAction)
				targetheight = actiongroup.Bottom + actiongroup.Margin.Bottom;
			else
				targetheight = typegroup.Bottom + typegroup.Margin.Bottom * 2;

			if(General.Map.FormatInterface.HasThingTag)
			{
				idgroup.Top = targetheight;
				targetheight = idgroup.Bottom + idgroup.Margin.Bottom * 2;
			}

			panel.Height = targetheight;

			//mxd. Arrange Apply/Cancel buttons
			applypanel.Top = panel.Bottom + panel.Margin.Bottom * 2;

			//mxd. Update window height
			this.Height = applypanel.Bottom + applypanel.Margin.Bottom + (this.Height - this.ClientRectangle.Height) + 1;
			
			// Setup types list
			thingtype.Setup();
		}

        #endregion

        #region ================== Methods

        private string evaluateFlagsValue()
        {
            int i = 1;
            int value = 0;
            foreach (CheckBox box in flags.Checkboxes)
            {
                if (box.CheckState == CheckState.Indeterminate) return "";
                if (box.CheckState == CheckState.Checked) value += i;
                i *= 2;
            }

            if (General.Map.SRB2)
            {
                float z = General.GetByIndex(things, 0).Position.z;
                foreach (Thing t in things)
                {
                    if (t.Position.z != z) return "";
                }

                return (value + ((int)z << 4)).ToString();
            }
            else return value.ToString();
        }

        // This sets up the form to edit the given things
        public void Setup(ICollection<Thing> things)
		{
			preventchanges = true;

			// Keep this list
			this.things = things;
			if(things.Count > 1) this.Text = "Edit Things (" + things.Count + ")";
			hint.Visible = things.Count > 1; //mxd
			hintlabel.Visible = things.Count > 1; //mxd
			thingtype.UseMultiSelection = things.Count > 1; //mxd
			
			////////////////////////////////////////////////////////////////////////
			// Set all options to the first thing properties
			////////////////////////////////////////////////////////////////////////

			Thing ft = General.GetByIndex(things, 0);
			
			// Set type
			thingtype.SelectType(ft.SRB2Type, ft.Parameter);
            
            // Flags
            ThingTypeInfo ti = General.Map.Data.GetThingInfoEx(ft.SRB2Type);
            IDictionary<string, string> newFlags = (ti == null || ti.Flags.Count == 0) ? General.Map.Config.ThingFlags : ti.Flags;

            foreach (CheckBox c in flags.Checkboxes)
				if(ft.Flags.ContainsKey(c.Tag.ToString())) c.Checked = ft.Flags[c.Tag.ToString()];
			
			// Coordination
			angle.Text = ft.AngleDoom.ToString();
			zlabel.Text = useabsoluteheight ? "Z:" : "Height:"; //mxd
			cbAbsoluteHeight.Checked = useabsoluteheight; //mxd

			//mxd
			ft.DetermineSector();
			float floorheight = (ft.Sector != null ? Sector.GetFloorPlane(ft.Sector).GetZ(ft.Position) : 0);
			posX.Text = ((int)ft.Position.x).ToString();
			posY.Text = ((int)ft.Position.y).ToString();
			posZ.Text = (useabsoluteheight ? ((int)Math.Round(ft.Position.z + floorheight)).ToString() : ((int)ft.Position.z).ToString());
            posZ.AllowNegative = General.Map.FormatInterface.MinThingHeight < 0;
			/*posX.ButtonStep = General.Map.Grid.GridSize;
			posY.ButtonStep = General.Map.Grid.GridSize;
			posZ.ButtonStep = General.Map.Grid.GridSize;*/

			// Action/tags
			action.Value = ft.Action;
			if(General.Map.FormatInterface.HasThingTag) //mxd
			{
				tagSelector.Setup(UniversalType.ThingTag); 
				tagSelector.SetTag(ft.Tag);
			}

			//mxd. Args
			argscontrol.SetValue(ft, true);

			////////////////////////////////////////////////////////////////////////
			// Now go for all lines and change the options when a setting is different
			////////////////////////////////////////////////////////////////////////

			thingprops = new List<ThingProperties>();
            bool allsametype = true;

			// Go for all things
			foreach(Thing t in things)
			{
				//mxd. Update sector info
				t.DetermineSector();

                if (t.SRB2Type != ft.SRB2Type) allsametype = false;
				
				// Type does not match?
				ThingTypeInfo info = thingtype.GetSelectedInfo(); //mxd

				if(info != null && info.Index != t.SRB2Type)
					thingtype.ClearSelectedType();
				
				// Flags
				foreach(CheckBox c in flags.Checkboxes)
				{
					if(c.CheckState == CheckState.Indeterminate) continue; //mxd
					if(t.IsFlagSet(c.Tag.ToString()) != c.Checked) 
					{
						c.ThreeState = true;
						c.CheckState = CheckState.Indeterminate;
					}
				}
				
				// Coordination
				if(t.AngleDoom.ToString() != angle.Text) angle.Text = "";
				
				//mxd. Position
				if(((int)t.Position.x).ToString() != posX.Text) posX.Text = "";
				if(((int)t.Position.y).ToString() != posY.Text) posY.Text = "";
				if(useabsoluteheight && t.Sector != null) 
				{
					if(((int)Math.Round(Sector.GetFloorPlane(t.Sector).GetZ(t.Position) + t.Position.z)).ToString() != posZ.Text)
						posZ.Text = "";
				} 
				else if(((int)t.Position.z).ToString() != posZ.Text)
				{
					posZ.Text = "";
				}

				// Action/tags
				if(t.Action != action.Value) action.Empty = true;
				if(General.Map.FormatInterface.HasThingTag && t.Tag != ft.Tag) tagSelector.ClearTag(); //mxd

				//mxd. Arguments
				argscontrol.SetValue(t, false);

				//mxd. Store initial properties
				thingprops.Add(new ThingProperties(t));
			}

            if (allsametype)
            {
                flags.UpdateCheckboxes(newFlags, General.Map.Config.ThingFlags);
                if (ti != null)
                {
                    groupBox4.Text = ti.AngleText;
					groupBox4.BackColor = (groupBox4.Text != "Angle") ? Color.LightGray : Color.White;
					groupBox4.ForeColor = Color.Black;
					label3.Text = ti.FlagsValueText + ":";
                }
            }

            preventchanges = false;

			//mxd. Trigger updates manually...
			preventmapchange = true;
			angle_WhenTextChanged(angle, EventArgs.Empty);
			flags_OnValueChanged(flags, EventArgs.Empty);
			preventmapchange = false;
            flagsvalue.Text = evaluateFlagsValue();
			label3.BackColor = (label3.Text != "Flags value:") ? Color.LightGray : Color.White;
			label3.ForeColor = Color.Black;

			argscontrol.UpdateScriptControls(); //mxd
			actionhelp.UpdateAction(action.GetValue()); //mxd
		}

		//mxd
		private void MakeUndo() 
		{
			if(undocreated) return;
			undocreated = true;

			//mxd. Make undo
			General.Map.UndoRedo.CreateUndo("Edit " + (things.Count > 1 ? things.Count + " things" : "thing"));
		}
		
		#endregion

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

		//mxd
		private void thingtype_OnTypeDoubleClicked() 
		{
			apply_Click(this, EventArgs.Empty);
		}
		
		// Action changes
		private void action_ValueChanges(object sender, EventArgs e)
		{
			int showaction = 0;

			// Only when line type is known, otherwise use the thing arguments
			if(General.Map.Config.LinedefActions.ContainsKey(action.Value)) showaction = action.Value;
			
			//mxd. Change the argument descriptions
			argscontrol.UpdateAction(showaction, preventchanges, (action.Empty ? null : thinginfo));

			if(!preventchanges)
			{
				MakeUndo();

				//mxd. Update what must be updated
				argscontrol.UpdateScriptControls();
				actionhelp.UpdateAction(showaction);
			}
		}

		// Browse Action clicked
		private void browseaction_Click(object sender, EventArgs e)
		{
			action.Value = ActionBrowserForm.BrowseAction(this, action.Value);
		}

		// Angle text changes
		private void angle_WhenTextChanged(object sender, EventArgs e)
		{
			if(preventchanges) return;
			preventchanges = true;
			anglecontrol.Angle = angle.GetResult(GZBuilder.Controls.AngleControl.NO_ANGLE);
			preventchanges = false;
			if(!preventmapchange) ApplyAngleChange(); //mxd
		}

		//mxd. Angle control clicked
		private void anglecontrol_AngleChanged(object sender, EventArgs e) 
		{
			if(preventchanges) return;
			angle.Text = anglecontrol.Angle.ToString();
			if(!preventmapchange) ApplyAngleChange();
		}

		// Apply clicked
		private void apply_Click(object sender, EventArgs e)
		{
			MakeUndo();
			
			List<string> defaultflags = new List<string>();

			// Verify the tag
			if(General.Map.FormatInterface.HasThingTag) //mxd
			{
				tagSelector.ValidateTag();//mxd
				if(((tagSelector.GetTag(0) < General.Map.FormatInterface.MinTag) || (tagSelector.GetTag(0) > General.Map.FormatInterface.MaxTag))) 
				{
					General.ShowWarningMessage("Thing tag must be between " + General.Map.FormatInterface.MinTag + " and " + General.Map.FormatInterface.MaxTag + ".", MessageBoxButtons.OK);
					return;
				}
			}

			// Verify the type
			if(!string.IsNullOrEmpty(thingtype.TypeStringValue) && ((thingtype.GetFullType(0) < General.Map.FormatInterface.MinThingType) || (thingtype.GetFullType(0) > General.Map.FormatInterface.MaxThingType)))
			{
				General.ShowWarningMessage("Thing type must be between " + General.Map.FormatInterface.MinThingType + " and " + General.Map.FormatInterface.MaxThingType + ".", MessageBoxButtons.OK);
				return;
			}

            // Verify the action
            if (General.Map.FormatInterface.HasThingAction && ((action.Value < General.Map.FormatInterface.MinAction) || (action.Value > General.Map.FormatInterface.MaxAction)))
			{
				General.ShowWarningMessage("Thing action must be between " + General.Map.FormatInterface.MinAction + " and " + General.Map.FormatInterface.MaxAction + ".", MessageBoxButtons.OK);
				return;
			}

			// Go for all the things
			int offset = 0; //mxd
			foreach(Thing t in things)
			{
				// Coordination
				if(cbRandomAngle.Checked) //mxd
				{
					int newangle = General.Random(0, 359);
					if(General.Map.Config.DoomThingRotationAngles) newangle = newangle / 45 * 45;
					t.Rotate(newangle);
				}

				//mxd. Check position
				float px = General.Clamp(t.Position.x, General.Map.Config.LeftBoundary, General.Map.Config.RightBoundary);
				float py = General.Clamp(t.Position.y, General.Map.Config.BottomBoundary, General.Map.Config.TopBoundary);
				if(t.Position.x != px || t.Position.y != py) t.Move(new Vector2D(px, py));
				
				// Apply all flags
				foreach(CheckBox c in flags.Checkboxes)
				{
					switch(c.CheckState)
					{
						case CheckState.Checked: t.SetFlag(c.Tag.ToString(), true); break;
						case CheckState.Unchecked: t.SetFlag(c.Tag.ToString(), false); break;
					}
				}

				// Action/tags
				t.Tag = General.Clamp(tagSelector.GetSmartTag(t.Tag, offset), General.Map.FormatInterface.MinTag, General.Map.FormatInterface.MaxTag); //mxd
				if(!action.Empty) t.Action = action.Value;

				//mxd. Apply args
				argscontrol.Apply(t, offset);
				
				// Update settings
				t.UpdateConfiguration();

				//mxd. Increase offset...
				offset++;
			}

			// Set as defaults
			foreach(CheckBox c in flags.Checkboxes)
				if(c.CheckState == CheckState.Checked) defaultflags.Add(c.Tag.ToString());
			General.Settings.DefaultThingType = thingtype.GetResult(General.Settings.DefaultThingType);
			General.Settings.DefaultThingAngle = angle.GetResult(General.Settings.DefaultThingAngle);
			General.Settings.SetDefaultThingFlags(defaultflags);
			
			// Done
			General.Map.IsChanged = true;
			if(OnValuesChanged != null)	OnValuesChanged(this, EventArgs.Empty); //mxd
			this.DialogResult = DialogResult.OK;
			this.Close();
		}

		// Cancel clicked
		private void cancel_Click(object sender, EventArgs e)
		{
			//mxd. Perform undo?
			if(undocreated) General.Map.UndoRedo.WithdrawUndo();
			
			// Be gone
			this.DialogResult = DialogResult.Cancel;
			this.Close();
		}

		//mxd
		private void cbAbsoluteHeight_CheckedChanged(object sender, EventArgs e) 
		{
			if(preventchanges) return;
			MakeUndo();

			useabsoluteheight = cbAbsoluteHeight.Checked;
            zlabel.Text = (useabsoluteheight ? "Z:" : "Height:");
			
			preventchanges = true;
			
			//update label text
			Thing ft = General.GetByIndex(things, 0);
			float z = ft.Position.z;
			if(useabsoluteheight && ft.Sector != null) z += Sector.GetFloorPlane(ft.Sector).GetZ(ft.Position);
			posZ.Text = ((float)Math.Round(z, General.Map.FormatInterface.VertexDecimals)).ToString();

			foreach(Thing t in things) 
			{
				z = t.Position.z;
				if(useabsoluteheight && t.Sector != null) z += Sector.GetFloorPlane(t.Sector).GetZ(t.Position);
				string ztext = ((float)Math.Round(z, General.Map.FormatInterface.VertexDecimals)).ToString();
				if(posZ.Text != ztext) 
				{
					posZ.Text = "";
					break;
				}
			}

			preventchanges = false;
		}

		//mxd
		private void cbRandomAngle_CheckedChanged(object sender, EventArgs e) 
		{
			angle.Enabled = !cbRandomAngle.Checked;
			anglecontrol.Enabled = !cbRandomAngle.Checked;
		}

		//mxd
		private void ThingEditForm_Shown(object sender, EventArgs e)
		{
			thingtype.FocusTextbox();
		}

		// Help
		private void ThingEditForm_HelpRequested(object sender, HelpEventArgs hlpevent)
		{
			General.ShowHelp("w_thingedit.html");
			hlpevent.Handled = true;
		}

		#endregion

		#region ================== mxd. Realtime events

		private void posX_WhenTextChanged(object sender, EventArgs e) 
		{
			if(preventchanges) return;
			MakeUndo(); //mxd
			int i = 0;

			// Update values
			foreach(Thing t in things)
				t.Move(new Vector2D(posX.GetResultFloat(thingprops[i++].X), t.Position.y));

			General.Map.IsChanged = true;
			if(OnValuesChanged != null)	OnValuesChanged(this, EventArgs.Empty);
		}

		private void posY_WhenTextChanged(object sender, EventArgs e) 
		{
			if(preventchanges) return;
			MakeUndo(); //mxd
			int i = 0;

			// Update values
			foreach(Thing t in things)
				t.Move(new Vector2D(t.Position.x, posY.GetResultFloat(thingprops[i++].Y)));

			General.Map.IsChanged = true;
			if(OnValuesChanged != null) OnValuesChanged(this, EventArgs.Empty);
		}

		private void posZ_WhenTextChanged(object sender, EventArgs e) 
		{
			if(preventchanges) return;
            MakeUndo(); //mxd
			int i = 0;

			if(string.IsNullOrEmpty(posZ.Text)) 
			{
				// Restore values
				foreach(Thing t in things)
					t.Move(new Vector3D(t.Position.x, t.Position.y, thingprops[i++].Z));
			} 
			else 
			{ 
				// Update values
				foreach(Thing t in things) 
				{
					float z = posZ.GetResultFloat(thingprops[i++].Z);
					if(useabsoluteheight && !posZ.CheckIsRelative() && t.Sector != null)
						z -= (float)Math.Round(Sector.GetFloorPlane(t.Sector).GetZ(t.Position.x, t.Position.y), General.Map.FormatInterface.VertexDecimals);
					t.Move(new Vector3D(t.Position.x, t.Position.y, General.Clamp(z, General.Map.FormatInterface.MinThingHeight, General.Map.FormatInterface.MaxThingHeight)));
				}
			}

            if (!flagsvalue_ignore)
            {
                flagsvalue_ignore = true;
                flagsvalue.Text = evaluateFlagsValue();
                flagsvalue_ignore = false;
            }
            General.Map.IsChanged = true;
			if(OnValuesChanged != null) OnValuesChanged(this, EventArgs.Empty);
        }

        private void flagsvalue_TextChanged(object sender, EventArgs e)
        {
            if (!flagsvalue_ignore && !string.IsNullOrEmpty(flagsvalue.Text))
            {
                flagsvalue_ignore = true;
                int value = General.Clamp(flagsvalue.GetResult(0),0,0xFFFF);
                int i = 1;
                flags_ignore = true;
                foreach (CheckBox box in flags.Checkboxes)
                {
                    box.Checked = ((value & i) == i);
                    i *= 2;
                }
                flags_ignore = false;
                flags_OnValueChanged(this, null);

                if (General.Map.SRB2)
                {
                    int z = value >> 4;
                    posZ.Text = z.ToString();
                }
                flagsvalue_ignore = false;
                cbAbsoluteHeight.Checked = false;
            }
        }

        // Selected type changes
        private void thingtype_OnTypeChanged(ThingTypeInfo value) 
		{
			thinginfo = value;

			// Update arguments
			action_ValueChanges(this, EventArgs.Empty);

			//mxd. Update things
			if(preventchanges ||
					(!string.IsNullOrEmpty(thingtype.TypeStringValue) &&
					thingtype.GetResult(0) < General.Map.FormatInterface.MinThingType
					|| thingtype.GetResult(0) > General.Map.FormatInterface.MaxThingType))
				return;

			MakeUndo(); //mxd

			foreach(Thing t in things) 
			{
				//Set type
				t.FullType = thingtype.GetFullType(t.FullType);

				// Update settings
				t.UpdateConfiguration();
			}

            IDictionary<string, string> newFlags = (thinginfo == null || thinginfo.Flags.Count == 0) ? General.Map.Config.ThingFlags : thinginfo.Flags;
            flags.UpdateCheckboxes(newFlags, General.Map.Config.ThingFlags);
            groupBox4.Text = (thinginfo == null) ? "Angle" : thinginfo.AngleText;
			groupBox4.BackColor = (groupBox4.Text != "Angle") ? Color.LightGray : Color.White;
			groupBox4.ForeColor = Color.Black;
			label3.Text = (thinginfo == null) ? "Flags value:" : thinginfo.FlagsValueText + ":";
			label3.BackColor = (label3.Text != "Flags value:") ? Color.LightGray : Color.White;
			label3.ForeColor = Color.Black;

			General.Map.IsChanged = true;
			if(OnValuesChanged != null) OnValuesChanged(this, EventArgs.Empty);
		}

		//mxd
		private void ApplyAngleChange() 
		{
			if(preventchanges) return;
			MakeUndo(); //mxd
			int i = 0;

			//restore values
			if(string.IsNullOrEmpty(angle.Text)) 
			{
				// Apply rotation
				foreach(Thing t in things)
					t.Rotate(thingprops[i++].AngleDoom);
			}
			else //update values
			{ 
				// Apply rotation
				foreach(Thing t in things)
					t.Rotate(angle.GetResult(thingprops[i++].AngleDoom));
			}

			General.Map.IsChanged = true;
			if(OnValuesChanged != null)	OnValuesChanged(this, EventArgs.Empty);
		}

		//mxd
		private void flags_OnValueChanged(object sender, EventArgs e) 
		{
			if(flags_ignore || preventchanges) return;

			// Gather enabled flags
			HashSet<string> activeflags = new HashSet<string>();
			foreach(CheckBox cb in flags.Checkboxes)
			{
				if(cb.CheckState != CheckState.Unchecked) activeflags.Add(cb.Tag.ToString());
			}

			// Check em
			List<string> warnings = ThingFlagsCompare.CheckFlags(activeflags);
			if(warnings.Count > 0) 
			{
				//got missing flags
				tooltip.SetToolTip(missingflags, string.Join(Environment.NewLine, warnings.ToArray()));
				missingflags.Visible = true;
				settingsgroup.ForeColor = Color.DarkRed;
				return;
			}

			//everything is OK
			missingflags.Visible = false;
			settingsgroup.ForeColor = SystemColors.ControlText;
            if (!flagsvalue_ignore)
            {
                flagsvalue_ignore = true;
                flagsvalue.Text = evaluateFlagsValue();
                flagsvalue_ignore = false;
            }
        }

		#endregion

	}
}