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

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Editing;
using CodeImp.DoomBuilder.Windows;
using System.Drawing;

#endregion

//JBR Parallel Linedef mode
namespace CodeImp.DoomBuilder.BuilderModes
{
	[EditMode(DisplayName = "Parallel Linedef",
			  AllowCopyPaste = false,
			  Volatile = true)]
	public sealed class ParallelLinedefMode : BaseClassicMode
	{
		#region ================== Constants

		private const float LINE_THICKNESS = 0.6f;
		private const int LINKED_LINEDEFS = 0;
		private const int UNLINKED_LINEDEFS = 1;
		private const int LINEDEFS = 1;
		private const int VERTICES = 2;
		private const int THINGS = 3;

		#endregion

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

		private EditMode basemode;
		private ICollection<Linedef> selected;
		private TracksTracer trackstracer;
		private bool clearlinedefs;

		#endregion

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

		// Just keep the base mode button checked
		public override string EditModeButtonName { get { return General.Editing.PreviousStableMode.Name; } }

		#endregion

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

		// Constructor
		public ParallelLinedefMode(EditMode basemode)
		{
			this.basemode = basemode;
		}

		// Disposer
		public override void Dispose()
		{
			// Not already disposed?
			if (!isdisposed)
			{
				// Clean up

				// Done
				base.Dispose();
			}
		}

		#endregion

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

		public List<Linedef> GetSelectedLinedefFromVertex(Vertex v)
		{
			List<Linedef> list = new List<Linedef>();
			foreach (Linedef ld in v.Linedefs)
			{
				if (ld.Selected) list.Add(ld);
			}
			return list;
		}

		#endregion

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

		public override void OnHelp()
		{
			General.ShowHelp("e_parallellinedef.html");
		}

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

			// Clear selection that is not the current mode
			if (clearlinedefs) General.Map.Map.ClearSelectedLinedefs();

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

