using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;

namespace OcadMapLibrary {

	public class LineSymbol : MapSymbol {

		public LineSymbol() {
		}

		MapStroke mainStroke;
		public MapStroke MainStroke {
			get {
				return mainStroke;
			}
			set {
				mainStroke = value;
			}
		}

		DashInfo mainDashInfo;
		public DashInfo MainDashInfo {
			get {
				return mainDashInfo;
			}
			set {
				mainDashInfo = value;
			}
		}

		MapStroke doubleLeftStroke;
		public MapStroke DoubleLeftStroke {
			get {
				return doubleLeftStroke;
			}
			set {
				doubleLeftStroke = value;
			}
		}

		MapStroke doubleRightStroke;
		public MapStroke DoubleRightStroke {
			get {
				return doubleRightStroke;
			}
			set {
				doubleRightStroke = value;
			}
		}

		MapColorRef doubleFillColor;
		public MapColorRef DoubleFillColor {
			get {
				return doubleFillColor;
			}
			set {
				doubleFillColor = value;
			}
		}

		double doubleMiddleWidth;
		public double DoubleMiddleWidth {
			get {
				return doubleMiddleWidth;
			}
			set {
				doubleMiddleWidth = value;
			}
		}

		double doubleDashGap;
		public double DoubleDashGap {
			get {
				return doubleDashGap;
			}
			set {
				doubleDashGap = value;
			}
		}

		double doubleDashLength;
		public double DoubleDashLength {
			get {
				return doubleDashLength;
			}
			set {
				doubleDashLength = value;
			}
		}

		bool doubleIsDashedLeft;
		public bool DoubleIsDashedLeft {
			get {
				return doubleIsDashedLeft;
			}
			set {
				doubleIsDashedLeft = value;
			}
		}

		bool doubleIsDashedRight;
		public bool DoubleIsDashedRight {
			get {
				return doubleIsDashedRight;
			}
			set {
				doubleIsDashedRight = value;
			}
		}

		PointSymbol mainSymbol;
		public PointSymbol MainSymbol {
			get {
				return mainSymbol;
			}
			set {
				mainSymbol = value;
			}
		}

		double mainSymbolSeparation;
		public double MainSymbolSeparation {
			get {
				return mainSymbolSeparation;
			}
			set {
				mainSymbolSeparation = value;
			}
		}

		int numMainSymbols;
		public int NumMainSymbols {
			get {
				return numMainSymbols;
			}
			set {
				numMainSymbols = value;
			}
		}

		PointSymbol cornerSymbol;
		public PointSymbol CornerSymbol {
			get {
				return cornerSymbol;
			}
			set {
				cornerSymbol = value;
			}
		}

		PointSymbol startSymbol;
		public PointSymbol StartSymbol {
			get {
				return startSymbol;
			}
			set {
				startSymbol = value;
			}
		}

		PointSymbol endSymbol;
		public PointSymbol EndSymbol {
			get {
				return endSymbol;
			}
			set {
				endSymbol = value;
			}
		}

		bool hasPointedEnds;
		public bool HasPointedEnds {
			get {
				return hasPointedEnds;
			}
			set {
				hasPointedEnds = value;
			}
		}

		double pointedStartDistance;
		public double PointedStartDistance {
			get {
				return pointedStartDistance;
			}
			set {
				pointedStartDistance = value;
			}
		}

		double pointedEndDistance;
		public double PointedEndDistance {
			get {
				return pointedEndDistance;
			}
			set {
				pointedEndDistance = value;
			}
		}

		public void DrawSymbol(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			if (IsHidden) {
				return;
			}
			DrawMainLine(data, drawingContext, mapColor);
			DrawMainSymbol(data, drawingContext, mapColor);
			DrawCornerSymbol(data, drawingContext, mapColor);
			DrawStartSymbol(data, drawingContext, mapColor);
			DrawEndSymbol(data, drawingContext, mapColor);
			DrawDoubleLine(data, drawingContext, mapColor);
		}

