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

namespace OcadMapLibrary {

	public abstract class OcadBaseFileReader {

		//Public properties/methods

		public OcadBaseFileReader() {
			isLoaded = false;
		}

		private bool isLoaded;
		public bool IsLoaded {
			get {
				return isLoaded;
			}
			set {
				isLoaded = value;
			}
		}

		public abstract MapInfo GetMapInfo();
		public abstract bool LoadMap(Stream stream);
		public abstract void UnloadMap();

		public abstract List<MapColor> GetColors();
		public abstract List<MapSymbol> GetSymbols();
		public abstract List<MapObject> GetObjects();


		//Non-public properties/methods

		protected static double BaseLengthUnit = 96.0 / 2.54 * 0.001;

		protected static double GetLength(double num) {
			return num * BaseLengthUnit;
		}

		protected static PenLineJoin FlagsToLineJoin(UInt16 flags) {
			if ((flags & 1) == 1) {
				return PenLineJoin.Round;
			} else if ((flags & 4) == 4) {
				return PenLineJoin.Miter;
			}
			return PenLineJoin.Bevel;
		}

		protected static Point ConvertToPoint(TCord coord) {
			Point point = new Point();
			point.X = GetLength(coord.x >> 8);
			point.Y = -GetLength(coord.y >> 8);
			return point;
		}

		static bool IsFirstCurvePoint(TCord coord) {
			int flags;
			flags = coord.x << 24;
			flags = flags >> 24;
			if ((flags & 1) == 1) {
				return true;
			}
			return false;
		}

		static void SetSegmentFlags(MapPathSegment segment, TCord coord) {
			int flags;
			flags = coord.x << 24;
			flags = flags >> 24;
			if ((flags & 4) == 4) {
				segment.IsLeftStroked = false;
			} else {
				segment.IsLeftStroked = true;
			}
			flags = coord.y << 24;
			flags = flags >> 24;
			if ((flags & 1) == 1) {
				segment.IsCornerPoint = true;
			}
			if ((flags & 4) == 4) {
				segment.IsRightStroked = false;
			} else {
				segment.IsRightStroked = true;
			}
			if ((flags & 8) == 8) {
				segment.IsDashPoint = true;
			}
		}

		protected static Rect ConvertToRect(TCord[] coords) {
			if (coords.Length < 4) {
				return Rect.Empty;
			}
			Point temp1;
			Point temp2;
			temp1 = ConvertToPoint(coords[3]);
			double x = temp1.X;
			double y = temp1.Y;
			temp1 = ConvertToPoint(coords[0]);
			temp2 = ConvertToPoint(coords[2]);
			double width = temp2.X - temp1.X;
			double height = temp1.Y - temp2.Y;
			if (width < 0) {
				x -= width;
				width = -width;
			}
			if (height < 0) {
				y += height;
				height = -height;
			}
			return new Rect(x, y, width, height);
		}

		protected static MapPathFigure ConvertToMapPathFigure(TCord[] coords, int startIndex, int endIndex){
			MapPathFigure result = new MapPathFigure();
			if (coords.Length == 0) {
				return result;
			}
			result.StartPoint = ConvertToPoint(coords[startIndex]);
			for (int i = startIndex+1; i <= endIndex; i++) {
				if (IsFirstCurvePoint(coords[i])) {
					if (i >= coords.Length - 2) {
						break;
					}
					MapBezierSegment bezierSegment = new MapBezierSegment(ConvertToPoint(coords[i]), ConvertToPoint(coords[i + 1]), ConvertToPoint(coords[i+2]));
					SetSegmentFlags(bezierSegment, coords[i + 2]);
					result.MapPathSegments.Add(bezierSegment);
					i += 2;
				} else {
					MapLineSegment lineSegment = new MapLineSegment(ConvertToPoint(coords[i]));
					SetSegmentFlags(lineSegment, coords[i]);
					result.MapPathSegments.Add(lineSegment);
				}
			}
			return result;
		}

		protected static MapPathFigure ConvertToMapPathFigure(TCord[] coords) {
			return ConvertToMapPathFigure(coords, 0, coords.Length - 1);
		}

		static bool IsFirstHolePoint(TCord coord) {
			int flags;
			flags = coord.y << 24;
			flags = flags >> 24;
			if ((flags & 2) == 2) {
				return true;
			}
			return false;
		}

		protected static MapAreaGeometry ConvertToMapAreaGeometry(TCord[] coords) {
			MapAreaGeometry result = new MapAreaGeometry();
			if (coords.Length == 0) {
				return result;
			}
			int holePos = -1;
			int i;
			for (i = 0; i < coords.Length; i++) {
				if (IsFirstHolePoint(coords[i])) {
					holePos = i;
					break;
				}
			}
			if(holePos>0){
				result.MainFigure = ConvertToMapPathFigure(coords, 0, holePos - 1);
			}
			if (holePos == -1) {
				result.MainFigure = ConvertToMapPathFigure(coords);
				return result;
			}
			int lastPos = holePos;
			for (i = holePos + 1; i < coords.Length; i++) {
				if (IsFirstHolePoint(coords[i])) {
					result.Holes.Add(ConvertToMapPathFigure(coords, lastPos, i - 1));
					lastPos = i;
				}
			}
			result.Holes.Add(ConvertToMapPathFigure(coords, lastPos, coords.Length - 1));
			return result;
		}