		// Mode engages
		public override void OnEngage()
		{
			base.OnEngage();
			renderer.SetPresentation(Presentation.Standard);

			// Make collections by selection
			if (basemode is VerticesMode)
			{
				selected = new List<Linedef>();
				ICollection<Vertex> selectedvertices = General.Map.Map.GetSelectedVertices(true);
				foreach (Vertex v in selectedvertices)
				{
					foreach (Linedef ld in v.Linedefs)
					{
						if (ld.Start.Selected && ld.End.Selected)
						{
							ld.Selected = true;
							if (!selected.Contains(ld)) selected.Add(ld);
						}
					}
				}
				clearlinedefs = true;
			}
			if (basemode is LinedefsMode)
			{
				selected = General.Map.Map.GetSelectedLinedefs(true);
			}
			if (basemode is SectorsMode)
			{
				selected = new List<Linedef>();
				ICollection<Sector> selectedsectors = General.Map.Map.GetSelectedSectors(true);
				foreach (Sector sc in selectedsectors)
				{
					foreach (Sidedef sd in sc.Sidedefs)
					{
						sd.Line.Selected = true;
						if (!selected.Contains(sd.Line)) selected.Add(sd.Line);
					}
				}
				clearlinedefs = true;
			}

			// Create the tracks tracer
			trackstracer = new TracksTracer(selected);

			// Check if tracing was valid
			if (!trackstracer.IsValid)
			{
				DialogResult dr = MessageBox.Show("Detected intersections while tracing!\nMake sure you're selecting vertices with maximum of 2 linedefs connected to them.\nResults may not be desirable, continue anyway?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
				if (dr != DialogResult.Yes)
				{
					OnCancel();
					return;
				}
			}
			BuilderPlug.Me.ParallelLinedefForm.SetNumOpenPaths(trackstracer.OpenPaths.Count);
			BuilderPlug.Me.ParallelLinedefForm.SetNumClosePaths(trackstracer.ClosePaths.Count);

			// Show toolbox window
			BuilderPlug.Me.ParallelLinedefForm.Show((Form)General.Interface);
		}

		// Disenagaging
		public override void OnDisengage()
		{
			base.OnDisengage();

			// Hide toolbox window
			BuilderPlug.Me.ParallelLinedefForm.Hide();
		}

		// This applies the curves and returns to the base mode
		public override void OnAccept()
		{
			// Create undo
			General.Map.UndoRedo.CreateUndo("Parallel (Linedef)");

			int createas = BuilderPlug.Me.ParallelLinedefForm.CreateAs;
			float distance = BuilderPlug.Me.ParallelLinedefForm.Distance;
			bool closeopenpath = BuilderPlug.Me.ParallelLinedefForm.CloseOpenPath;
			bool backwards = BuilderPlug.Me.ParallelLinedefForm.Backwards;
			ThingBrowser2Form.Result thingresult = new ThingBrowser2Form.Result();

			// Ask for thing type...
			if (createas == THINGS)
			{
				BuilderPlug.Me.ParallelLinedefForm.Hide();

				// Pick the thing to add...
				thingresult = ThingBrowser2Form.BrowseThing(BuilderPlug.Me.ParallelLinedefForm);
				if (!thingresult.OK)
				{
					General.Map.UndoRedo.WithdrawUndo();
					General.Map.UndoRedo.WithdrawUndo();
					General.Interface.DisplayStatus(StatusType.Warning, "Operation aborted.");
					return;
				}

				BuilderPlug.Me.ParallelLinedefForm.Show();
				BuilderPlug.Me.ParallelLinedefForm.Activate();
			}

			// Go for all selected lines
			foreach (List<Vector2D> path in trackstracer.OpenPaths)
			{
				List<Vector2D> parallelpath = TracksTracer.ParallelizeOpenPath(path, distance, backwards);
				if (createas == LINKED_LINEDEFS)
				{
					List<DrawnVertex> dvl = new List<DrawnVertex>();
					if (closeopenpath) TracksTracer.AddPointToDrawnVertex(dvl, path[0], true, true);
					TracksTracer.AddPathToDrawnVertex(dvl, parallelpath, true, true);
					if (closeopenpath) TracksTracer.AddPointToDrawnVertex(dvl, path[path.Count - 1], true, true);
					if (!Tools.DrawLines(dvl, true, BuilderPlug.Me.AutoAlignTextureOffsetsOnCreate))
					{
						// Drawing failed
						// NOTE: I have to call this twice, because the first time only cancels this volatile mode
						General.Map.UndoRedo.WithdrawUndo();
						General.Map.UndoRedo.WithdrawUndo();
						return;
					}
				}
				else if (createas == UNLINKED_LINEDEFS)
				{
					// Unlinked linedef
					Vertex oldv = General.Map.Map.CreateVertex(parallelpath[0]);
					for (int i = 1; i < parallelpath.Count; i++)
					{
						Vertex newv = General.Map.Map.CreateVertex(parallelpath[i]);
						General.Map.Map.CreateLinedef(oldv, newv);
						oldv = newv;
					}
					if (closeopenpath)
					{
						Vertex vtf = General.Map.Map.CreateVertex(path[0]);
						Vertex vtl = General.Map.Map.CreateVertex(path[path.Count - 1]);
						Vertex vpf = General.Map.Map.CreateVertex(parallelpath[0]);
						Vertex vpl = General.Map.Map.CreateVertex(parallelpath[parallelpath.Count - 1]);
						General.Map.Map.CreateLinedef(vtf, vpf);
						General.Map.Map.CreateLinedef(vpl, vtl);
					}
				}
				else if (createas == VERTICES)
				{
					// Vertex
					foreach (Vector2D pos in parallelpath)
					{
						General.Map.Map.CreateVertex(pos);
					}
				}
				else if (createas == THINGS)
				{
					foreach (Vector2D pos in parallelpath)
					{
						Thing tp = General.Map.Map.CreateThing();
						if (tp != null)
						{
							General.Settings.ApplyDefaultThingSettings(tp);
							thingresult.Apply(tp);
							tp.Move(pos);
							tp.UpdateConfiguration();
						}
					}
				}
			}
			foreach (List<Vector2D> path in trackstracer.ClosePaths)
			{
				List<Vector2D> parallelpath = TracksTracer.ParallelizeClosePath(path, distance, backwards);
				if (createas == LINKED_LINEDEFS)
				{
					List<DrawnVertex> dvl = new List<DrawnVertex>();
					TracksTracer.AddPathToDrawnVertex(dvl, parallelpath, true, true);
					if (!Tools.DrawLines(dvl, true, BuilderPlug.Me.AutoAlignTextureOffsetsOnCreate))
					{
						// Drawing failed
						// NOTE: I have to call this twice, because the first time only cancels this volatile mode
						General.Map.UndoRedo.WithdrawUndo();
						General.Map.UndoRedo.WithdrawUndo();
						return;
					}
				}
				else if (createas == UNLINKED_LINEDEFS)
				{
					// Unlinked linedef
					Vertex oldv = General.Map.Map.CreateVertex(parallelpath[0]);
					for (int i = 1; i < parallelpath.Count; i++)
					{
						Vertex newv = General.Map.Map.CreateVertex(parallelpath[i]);
						General.Map.Map.CreateLinedef(oldv, newv);
						oldv = newv;
					}
				}
				else if (createas == VERTICES)
				{
					// Vertex
					for (int i = 0; i < parallelpath.Count - 1; i++)
					{
						General.Map.Map.CreateVertex(parallelpath[i]);
					}
				}
				else if (createas == THINGS)
				{
					// Thing
					for (int i = 0; i < parallelpath.Count - 1; i++)
					{
						Thing tp = General.Map.Map.CreateThing();
						if (tp != null)
						{
							General.Settings.ApplyDefaultThingSettings(tp);
							thingresult.Apply(tp);
							tp.Move(parallelpath[i]);
							tp.UpdateConfiguration();
						}
					}
				}
			}

			// Announce how many we created
			int num = trackstracer.OpenPaths.Count + trackstracer.ClosePaths.Count;
			if (num == 0)
			{
				General.Interface.DisplayStatus(StatusType.Warning, "No parallel tracks created.");
				General.Map.UndoRedo.WithdrawUndo();
				General.Map.UndoRedo.WithdrawUndo();
				return;
			}
			if (num > 1)
				General.Interface.DisplayStatus(StatusType.Action, "Created " + num.ToString() + " parallel tracks.");
			else
				General.Interface.DisplayStatus(StatusType.Action, "Created a parallel track.");

			if (createas == THINGS)
			{
				//Update Things filter
				General.Map.ThingsFilter.Update();
			}

			// Snap to map format accuracy
			General.Map.Map.SnapAllToAccuracy();

			// Clear selection
			General.Map.Map.ClearAllSelected();

			// Update cached values
			General.Map.Map.Update();

			// Edit new sectors?
			List<Sector> newsectors = General.Map.Map.GetMarkedSectors(true);
			if (BuilderPlug.Me.EditNewSector && (newsectors.Count > 0))
				General.Interface.ShowEditSectors(newsectors);

			// Update the used textures
			General.Map.Data.UpdateUsedTextures();

			//mxd
			General.Map.Renderer2D.UpdateExtraFloorFlag();

			// Map is changed
			General.Map.IsChanged = true;

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

		// Redrawing display
		public override void OnRedrawDisplay()
		{
			renderer.RedrawSurface();
			float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale;

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

			// Render things
			if (renderer.StartThings(true))
			{
				renderer.RenderThingSet(General.Map.Map.Things, Presentation.THINGS_ALPHA);
				renderer.RenderNiGHTSPath();
				renderer.Finish();
			}

			int createas = BuilderPlug.Me.ParallelLinedefForm.CreateAs;
			float distance = BuilderPlug.Me.ParallelLinedefForm.Distance;
			bool closeopenpath = BuilderPlug.Me.ParallelLinedefForm.CloseOpenPath;
			bool backwards = BuilderPlug.Me.ParallelLinedefForm.Backwards;

			// Render overlay
			bool renderlines = (createas <= LINEDEFS);
			if (renderer.StartOverlay(true))
			{
				foreach (List<Vector2D> path in trackstracer.OpenPaths)
				{
					// Selected path in info color
					for (int i = 1; i < path.Count; i++)
						renderer.RenderLine(path[i - 1], path[i], LINE_THICKNESS, General.Colors.InfoLine, true);
					renderer.RenderRectangleFilled(new RectangleF(path[0].x - vsize, path[0].y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine, true);
					renderer.RenderRectangleFilled(new RectangleF(path[path.Count - 1].x - vsize, path[path.Count - 1].y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine, true);

					// Parallel path
					List<Vector2D> parallelpath = TracksTracer.ParallelizeOpenPath(path, distance, backwards);
					renderer.RenderRectangleFilled(new RectangleF(parallelpath[0].x - vsize, parallelpath[0].y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.Highlight, true);
					for (int i = 1; i < parallelpath.Count; i++)
					{
						if (renderlines) renderer.RenderLine(parallelpath[i - 1], parallelpath[i], LINE_THICKNESS, General.Colors.Highlight, true);
						renderer.RenderRectangleFilled(new RectangleF(parallelpath[i].x - vsize, parallelpath[i].y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.Highlight, true);
					}
					if (closeopenpath && renderlines)
					{
						renderer.RenderLine(path[0], parallelpath[0], LINE_THICKNESS, General.Colors.Highlight, true);
						renderer.RenderLine(path[path.Count - 1], parallelpath[parallelpath.Count - 1], LINE_THICKNESS, General.Colors.Highlight, true);
					}
				}
				foreach (List<Vector2D> path in trackstracer.ClosePaths)
				{
					// Selected path in info color
					for (int i = 1; i < path.Count; i++)
						renderer.RenderLine(path[i - 1], path[i], LINE_THICKNESS, General.Colors.InfoLine, true);
					renderer.RenderRectangleFilled(new RectangleF(path[0].x - vsize, path[0].y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine, true);
					renderer.RenderRectangleFilled(new RectangleF(path[path.Count - 1].x - vsize, path[path.Count - 1].y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.InfoLine, true);

					// Parallel path
					List<Vector2D> parallelpath = TracksTracer.ParallelizeClosePath(path, distance, backwards);
					renderer.RenderRectangleFilled(new RectangleF(parallelpath[0].x - vsize, parallelpath[0].y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.Highlight, true);
					for (int i = 1; i < parallelpath.Count; i++)
					{
						if (renderlines) renderer.RenderLine(parallelpath[i - 1], parallelpath[i], LINE_THICKNESS, General.Colors.Highlight, true);
						renderer.RenderRectangleFilled(new RectangleF(parallelpath[i].x - vsize, parallelpath[i].y - vsize, vsize * 2.0f, vsize * 2.0f), General.Colors.Highlight, true);
					}
				}
				renderer.Finish();
			}

			renderer.Present();
		}

		#endregion
	}
}
