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

/*
 * Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
 * Copyright (c) 2014 Boris Iwanski
 * 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;
using System.Drawing;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using System.Linq;
using System.Diagnostics;
using CodeImp.DoomBuilder.Windows;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.IO;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Editing;
using CodeImp.DoomBuilder.Actions;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Types;
using CodeImp.DoomBuilder.BuilderModes;
// using CodeImp.DoomBuilder.GZBuilder.Geometry;

#endregion

namespace CodeImp.DoomBuilder.ThreeDFloorMode
{
    public class SlopeObject
    {
        private ThreeDFloor threedfloor;
        private Vector2D position;
		private int v;

        public ThreeDFloor ThreeDFloor { get { return threedfloor; } set { threedfloor = value; } }
        public Vector2D Position { get { return position; } set { position = value; } }
		public int V { get { return v; } set { v = value; } }
    }

    [EditMode(DisplayName = "Slope Mode",
              SwitchAction = "threedslopemode",		// Action name used to switch to this mode
              ButtonImage = "SlopeModeIcon.png",	// Image resource name for the button
              ButtonOrder = int.MinValue + 501,	// Position of the button (lower is more to the left)
              ButtonGroup = "000_editing",
			  SupportedMapFormats = new[] { "UniversalMapSetIO" },
			  RequiredMapFeatures = new[] { "PlaneEquationSupport" },
			  UseByDefault = true,
              IsDeprecated = true,
              DeprecationMessage = "Please use the visual sloping functionality instead.")]

    public class SlopeMode : ClassicMode
	{
		#region ================== Constants

		#endregion

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

		// Highlighted item
        private SlopeVertex highlightedslope;
		private Sector highlightedsector;
		private Association[] association = new Association[Thing.NUM_ARGS];
		private List<SlopeVertexGroup> copyslopevertexgroups;

        private List<ThreeDFloor> threedfloors;
		bool dragging = false;

		private List<TextLabel> labels;
		private FlatVertex[] overlaygeometry;
		private FlatVertex[] overlaytaggedgeometry;
		private FlatVertex[] selectedsectorgeometry;

		private Vector2D dragstartmappos;
		private List<Vector2D> oldpositions;

		private bool contextmenuclosing = false;
		
		#endregion

		#region ================== Properties

		public Sector HighlightedSector { get { return highlightedsector; } }
		public bool ContextMenuClosing { get { return contextmenuclosing; } set { contextmenuclosing = value; } }

		#endregion

		#region ================== Constructor / Disposer

		#endregion

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

		public override void OnHelp()
		{
			General.ShowHelp("gzdb/features/classic_modes/mode_slopes.html");
		}

		// Cancel mode
		public override void OnCancel()
		{
			base.OnCancel();

			// Return to previous stable mode
			General.Editing.ChangeMode(General.Editing.PreviousStableMode.Name);
		}

		// Mode engages
		public override void OnEngage()
		{
            base.OnEngage();

			if (BuilderPlug.Me.SlopeDataSector == null || BuilderPlug.Me.SlopeDataSector.IsDisposed)
			{
				General.Map.UndoRedo.CreateUndo("Set up slope data sector");

				SlopeDataSectorDialog sdsd = new SlopeDataSectorDialog();
				DialogResult dr = sdsd.ShowDialog();

				if (dr == DialogResult.Cancel)
				{
					General.Map.UndoRedo.WithdrawUndo();
					General.Editing.CancelMode();
					return;
				}

				if (dr == DialogResult.OK)
				{
					BuilderPlug.Me.SlopeDataSector = General.Map.Map.GetMarkedSectors(true)[0];
					BuilderPlug.Me.StoreSlopeVertexGroupsInSector();
				}
			}
			else
			{
				BuilderPlug.Me.LoadSlopeVertexGroupsFromSector();
			}

            renderer.SetPresentation(Presentation.Things);

			General.Interface.AddButton(BuilderPlug.Me.MenusForm.UpdateSlopes);

            // Convert geometry selection to sectors
            General.Map.Map.ConvertSelection(SelectionType.Sectors);

            // Get all 3D floors in the map
            threedfloors = BuilderPlug.GetThreeDFloors(General.Map.Map.Sectors.ToList());

			SetupLabels();

			foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
			{
				svg.FindSectors();
			}

			// Update overlay surfaces, so that selected sectors are drawn correctly
			updateOverlaySurfaces();
		}

		// Mode disengages
		public override void OnDisengage()
		{
			base.OnDisengage();

			General.Interface.RemoveButton(BuilderPlug.Me.MenusForm.UpdateSlopes);
			
			// Hide highlight info
			General.Interface.HideInfo();
		}

		// This redraws the display
		public override void OnRedrawDisplay()
		{
			renderer.RedrawSurface();

			// Render lines and vertices
			if(renderer.StartPlotter(true))
			{
				renderer.PlotLinedefSet(General.Map.Map.Linedefs);
				renderer.PlotVerticesSet(General.Map.Map.Vertices);

				foreach (Sector s in General.Map.Map.GetSelectedSectors(true).ToList())
					renderer.PlotSector(s, General.Colors.Selection);

				if ((highlightedsector != null) && !highlightedsector.IsDisposed)
					renderer.PlotSector(highlightedsector, General.Colors.Highlight);

				renderer.Finish();
			}

			// Render things
			if(renderer.StartThings(true))
			{
				renderer.RenderThingSet(General.Map.ThingsFilter.HiddenThings, Presentation.THINGS_HIDDEN_ALPHA);
				renderer.RenderThingSet(General.Map.ThingsFilter.VisibleThings, 1.0f);
				renderer.RenderSRB2Extras();

				renderer.Finish();
			}

            UpdateOverlay();

			renderer.Present();
		}

		private void SetupLabels()
		{
			Dictionary<Sector, SectorLabelInfo> sectorlabels = new Dictionary<Sector, SectorLabelInfo>();
			PixelColor white = new PixelColor(255, 255, 255, 255);

			if (labels != null)
			{
				// Dispose old labels
				foreach (TextLabel l in labels)
					l.Dispose();

				labels.Clear();
			}
			else
			{
				labels = new List<TextLabel>();
			}

			// Go through all sectors that belong to a SVG and set which SVG their floor and
			// ceiling belongs to
			foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
			{
				// Process all directly affected sectors
				foreach (Sector s in svg.Sectors)
				{
					if(!sectorlabels.ContainsKey(s))
						sectorlabels.Add(s, new SectorLabelInfo());

					if ((svg.SectorPlanes[s] & PlaneType.Floor) == PlaneType.Floor)
						sectorlabels[s].AddSlopeVertexGroup(PlaneType.Floor, svg);

					if ((svg.SectorPlanes[s] & PlaneType.Ceiling) == PlaneType.Ceiling)
						sectorlabels[s].AddSlopeVertexGroup(PlaneType.Ceiling, svg);
				}

				// Process all tagged sectors
				foreach(Sector s in svg.TaggedSectors)
				{
					if (!sectorlabels.ContainsKey(s))
						sectorlabels.Add(s, new SectorLabelInfo());

					// Bottom and Top are just virtual, the control sector has Floor and Ceiling
					if ((svg.SectorPlanes[s] & PlaneType.Floor) == PlaneType.Floor)
						sectorlabels[s].AddSlopeVertexGroup(PlaneType.Bottom, svg);

					if ((svg.SectorPlanes[s] & PlaneType.Ceiling) == PlaneType.Ceiling)
						sectorlabels[s].AddSlopeVertexGroup(PlaneType.Top, svg);
				}
			}

			// Create the labels for each sector and add them to the label list
			if (BuilderPlug.Me.SectorLabelDisplayOption != LabelDisplayOption.Never || General.Interface.AltState == true)
			{
				foreach (Sector s in sectorlabels.Keys)
				{
					bool showlabel = true;

					if (BuilderPlug.Me.SectorLabelDisplayOption == LabelDisplayOption.WhenHighlighted && General.Interface.AltState == false)
					{
						showlabel = false;
						foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
							if ((svg.Sectors.Contains(s) || svg.TaggedSectors.Contains(s)) && svg.Vertices.Contains(highlightedslope))
								showlabel = true;
					}

					if(showlabel)
						labels.AddRange(sectorlabels[s].CreateLabels(s, highlightedslope, renderer.Scale));
				}
			}

			// Z position labels for slope vertices
			if (BuilderPlug.Me.SlopeVertexLabelDisplayOption != LabelDisplayOption.Never || General.Interface.AltState == true)
			{
				foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
				{
					for (int i = 0; i < svg.Vertices.Count; i++)
					{
						if (BuilderPlug.Me.SlopeVertexLabelDisplayOption == LabelDisplayOption.Always || General.Interface.AltState == true || svg.Vertices.Contains(highlightedslope))
						{
							SlopeVertex sv = svg.Vertices[i];
							float scale = 1 / renderer.Scale;
							double x = sv.Pos.x;
							double y = sv.Pos.y - 14 * scale;
							string value = String.Format("Z: {0}", sv.Z);
							bool showlabel = true;

							// Rearrange labels if they'd be (exactly) on each other
							// TODO: do something like that also for overlapping labels
							foreach (TextLabel l in labels)
							{
								if (l.Location.x == x && l.Location.y == y) {
									// Reduce visual clutter by de-duping stacked labels, when "show all labels" is enabled
									if (l.Text == value) {
										showlabel = false; //dedupe

										// If any of the shared label lines are highlighted/selected, then override the color
										if (svg.Vertices.Contains(highlightedslope))
											l.Color = General.Colors.Highlight.WithAlpha(255);
										else if (sv.Selected)
											l.Color = General.Colors.Selection.WithAlpha(255);
									} else {
										// Adjust the label position down one line
										y -= l.TextSize.Height * scale;
									}
								}
							}

							// Only proceed if the label was not deduped
							if (showlabel)
							{
								TextLabel label = new TextLabel();
								label.TransformCoords = true;
								label.Location = new Vector2D(x, y);
								label.AlignX = TextAlignmentX.Center;
								label.AlignY = TextAlignmentY.Middle;
								label.BackColor = General.Colors.Background.WithAlpha(128);
								label.Text = value;

								if (svg.Vertices.Contains(highlightedslope))
									label.Color = General.Colors.Highlight.WithAlpha(255);
								else if (sv.Selected)
									label.Color = General.Colors.Selection.WithAlpha(255);
								else
									label.Color = white;

								labels.Add(label);
							}
						}
					}
				}
			}
		}

		// This updates the overlay
        private void UpdateOverlay()
        {
            float size = 9 / renderer.Scale;

			SetupLabels();

            if (renderer.StartOverlay(true))
            {
				if(overlaygeometry != null)
					renderer.RenderHighlight(overlaygeometry, General.Colors.ModelWireframe.WithAlpha(64).ToInt());

				if (overlaytaggedgeometry != null)
					renderer.RenderHighlight(overlaytaggedgeometry, General.Colors.Vertices.WithAlpha(64).ToInt());

				if (selectedsectorgeometry != null)
					renderer.RenderHighlight(selectedsectorgeometry, General.Colors.Selection.WithAlpha(64).ToInt());

				if (BuilderPlug.Me.UseHighlight && highlightedsector != null)
				{
					renderer.RenderHighlight(highlightedsector.FlatVertices, General.Colors.Highlight.WithAlpha(64).ToInt());
				}

				List<SlopeVertex> vertices = new List<SlopeVertex>();
				List<Line2D> highlightlines = new List<Line2D>();

				// TMP
				foreach(Line3D l in BuilderPlug.Me.drawlines)
				{
					renderer.RenderLine(
						new Vector2D(l.Start.x, l.Start.z),
						new Vector2D(l.End.x, l.End.z),
						1, new PixelColor(255, 255, 255, 255), true
						);
				}

				foreach(Vector3D v in BuilderPlug.Me.drawpoints)
				{
					renderer.RenderLine(
						new Vector2D(v.x, v.z+2),
						new Vector2D(v.x, v.z-2),
						1, new PixelColor(255, 255, 0, 0), true);
				}

				// Store all slope vertices and draw the lines between them. If the lines connect highlighted slope vertices
				// draw them later, so they are on top
				foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
				{
					bool highlighted = svg.Vertices.Where(o => o == highlightedslope).Count() > 0;

					for (int i = 0; i < svg.Vertices.Count; i++)
					{
						vertices.Add(svg.Vertices[i]);

						if (i < svg.Vertices.Count - 1)
						{
							if (highlighted)
								highlightlines.Add(new Line2D(svg.Vertices[0].Pos, svg.Vertices[i + 1].Pos));
							else
								renderer.RenderLine(svg.Vertices[0].Pos, svg.Vertices[i + 1].Pos, 1, new PixelColor(255, 255, 255, 255), true);
						}
					}
				}

				// Draw highlighted lines
				foreach (Line2D line in highlightlines)
					renderer.RenderLine(line.v1, line.v2, 1, General.Colors.Highlight, true);

				// Sort the slope vertex list and draw them. The sorting ensures that selected vertices are always drawn on top
				foreach(SlopeVertex sv in vertices.OrderBy(o=>o.Selected))
				{
					PixelColor c = General.Colors.Indication;
					Vector3D v = sv.Pos;

					if (sv.Selected)
						c = General.Colors.Selection;

					renderer.RenderRectangleFilled(new RectangleF((float)(v.x - size / 2), (float)(v.y - size / 2), size, size), General.Colors.Background, true);
					renderer.RenderRectangle(new RectangleF((float)(v.x - size / 2), (float)(v.y - size / 2), size, size), 2, c, true);
				}

				// Draw highlighted slope vertex
				if (highlightedslope != null)
				{
					renderer.RenderRectangleFilled(new RectangleF((float)(highlightedslope.Pos.x - size / 2), (float)(highlightedslope.Pos.y - size / 2), size, size), General.Colors.Background, true);
					renderer.RenderRectangle(new RectangleF((float)(highlightedslope.Pos.x - size / 2), (float)(highlightedslope.Pos.y - size / 2), size, size), 2, General.Colors.Highlight, true);
				}

				foreach (TextLabel l in labels)
					renderer.RenderText(l);

				if (selecting)
					RenderMultiSelection();

                renderer.Finish();
            }           
        }

		private void updateOverlaySurfaces()
		{
			string[] fieldnames = new string[] { "user_floorplane_id", "user_ceilingplane_id" };
			ICollection<Sector> orderedselection = General.Map.Map.GetSelectedSectors(true);
			List<FlatVertex> vertslist = new List<FlatVertex>();
			List<Sector> highlightedsectors = new List<Sector>();
			List<Sector> highlightedtaggedsectors = new List<Sector>();

			// Highlighted slope
			if (highlightedslope != null)
			{
				SlopeVertexGroup svg = BuilderPlug.Me.GetSlopeVertexGroup(highlightedslope);

				// All sectors the slope applies to
				foreach (Sector s in svg.Sectors)
				{
					if (s != null && !s.IsDisposed)
					{
						vertslist.AddRange(s.FlatVertices);
						highlightedsectors.Add(s);
					}
				}

				overlaygeometry = vertslist.ToArray();

				// All sectors that are tagged because of 3D floors
				vertslist = new List<FlatVertex>();

				foreach (Sector s in svg.TaggedSectors)
				{
					if (s != null && !s.IsDisposed)
					{
						vertslist.AddRange(s.FlatVertices);
						highlightedtaggedsectors.Add(s);
					}
				}

				overlaytaggedgeometry = vertslist.ToArray();
			}
			else
			{
				overlaygeometry = new FlatVertex[0];
				overlaytaggedgeometry = new FlatVertex[0];
			}

			// Selected sectors
			vertslist = new List<FlatVertex>();

			foreach (Sector s in orderedselection)
				if(!highlightedsectors.Contains(s))
					vertslist.AddRange(s.FlatVertices);

			selectedsectorgeometry = vertslist.ToArray();
		}

		// This highlights a new item
		protected void HighlightSector(Sector s)
		{
			// Update display

			highlightedsector = s;
			/*
			if (renderer.StartPlotter(false))
			{
				// Undraw previous highlight
				if ((highlightedsector != null) && !highlightedsector.IsDisposed)
					renderer.PlotSector(highlightedsector);

				// Set new highlight
				highlightedsector = s;

				// Render highlighted item
				if ((highlightedsector != null) && !highlightedsector.IsDisposed)
					renderer.PlotSector(highlightedsector, General.Colors.Highlight);

				// Done
				renderer.Finish();
			}

			UpdateOverlay();
			renderer.Present();
			*/
			General.Interface.RedrawDisplay();

			// Show highlight info
			if ((highlightedsector != null) && !highlightedsector.IsDisposed)
				General.Interface.ShowSectorInfo(highlightedsector);
			else
				General.Interface.HideInfo();
		}

		// This selectes or deselects a sector
		protected void SelectSector(Sector s, bool selectstate)
		{
			bool selectionchanged = false;

			if (!s.IsDisposed)
			{
				// Select the sector?
				if (selectstate && !s.Selected)
				{
					s.Selected = true;
					selectionchanged = true;
				}
				// Deselect the sector?
				else if (!selectstate && s.Selected)
				{
					s.Selected = false;
					selectionchanged = true;
				}

				// Selection changed?
				if (selectionchanged)
				{
					// Make update lines selection
					foreach (Sidedef sd in s.Sidedefs)
					{
						bool front, back;
						if (sd.Line.Front != null) front = sd.Line.Front.Sector.Selected; else front = false;
						if (sd.Line.Back != null) back = sd.Line.Back.Sector.Selected; else back = false;
						sd.Line.Selected = front | back;
					}

					//mxd. Also (de)select things?
					if (General.Interface.AltState)
					{
						foreach (Thing t in General.Map.ThingsFilter.VisibleThings)
						{
							t.DetermineSector();
							if (t.Sector != s) continue;
							t.Selected = s.Selected;
						}
					}
				}
			}
		}

		public void ResetHighlightedSector()
		{
			HighlightSector(null);
		}
		
		// Selection
		protected override void OnSelectBegin()
		{
			// Item highlighted?
			if(highlightedslope != null)
			{
				// Flip selection
				highlightedslope.Selected = !highlightedslope.Selected;

				updateOverlaySurfaces();
				UpdateOverlay();
			}

			base.OnSelectBegin();
		}

		// End selection
		protected override void OnSelectEnd()
		{
			// Not ending from a multi-selection?
			if(!selecting)
			{
				// Item highlighted?
				if (highlightedslope != null)
				{
					updateOverlaySurfaces();
					UpdateOverlay();
				}

				if (highlightedsector != null)
				{
					if (!contextmenuclosing)
					{
						SelectSector(highlightedsector, !highlightedsector.Selected);

						updateOverlaySurfaces();
						General.Interface.RedrawDisplay();
					}
				}

				contextmenuclosing = false;
			}

			base.OnSelectEnd();
		}

		// Done editing
		protected override void OnEditEnd()
		{
			base.OnEditEnd();

			if (dragging) return;

			if (highlightedslope != null)
			{
				SlopeVertex sv = highlightedslope;

				List<SlopeVertex> vertices = GetSelectedSlopeVertices();

				if (!vertices.Contains(highlightedslope))
					vertices.Add(highlightedslope);

				SlopeVertexEditForm svef = new SlopeVertexEditForm();
				svef.Setup(vertices);

				DialogResult result = svef.ShowDialog((Form)General.Interface);

				if (result == DialogResult.OK)
				{
					General.Map.IsChanged = true;

					BuilderPlug.Me.UpdateSlopes();
				}

				highlightedslope = null;
			}
			else if(highlightedsector != null)
			{
				if (General.Map.Map.SelectedSectorsCount == 0)
				{
					BuilderPlug.Me.MenusForm.AddSectorsContextMenu.Tag = new List<Sector>() { highlightedsector };
				}
				else
				{
					BuilderPlug.Me.MenusForm.AddSectorsContextMenu.Tag = General.Map.Map.GetSelectedSectors(true).ToList();
				}

				BuilderPlug.Me.MenusForm.AddSectorsContextMenu.Show(Cursor.Position);
			}

			updateOverlaySurfaces();
			UpdateOverlay();

			General.Interface.RedrawDisplay();
		}

		//Build a list of the closest svs, that share the same distance away from the mouse cursor
		private List<SlopeVertex> GetVertexStack()
		{
			List<SlopeVertex> stack = new List<SlopeVertex>();
			double d, last = double.MaxValue;

			foreach(SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups) {
				foreach(SlopeVertex sv in svg.Vertices)
				{
					d = Vector2D.Distance(sv.Pos, mousemappos);
					if (d <= BuilderModes.BuilderPlug.Me.HighlightRange / renderer.Scale) {
						if (d > last)
							continue; //discard
						else if (d < last)
							stack.Clear();

						stack.Add(sv);
						last = d;
					}
				}
			}

			return stack;
		}

		// Mouse moves
		public override void OnMouseMove(MouseEventArgs e)
		{
			base.OnMouseMove(e);

			if (selectpressed && !editpressed && !selecting)
			{
				// Check if moved enough pixels for multiselect
				Vector2D delta = mousedownpos - mousepos;
				if ((Math.Abs(delta.x) > 2) || (Math.Abs(delta.y) > 2))
				{
					// Start multiselecting
					StartMultiSelection();
				}
			}
			else if(e.Button == MouseButtons.None)
			{
				SlopeVertex oldhighlight = highlightedslope;
				Sector oldhighlightedsector = highlightedsector;

				//select the closest handle within grabbing distance
				List<SlopeVertex> stack = GetVertexStack();
				if (stack.Count > 0) {
					SlopeVertex sv = stack[0];
					if (sv != highlightedslope)
					{
						//if the "closest" handle is the same distance away as the already highlighted handle, then do nothing
						if (highlightedslope == null || (Vector2D.Distance(sv.Pos, mousemappos) != Vector2D.Distance(highlightedslope.Pos, mousemappos))) {
							highlightedslope = sv;
						}
					}
				} else {
					//nothing within distance, so reset the highlight
					highlightedslope = null;
				}

				// If no slope vertex is highlighted, check if a sector should be
				if (highlightedslope == null)
				{
					// Find the nearest linedef within highlight range
					Linedef l = General.Map.Map.NearestLinedef(mousemappos);
					if (l != null)
					{
						// Check on which side of the linedef the mouse is
						double side = l.SideOfLine(mousemappos);
						if (side > 0)
						{
							// Is there a sidedef here?
							if (l.Back != null)
							{
								// Highlight if not the same
								if (l.Back.Sector != highlightedsector) HighlightSector(l.Back.Sector);
							}
							else
							{
								// Highlight nothing
								if (highlightedsector != null) HighlightSector(null);
							}
						}
						else
						{
							// Is there a sidedef here?
							if (l.Front != null)
							{
								// Highlight if not the same
								if (l.Front.Sector != highlightedsector) HighlightSector(l.Front.Sector);
							}
							else
							{
								// Highlight nothing
								if (highlightedsector != null) HighlightSector(null);
							}
						}
					}
				}
				else
				{
					HighlightSector(null);
				}

				if (highlightedslope != oldhighlight)
				{
					updateOverlaySurfaces();
					UpdateOverlay();
					General.Interface.RedrawDisplay();
				}
			}
			else if (dragging && highlightedslope != null)
			{
				Vector2D newpos = SnapToNearest(mousemappos);
				Vector2D offset = highlightedslope.Pos - newpos;

				foreach (SlopeVertex sl in GetSelectedSlopeVertices())
					sl.Pos -= offset;
				
				highlightedslope.Pos = newpos;

				General.Map.IsChanged = true;

				updateOverlaySurfaces();
				UpdateOverlay();
				General.Interface.RedrawDisplay();
			}
			else if (selecting)
			{
				UpdateOverlay();
				General.Interface.RedrawDisplay();
			}
		}

		// Mouse leaves
		public override void OnMouseLeave(EventArgs e)
		{
			base.OnMouseLeave(e);

			// Highlight nothing
			highlightedslope = null;
		}

		public override void OnKeyDown(KeyEventArgs e)
		{
			base.OnKeyDown(e);

			if (e.Alt)
			{
				General.Interface.RedrawDisplay();
			}
		}

		public override void OnKeyUp(KeyEventArgs e)
		{
			base.OnKeyUp(e);

			if (!e.Alt)
			{
				General.Interface.RedrawDisplay();
			}
		}

		// Mouse wants to drag
		protected override void OnDragStart(MouseEventArgs e)
		{
			base.OnDragStart(e);

			if (e.Button == MouseButtons.Right)
			{
				dragging = true;
				dragstartmappos = mousemappos;

				oldpositions = new List<Vector2D>();

				foreach(SlopeVertex sl in GetSelectedSlopeVertices())
					if(sl.Selected)
						oldpositions.Add(sl.Pos);


				if(highlightedslope != null)
					oldpositions.Add(highlightedslope.Pos);
			}
		}
		//retrieves the current mouse position on the grid, snapped as necessary
		private Vector2D SnapToNearest(Vector2D vm)
		{
			double vrange = 20f / renderer.Scale;
			bool snaptogrid = General.Interface.ShiftState ^ General.Interface.SnapToGrid; //allow temporary disable of snap by holding shift

			if (General.Interface.AutoMerge) //only snap to geometry if the option is enabled
			{
				// Try the nearest slope vertex
				SlopeVertex nh = NearestSlopeVertexSquareRange(vm, vrange);
				if (nh != null)
					return nh.Pos;

				// Try the nearest map vertex
				Vertex nv = General.Map.Map.NearestVertexSquareRange(vm, vrange);
				if (nv != null)
					return nv.Position;

				// Try the nearest linedef
				Linedef nl = General.Map.Map.NearestLinedefRange(vm, vrange);
				if (nl != null)
				{
					// Snap to grid?
					if (snaptogrid)
					{
						// Get grid intersection coordinates
						List<Vector2D> coords = nl.GetGridIntersections();

						// Find nearest grid intersection
						bool found = false;
						double found_distance = float.MaxValue;
						Vector2D found_coord = new Vector2D();
						foreach (Vector2D v in coords)
						{
							Vector2D delta = vm - v;
							if (delta.GetLengthSq() < found_distance)
							{
								found_distance = delta.GetLengthSq();
								found_coord = v;
								found = true;
							}
						}

						if (found)
							return found_coord;
					}
					else
					{
						return nl.NearestOnLine(vm);
					}
				}
			}

			//Just get the current mouse location instead
			if (snaptogrid)
				return General.Map.Grid.SnappedToGrid(vm);
			return vm;
		}

		/// <summary>This finds the thing closest to the specified position.</summary>
		public SlopeVertex NearestSlopeVertexSquareRange(Vector2D pos, double maxrange)
		{
			List<SlopeVertex> verts = GetUnSelectedSlopeVertices();
			if (highlightedslope != null)
				verts.Remove(highlightedslope);

			return NearestSlopeVertexSquareRange(verts, pos, maxrange);
		}

		/// <summary>This finds the slope vertex closest to the specified position.</summary>
		public SlopeVertex NearestSlopeVertexSquareRange(ICollection<SlopeVertex> selection, Vector2D pos, double maxrange)
		{
			RectangleF range = RectangleF.FromLTRB((float)(pos.x - maxrange), (float)(pos.y - maxrange), (float)(pos.x + maxrange), (float)(pos.y + maxrange));
			SlopeVertex closest = null;
			double distance = double.MaxValue;

			// Go for all vertices in selection
			foreach (SlopeVertex v in selection)
			{
				double px = v.Pos.x;
				double py = v.Pos.y;

				//mxd. Within range?
				if ((v.Pos.x < range.Left) || (v.Pos.x > range.Right)
					|| (v.Pos.y < range.Top) || (v.Pos.y > range.Bottom))
					continue;

				// Close than previous find?
				double d = Math.Abs(px - pos.x) + Math.Abs(py - pos.y);
				if (d < distance)
				{
					// This one is closer
					closest = v;
					distance = d;
				}
			}

			// Return result
			return closest;
		}

		// Mouse wants to drag
		protected override void OnDragStop(MouseEventArgs e)
		{
			base.OnDragStop(e);

			General.Map.UndoRedo.CreateUndo("Drag slope vertex");

			BuilderPlug.Me.StoreSlopeVertexGroupsInSector();
			General.Map.Map.Update();

			BuilderPlug.Me.UpdateSlopes();

			dragging = false;
		}


		// This is called wheh selection ends
		protected override void OnEndMultiSelection()
		{
			bool selectionvolume = ((Math.Abs(base.selectionrect.Width) > 0.1f) && (Math.Abs(base.selectionrect.Height) > 0.1f));

			if(BuilderPlug.Me.AutoClearSelection && !selectionvolume)
				General.Map.Map.ClearSelectedThings();

			if(selectionvolume)
			{
				if(General.Interface.ShiftState ^ BuilderPlug.Me.AdditiveSelect)
				{
					// Go for all slope vertices
					foreach (SlopeVertex sl in GetAllSlopeVertices())
					{
						sl.Selected |= ((sl.Pos.x >= selectionrect.Left) &&
										(sl.Pos.y >= selectionrect.Top) &&
										(sl.Pos.x <= selectionrect.Right) &&
										(sl.Pos.y <= selectionrect.Bottom));
					}
				}
				else
				{
					// Go for all slope vertices
					foreach (SlopeVertex sl in GetAllSlopeVertices())
					{
						sl.Selected |= ((sl.Pos.x >= selectionrect.Left) &&
										(sl.Pos.y >= selectionrect.Top) &&
										(sl.Pos.x <= selectionrect.Right) &&
										(sl.Pos.y <= selectionrect.Bottom));
					}
				}
			}
			
			base.OnEndMultiSelection();

			// Clear overlay
			if(renderer.StartOverlay(true)) renderer.Finish();

			// Redraw
			General.Interface.RedrawDisplay();
		}

		// This is called when the selection is updated
		protected override void OnUpdateMultiSelection()
		{
			base.OnUpdateMultiSelection();

			UpdateOverlay();
		}

		public override bool OnCopyBegin()
		{
			copyslopevertexgroups = new List<SlopeVertexGroup>();

			foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
			{
				bool copy = false;

				// Check if the current SVG has to be copied
				foreach (SlopeVertex sv in svg.Vertices)
				{
					if (sv.Selected)
					{
						copy = true;
						break;
					}
				}

				if (copy)
				{
					List<SlopeVertex> newsv = new List<SlopeVertex>();

					foreach (SlopeVertex sv in svg.Vertices)
						newsv.Add(new SlopeVertex(sv.Pos, sv.Z));

					// Use -1 for id, since a real id will be assigned when pasting
					copyslopevertexgroups.Add(new SlopeVertexGroup(-1, newsv));
				}
			}

			return true;
		}

		public override bool OnPasteBegin(PasteOptions options)
		{
			if (copyslopevertexgroups == null || copyslopevertexgroups.Count == 0)
				return false;

			// Unselect all slope vertices, so the pasted vertices can be selected
			foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
				svg.SelectVertices(false);

			double l = copyslopevertexgroups[0].Vertices[0].Pos.x;
			double r = copyslopevertexgroups[0].Vertices[0].Pos.x;
			double t = copyslopevertexgroups[0].Vertices[0].Pos.y;
			double b = copyslopevertexgroups[0].Vertices[0].Pos.y;

			// Find the outer dimensions of all SVGs to paste
			foreach (SlopeVertexGroup svg in copyslopevertexgroups)
			{
				foreach (SlopeVertex sv in svg.Vertices)
				{
					if (sv.Pos.x < l) l = sv.Pos.x;
					if (sv.Pos.x > r) r = sv.Pos.x;
					if (sv.Pos.y > t) t = sv.Pos.y;
					if (sv.Pos.y < b) b = sv.Pos.y;
				}
			}

			Vector2D center = new Vector2D(l + ((r - l) / 2), b + ((t - b) / 2));
			Vector2D diff = center - General.Map.Grid.SnappedToGrid(mousemappos);

			foreach (SlopeVertexGroup svg in copyslopevertexgroups)
			{
				int id;
				List<SlopeVertex> newsv = new List<SlopeVertex>();

				foreach (SlopeVertex sv in svg.Vertices)
				{
					newsv.Add(new SlopeVertex(new Vector2D(sv.Pos.x - diff.x, sv.Pos.y - diff.y), sv.Z));
				}

				SlopeVertexGroup newsvg = BuilderPlug.Me.AddSlopeVertexGroup(newsv, out id);
				newsvg.SelectVertices(true);
			}

			// Redraw the display, so that pasted SVGs are shown immediately
			General.Interface.RedrawDisplay();

			// Don't go into the standard process for pasting, so tell the core that
			// pasting should not proceed
			return false;
		}

		public List<SlopeVertex> GetSelectedSlopeVertices()
		{
			List<SlopeVertex> selected = new List<SlopeVertex>();

			foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
			{
				foreach (SlopeVertex sv in svg.Vertices)
				{
					if (sv.Selected)
						selected.Add(sv);
				}
			}

			return selected;
		}

		public List<SlopeVertex> GetUnSelectedSlopeVertices()
		{
			List<SlopeVertex> notselected = new List<SlopeVertex>();

			foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
			{
				foreach (SlopeVertex sv in svg.Vertices)
				{
					if (!sv.Selected)
						notselected.Add(sv);
				}
			}

			return notselected;
		}

		public List<SlopeVertex> GetAllSlopeVertices()
		{
			List<SlopeVertex> selected = new List<SlopeVertex>();

			foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
			{
				foreach (SlopeVertex sv in svg.Vertices)
				{
					selected.Add(sv);
				}
			}

			return selected;
		}

		public List<SlopeVertexGroup> GetSelectedSlopeVertexGroups()
		{
			List<SlopeVertexGroup> svgs = new List<SlopeVertexGroup>();

			foreach (SlopeVertex sv in GetSelectedSlopeVertices())
			{
				SlopeVertexGroup svg = BuilderPlug.Me.GetSlopeVertexGroup(sv);

				if (!svgs.Contains(svg))
					svgs.Add(svg);
			}

			return svgs;
		}

		#endregion

		#region ================== Actions

		[BeginAction("drawfloorslope")]
		public void DrawFloorSlope()
		{
			BuilderPlug.Me.MenusForm.CeilingSlope.Checked = false;
			BuilderPlug.Me.MenusForm.FloorSlope.Checked = true;
			BuilderPlug.Me.MenusForm.FloorAndCeilingSlope.Checked = false;

			General.Interface.DisplayStatus(StatusType.Info, "Applying drawn slope to floor");
		}

		[BeginAction("drawceilingslope")]
		public void DrawCeilingSlope()
		{
			BuilderPlug.Me.MenusForm.CeilingSlope.Checked = true;
			BuilderPlug.Me.MenusForm.FloorSlope.Checked = false;
			BuilderPlug.Me.MenusForm.FloorAndCeilingSlope.Checked = false;

			General.Interface.DisplayStatus(StatusType.Info, "Applying drawn slope to ceiling");
		}

		[BeginAction("drawfloorandceilingslope")]
		public void DrawFloorAndCeilingSlope()
		{
			BuilderPlug.Me.MenusForm.CeilingSlope.Checked = false;
			BuilderPlug.Me.MenusForm.FloorSlope.Checked = false;
			BuilderPlug.Me.MenusForm.FloorAndCeilingSlope.Checked = true;

			General.Interface.DisplayStatus(StatusType.Info, "Applying drawn slope to floor and ceiling");
		}

		[BeginAction("threedflipslope")]
		public void FlipSlope()
		{
			if (highlightedslope == null)
				return;

			MessageBox.Show("Flipping temporarily removed");

			/*
			if (highlightedslope.IsOrigin)
			{
				origin = highlightedslope.ThreeDFloor.Slope.Origin + highlightedslope.ThreeDFloor.Slope.Direction;
				direction = highlightedslope.ThreeDFloor.Slope.Direction * (-1);
			}
			else 
			{
				origin = highlightedslope.ThreeDFloor.Slope.Origin + highlightedslope.ThreeDFloor.Slope.Direction;
				direction = highlightedslope.ThreeDFloor.Slope.Direction * (-1);
			}

			highlightedslope.ThreeDFloor.Slope.Origin = origin;
			highlightedslope.ThreeDFloor.Slope.Direction = direction;

			highlightedslope.ThreeDFloor.Rebuild = true;

			BuilderPlug.ProcessThreeDFloors(new List<ThreeDFloor> { highlightedslope.ThreeDFloor }, highlightedslope.ThreeDFloor.TaggedSectors);

			UpdateSlopeObjects();

			// Redraw
			General.Interface.RedrawDisplay();
			*/
		}

		// This clears the selection
		[BeginAction("clearselection", BaseAction = true)]
		public void ClearSelection()
		{
			int numselected = 0;
			// Clear selection
			foreach (SlopeVertexGroup svg in BuilderPlug.Me.SlopeVertexGroups)
			{
				foreach (SlopeVertex sv in svg.Vertices)
				{
					if (sv.Selected)
					{
						sv.Selected = false;
						numselected++;
					}
					
				}
			}

			// Clear selected sectors when no SVGs are selected
			if (numselected == 0)
				General.Map.Map.ClearAllSelected();
			
			// Redraw
			updateOverlaySurfaces();
			UpdateOverlay();
			General.Interface.RedrawDisplay();
		}
		

		[BeginAction("deleteitem", BaseAction = true)]
		public void DeleteItem()
		{
			// Make list of selected things
			List<SlopeVertex> selected = new List<SlopeVertex>(GetSelectedSlopeVertices());

			if(highlightedslope != null)
			{
				selected.Add(highlightedslope);
			}
			
			// Anything to do?
			if(selected.Count > 0)
			{
				List<SlopeVertexGroup> groups = new List<SlopeVertexGroup>();

				General.Map.UndoRedo.CreateUndo("Delete slope");

				foreach (SlopeVertex sv in selected)
				{
					SlopeVertexGroup svg = BuilderPlug.Me.GetSlopeVertexGroup(sv);

					if (!groups.Contains(svg))
						groups.Add(svg);
				}

				foreach (SlopeVertexGroup svg in groups)
				{
                    svg.RemovePlanes();
					svg.RemoveUndoRedoUDMFFields(BuilderPlug.Me.SlopeDataSector);

					BuilderPlug.Me.SlopeVertexGroups.Remove(svg);
				}				

				General.Map.IsChanged = true;

				// Invoke a new mousemove so that the highlighted item updates
				MouseEventArgs e = new MouseEventArgs(MouseButtons.None, 0, (int)mousepos.x, (int)mousepos.y, 0);
				OnMouseMove(e);

				// Redraw screen
				General.Interface.RedrawDisplay();
			}
		}


		[BeginAction("cyclehighlighted3dfloorup")]
		public void CycleHighlighted3DFloorUp()
		{
			if (highlightedslope == null)
				return;
			List<SlopeVertex> stack = GetVertexStack();
			if (stack.Count == 0)
				return;

			int idx = stack.IndexOf(highlightedslope) + 1;
			if (idx >= stack.Count)
				idx = 0;
			highlightedslope = stack[idx];

			updateOverlaySurfaces();
			UpdateOverlay();
			General.Interface.RedrawDisplay();
		}

		[BeginAction("cyclehighlighted3dfloordown")]
		public void CycleHighlighted3DFloorDown()
		{
			if (highlightedslope == null)
				return;
			List<SlopeVertex> stack = GetVertexStack();
			if (stack.Count == 0)
				return;

			int idx = stack.IndexOf(highlightedslope) - 1;
			if (idx < 0)
				idx = stack.Count - 1;
			highlightedslope = stack[idx];

			updateOverlaySurfaces();
			UpdateOverlay();
			General.Interface.RedrawDisplay();
		}
		
		#endregion
	}
}