		void DrawMainLine(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			if (MainStroke == null) {
				return;
			}
			if (!MainStroke.MatchesColor(mapColor) || MainStroke.Thickness <= 0) {
				return;
			}
			if (!data.HasLength()) {
				return;
			}
			if ((MainDashInfo.DashMainLength > 0 && (MainDashInfo.DashMainGap > 0 || MainDashInfo.DashSecondaryGap > 0)) || (MainDashInfo.DashEndLength > 0 && (MainDashInfo.DashEndGap > 0 || MainDashInfo.DashMainGap > 0))) {
				List<MapPathFigure> mapPathFigures = data.SplitByCorner();
				int minNumGaps = MainDashInfo.MinNumGaps;
				List<int> minGapsList = GetMinNumGapsList(mapPathFigures, minNumGaps, MainDashInfo.DashMainLength + MainDashInfo.DashMainGap);
				for (int i = 0; i < mapPathFigures.Count; i++) {
					DrawStrokeDashSection(mapPathFigures[i], minGapsList[i], MainStroke, drawingContext, mapColor);
				}
			}else if (HasPointedEnds && (PointedStartDistance > 0 || PointedEndDistance > 0)) {
				PathFigure pathFigure = data.GetPointedPath(MainStroke.Thickness, PointedStartDistance, PointedEndDistance);
				if (pathFigure == null) {
					return;
				}
				PathGeometry geometry = new PathGeometry(new PathFigure[] { pathFigure });
				drawingContext.DrawGeometry(new SolidColorBrush(mapColor.Color), null, geometry);
			} else {
				MainStroke.DrawGeometry(data.GetGeometry(), drawingContext, mapColor);
			}
		}

		int GetLargestIndex(List<double> lengths) {
			double maxLength = 0;
			int index = 0;
			for (int i = 0; i < lengths.Count; i++) {
				if (lengths[i] > maxLength) {
					index = i;
				}
			}
			return index;
		}

		List<int> GetMinNumGapsList(List<MapPathFigure> mapPathFigures, int minNumGaps, double totalGapDashLength) {
			double tolerance = 0.5;//unknown value
			List<int> result = new List<int>();
			for (int i = 0; i < mapPathFigures.Count; i++) {
				result.Add(0);
			}
			if (minNumGaps <= 0) {
				return result;
			}
			int numGapsLeft = minNumGaps;
			List<double> lengths = new List<double>();
			for (int i = 0; i < mapPathFigures.Count; i++) {
				lengths.Add(mapPathFigures[i].GetLength());
			}
			while (numGapsLeft > 0) {
				int index = GetLargestIndex(lengths);
				double length = lengths[index];
				for (int i = 0; i < lengths.Count; i++) {
					if (Math.Abs(length - lengths[i]) < tolerance) {
						lengths[i] -= totalGapDashLength;
						result[i]++;
						numGapsLeft--;
					}
				}
			}
			return result;
		}

		DashInfo GetCorrectedDashInfo(double totalLength, DashInfo dashInfo, out int numGaps) {
			DashInfo newInfo = dashInfo.Clone();
			numGaps = 0;
			if (totalLength <= 0) {
				return null;
			}
			if (!(newInfo.DashMainLength > 0  || newInfo.DashEndLength > 0)) {
				return null;
			}
			double endLength = newInfo.DashEndLength;
			double mainLength = newInfo.DashMainLength;
			if (mainLength <= 0) {
				mainLength = Math.Max(totalLength - (endLength + newInfo.DashMainGap) * 2, 0);
			}
			int numDashes;
			int minDashes = Math.Max(newInfo.MinNumGaps, 0) - 1;
			if (mainLength > 0) {
				numDashes = (int)Math.Round((totalLength - endLength * 2 - newInfo.DashMainGap) / (mainLength + newInfo.DashMainGap) + 0.1);//0.1 is an unknown correction factor
				if (numDashes < minDashes) {
					numDashes = minDashes;
				}
			} else {
				numDashes = minDashes;
			}
			if (numDashes < 0) {
				endLength = totalLength / 2;
			} else {
				double scaleFactor;
				scaleFactor = (totalLength - newInfo.DashMainGap * (numDashes + 1)) / (endLength * 2 + mainLength * numDashes);
				if (scaleFactor < 0) {
					return null;
				}
				mainLength *= scaleFactor;
				endLength *= scaleFactor;
			}
			newInfo.DashMainLength = mainLength;
			newInfo.DashEndLength = endLength;
			numGaps = numDashes + 1;
			return newInfo;
		}

