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

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using CodeImp.DoomBuilder.Config;
using CodeImp.DoomBuilder.Editing;
using CodeImp.DoomBuilder.Rendering;
using CodeImp.DoomBuilder.Geometry;
using CodeImp.DoomBuilder.Actions;
using CodeImp.DoomBuilder.Windows;
using CodeImp.DoomBuilder.Map;
using CodeImp.DoomBuilder.BuilderModes.Interface;

#endregion

namespace CodeImp.DoomBuilder.BuilderModes.ClassicModes 
{
	[EditMode(DisplayName = "Bridge Mode",
			  SwitchAction = "bridgemode",
			  ButtonImage = "BridgeMode.png",
			  ButtonOrder = 2,
			  ButtonGroup = "002_modify",
			  AllowCopyPaste = false,
			  Volatile = true,
			  Optional = false)]

	public class BridgeMode : BaseClassicMode
	{
		#region ================== Constants

		private const float GRIP_SIZE = 9.0f;
		private const float LINE_THICKNESS = 0.8f;

		internal const int MAX_SUBDIVISIONS = 32;
		internal const int MIN_SUBDIVISIONS = 0;

		#endregion

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

		private Vector2D[] pointGroup1;
		private Vector2D[] pointGroup2;

		private SectorProperties[] sectorProps1;
		private SectorProperties[] sectorProps2;

		private double[] relLenGroup1;
		private double[] relLenGroup2;

		private ControlHandle[] controlHandles;
		private int curControlHandle = -1;

		private PixelColor handleColor;

		private List<Vector2D[]> curves;
		private int segmentsCount;

		// Options
		private bool snaptogrid; // SHIFT to toggle

		//tools form
		private BridgeModeForm form;

		#endregion

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

		public BridgeMode() 
		{
			// We have no destructor
			GC.SuppressFinalize(this);
		}

		#endregion

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

		// Engaging
		public override void OnEngage() 
		{
			base.OnEngage();

			renderer.SetPresentation(Presentation.Standard);
			
			//check selection
			ICollection<Linedef> selection = General.Map.Map.GetSelectedLinedefs(true);

			List<Line> lines = new List<Line>();
			foreach(Linedef ld in selection) 
			{
				Line l = new Line(ld);
				lines.Add(l);
			}

			//do we have valid selection?
			if(!Setup(lines)) 
			{
				FinishDraw();
				return;
			}

			//show form
			form = new BridgeModeForm();
			form.OnCancelClick += form_OnCancelClick;
			form.OnOkClick += form_OnOkClick;
			form.OnFlipClick += form_OnFlipClick;
			form.OnSubdivisionChanged += form_OnSubdivisionChanged;
			form.Show(General.Interface);
			General.Interface.FocusDisplay();

			handleColor = General.Colors.BrightColors[new Random().Next(General.Colors.BrightColors.Length - 1)];
			Update();
		}

		// When select button is pressed
		protected override void OnSelectBegin() 
		{
			base.OnSelectBegin();

			//check if control handle is selected
			for(int i = 0; i < 4; i++) 
			{
				if(mousemappos.x <= controlHandles[i].Position.x + GRIP_SIZE 
					&& mousemappos.x >= controlHandles[i].Position.x - GRIP_SIZE 
					&& mousemappos.y <= controlHandles[i].Position.y + GRIP_SIZE 
					&& mousemappos.y >= controlHandles[i].Position.y - GRIP_SIZE) 
				{
					curControlHandle = i;
					General.Interface.SetCursor(Cursors.Cross);
					return;
				}
			}
			curControlHandle = -1;
		}

		// When select button is released
		protected override void OnSelectEnd() 
		{
			base.OnSelectEnd();
			General.Interface.SetCursor(Cursors.Default);
			curControlHandle = -1;
		}

