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

namespace OcadMapLibrary {

	public class MapPathFigure {

		public MapPathFigure() {
			mapPathSegments = new List<MapPathSegment>();
		}

		List<MapPathSegment> mapPathSegments;
		public List<MapPathSegment> MapPathSegments {
			get {
				return mapPathSegments;
			}
		}

		Point startPoint;
		public Point StartPoint {
			get {
				return startPoint;
			}
			set {
				startPoint = value;
			}
		}

		public bool HasLength() {
			Point currentPoint = StartPoint;
			for (int i = 0; i < mapPathSegments.Count; i++) {
				if (mapPathSegments[i].HasLength(currentPoint)) {
					return true;
				}
				currentPoint = mapPathSegments[i].EndPoint;
			}
			return false;
		}

		public double GetLength() {
			double result = 0;
			Point currentPoint = StartPoint;
			for (int i = 0; i < mapPathSegments.Count; i++) {
				result += mapPathSegments[i].GetLength(currentPoint);
				currentPoint = mapPathSegments[i].EndPoint;
			}
			return result;
		}

		public PathFigure GetPathFigure() {
			PathFigure result = new PathFigure();
			result.StartPoint = StartPoint;
			for (int i = 0; i < mapPathSegments.Count; i++) {
				result.Segments.Add(mapPathSegments[i].GetPathSegment());
			}
			return result;
		}

		public PathGeometry GetGeometry() {
			PathFigure pathFigure = GetPathFigure();
			PathGeometry geometry = new PathGeometry(new PathFigure[] { pathFigure });
			return geometry;
		}

		Point ShiftPoint(Point point, double leftAngle, double rightAngle, double shift) {
			double averageAngle = (leftAngle + rightAngle) / 2;
			double differenceAverageAngle;
			differenceAverageAngle = (leftAngle - rightAngle) / 2;
			double cosAngle = Math.Cos(differenceAverageAngle);
			double minAbsAngle = 0.1;//For very sharp corners
			if (cosAngle >= 0) {
				if (cosAngle < minAbsAngle) {
					cosAngle = minAbsAngle;
				}
			} else {
				if (cosAngle > -minAbsAngle) {
					cosAngle = -minAbsAngle;
				}
			}
			double distanceFromLine = shift / cosAngle;
			Point newPoint = new Point(point.X + Math.Sin(averageAngle) * distanceFromLine, point.Y - Math.Cos(averageAngle) * distanceFromLine);
			return newPoint;
		}

		public PathFigure GetShiftedPathFigure(double shift) {
			return GetShiftedPathFigure(shift, shift);
		}

		public PathFigure GetShiftedPathFigure(double shiftStart, double shiftEnd) {
			PathFigure result = new PathFigure();
			if (mapPathSegments.Count == 0) {
				return result;
			}
			double totalLength = GetLength();
			double currentShift = shiftStart;
			Point currentPoint = StartPoint;
			double lastAngle = mapPathSegments[0].GetAngleAtStart(currentPoint);
			result.StartPoint = ShiftPoint(StartPoint, lastAngle, lastAngle, currentShift);
			double distance = 0;
			for (int i = 0; i < mapPathSegments.Count; i++) {
				List<MapLineSegment> flattened = mapPathSegments[i].Flatten(currentPoint);
				for (int j = 0; j < flattened.Count; j++) {
					bool isStroked = true;
					distance += flattened[j].GetLength(currentPoint);
					currentShift = shiftStart + (shiftEnd - shiftStart) * distance / totalLength;
					if (currentShift >= 0 && flattened[j].IsLeftStroked == false) {
						isStroked = false;
					}
					if (currentShift < 0 && flattened[j].IsRightStroked == false) {
						isStroked = false;
					}
					currentPoint = flattened[j].EndPoint;
					double newAngle = lastAngle;
					if (j < flattened.Count - 1) {
						newAngle = flattened[j + 1].GetAngle(currentPoint);
					} else {
						if (i < mapPathSegments.Count - 1) {
							newAngle = mapPathSegments[i + 1].GetAngleAtStart(currentPoint);
						}
					}
					Point newPoint = ShiftPoint(currentPoint, lastAngle, newAngle, currentShift);
					LineSegment lineSegment = new LineSegment(newPoint, isStroked);
					result.Segments.Add(lineSegment);
					lastAngle = newAngle;
				}
			}
			return result;
		}