		void DrawStrokeDashSection(MapPathFigure data, int minNumGaps, MapStroke mapStroke, DrawingContext drawingContext, MapColor mapColor) {
			double totalLength = data.GetLength();
			int numGaps;
			DashInfo newDashInfo = GetCorrectedDashInfo(totalLength, MainDashInfo, out numGaps);
			if (newDashInfo == null) {
				return;
			}
			double endLength = newDashInfo.DashEndLength;
			double mainLength = newDashInfo.DashMainLength;
			MapPathFigure startFigure = null;
			MapPathFigure midFigure = null;
			MapPathFigure endFigure = null;
			if (endLength == 0) {
				midFigure = data;
			} else {
				List<MapPathFigure> mapPathFigures;
				mapPathFigures = data.SplitAtLength(endLength);
				startFigure = mapPathFigures[0];
				if (totalLength - endLength * 2 > 0) {
					mapPathFigures = mapPathFigures[1].SplitAtLength(totalLength - endLength * 2);
					midFigure = mapPathFigures[0];
					endFigure = mapPathFigures[1];
				} else {
					endFigure = mapPathFigures[1];
				}
			}
			if (startFigure != null && endFigure != null) {
				if (newDashInfo.DashEndGap > 0) {
					double endDashLength = (endLength - newDashInfo.DashEndGap) / 2;
					if (endDashLength > 0) {
						DoubleCollection dashes = new DoubleCollection(new double[] { endDashLength, newDashInfo.DashEndGap });
						MainStroke.DrawGeometryWithDashes(startFigure.GetGeometry(), dashes, 0, drawingContext, mapColor);
						MainStroke.DrawGeometryWithDashes(endFigure.GetGeometry(), dashes, 0, drawingContext, mapColor);
					}
				} else {
					MainStroke.DrawGeometry(startFigure.GetGeometry(), drawingContext, mapColor);
					MainStroke.DrawGeometry(endFigure.GetGeometry(), drawingContext, mapColor);
				}
			}
			if (midFigure != null) {
				if (mainLength > 0) {
					if (newDashInfo.DashSecondaryGap > 0) {
						double mainDashLength = (mainLength - newDashInfo.DashSecondaryGap) / 2;
						if (mainDashLength > 0) {
							DoubleCollection dashes = new DoubleCollection(new double[] { mainDashLength, newDashInfo.DashSecondaryGap, mainDashLength, newDashInfo.DashMainGap });
							MainStroke.DrawGeometryWithDashes(midFigure.GetGeometry(), dashes, mainLength, drawingContext, mapColor);
						}
					} else {
						DoubleCollection dashes = new DoubleCollection(new double[] { mainLength, newDashInfo.DashMainGap });
						MainStroke.DrawGeometryWithDashes(midFigure.GetGeometry(), dashes, mainLength, drawingContext, mapColor);
					}
				} else {
					MainStroke.DrawGeometry(midFigure.GetGeometry(), drawingContext, mapColor);
				}
			}
		}

		void DrawMainSymbol(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			if (MainSymbol == null) {
				return;
			}
			if (!MainSymbol.ContainsColor(mapColor) || MainDashInfo.DashMainLength <= 0) {
				return;
			}
			if (!data.HasLength()) {
				return;
			}
			List<MapPathFigure> mapPathFigures = data.SplitByCorner();
			int minNumGaps = MainDashInfo.MinNumGaps;
			List<int> minGapsList = GetMinNumGapsList(mapPathFigures, minNumGaps, MainDashInfo.DashMainLength + MainDashInfo.DashMainGap);
			for (int i = 0; i < mapPathFigures.Count; i++) {
				DrawMainSymbolSection(MainSymbol, mapPathFigures[i], minGapsList[i], drawingContext, mapColor);
			}
		}

		void DrawMainSymbolSection(PointSymbol symbol, MapPathFigure data, int minNumGaps, DrawingContext drawingContext, MapColor mapColor) {
			double totalLength = data.GetLength();
			int numGaps;
			DashInfo newDashInfo = GetCorrectedDashInfo(totalLength, MainDashInfo, out numGaps);
			if (newDashInfo == null) {
				return;
			}
			double endLength = newDashInfo.DashEndLength;
			double mainLength = newDashInfo.DashMainLength;
			double distance = endLength + newDashInfo.DashMainGap;
			for (int j = 0; j < numGaps; j++) {
				if (distance + (NumMainSymbols - 1) * MainSymbolSeparation > totalLength) {
					break;
				}
				for (int k = 0; k < NumMainSymbols; k++) {
					Point? newPoint;
					double angle = 0;
					newPoint = data.GetPointAtLength(distance);
					if (newPoint == null) {
						break;
					}
					angle = data.GetAngleAtLength(distance) / Math.PI * 180.0;
					Transform rotate = new RotateTransform(angle);
					Transform translate = new TranslateTransform(newPoint.Value.X, newPoint.Value.Y);
					drawingContext.PushTransform(translate);
					drawingContext.PushTransform(rotate);
					symbol.DrawSymbol(drawingContext, mapColor);
					drawingContext.Pop();
					drawingContext.Pop();
					distance += MainSymbolSeparation;
				}
				distance += mainLength + newDashInfo.DashMainGap - NumMainSymbols * MainSymbolSeparation;
			}
		}