		// Mouse moves
		public override void OnMouseMove(MouseEventArgs e) 
		{
			base.OnMouseMove(e);
			if(panning) return; //mxd. Skip all this jass while panning

			if(curControlHandle != -1) 
			{
				ControlHandle handle = controlHandles[curControlHandle];
				
				handle.Position = (snaptogrid ? General.Map.Grid.SnappedToGrid(mousemappos) : mousemappos);

				if(form.MirrorMode) 
				{
					Vector2D pos = handle.RelativePosition;
					//handle angle
					double angle = Math.Atan2(-pos.y, -pos.x) + Angle2D.PIHALF;
					//angle of line, connecting handles ControlledPoints
					double dirAngle = -Math.Atan2(handle.Pair.ControlledPoint.y - handle.ControlledPoint.y, handle.Pair.ControlledPoint.x - handle.ControlledPoint.x);
					double length = Math.Sqrt(Math.Pow(Math.Abs(pos.x), 2.0) + Math.Pow(Math.Abs(pos.y), 2.0));
					double mirroredAngle = angle + dirAngle * 2.0;

					handle.Pair.RelativePosition = new Vector2D(Math.Sin(mirroredAngle) * length, Math.Cos(mirroredAngle) * length);
				} 
				else if(form.CopyMode) 
				{
					handle.Pair.RelativePosition = handle.RelativePosition;
				}    
				Update();
			}
		}

		// Accepted
		public override void OnAccept() 
		{
			Cursor.Current = Cursors.AppStarting;
			General.Settings.FindDefaultDrawSettings();

			//get vertices
			List<List<Vector2D[]>> shapes = GetShapes();
			List<List<List<DrawnVertex>>> drawShapes = new List<List<List<DrawnVertex>>>();

			//set stitch range
			float stitchrange = BuilderPlug.Me.StitchRange;
			BuilderPlug.Me.StitchRange = 0.1f;

			for(int i = 0; i < shapes.Count; i++) 
			{
				List<List<DrawnVertex>> shapesRow = new List<List<DrawnVertex>>();
				for(int c = 0; c < shapes[i].Count; c++) 
				{
					List<DrawnVertex> points = new List<DrawnVertex>();
					for(int p = 0; p < shapes[i][c].Length; p++)
						points.Add(DrawGeometryMode.GetCurrentPosition(shapes[i][c][p], true, false, false, false, renderer, points));
					shapesRow.Add(points);
				}
				drawShapes.Add(shapesRow);
			}

			//restore stitch range
			BuilderPlug.Me.StitchRange = stitchrange;

			//draw lines
			if(drawShapes.Count > 0) 
			{
				// Make undo for the draw
				General.Map.UndoRedo.CreateUndo("Bridge ("+form.Subdivisions+" subdivisions)");

				List<List<SectorProperties>> sectorProps = new List<List<SectorProperties>>();
				List<List<HashSet<Sector>>> newSectors = new List<List<HashSet<Sector>>>();
				
				//create sector properties collection
				//sector row
				for(int i = 0; i < drawShapes.Count; i++) 
				{
					sectorProps.Add(new List<SectorProperties>());
					
					//sector in row
					for(int c = 0; c < drawShapes[i].Count; c++)
						sectorProps[i].Add(GetSectorProperties(i, c));
				}

				// Make the drawing
				//sector row
				for(int i = 0; i < drawShapes.Count; i++) 
				{
					newSectors.Add(new List<HashSet<Sector>>());

					//sector in row
					for(int c = 0; c < drawShapes[i].Count; c++) 
					{
						if(!Tools.DrawLines(drawShapes[i][c], false, true)) 
						{
							// Drawing failed
							// NOTE: I have to call this twice, because the first time only cancels this volatile mode
							General.Interface.DisplayStatus(StatusType.Warning, "Failed to create a Bezier Path...");
							General.Map.UndoRedo.WithdrawUndo();
							General.Map.UndoRedo.WithdrawUndo();
							return;
						}

						HashSet<Sector> newsectors = General.Map.Map.GetUnselectedSectorsFromLinedefs(General.Map.Map.GetMarkedLinedefs(true));
						newSectors[i].Add(newsectors);

						//set floor/ceiling heights and brightness
						foreach(Sector s in newsectors)
						{
							SectorProperties sp = sectorProps[i][c];
							s.Brightness = sp.Brightness;
							s.FloorHeight = sp.FloorHeight;
							s.CeilHeight = (sp.CeilingHeight < sp.FloorHeight ? sp.FloorHeight + 8 : sp.CeilingHeight);
						}
					}
				}

				//apply textures
				//sector row
				for(int i = 0; i < newSectors.Count; i++) 
				{
					//sector in row
					for(int c = 0; c < newSectors[i].Count; c++) 
					{
						foreach(Sector s in newSectors[i][c]) 
						{
							foreach(Sidedef sd in s.Sidedefs)
							{
								if(sd.LowRequired())
									sd.SetTextureLow(sectorProps[i][c].LowTexture);
								if(sd.HighRequired())
									sd.SetTextureHigh(sectorProps[i][c].HighTexture);    
							}
						}
					}
				}

				//apply textures to front/back sides of shape
				//sector row
				for(int i = 0; i < newSectors.Count; i++) 
				{
					//first/last sector in row
					for(int c = 0; c < newSectors[i].Count; c += newSectors[i].Count-1) 
					{
						foreach(Sector s in newSectors[i][c]) 
						{
							foreach(Sidedef sd in s.Sidedefs) 
							{
								if(sd.Other != null) 
								{
									if(sd.Other.LowRequired() && sd.Other.LowTexture == "-")
										sd.Other.SetTextureLow(sectorProps[i][c].LowTexture);
									if(sd.Other.HighRequired() && sd.Other.HighTexture == "-")
										sd.Other.SetTextureHigh(sectorProps[i][c].HighTexture);
								}
							}
						}
					}
				}

				General.Interface.DisplayStatus(StatusType.Action, "Created a Bridge with " + form.Subdivisions + " subdivisions.");

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

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

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

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

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

			//close form
			if(form != null) form.Close();

			// Done
			Cursor.Current = Cursors.Default;

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

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

			//close form
			if(form != null) form.Dispose();

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

		// When a key is released
		public override void OnKeyUp(KeyEventArgs e) 
		{
			base.OnKeyUp(e);
			if(snaptogrid != (General.Interface.ShiftState ^ General.Interface.SnapToGrid)) Update();
		}

		// When a key is pressed
		public override void OnKeyDown(KeyEventArgs e) 
		{
			base.OnKeyDown(e);
			if(snaptogrid != (General.Interface.ShiftState ^ General.Interface.SnapToGrid)) Update();
		}

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

			// 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, General.Settings.ActiveThingsAlpha);
				renderer.RenderSRB2Extras();
				renderer.Finish();
			}

			// Normal update
			Update();
		}

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