		public List<MapPathFigure> SplitAtLength(double length) {
			List<MapPathFigure> result = new List<MapPathFigure>();
			if (length <= 0) {
				return result;
			}
			MapPathFigure startFigure = new MapPathFigure();
			result.Add(startFigure);
			startFigure.StartPoint = StartPoint;
			if (length >= GetLength()) {
				result.Add(Clone());
				return result;
			}
			MapPathFigure endFigure = new MapPathFigure();
			double dist = 0;
			Point currentPoint = StartPoint;
			int pos = 0;
			for (int i = 0; i < mapPathSegments.Count; i++) {
				pos++;
				List<MapPathSegment> segments = mapPathSegments[i].SplitAtLength(currentPoint, length - dist);
				if (segments.Count > 0) {
					startFigure.mapPathSegments.Add(segments[0]);
				}
				if (segments.Count > 1) {
					endFigure.mapPathSegments.Add(segments[1]);
					currentPoint = segments[0].EndPoint;
					break;
				}
				dist += mapPathSegments[i].GetLength(currentPoint);
				currentPoint = mapPathSegments[i].EndPoint;
			}
			endFigure.StartPoint = currentPoint;
			for (int i = pos; i < mapPathSegments.Count; i++) {
				endFigure.MapPathSegments.Add(mapPathSegments[i].Clone());
			}
			result.Add(endFigure);
			return result;
		}

		public MapPathFigure Clone() {
			MapPathFigure clone = new MapPathFigure();
			clone.StartPoint = StartPoint;
			for (int i = 0; i < mapPathSegments.Count; i++) {
				clone.MapPathSegments.Add(mapPathSegments[i].Clone());
			}
			return clone;
		}

		public List<MapPathFigure> SplitByCorner() {
			List<MapPathFigure> result = new List<MapPathFigure>();
			MapPathFigure currentFigure;
			currentFigure = new MapPathFigure();
			currentFigure.StartPoint = StartPoint;
			Point currentPoint = StartPoint;
			for (int i = 0; i < mapPathSegments.Count; i++) {
				currentFigure.MapPathSegments.Add(mapPathSegments[i].Clone());
				currentPoint = mapPathSegments[i].EndPoint;
				if (mapPathSegments[i].IsCornerPoint || i == mapPathSegments.Count - 1) {
					result.Add(currentFigure);
					currentFigure = new MapPathFigure();
					currentFigure.StartPoint = currentPoint;
				}
			}
			return result;
		}

		public Point? GetPointAtLength(double length) {
			double dist = 0;
			Point currentPoint = StartPoint;
			for (int i = 0; i < mapPathSegments.Count; i++) {
				double currentLength = mapPathSegments[i].GetLength(currentPoint);
				Point? point = mapPathSegments[i].GetPointAtLength(currentPoint, length - dist);
				if (point != null) {
					return point.Value;
				}
				dist += mapPathSegments[i].GetLength(currentPoint);
				currentPoint = mapPathSegments[i].EndPoint;
			}
			return null;
		}

		public double GetAngleAtLength(double length) {
			double dist = 0;
			Point currentPoint = StartPoint;
			for (int i = 0; i < mapPathSegments.Count; i++) {
				double currentLength = mapPathSegments[i].GetLength(currentPoint);
				if (currentLength + dist > length || (currentLength + dist == length && i == mapPathSegments.Count - 1)) {
					return mapPathSegments[i].GetAngleAtLength(currentPoint, length - dist);
				}
				if (currentLength + dist == length && i < mapPathSegments.Count - 1) {
					return (mapPathSegments[i].GetAngleAtEnd(currentPoint) + mapPathSegments[i + 1].GetAngleAtStart(mapPathSegments[i].EndPoint)) / 2;
				}
				dist += mapPathSegments[i].GetLength(currentPoint);
				currentPoint = mapPathSegments[i].EndPoint;
			}
			return 0;
		}

