//	Spludlow Software
//	Copyright © Samuel P. Ludlow 2020 All Rights Reserved
//	Distributed under the terms of the GNU General Public License version 3
//	Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
//	https://www.spludlow.co.uk/LICENCE.TXT
//	The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission
//	v1.14

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.IO;
using System.Drawing;
using System.Drawing.Printing;
using System.Drawing.Imaging;
using System.Data;

using System.Drawing.Drawing2D;

namespace Spludlow.Printing
{
	/// <summary>
	/// Perform "Graphics" printing, to system printers PrintPaper() or bitmap files PrintBitmap()
	/// </summary>
	public class GraphicsPrinter : IPrintDoc
	{
		public static void SetUpGraphics(Graphics graphics, bool dontAntiAlias)
		{
			if (dontAntiAlias == false)
			{
				graphics.SmoothingMode = SmoothingMode.HighQuality;
				graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
				graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
			}
			else
			{
				graphics.SmoothingMode = SmoothingMode.None;
				graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
				graphics.PixelOffsetMode = PixelOffsetMode.None;
			}
		}

		private Graphics Graphics;

		private Spludlow.Printing.PrintDocReader PrintDocReader;

		private bool PageBreak = false;

		private int PageIndex;

		private string Filename;
		private Spludlow.Drawing.BitmapFormats BitmapFormats;
		private Bitmap PageBitmap = null;
		private string Digits;
		List<string> SaveFilenames;


		private PointF HardwareOffset;

		public GraphicsPrinter()
		{
		}

		public void PrintPaper(string printData, string printerInfo)
		{
			string printerName = null;
			string paperSource = null;

			if (printerInfo != null && printerInfo.Length > 0)
			{
				string[] words = Spludlow.Text.Split(printerInfo, ',');

				printerName = words[0];

				if (words.Length > 1)
					paperSource = words[1];
			}

			this.PrintPaper(printData, printerName, paperSource);
		}