		#endregion

		#region ================== Setup/Update/Utility

		//this checks if initial data is valid
		private bool Setup(List<Line> lines) 
		{
			if(!SetupPointGroups(lines)) return false;

			//setup control handles
			Vector2D center1 = CurveTools.GetPointOnLine(pointGroup1[0], pointGroup1[segmentsCount - 1], 0.5f);
			Vector2D center2 = CurveTools.GetPointOnLine(pointGroup2[0], pointGroup2[segmentsCount - 1], 0.5f);

			Vector2D loc1 = GetHandleLocation(pointGroup1[0], pointGroup1[segmentsCount - 1], center2);
			Vector2D loc2 = GetHandleLocation(pointGroup2[0], pointGroup2[segmentsCount - 1], center1);

			ControlHandle ch1 = new ControlHandle {ControlledPoint = pointGroup1[0], RelativePosition = loc1};
			ControlHandle ch2 = new ControlHandle {ControlledPoint = pointGroup2[0], RelativePosition = loc2};
			ControlHandle ch3 = new ControlHandle {ControlledPoint = pointGroup1[segmentsCount - 1], RelativePosition = loc1};
			ControlHandle ch4 = new ControlHandle {ControlledPoint = pointGroup2[segmentsCount - 1], RelativePosition = loc2};

			ch1.Pair = ch3;
			ch2.Pair = ch4;
			ch3.Pair = ch1;
			ch4.Pair = ch2;

			controlHandles = new[] {ch1, ch2, ch3, ch4};

			//setup relative segments lengths
			relLenGroup1 = GetRelativeLengths(pointGroup1);
			relLenGroup2 = GetRelativeLengths(pointGroup2);

			return true;
		}