		//Creation functions shared between Ocad versions

		protected PointSymbolElement CreateElement(TSymElt elt, bool subtractStrokeWidth) {
			MapColorRef color = new MapColorRef();
			color.MapColorID = elt.stColor;
			if (elt.stPoly.Length <= 0) {
				return null;
			}
			switch (elt.stType) {
				case 1: {
						PointSymbolLine pointSymbolElement = new PointSymbolLine();
						MapPathFigure mapPathFigure = ConvertToMapPathFigure(elt.stPoly);
						pointSymbolElement.StrokeColor = color;
						pointSymbolElement.StrokeThickness = GetLength(elt.stLineWidth);
						pointSymbolElement.Data = mapPathFigure;
						return pointSymbolElement;
					}
				case 2: {
						PointSymbolArea pointSymbolElement = new PointSymbolArea();
						MapAreaGeometry mapAreaGeometry = ConvertToMapAreaGeometry(elt.stPoly);
						pointSymbolElement.FillColor = color;
						pointSymbolElement.Data = mapAreaGeometry;
						return pointSymbolElement;
					}
				case 3: {
						PointSymbolEllipse pointSymbolElement = new PointSymbolEllipse();
						Point point = ConvertToPoint(elt.stPoly[0]);
						double radius;
						if (subtractStrokeWidth) {
							radius = GetLength(elt.stDiameter) / 2 - GetLength(elt.stLineWidth) / 2;
						} else {
							radius = GetLength(elt.stDiameter) / 2;
						}
						pointSymbolElement.Center = point;
						pointSymbolElement.Radius = radius;
						pointSymbolElement.StrokeThickness = GetLength(elt.stLineWidth);
						pointSymbolElement.StrokeColor = color;
						return pointSymbolElement;
					}
				case 4: {
						PointSymbolFilledEllipse pointSymbolElement = new PointSymbolFilledEllipse();
						Point point = ConvertToPoint(elt.stPoly[0]);
						pointSymbolElement.FillColor = color;
						pointSymbolElement.Center = point;
						pointSymbolElement.Radius = GetLength(elt.stDiameter) / 2;
						return pointSymbolElement;
					}
				default:
					return null;
			}
		}

		protected PointSymbol CreatePointSymbol(TSymElt[] elts, bool subtractStrokeWidth) {
			if (elts.Length == 0) {
				return null;
			}
			PointSymbol mapSymbol = new PointSymbol();
			for (int i = 0; i < elts.Length; i++) {
				PointSymbolElement elt = CreateElement(elts[i], subtractStrokeWidth);
				if (elt != null) {
					mapSymbol.PointSymbolElements.Add(elt);
				}
			}
			return mapSymbol;
		}


		//Ocad classes shared between versions

		protected class TSymbolBlock {

			public Int32 NextBlock;
			public Int32[] FilePos = new Int32[256];

			public bool Load(Stream stream) {
				BinaryReader br = new BinaryReader(stream);
				NextBlock = br.ReadInt32();
				for (int i = 0; i < FilePos.Length; i++) {
					FilePos[i] = br.ReadInt32();
				}
				return true;
			}

		}

		protected class TSymElt {

			public Int16 stType;
			public UInt16 stFlags; //word
			public Int16 stColor;
			public Int16 stLineWidth;
			public Int16 stDiameter;
			public Int16 stnPoly;
			public Int16 stRes1;
			public Int16 stRes2;
			public TCord[] stPoly;

			public bool Load(Stream stream) {
				BinaryReader br = new BinaryReader(stream);
				stType = br.ReadInt16();
				stFlags = br.ReadUInt16();
				stColor = br.ReadInt16();
				stLineWidth = br.ReadInt16();
				stDiameter = br.ReadInt16();
				stnPoly = br.ReadInt16();
				stRes1 = br.ReadInt16();
				stRes2 = br.ReadInt16();
				stPoly = new TCord[stnPoly];
				for (int i = 0; i < stPoly.Length; i++) {
					TCord cord = new TCord();
					cord.Load(stream);
					stPoly[i] = cord;
				}
				return true;
			}

		}

		protected class TCord {

			public Int32 x;
			public Int32 y;

			public bool Load(Stream stream) {
				BinaryReader br = new BinaryReader(stream);
				x = br.ReadInt32();
				y = br.ReadInt32();
				return true;
			}

		}

	}

}