		void DrawCornerSymbol(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			if (CornerSymbol == null) {
				return;
			}
			if (!CornerSymbol.ContainsColor(mapColor)) {
				return;
			}
			if (!data.HasLength()) {
				return;
			}
			List<MapPathFigure> mapPathFigures = data.SplitByCorner();
			for (int i = 0; i < mapPathFigures.Count - 1; i++) {
				double angle = (mapPathFigures[i].GetAngleAtEnd() + mapPathFigures[i + 1].GetAngleAtStart()) / 2;
				angle = angle / Math.PI * 180.0;
				Point newPoint = mapPathFigures[i + 1].StartPoint;
				Transform rotate = new RotateTransform(angle);
				Transform translate = new TranslateTransform(newPoint.X, newPoint.Y);
				drawingContext.PushTransform(translate);
				drawingContext.PushTransform(rotate);
				CornerSymbol.DrawSymbol(drawingContext, mapColor);
				drawingContext.Pop();
				drawingContext.Pop();
			}
		}

		void DrawStartSymbol(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			if (StartSymbol == null) {
				return;
			}
			if (!StartSymbol.ContainsColor(mapColor)) {
				return;
			}
			if (!data.HasLength()) {
				return;
			}
			double angle = data.GetAngleAtStart();
			angle = angle / Math.PI * 180.0;
			Point newPoint = data.StartPoint;
			Transform rotate = new RotateTransform(angle);
			Transform translate = new TranslateTransform(newPoint.X, newPoint.Y);
			drawingContext.PushTransform(translate);
			drawingContext.PushTransform(rotate);
			StartSymbol.DrawSymbol(drawingContext, mapColor);
			drawingContext.Pop();
			drawingContext.Pop();
		}

		void DrawEndSymbol(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			if (EndSymbol == null) {
				return;
			}
			if (!EndSymbol.ContainsColor(mapColor)) {
				return;
			}
			if (!data.HasLength()) {
				return;
			}
			double angle = data.GetAngleAtEnd();
			angle = angle / Math.PI * 180.0;
			Point newPoint = data.GetEndPoint();
			Transform rotate = new RotateTransform(angle);
			Transform translate = new TranslateTransform(newPoint.X, newPoint.Y);
			drawingContext.PushTransform(translate);
			drawingContext.PushTransform(rotate);
			EndSymbol.DrawSymbol(drawingContext, mapColor);
			drawingContext.Pop();
			drawingContext.Pop();
		}

		void DrawDoubleLine(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			DrawDoubleFill(data, drawingContext, mapColor);
			DrawDoubleLeftLine(data, drawingContext, mapColor);
			DrawDoubleRightLine(data, drawingContext, mapColor);
		}

		void DrawDoubleFill(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			if (DoubleFillColor == null) {
				return;
			}
			if (!DoubleFillColor.Matches(mapColor) || DoubleMiddleWidth <= 0) {
				return;
			}
			if (!data.HasLength()) {
				return;
			}
			Pen pen = new Pen(new SolidColorBrush(mapColor.Color), DoubleMiddleWidth);
			drawingContext.DrawGeometry(null, pen, data.GetGeometry());
		}

		private void DrawDoubleLeftLine(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			if (DoubleLeftStroke == null) {
				return;
			}
			if (!DoubleLeftStroke.MatchesColor(mapColor) || DoubleLeftStroke.Thickness <= 0) {
				return;
			}
			if (!data.HasLength()) {
				return;
			}
			PathFigure shiftedFigure = data.GetShiftedPathFigure(DoubleMiddleWidth / 2 + DoubleLeftStroke.Thickness / 2);
			Geometry geometry = new PathGeometry(new PathFigure[] { shiftedFigure });
			if (DoubleDashGap > 0 && DoubleDashLength > 0 && DoubleIsDashedLeft == true) {
				DoubleCollection dashes = new DoubleCollection(new double[] { DoubleDashLength, DoubleDashGap });
				DoubleLeftStroke.DrawGeometryWithDashes(geometry, dashes, 0, drawingContext, mapColor);
			} else {
				DoubleLeftStroke.DrawGeometry(geometry, drawingContext, mapColor);
			}
		}