		private void Update() 
		{
			if(renderer.StartOverlay(true)) 
			{
				snaptogrid = General.Interface.ShiftState ^ General.Interface.SnapToGrid;
				
				PixelColor linesColor = snaptogrid ? General.Colors.Selection : General.Colors.Highlight;
				
				//draw curves
				curves = new List<Vector2D[]>();

				for(int i = 0; i < segmentsCount; i++) 
				{
					Vector2D cp1 = CurveTools.GetPointOnLine(controlHandles[0].Position, controlHandles[2].Position, relLenGroup1[i]);
					Vector2D cp2 = CurveTools.GetPointOnLine(controlHandles[1].Position, controlHandles[3].Position, relLenGroup2[i]);
					curves.Add(CurveTools.GetCubicCurve(pointGroup1[i], pointGroup2[i], cp1, cp2, form.Subdivisions));

					for(int c = 1; c < curves[i].Length; c++)
						renderer.RenderLine(curves[i][c - 1], curves[i][c], LINE_THICKNESS, linesColor, true);
				}

				//draw connecting lines
				if(form.Subdivisions > 1) 
				{
					for(int i = 1; i < segmentsCount; i++) 
					{
						for(int c = 1; c < form.Subdivisions; c++) 
						{
							renderer.RenderLine(curves[i-1][c], curves[i][c], LINE_THICKNESS, linesColor, true);
						}
					}
				}

				//draw vertices
				float vsize = (renderer.VertexSize + 1.0f) / renderer.Scale;

				foreach(Vector2D[] points in curves)
				{
					for(int i = 1; i < points.Length - 1; i++ ) 
					{
						renderer.RenderRectangleFilled(new RectangleF((float)(points[i].x - vsize), (float)(points[i].y - vsize), vsize * 2.0f, vsize * 2.0f), linesColor, true);
					}
				}

				//draw handle lines
				renderer.RenderLine(pointGroup1[0], controlHandles[0].Position, LINE_THICKNESS, handleColor, true);
				renderer.RenderLine(pointGroup2[0], controlHandles[1].Position, LINE_THICKNESS, handleColor, true);
				renderer.RenderLine(pointGroup1[segmentsCount - 1], controlHandles[2].Position, LINE_THICKNESS, handleColor, true);
				renderer.RenderLine(pointGroup2[segmentsCount - 1], controlHandles[3].Position, LINE_THICKNESS, handleColor, true);

				//draw handles
				float gripsize = GRIP_SIZE / renderer.Scale;

				for(int i = 0; i < 4; i++) 
				{
					RectangleF handleRect = new RectangleF((float)(controlHandles[i].Position.x - gripsize * 0.5f), (float)(controlHandles[i].Position.y - gripsize * 0.5f), gripsize, gripsize);
					renderer.RenderRectangleFilled(handleRect, General.Colors.Background, true);
					renderer.RenderRectangle(handleRect, 2, General.Colors.Highlight, true);
				}
				renderer.Finish();
			}
			renderer.Present();
		}

		private SectorProperties GetSectorProperties(int lineIndex, int sectorIndex) 
		{
			float delta = sectorIndex / (float)form.Subdivisions;
			delta += (1.0f - delta) / form.Subdivisions;
			SectorProperties sp = new SectorProperties();

			sp.Brightness = IntepolateValue(sectorProps1[lineIndex].Brightness, sectorProps2[lineIndex].Brightness, delta, form.BrightnessMode);
			sp.FloorHeight = IntepolateValue(sectorProps1[lineIndex].FloorHeight, sectorProps2[lineIndex].FloorHeight, delta, form.FloorAlignMode);
			sp.CeilingHeight = IntepolateValue(sectorProps1[lineIndex].CeilingHeight, sectorProps2[lineIndex].CeilingHeight, delta, form.CeilingAlignMode);

			//textures
			sp.LowTexture = sectorProps1[lineIndex].LowTexture != "-" ? sectorProps1[lineIndex].LowTexture : sectorProps2[lineIndex].LowTexture;
			sp.HighTexture = sectorProps1[lineIndex].HighTexture != "-" ? sectorProps1[lineIndex].HighTexture : sectorProps2[lineIndex].HighTexture;

			return sp;
		}