		public void PrintPaper(string printData, string printerName, string paperSource)
		{
			this.Filename = null;

			this.PageIndex = 0;

			this.PageBreak = false;

			this.HardwareOffset = new PointF(-1, -1);

			using (this.PrintDocReader = new PrintDocReader(printData, this))
			{
				string pageSize = Spludlow.Printing.PageSizes.PaperName(this.PrintDocReader.Header.PageSize);
				bool landscape = false;
				if (pageSize.EndsWith("*") == true)
				{
					pageSize = pageSize.Substring(0, pageSize.Length - 1);
					landscape = true;
				}

				string filename = null;
				if (paperSource != null && (paperSource.Contains(@":\") == true || paperSource.Contains(@"\\") == true))	//	Paper source is filename
				{
					filename = paperSource;
					paperSource = null;
				}
				
				using (PrintDocument printDocument = Spludlow.Printing.PrinterSetting.CreatePrintDocument(pageSize, landscape, printerName, paperSource))
				{
					printDocument.DocumentName = this.PrintDocReader.Header.Description;

					printDocument.PrintPage += new PrintPageEventHandler(printDocument_PrintPage);

					if (filename != null)
					{
						printDocument.PrinterSettings.GetHdevmode();

						printDocument.PrinterSettings.PrintToFile = true;
						printDocument.PrinterSettings.PrintFileName = filename;
					}

					printDocument.Print();
				}
			}
		}

		void printDocument_PrintPage(object sender, PrintPageEventArgs e)
		{
			this.Graphics = e.Graphics;

			this.Graphics.PageUnit = GraphicsUnit.Millimeter;

			if (this.HardwareOffset.X == -1)
			{
				PointF offset = Spludlow.Printing.PrinterSetting.ConfigPrinterOffset(e.PageSettings);

				bool offsetDefined = (offset.X != -1);
				bool configZero = (offsetDefined == true && (offset.X == 0 && offset.Y == 0));

				if (offsetDefined == false || configZero == true)
				{
					float xOffset = (float)Math.Round(e.PageSettings.PrintableArea.X * 0.254F, 1);
					float yOffset = (float)Math.Round(e.PageSettings.PrintableArea.Y * 0.254F, 1);

					if (configZero == false)
					{
						offset = new PointF(xOffset, yOffset);
					}
					else
					{
						Spludlow.Log.Warning("Printer Configured 0,0 Offset. Auto Offset would be: " + xOffset + ", " + yOffset +
							", printer:" + e.PageSettings.PrinterSettings.PrinterName + ", landscape:" + e.PageSettings.Landscape + ", tray:" + e.PageSettings.PaperSource.SourceName);
					}
				}

				this.HardwareOffset = offset;
			}

			this.Graphics.TranslateTransform(-this.HardwareOffset.X, -this.HardwareOffset.Y);		//	Could get reset !!!

			SetUpGraphics(this.Graphics, false);

			e.HasMorePages = false;

			while (this.PrintDocReader.PrintNext() == true)
			{
				if (this.PageBreak == true)
				{
					this.PageBreak = false;
					e.HasMorePages = true;
					break;
				}
			}
		}
		
		public string[] PrintBitmap(string printData, string printerInfo)
		{
			if (printerInfo == null || printerInfo.Length == 0)
				printerInfo = Spludlow.WebServices.CreateProgramDataTempFilename(".png");

			string[] parts = Spludlow.Text.Split(printerInfo, ',');

			if (parts.Length < 1 || parts.Length > 3)
				throw new ApplicationException("PrintBitmap Bad argument count: " + printerInfo);

			string filename = parts[0];
			int dpi = 600;
			PixelFormat pixelFormat = PixelFormat.Format24bppRgb;
			ImageFormat imageFormat = Spludlow.Drawing.BitmapFormats.ParseImageFormat(Path.GetExtension(filename).Substring(1));

			if (imageFormat == ImageFormat.Gif)
				pixelFormat = PixelFormat.Format8bppIndexed;

			if (parts.Length > 1)
				dpi = (int)Single.Parse(parts[1]);		//	DPI should all use float, needs changing from int

			if (parts.Length > 2)
				pixelFormat = Spludlow.Drawing.BitmapFormats.ParsePixelFormat(parts[2]);

			return this.PrintBitmap(printData, filename, dpi, pixelFormat, imageFormat);
		}

		public string[] PrintBitmap(string printData, string filename, int dpi, PixelFormat pixelFormat, ImageFormat imageFormat)
		{
			this.Filename = filename;

			this.PageIndex = -1;

			this.SaveFilenames = new List<string>();

			this.BitmapFormats = new Drawing.BitmapFormats(dpi, pixelFormat, imageFormat);

			using (this.PrintDocReader = new PrintDocReader(printData, this))
			{
				this.Digits = new string('0', this.PrintDocReader.Header.PageCount.ToString().Length);

				this.NewPage();

				this.PrintDocReader.Print();
			}

			this.Save();

			return this.SaveFilenames.ToArray();
		}

		public void NewPage()
		{
			//	Paper Print
			if (this.Filename == null)
			{
				this.PageBreak = true;
			}
			else
			{	//	Bitmap Print
				this.Save();

				int width = Spludlow.Printing.PageSizes.MillimetersToPixels(this.PrintDocReader.Header.PageSize.Width, this.BitmapFormats.Dpi);
				int height = Spludlow.Printing.PageSizes.MillimetersToPixels(this.PrintDocReader.Header.PageSize.Height, this.BitmapFormats.Dpi);

				this.PageBitmap = new Bitmap(width, height, this.BitmapFormats.PixelFormat);

				this.PageBitmap.SetResolution(this.BitmapFormats.Dpi, this.BitmapFormats.Dpi);

				this.Graphics = Graphics.FromImage(this.PageBitmap);

				SetUpGraphics(this.Graphics, true);        //	give fluffy bar codes with false

				this.Graphics.PageUnit = GraphicsUnit.Millimeter;

				this.Graphics.Clear(Color.White);
			}

			++this.PageIndex;
		}

		public void Save()
		{
			if (this.PageBitmap == null)
				return;

			this.Graphics.Dispose();

			string filename = this.Filename;

			if (this.PrintDocReader.Header.PageCount > 1)
				filename = Path.GetDirectoryName(this.Filename) + @"\" + Path.GetFileNameWithoutExtension(this.Filename) + "-" + (this.PageIndex + 1).ToString(this.Digits) + Path.GetExtension(this.Filename);

			this.PageBitmap.Save(filename, this.BitmapFormats.ImageFormat);

			this.SaveFilenames.Add(filename);

			this.PageBitmap.Dispose();
			this.PageBitmap = null;
		}

		public void Cross(float x, float y)
		{
			float size = 1.0F;
			float width = 0.25F;

			this.Line(x - size, y, x + size, y, width, System.Drawing.Color.Black);
			this.Line(x, y - size, x, y + size, width, System.Drawing.Color.Black);
		}

		public void Text(string text, string font, float x, float y, System.Drawing.Color colour, float angle, StringAlignment alignment, float strokeWidth, Color strokeColour)
		{
			StringFormat format = new StringFormat();

			format.Alignment = alignment;

			if (format.Alignment == StringAlignment.Center)
			{
				format.LineAlignment = StringAlignment.Center;
			}

			PointF drawPoint = new PointF(x, y);

			if (angle != 0)
			{
				this.Graphics.TranslateTransform(x, y);
				this.Graphics.RotateTransform(angle);

				drawPoint = new PointF(0, 0);
			}

			if (strokeWidth != 0 && strokeColour != Color.Empty)
			{
				GraphicsPath graphicsPath = new GraphicsPath();

				Spludlow.Drawing.FontInfo fontInfo = new Drawing.FontInfo(font);
				
				float PointsInMm = 2.83464567F;

				float emSize = fontInfo.Size / PointsInMm;

				Point point = new Point((int)x, (int)y);

				graphicsPath.AddString(text, Spludlow.Drawing.ArtChest.Font(font).FontFamily, (int)fontInfo.FontStyles, emSize, point, format);

				Brush fillBrush = Spludlow.Drawing.ArtChest.SolidBrush(colour);
				Pen outlinePen = Spludlow.Drawing.ArtChest.Pen(strokeWidth / PointsInMm, strokeColour);

				this.Graphics.FillPath(fillBrush, graphicsPath);
				this.Graphics.DrawPath(outlinePen, graphicsPath);
			}
			else
			{
				this.Graphics.DrawString(text, Spludlow.Drawing.ArtChest.Font(font), Spludlow.Drawing.ArtChest.SolidBrush(colour), drawPoint, format);
			}

			if (angle != 0)
				this.Graphics.ResetTransform();

		}

		public void TextBox(string text, string fontInfo, float x, float y, float width, float height, System.Drawing.Color colour, System.Drawing.StringAlignment alignment, float strokeWidth, System.Drawing.Color strokeColour)
		{
			this.TextBox(text, fontInfo, x, y, width, height, colour, 0.0F, alignment, strokeWidth, strokeColour);
		}
		public void TextBox(string text, string font, float x, float y, float width, float height, System.Drawing.Color colour, float angle, System.Drawing.StringAlignment alignment, float strokeWidth, System.Drawing.Color strokeColour)
		{
			StringFormat format = new StringFormat();	//	dispose !!!

			format.Alignment = alignment;

			if (format.Alignment == StringAlignment.Center)
				format.LineAlignment = StringAlignment.Center;

			RectangleF rectangle = new RectangleF(x, y, width, height);


			if (angle != 0)
			{
				switch (angle)
				{
					case 90:
						rectangle = new RectangleF(0, 0, height, width);
						this.Graphics.TranslateTransform(x + width, y);
						break;
					case 180:
						rectangle = new RectangleF(0, 0, width, height);
						this.Graphics.TranslateTransform(x + width, y + height);
						break;
					case 270:
						rectangle = new RectangleF(0, 0, height, width);
						this.Graphics.TranslateTransform(x, y + height);
						break;
					default:
						throw new ApplicationException("Text Box rotate angle must be 90, 180, oe 270 only");
				}

				this.Graphics.RotateTransform(angle);
			}

			if (strokeWidth != 0 && strokeColour != Color.Empty)
			{
				GraphicsPath graphicsPath = new GraphicsPath();	//	Dispose !!

				Spludlow.Drawing.FontInfo fontInfo = new Drawing.FontInfo(font);


				float PointsInMm = 2.83464567F;

				float emSize = fontInfo.Size / PointsInMm;

				graphicsPath.AddString(text, Spludlow.Drawing.ArtChest.Font(font).FontFamily, (int)fontInfo.FontStyles, emSize, rectangle, format);

				Brush fillBrush = Spludlow.Drawing.ArtChest.SolidBrush(colour);
				Pen outlinePen = Spludlow.Drawing.ArtChest.Pen(strokeWidth / PointsInMm, strokeColour);

				this.Graphics.FillPath(fillBrush, graphicsPath);
				this.Graphics.DrawPath(outlinePen, graphicsPath);
			}
			else
			{
				this.Graphics.DrawString(text, Spludlow.Drawing.ArtChest.Font(font), Spludlow.Drawing.ArtChest.SolidBrush(colour), rectangle, format);
			}

			if (angle != 0)
				this.Graphics.ResetTransform();
		}



		public void Line(float x1, float y1, float x2, float y2, float lineWidth, Color lineColour)
		{
			this.Graphics.DrawLine(Spludlow.Drawing.ArtChest.Pen(lineWidth, lineColour), x1, y1, x2, y2);
		}

		public void Rectangle(float x, float y, float width, float height, float lineWidth, Color lineColour, Color fillColour)
		{
			lineWidth *= 0.3527777777778F;

			if (fillColour != Color.Empty)
				this.Graphics.FillRectangle(Spludlow.Drawing.ArtChest.SolidBrush(fillColour), x, y, width, height);

			if (lineColour != Color.Empty && lineWidth != 0)
				this.Graphics.DrawRectangle(Spludlow.Drawing.ArtChest.Pen(lineWidth, lineColour), x, y, width, height);
		}


		public void Cirlce(float x, float y, float radius, float lineWidth, System.Drawing.Color lineColour, System.Drawing.Color fillColour)
		{
			x -= radius;
			y -= radius;

			if (fillColour != Color.Empty)
				this.Graphics.FillEllipse(Spludlow.Drawing.ArtChest.SolidBrush(fillColour), x, y, radius * 2, radius * 2);

			if (lineColour != Color.Empty && lineWidth != 0)
				this.Graphics.DrawEllipse(Spludlow.Drawing.ArtChest.Pen(lineWidth, lineColour), x, y, radius * 2, radius * 2);
		}

		public void Ellipse(float x, float y, float xRadius, float yRadius, float lineWidth, System.Drawing.Color lineColour, System.Drawing.Color fillColour)
		{
			if (fillColour != Color.Empty)
				this.Graphics.FillEllipse(Spludlow.Drawing.ArtChest.SolidBrush(fillColour), x, y, xRadius * 2, yRadius * 2);

			if (lineColour != Color.Empty && lineWidth != 0)
				this.Graphics.DrawEllipse(Spludlow.Drawing.ArtChest.Pen(lineWidth, lineColour), x, y, xRadius * 2, yRadius * 2);
		}

		public void Arc(float x, float y, float xRadius, float yRadius, float startAngle, float endAngle, float lineWidth, System.Drawing.Color lineColour)
		{
			this.Graphics.DrawArc(Spludlow.Drawing.ArtChest.Pen(lineWidth, lineColour), x - xRadius, y - yRadius, xRadius * 2, yRadius * 2, startAngle - 90, endAngle - startAngle);
		}

		public void ArcSector(float x, float y, float xOuterRadius, float yOuterRadius, float xInnerRadius, float yInnerRadius, float startAngle, float endAngle, float lineWidth, Color lineColour, Color fillColour)
		{
			PointF[] points = Spludlow.Drawing.Maths.ArcSector(x, y, xOuterRadius, yOuterRadius, xInnerRadius, yInnerRadius, startAngle, endAngle);

			this.Shape(points, lineWidth, lineColour, fillColour);
		}

		public void Polygon(float x, float y, float xRadius, float yRadius, int sides, float rotateAngle, float lineWidth, System.Drawing.Color lineColour, System.Drawing.Color fillColour)
		{
			PointF[] points = Spludlow.Drawing.Maths.Polygon(x, y, xRadius, yRadius, sides, rotateAngle);

			this.Shape(points, lineWidth, lineColour, fillColour);
		}

		public void Shape(PointF[] points, float lineWidth, Color lineColour, Color fillColour)
		{
			if (fillColour != Color.Empty)
				this.Graphics.FillPolygon(Spludlow.Drawing.ArtChest.SolidBrush(fillColour), points);

			if (lineColour != Color.Empty && lineWidth != 0)
				this.Graphics.DrawPolygon(Spludlow.Drawing.ArtChest.Pen(lineWidth, lineColour), points);
		}

		public void Curve(PointF[] points, float lineWidth, Color lineColour)
		{
			this.Graphics.DrawCurve(Spludlow.Drawing.ArtChest.Pen(lineWidth, lineColour), points);
		}

		public void Lines(PointF[] points, float lineWidth, Color lineColour)
		{
			this.Graphics.DrawLines(Spludlow.Drawing.ArtChest.Pen(lineWidth, lineColour), points);
		}

		public void Place(string filename, float x, float y, float scale, int sourcePageNumber)
		{
			filename = Spludlow.WebServices.GetProgramDataTempFile(filename);

			if (Path.GetExtension(filename).ToLower() == ".pdf")
				this.PlaceVector(filename, x, y, scale, sourcePageNumber);
			else
				this.PlaceBitmap(filename, x, y, scale);
		}

		public void PlaceBitmap(string filename, float x, float y, float scale)
		{
			using (Image image = Image.FromFile(filename))
			{
				if (scale == 1)
				{
					this.Graphics.DrawImageUnscaled(image, (int)x, (int)y);
				}
				else
				{
					float width = Spludlow.Printing.PageSizes.PixelsToMillimeters(image.Width, (int)image.HorizontalResolution) * scale;
					float height = Spludlow.Printing.PageSizes.PixelsToMillimeters(image.Height, (int)image.VerticalResolution) * scale;

					this.Graphics.DrawImage(image, x, y, width, height);
				}
			}
		}

		private void PlaceVector(string filename, float x, float y, float scale, int sourcePageNumber)
		{
			int dpi = (int)(this.Graphics.DpiX * scale);

			using (Spludlow.TempDirectory tempDir = new TempDirectory())
			{
				string[] filenames = Spludlow.Printing.GhostScript.RenderPdf(filename, tempDir + @"\output.png", dpi, true);

				if (sourcePageNumber > filenames.Length)
					throw new ApplicationException("Graphics Printer, Place Vector: Not that many pages: " + sourcePageNumber + ", pages in PDF: " + filenames.Length);

				this.PlaceBitmap(filenames[sourcePageNumber - 1], x, y, scale);
			}
		}

		public void PlaceAngle(string filename, float x, float y, float scale, float angle, int sourcePageNumber)
		{
			filename = Spludlow.WebServices.GetProgramDataTempFile(filename);

			if (Path.GetExtension(filename).ToLower() == ".pdf")
				this.PlaceVectorAngle(filename, x, y, scale, angle, sourcePageNumber);
			else
				this.PlaceBitmapAngle(filename, x, y, scale, angle);
		}

		public void PlaceBitmapAngle(string filename, float x, float y, float scale, float angle)
		{
			using (Image image = Image.FromFile(filename))
			{
				float width = Spludlow.Printing.PageSizes.PixelsToMillimeters(image.Width, (int)image.HorizontalResolution);
				float height = Spludlow.Printing.PageSizes.PixelsToMillimeters(image.Height, (int)image.VerticalResolution);

				float halfWidth = width / 2.0F;
				float halfHeight = height / 2.0F;

				PointF origin = new PointF(x, y);

				PointF upperLeft = new PointF(x - halfWidth, y - halfHeight);
				PointF upperRight = new PointF(x + halfWidth, y - halfHeight);
				PointF lowerLeft = new PointF(x - halfWidth, y + halfHeight);

				upperLeft = Spludlow.Drawing.Maths.TransformPoint(upperLeft, scale, angle, origin);
				upperRight = Spludlow.Drawing.Maths.TransformPoint(upperRight, scale, angle, origin);
				lowerLeft = Spludlow.Drawing.Maths.TransformPoint(lowerLeft, scale, angle, origin);

				this.Graphics.DrawImage(image, new PointF[] { upperLeft, upperRight, lowerLeft });
			}
		}

		private void PlaceVectorAngle(string filename, float x, float y, float scale, float angle, int sourcePageNumber)
		{
			int dpi = (int)(this.Graphics.DpiX * scale);

			using (Spludlow.TempDirectory tempDir = new TempDirectory())
			{
				string[] filenames = Spludlow.Printing.GhostScript.RenderPdf(filename, tempDir + @"\output.png", dpi, true);

				if (sourcePageNumber > filenames.Length)
					throw new ApplicationException("Graphics Printer, Place Vector: Not that many pages: " + sourcePageNumber + ", pages in PDF: " + filenames.Length);

				this.PlaceBitmapAngle(filenames[sourcePageNumber - 1], x, y, scale, angle);
			}
		}

		public void BarCode(string data, string type, float x, float y, float width, float height, Color foreColour, Color backColour, bool rotate)
		{
			//	Switch off-on alntilising !

			Spludlow.Drawing.BarCode.DrawBarcode(this, data, type, x, y, width, height, foreColour, backColour, rotate);
		}
	}
}