		private void DrawDoubleRightLine(MapPathFigure data, DrawingContext drawingContext, MapColor mapColor) {
			if (DoubleRightStroke == null) {
				return;
			}
			if (!DoubleRightStroke.MatchesColor(mapColor) || DoubleRightStroke.Thickness <= 0) {
				return;
			}
			if (!data.HasLength()) {
				return;
			}
			PathFigure shiftedFigure = data.GetShiftedPathFigure(-(DoubleMiddleWidth / 2 + DoubleRightStroke.Thickness / 2));
			Geometry geometry = new PathGeometry(new PathFigure[] { shiftedFigure });
			if (DoubleDashGap > 0 && DoubleDashLength > 0 && DoubleIsDashedRight == true) {
				DoubleCollection dashes = new DoubleCollection(new double[] { DoubleDashLength, DoubleDashGap });
				DoubleRightStroke.DrawGeometryWithDashes(geometry, dashes, 0, drawingContext, mapColor);
			} else {
				DoubleRightStroke.DrawGeometry(geometry, drawingContext, mapColor);
			}
		}


		public Rect GetBoundingRect(MapPathFigure data) {
			Rect rect = Rect.Empty;
			if (IsHidden) {
				return rect;
			}
			if (!data.HasLength()) {
				return rect;
			}
			if (MainStroke != null) {
				if (MainStroke.Thickness > 0) {
					PathGeometry geometry = data.GetGeometry();
					Pen pen = new Pen(Brushes.Black, MainStroke.Thickness);
					rect.Union(geometry.GetRenderBounds(pen));
				}
			}
			if (DoubleFillColor != null) {
				if (DoubleMiddleWidth > 0) {
					PathGeometry geometry = data.GetGeometry();
					Pen pen = new Pen(Brushes.Black, DoubleMiddleWidth);
					rect.Union(geometry.GetRenderBounds(pen));
				}
			}
			if (DoubleLeftStroke != null) {
				if (DoubleLeftStroke.Thickness > 0) {
					PathFigure shiftedFigure = data.GetShiftedPathFigure(DoubleMiddleWidth / 2 + DoubleLeftStroke.Thickness / 2);
					Geometry geometry = new PathGeometry(new PathFigure[] { shiftedFigure });
					Pen pen = new Pen(Brushes.Black, DoubleLeftStroke.Thickness);
					rect.Union(geometry.GetRenderBounds(pen));
				}
			}
			if (DoubleRightStroke != null) {
				if (DoubleRightStroke.Thickness > 0) {
					PathFigure shiftedFigure = data.GetShiftedPathFigure(-(DoubleMiddleWidth / 2 + DoubleRightStroke.Thickness / 2));
					Geometry geometry = new PathGeometry(new PathFigure[] { shiftedFigure });
					Pen pen = new Pen(Brushes.Black, DoubleRightStroke.Thickness);
					rect.Union(geometry.GetRenderBounds(pen));
				}
			}
			if (MainSymbol != null) {
				if (MainDashInfo.DashMainLength > 0) {
					//this is approximate
					Rect temp = MainSymbol.GetBoundingRect(new Point(0, 0));
					Geometry geometry = data.GetGeometry();
					Pen pen = new Pen(Brushes.Black, Point.Subtract(temp.TopLeft, temp.BottomRight).Length);
					rect.Union(geometry.GetRenderBounds(pen));
				}
			}
			if (CornerSymbol != null) {
				//this is approximate
				Rect temp = CornerSymbol.GetBoundingRect(new Point(0, 0));
				Geometry geometry = data.GetGeometry();
				Pen pen = new Pen(Brushes.Black, Point.Subtract(temp.TopLeft, temp.BottomRight).Length);
				rect.Union(geometry.GetRenderBounds(pen));
			}
			if (StartSymbol != null) {
				Rect temp = StartSymbol.GetBoundingRect(new Point(0,0));
				RotateTransform rotate = new RotateTransform(data.GetAngleAtStart() / Math.PI * 180.0);
				temp = rotate.TransformBounds(temp);
				temp.Offset(new Vector(data.StartPoint.X,data.StartPoint.Y));
				rect.Union(temp);
			}
			if (EndSymbol != null) {
				Rect temp = EndSymbol.GetBoundingRect(new Point(0, 0));
				RotateTransform rotate = new RotateTransform(data.GetAngleAtEnd() / Math.PI * 180.0);
				temp = rotate.TransformBounds(temp);
				temp.Offset(new Vector(data.GetEndPoint().X, data.GetEndPoint().Y));
				rect.Union(temp);
			}
			return rect;
		}

	}

}