		//this returns a list of shapes to draw
		private List<List<Vector2D[]>> GetShapes() 
		{
			List<List<Vector2D[]>> shapes = new List<List<Vector2D[]>>();

			for(int i = 1; i < segmentsCount; i++) 
			{
				List<Vector2D[]> segShapes = new List<Vector2D[]>();

				for(int c = 1; c <= form.Subdivisions; c++) 
				{
					Vector2D p0 = curves[i - 1][c - 1];
					Vector2D p1 = curves[i - 1][c];
					Vector2D p2 = curves[i][c];
					Vector2D p3 = curves[i][c - 1];
					segShapes.Add(new[] { p0, p1, p2, p3, p0 });
				}
				shapes.Add(segShapes);
			}

			return shapes;
		}

		#endregion

		#region ================== Point ops

		//this returns an array of linedef lengths relative to total segment length
		private double[] GetRelativeLengths(Vector2D[] pointGroup) 
		{
			double[] relLenGroup = new double[pointGroup.Length];
			relLenGroup[0] = 0.0f;

			//get length and angle of line, which defines the shape
			double length = Vector2D.Distance(pointGroup[0], pointGroup[segmentsCount - 1]);
			double angle = Math.Atan2(pointGroup[0].y - pointGroup[segmentsCount - 1].y, pointGroup[0].x - pointGroup[segmentsCount - 1].x);

			//get relative length of every line
			for(int i = 1; i < pointGroup.Length - 1; i++) 
			{
				Vector2D p0 = pointGroup[i - 1];
				Vector2D p1 = pointGroup[i];
				double curAngle = Math.Atan2(p0.y - p1.y, p0.x - p1.x);
				double diff = (angle + Angle2D.PI) - (curAngle + Angle2D.PI);
				double segLen = (int)(Vector2D.Distance(p0, p1) * Math.Cos(diff));
				relLenGroup[i] = relLenGroup[i - 1] + segLen / length;
			}

			relLenGroup[pointGroup.Length - 1] = 1.0f;

			return relLenGroup;
		}

		//this returns relative handle location
		private static Vector2D GetHandleLocation(Vector2D start, Vector2D end, Vector2D direction) 
		{
			double angle = -Math.Atan2(start.y - end.y, start.x - end.x);
			double dirAngle = -Math.Atan2(direction.y - start.y, direction.x - start.x);
			double length = Math.Sqrt(Math.Pow(Math.Abs(start.x - end.x), 2.0) + Math.Pow(Math.Abs(start.y - end.y), 2.0)) * 0.3f;
			double diff = (angle + Angle2D.PI) - (dirAngle + Angle2D.PI);

			if(diff > Angle2D.PI || (diff < 0 && diff > -Angle2D.PI)) angle += Angle2D.PI;

			return new Vector2D(Math.Sin(angle) * length, Math.Cos(angle) * length);
		}

//LINE DRAWING
		//returns true if 2 lines intersect
		private static bool LinesIntersect(Line line1, Line line2) 
		{
			double zn = (line2.End.y - line2.Start.y) * (line1.End.x - line1.Start.x) - (line2.End.x - line2.Start.x) * (line1.End.y - line1.Start.y);
			double ch1 = (line2.End.x - line2.Start.x) * (line1.Start.y - line2.Start.y) - (line2.End.y - line2.Start.y) * (line1.Start.x - line2.Start.x);
			double ch2 = (line1.End.x - line1.Start.x) * (line1.Start.y - line2.Start.y) - (line1.End.y - line1.Start.y) * (line1.Start.x - line2.Start.x);

			if(zn == 0) return false;
			return (ch1 / zn <= 1 && ch1 / zn >= 0) && (ch2 / zn <= 1 && ch2 / zn >= 0);
		}

		#endregion

		#region ================== Line sorting