		public double GetAngleAtStart() {
			return mapPathSegments[0].GetAngleAtStart(StartPoint);
		}

		public double GetAngleAtEnd() {
			if (mapPathSegments.Count > 1) {
				return mapPathSegments[mapPathSegments.Count - 1].GetAngleAtEnd(mapPathSegments[mapPathSegments.Count - 2].EndPoint);
			} else {
				return mapPathSegments[mapPathSegments.Count - 1].GetAngleAtEnd(StartPoint);
			}
		}

		public Point GetEndPoint() {
			return mapPathSegments[mapPathSegments.Count - 1].EndPoint;
		}

		void Reverse() {
			Point oldStartPoint = StartPoint;
			Point currentPoint;
			StartPoint = mapPathSegments[mapPathSegments.Count - 1].EndPoint;
			List<MapPathSegment> newSegments = new List<MapPathSegment>();
			for (int i = 0; i < MapPathSegments.Count; i++) {
				if (i < mapPathSegments.Count - 1) {
					currentPoint = mapPathSegments[mapPathSegments.Count - 2 - i].EndPoint;
				} else {
					currentPoint = oldStartPoint;
				}
				MapPathSegment currentSegment = MapPathSegments[mapPathSegments.Count - 1 - i];
				currentSegment.Reverse(currentPoint);
				newSegments.Add(currentSegment);
			}
			mapPathSegments = newSegments;
		}

		public PathFigure GetPointedPath(double width, double startDistance, double endDistance) {
			double minDistance = 0.0001;
			double newStartDistance = startDistance;
			double newEndDistance = endDistance;
			double totalLength = GetLength();
			if (totalLength < minDistance) {
				return null;
			}
			if (startDistance + endDistance > totalLength-minDistance) {
				newStartDistance = startDistance * totalLength / (startDistance + endDistance + minDistance);
				newEndDistance = endDistance * totalLength / (startDistance + endDistance + minDistance);
			}
			newStartDistance = Math.Max(minDistance / 3, newStartDistance);
			newEndDistance = Math.Max(minDistance / 3, newEndDistance);
			PathFigure newFigure = new PathFigure();
			newFigure.StartPoint = StartPoint;
			List<MapPathFigure> mapPathFigures;
			mapPathFigures = SplitAtLength(newStartDistance);
			MapPathFigure startFigure = mapPathFigures[0];
			mapPathFigures = mapPathFigures[1].SplitAtLength(totalLength-newStartDistance-newEndDistance);
			MapPathFigure midFigure = mapPathFigures[0];
			MapPathFigure endFigure = mapPathFigures[1];
			PathFigure tempFigure;
			tempFigure = startFigure.GetShiftedPathFigure(0, width / 2);
			for (int i = 0; i < tempFigure.Segments.Count; i++) {
				newFigure.Segments.Add(tempFigure.Segments[i].Clone());
			}
			tempFigure = midFigure.GetShiftedPathFigure(width / 2);
			for (int i = 0; i < tempFigure.Segments.Count; i++) {
				newFigure.Segments.Add(tempFigure.Segments[i].Clone());
			}
			tempFigure = endFigure.GetShiftedPathFigure(width / 2, 0);
			for (int i = 0; i < tempFigure.Segments.Count; i++) {
				newFigure.Segments.Add(tempFigure.Segments[i].Clone());
			}
			endFigure.Reverse();
			midFigure.Reverse();
			startFigure.Reverse();
			tempFigure = endFigure.GetShiftedPathFigure(0, width / 2);
			for (int i = 0; i < tempFigure.Segments.Count; i++) {
				newFigure.Segments.Add(tempFigure.Segments[i].Clone());
			}
			tempFigure = midFigure.GetShiftedPathFigure(width / 2);
			for (int i = 0; i < tempFigure.Segments.Count; i++) {
				newFigure.Segments.Add(tempFigure.Segments[i].Clone());
			}
			tempFigure = startFigure.GetShiftedPathFigure(width / 2, 0);
			for (int i = 0; i < tempFigure.Segments.Count; i++) {
				newFigure.Segments.Add(tempFigure.Segments[i].Clone());
			}
			return newFigure;
		}

	}

}