		//this gets two arrays of connected points from given lines. Returns true if all went well.
		private bool SetupPointGroups(List<Line> linesList) 
		{
			//find prev/next lines for each line
			for(int i = 0; i < linesList.Count; i++) 
			{
				Line curLine = linesList[i];

				for(int c = 0; c < linesList.Count; c++) 
				{
					if(c != i) //don't wanna play with ourselves :)
					{
						Line line = linesList[c];

						//check start and end points
						if(curLine.Start == line.Start) 
						{
							line.Invert();
							curLine.Previous = line;
						} 
						else if(curLine.Start == line.End) 
						{
							curLine.Previous = line;
						} 
						else if(curLine.End == line.End) 
						{
							line.Invert();
							curLine.Next = line;
						} 
						else if(curLine.End == line.Start) 
						{
							curLine.Next = line;
						}
					}
				}
			}

			List<List<Vector2D>> pointGroups = new List<List<Vector2D>>();
			List<List<Line>> sortedLines = new List<List<Line>>();

			//now find start lines
			for(int i = 0; i < linesList.Count; i++) 
			{
				Line curLine = linesList[i];

				if(curLine.Previous == null) //found start
				{ 
					//collect points
					Line l = curLine;
					List<Vector2D> points = new List<Vector2D>();
					List<Line> lines = new List<Line>();
					points.Add(l.Start);

					do 
					{
						points.Add(l.End);
						lines.Add(l);
					} while((l = l.Next) != null);

					pointGroups.Add(points);
					sortedLines.Add(lines);
				}
			}

			if(pointGroups.Count != 2) 
			{
				General.Interface.DisplayStatus(StatusType.Warning, "Incorrect number of linedef groups! Expected 2, but got " + pointGroups.Count);
				return false;
			}

			if(pointGroups[0].Count != pointGroups[1].Count) 
			{
				General.Interface.DisplayStatus(StatusType.Warning, "Linedefs groups must have equal length! Got " + pointGroups[0].Count + " in first group and " + pointGroups[1].Count + " in second.");
				return false;
			}

			//check if lines from first group intersect with lines from second group
			foreach(Line l1 in sortedLines[0]) 
			{
				foreach(Line l2 in sortedLines[1]) 
				{
					if(LinesIntersect(l1, l2)) 
					{
						General.Interface.DisplayStatus(StatusType.Warning, "One or more lines from first group intersect with one or more lines from second group!");
						return false;
					}
				}
			}

			//both groups count should match at this point
			segmentsCount = pointGroups[0].Count;

			//collect sector properties
			sectorProps1 = new SectorProperties[sortedLines[0].Count];
			for(int i = 0; i < sortedLines[0].Count; i++ ) 
			{
				sectorProps1[i] = sortedLines[0][i].SectorProperties;
			}
			sectorProps2 = new SectorProperties[sortedLines[1].Count];
			for(int i = 0; i < sortedLines[1].Count; i++) 
			{
				sectorProps2[i] = sortedLines[1][i].SectorProperties;
			}

			//check if we need to reverse one of point groups
			Line line1 = new Line(pointGroups[0][0], pointGroups[1][0]);
			Line line2 = new Line(pointGroups[0][segmentsCount - 1], pointGroups[1][segmentsCount - 1]);

			if(LinesIntersect(line1, line2)) 
			{
				pointGroups[0].Reverse();
				Array.Reverse(sectorProps1);
			}

			//fill point groups
			pointGroup1 = new Vector2D[segmentsCount];
			pointGroup2 = new Vector2D[segmentsCount];

			pointGroups[0].CopyTo(pointGroup1);
			pointGroups[1].CopyTo(pointGroup2);

			return true;
		}

		#endregion

		#region ================== Easing functions

		private static int IntepolateValue(int val1, int val2, float delta, string mode) 
		{
			switch(mode) 
			{
				case BridgeInterpolationMode.HIGHEST:
				case BridgeInterpolationMode.BRIGHTNESS_HIGHEST:
					return Math.Max(val1, val2);

				case BridgeInterpolationMode.LOWEST:
				case BridgeInterpolationMode.BRIGHTNESS_LOWEST:
					return Math.Min(val1, val2);
				
				case BridgeInterpolationMode.LINEAR:
					return (int)Math.Round(InterpolationTools.Linear(val1, val2, delta));

				case BridgeInterpolationMode.IN_SINE:
					return (int)Math.Round(InterpolationTools.EaseInSine(val1, val2, delta));

				case BridgeInterpolationMode.OUT_SINE:
					return (int)Math.Round(InterpolationTools.EaseOutSine(val1, val2, delta));

				case BridgeInterpolationMode.IN_OUT_SINE:
					return (int)Math.Round(InterpolationTools.EaseInOutSine(val1, val2, delta));

				default:
					throw new Exception("DrawBezierPathMode.IntepolateValue: \"" + mode + "\" mode is not supported!");
			}
		}

		#endregion

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

		private void form_OnSubdivisionChanged(object sender, EventArgs e) 
		{
			Update();
		}

		private void form_OnOkClick(object sender, EventArgs e) 
		{
			FinishDraw();
		}

		private void form_OnCancelClick(object sender, EventArgs e) 
		{
			OnCancel();
		}

		private void form_OnFlipClick(object sender, EventArgs e) 
		{
			Array.Reverse(pointGroup1);
			Array.Reverse(sectorProps1);
			
			//swap handles position
			Vector2D p = controlHandles[0].Position;
			controlHandles[0].Position = controlHandles[2].Position;
			controlHandles[2].Position = p;

			Update();
		}

		#endregion

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

		// Finish drawing
		[BeginAction("finishdraw")]
		private void FinishDraw() 
		{
			// Accept the changes
			General.Editing.AcceptMode();
		}

		[BeginAction("increasesubdivlevel")]
		private void IncreaseSubdivLevel() 
		{
			if(form != null && form.Subdivisions < MAX_SUBDIVISIONS) form.Subdivisions++;
		}

		[BeginAction("decreasesubdivlevel")]
		private void DecreaseSubdivLevel() 
		{
			if(form != null && form.Subdivisions > MIN_SUBDIVISIONS) form.Subdivisions--;
		}

		#endregion
	}

	#region ================== Helper classes

	internal struct SectorProperties 
	{
		public int FloorHeight;
		public int CeilingHeight;
		public int Brightness;
		public string HighTexture;
		public string LowTexture;
	}

	internal class ControlHandle
	{
		public Vector2D Position;
		public Vector2D ControlledPoint; //point, to which this handle is assigned
		public Vector2D RelativePosition 
		{
			get 
			{
				return new Vector2D(Position.x - ControlledPoint.x, Position.y - ControlledPoint.y);
			}
			set 
			{
				Position = new Vector2D(ControlledPoint.x + value.x, ControlledPoint.y + value.y);
			}
		}
		public ControlHandle Pair; //second handle, to which this handle is paired
	}

	internal class Line 
	{
		public Vector2D Start { get { return start; } }
		private Vector2D start;

		public Vector2D End { get { return end; } }
		private Vector2D end;
		
		public SectorProperties SectorProperties;

		public Line Previous;
		public Line Next;

		public Line(Linedef ld) 
		{
			start = new Vector2D((int)ld.Start.Position.x, (int)ld.Start.Position.y);
			end = new Vector2D((int)ld.End.Position.x, (int)ld.End.Position.y);
			SectorProperties = new SectorProperties();

			if(ld.Back != null) 
			{
				SectorProperties.CeilingHeight = ld.Back.Sector.CeilHeight;
				SectorProperties.FloorHeight = ld.Back.Sector.FloorHeight;
				SectorProperties.Brightness = ld.Back.Sector.Brightness;
				SectorProperties.HighTexture = ld.Back.HighTexture != "-" ? ld.Back.HighTexture : ld.Back.MiddleTexture;
				SectorProperties.LowTexture = ld.Back.LowTexture != "-" ? ld.Back.LowTexture : ld.Back.MiddleTexture;
			}
			else if(ld.Front != null)
			{
				SectorProperties.CeilingHeight = ld.Front.Sector.CeilHeight;
				SectorProperties.FloorHeight = ld.Front.Sector.FloorHeight;
				SectorProperties.Brightness = ld.Front.Sector.Brightness;
				SectorProperties.HighTexture = ld.Front.HighTexture != "-" ? ld.Front.HighTexture : ld.Front.MiddleTexture;
				SectorProperties.LowTexture = ld.Front.LowTexture != "-" ? ld.Front.LowTexture : ld.Front.MiddleTexture;
			}
			else
			{
				SectorProperties.CeilingHeight = 128;
				SectorProperties.FloorHeight = 0;
				SectorProperties.Brightness = 192;
				SectorProperties.HighTexture = "-";
				SectorProperties.LowTexture = "-";
			}
		}

		public Line(Vector2D start, Vector2D end) 
		{
			this.start = start;
			this.end = end;
		}

		public void Invert() 
		{
			Vector2D s = start;
			start = end;
			end = s;
		}
	}

	#endregion
}
