using System;
using System.Printing;
using System.Windows.Documents;
using System.Windows.Documents.Serialization;
using System.Windows.Media;
using System.Windows.Xps;
using System.Windows.Xps.Packaging;
using System.Drawing.Printing;
using System.Reflection;

namespace ghostnet_wpf_example
{
	public enum PrintStatus_t
	{
		PRINT_READY,
		PRINT_BUSY,
		PRINT_ERROR,
		PRINT_CANCELLED,
		PRINT_DONE
	};

	/* Class for handling async print progress callback */
	public class gsPrintEventArgs : EventArgs
	{
		private PrintStatus_t m_status;
		private bool m_completed;
		private int m_page;
		private int m_page_start;
		private int m_num_pages;
		private String m_filename;

		public String FileName
		{
			get { return m_filename; }
		}
		public PrintStatus_t Status
		{
			get { return m_status; }
		}

		public bool Completed
		{
			get { return m_completed; }
		}

		public int Page
		{
			get { return m_page; }
		}

		public int PageStart
		{
			get { return m_page_start; }
		}

		public int NumPages
		{
			get { return m_num_pages; }
		}

		public gsPrintEventArgs(PrintStatus_t status, bool completed, int page,
			int page_start, int num_pages, String filename)
		{
			m_completed = completed;
			m_status = status;
			m_page = page;
			m_page_start = page_start;
			m_num_pages = num_pages;
			m_filename = filename;
		}
	}

	/* A page range paginator to override the DocumentPaginator */
	public class GSDocumentPaginator : DocumentPaginator
	{
		private int _first;
		private int _last;
		private DocumentPaginator _basepaginator;
		public GSDocumentPaginator(DocumentPaginator inpaginator, int firstpage,
									int lastpage)
		{
			_first = firstpage - 1;
			_last = lastpage - 1;
			_basepaginator = inpaginator;

			_last = Math.Min(_last, _basepaginator.PageCount - 1);
		}

		/* Wrap fixed page to avoid exception of fixed page in fixed page */
		public override DocumentPage GetPage(int page_num)
		{
			var page = _basepaginator.GetPage(page_num + _first);

			var vis_cont = new ContainerVisual();
			if (page.Visual is FixedPage)
			{
				foreach (var child in ((FixedPage)page.Visual).Children)
				{
					var clone = (System.Windows.UIElement)child.GetType().GetMethod("MemberwiseClone",
						BindingFlags.Instance | BindingFlags.NonPublic).Invoke(child, null);

					var parentField = clone.GetType().GetField("_parent",
						BindingFlags.Instance | BindingFlags.NonPublic);
					if (parentField != null)
					{
						parentField.SetValue(clone, null);
						vis_cont.Children.Add(clone);
					}
				}
				return new DocumentPage(vis_cont, page.Size, page.BleedBox,
										page.ContentBox);
			}
			return page;
		}

		public override bool IsPageCountValid
		{
			get { return true; }
		}

		public override int PageCount
		{
			get
			{
				if (_first > _basepaginator.PageCount - 1)
					return 0;
				if (_first > _last)
					return 0;
				return _last - _first + 1;
			}
		}

		public override System.Windows.Size PageSize
		{
			get { return _basepaginator.PageSize; }
			set { _basepaginator.PageSize = value; }
		}

		public override IDocumentPaginatorSource Source
		{
			get { return _basepaginator.Source; }
		}

		public override void ComputePageCount()
		{
			base.ComputePageCount();
		}

		protected override void OnGetPageCompleted(GetPageCompletedEventArgs e)
		{
			base.OnGetPageCompleted(e);
		}
	}

	public class xpsprint
	{
		private XpsDocumentWriter m_docWriter = null;
		internal delegate void AsyncPrintCallBack(object printObject, gsPrintEventArgs info);
		internal event AsyncPrintCallBack PrintUpdate;
		private bool m_busy;
		private int m_num_pages;
		private int m_first_page;
		private String m_filename;

		public bool IsBusy()
		{
			return m_busy;
		}

		public xpsprint()
		{
			m_busy = false;
		}

		public void Done()
		{
			gsPrintEventArgs info = new gsPrintEventArgs(PrintStatus_t.PRINT_DONE,
					true, 0, 0, 0, this.m_filename);
			PrintUpdate(this, info);
			m_busy = false;
		}

		/* Main print entry point */
		public void Print(PrintQueue queu, XpsDocument xpsDocument, Print printcontrol,
			bool print_all, int from, int to, String filename, bool tempfile)
		{
			XpsDocumentWriter docwrite;
			FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence();
			DocumentReference docReference = fixedDocSeq.References[0] ;
			FixedDocument doc = docReference.GetDocument(false);

			PrintTicket Ticket = SetUpTicket(queu, printcontrol, fixedDocSeq, tempfile);
			docwrite = GetDocWriter(queu);
			m_busy = true;
			m_filename = filename;
#if DISABLED_FOR_NOW
			docwrite.WritingPrintTicketRequired +=
			 new WritingPrintTicketRequiredEventHandler(PrintTicket);
#endif
			PrintPages(docwrite, doc, print_all, from, to, Ticket);
		}

		/* Set up the print ticket */
		private PrintTicket SetUpTicket(PrintQueue queue, Print printcontrol,
			FixedDocumentSequence fixdoc, bool tempfile)
		{
			PrintTicket Ticket = new PrintTicket();

			PageMediaSizeName name = PaperKindToPageMediaSize(printcontrol.m_pagedetails.PaperSize.Kind);
			PageMediaSize mediasize = new PageMediaSize(name, printcontrol.m_pagedetails.PaperSize.Width, printcontrol.m_pagedetails.PaperSize.Height);

			/* Media size */
			Ticket.PageMediaSize = mediasize;
			/* Scale to fit */
			Ticket.PageScalingFactor = (int)Math.Round(printcontrol.m_page_scale * 100.0);

			System.Windows.Size page_size = new System.Windows.Size(mediasize.Width.Value, mediasize.Height.Value);
			DocumentPaginator paginator = fixdoc.DocumentPaginator;
			paginator.PageSize = page_size;

			/* Copy Count */
			Ticket.CopyCount = printcontrol.m_numcopies;

			if (printcontrol.m_pagedetails.Landscape)
				Ticket.PageOrientation = PageOrientation.Landscape;
			else
				Ticket.PageOrientation = PageOrientation.Portrait;

			/* Orientation. If we had a tempfile, then gs did a conversion
			 * and adjusted everything for us.  Other wise we may need to
			 * rotate the document if it was xps to start with. */
			if (printcontrol.m_isrotated && !tempfile)
				if (printcontrol.m_pagedetails.Landscape)
					Ticket.PageOrientation = PageOrientation.Portrait;
				else
					Ticket.PageOrientation = PageOrientation.Landscape;

			System.Printing.ValidationResult result = queue.MergeAndValidatePrintTicket(queue.UserPrintTicket, Ticket);
			queue.UserPrintTicket = result.ValidatedPrintTicket;
			queue.Commit();
			return result.ValidatedPrintTicket;
		}

		/* Send it */
		private void PrintPages(XpsDocumentWriter xpsdw, FixedDocument fixdoc,
			bool print_all, int from, int to, PrintTicket Ticket)
		{
			m_docWriter = xpsdw;
			xpsdw.WritingCompleted +=
				new WritingCompletedEventHandler(AsyncCompleted);
			xpsdw.WritingProgressChanged +=
				new WritingProgressChangedEventHandler(AsyncProgress);

			DocumentPaginator paginator = fixdoc.DocumentPaginator;
			try
			{
				if (print_all)
				{
					m_first_page = 1;
					m_num_pages = paginator.PageCount;
					xpsdw.Write(paginator, Ticket);
				}
				else
				{
					/* Create an override paginator to pick only the pages we want */
					GSDocumentPaginator gspaginator =
						new GSDocumentPaginator(paginator, from, to);
					m_first_page = from;
					m_num_pages = paginator.PageCount;
					xpsdw.Write(gspaginator, Ticket);
				}
			}
			catch (Exception)
			{
				/* Something went wrong with this particular print driver
				 * simply notify the user and clean up everything */
				gsPrintEventArgs info = new gsPrintEventArgs(PrintStatus_t.PRINT_ERROR,
						false, 0, this.m_first_page, this.m_num_pages, this.m_filename);
				PrintUpdate(this, info);
				return;
			}
		}
		public void CancelAsync()
		{
			/* ick.  This does not work in windows 8. causes crash. */
			/* https://connect.microsoft.com/VisualStudio/feedback/details/778145/xpsdocumentwriter-cancelasync-cause-crash-in-win8 */
			m_docWriter.CancelAsync();
		}

		/* Done */
		private void AsyncCompleted(object sender, WritingCompletedEventArgs e)
		{
			PrintStatus_t status;

			if (e.Cancelled)
				status = PrintStatus_t.PRINT_CANCELLED;
			else if (e.Error != null)
				status = PrintStatus_t.PRINT_ERROR;
			else
				status = PrintStatus_t.PRINT_READY;

			if (PrintUpdate != null)
			{
				gsPrintEventArgs info = new gsPrintEventArgs(status, true, 0,
					this.m_first_page, this.m_num_pages, this.m_filename);
				PrintUpdate(this, info);
			}
			m_busy = false;
		}

		/* Do this update with each fixed document (page) that is handled */
		private void AsyncProgress(object sender, WritingProgressChangedEventArgs e)
		{
			if (PrintUpdate != null)
			{
				gsPrintEventArgs info = new gsPrintEventArgs(PrintStatus_t.PRINT_BUSY,
									false, e.Number, this.m_first_page, this.m_num_pages,
									this.m_filename);
				PrintUpdate(this, info);
			}
		}
#if DISABLED_FOR_NOW
		/* Print ticket handling. You can customize for PrintTicketLevel at
		   FixedDocumentSequencePrintTicket, FixedDocumentPrintTicket,
		   or FixedPagePrintTicket.  We may want to play around with this some */
		private void PrintTicket(Object sender, WritingPrintTicketRequiredEventArgs e)
		{
			if (e.CurrentPrintTicketLevel ==
					PrintTicketLevel.FixedDocumentSequencePrintTicket)
			{
				PrintTicket pts = new PrintTicket();
				e.CurrentPrintTicket = pts;
			}
		}
#endif
		/* Create the document write */
		private XpsDocumentWriter GetDocWriter(PrintQueue pq)
		{
			XpsDocumentWriter xpsdw = PrintQueue.CreateXpsDocumentWriter(pq);
			return xpsdw;
		}

		/* Two paths for designating printing = a pain in the ass.*/
		static PageMediaSizeName PaperKindToPageMediaSize(PaperKind paperKind)
		{
			switch (paperKind)
			{
				case PaperKind.Custom:
					return PageMediaSizeName.Unknown;
				case PaperKind.Letter:
					return PageMediaSizeName.NorthAmericaLetter;
				case PaperKind.Legal:
					return PageMediaSizeName.NorthAmericaLegal;
				case PaperKind.A4:
					return PageMediaSizeName.ISOA4;
				case PaperKind.CSheet:
					return PageMediaSizeName.NorthAmericaCSheet;
				case PaperKind.DSheet:
					return PageMediaSizeName.NorthAmericaDSheet;
				case PaperKind.ESheet:
					return PageMediaSizeName.NorthAmericaESheet;
				case PaperKind.LetterSmall:
					return PageMediaSizeName.Unknown;
				case PaperKind.Tabloid:
					return PageMediaSizeName.NorthAmericaTabloid;
				case PaperKind.Ledger:
					return PageMediaSizeName.Unknown;
				case PaperKind.Statement:
					return PageMediaSizeName.NorthAmericaStatement;
				case PaperKind.Executive:
					return PageMediaSizeName.NorthAmericaExecutive;
				case PaperKind.A3:
					return PageMediaSizeName.ISOA3;
				case PaperKind.A4Small:
					return PageMediaSizeName.Unknown;
				case PaperKind.A5:
					return PageMediaSizeName.ISOA5;
				case PaperKind.B4:
					return PageMediaSizeName.ISOB4;
				case PaperKind.B5:
					return PageMediaSizeName.Unknown;
				case PaperKind.Folio:
					return PageMediaSizeName.OtherMetricFolio;
				case PaperKind.Quarto:
					return PageMediaSizeName.NorthAmericaQuarto;
				case PaperKind.Standard10x14:
					return PageMediaSizeName.Unknown;
				case PaperKind.Standard11x17:
					return PageMediaSizeName.Unknown;
				case PaperKind.Note:
					return PageMediaSizeName.NorthAmericaNote;
				case PaperKind.Number9Envelope:
					return PageMediaSizeName.NorthAmericaNumber9Envelope;
				case PaperKind.Number10Envelope:
					return PageMediaSizeName.NorthAmericaNumber10Envelope;
				case PaperKind.Number11Envelope:
					return PageMediaSizeName.NorthAmericaNumber11Envelope;
				case PaperKind.Number12Envelope:
					return PageMediaSizeName.NorthAmericaNumber12Envelope;
				case PaperKind.Number14Envelope:
					return PageMediaSizeName.NorthAmericaNumber14Envelope;
				case PaperKind.DLEnvelope:
					return PageMediaSizeName.ISODLEnvelope;
				case PaperKind.C5Envelope:
					return PageMediaSizeName.ISOC5Envelope;
				case PaperKind.C3Envelope:
					return PageMediaSizeName.ISOC3Envelope;
				case PaperKind.C4Envelope:
					return PageMediaSizeName.ISOC4Envelope;
				case PaperKind.C6Envelope:
					return PageMediaSizeName.ISOC6Envelope;
				case PaperKind.C65Envelope:
					return PageMediaSizeName.ISOC6C5Envelope;
				case PaperKind.B4Envelope:
					return PageMediaSizeName.ISOB4Envelope;
				case PaperKind.B5Envelope:
					return PageMediaSizeName.ISOB5Envelope;
				case PaperKind.B6Envelope:
					return PageMediaSizeName.Unknown;
				case PaperKind.ItalyEnvelope:
					return PageMediaSizeName.OtherMetricItalianEnvelope;
				case PaperKind.MonarchEnvelope:
					return PageMediaSizeName.NorthAmericaMonarchEnvelope;
				case PaperKind.PersonalEnvelope:
					return PageMediaSizeName.NorthAmericaPersonalEnvelope;
				case PaperKind.USStandardFanfold:
					return PageMediaSizeName.Unknown;
				case PaperKind.GermanStandardFanfold:
					return PageMediaSizeName.NorthAmericaGermanStandardFanfold;
				case PaperKind.GermanLegalFanfold:
					return PageMediaSizeName.NorthAmericaGermanLegalFanfold;
				case PaperKind.IsoB4:
					return PageMediaSizeName.ISOB4;
				case PaperKind.JapanesePostcard:
					return PageMediaSizeName.JapanHagakiPostcard;
				case PaperKind.Standard9x11:
					return PageMediaSizeName.Unknown;
				case PaperKind.Standard10x11:
					return PageMediaSizeName.Unknown;
				case PaperKind.Standard15x11:
					return PageMediaSizeName.Unknown;
				case PaperKind.InviteEnvelope:
					return PageMediaSizeName.OtherMetricInviteEnvelope;
				case PaperKind.LetterExtra:
					return PageMediaSizeName.NorthAmericaLetterExtra;
				case PaperKind.LegalExtra:
					return PageMediaSizeName.NorthAmericaLegalExtra;
				case PaperKind.TabloidExtra:
					return PageMediaSizeName.NorthAmericaTabloidExtra;
				case PaperKind.A4Extra:
					return PageMediaSizeName.ISOA4Extra;
				case PaperKind.LetterTransverse:
					return PageMediaSizeName.Unknown;
				case PaperKind.A4Transverse:
					return PageMediaSizeName.Unknown;
				case PaperKind.LetterExtraTransverse:
					return PageMediaSizeName.Unknown;
				case PaperKind.APlus:
					return PageMediaSizeName.Unknown;
				case PaperKind.BPlus:
					return PageMediaSizeName.Unknown;
				case PaperKind.LetterPlus:
					return PageMediaSizeName.NorthAmericaLetterPlus;
				case PaperKind.A4Plus:
					return PageMediaSizeName.OtherMetricA4Plus;
				case PaperKind.A5Transverse:
					return PageMediaSizeName.Unknown;
				case PaperKind.B5Transverse:
					return PageMediaSizeName.Unknown;
				case PaperKind.A3Extra:
					return PageMediaSizeName.ISOA3Extra;
				case PaperKind.A5Extra:
					return PageMediaSizeName.ISOA5Extra;
				case PaperKind.B5Extra:
					return PageMediaSizeName.ISOB5Extra;
				case PaperKind.A2:
					return PageMediaSizeName.ISOA2;
				case PaperKind.A3Transverse:
					return PageMediaSizeName.Unknown;
				case PaperKind.A3ExtraTransverse:
					return PageMediaSizeName.Unknown;
				case PaperKind.JapaneseDoublePostcard:
					return PageMediaSizeName.JapanDoubleHagakiPostcard;
				case PaperKind.A6:
					return PageMediaSizeName.ISOA6;
				case PaperKind.JapaneseEnvelopeKakuNumber2:
					return PageMediaSizeName.JapanKaku2Envelope;
				case PaperKind.JapaneseEnvelopeKakuNumber3:
					return PageMediaSizeName.JapanKaku3Envelope;
				case PaperKind.JapaneseEnvelopeChouNumber3:
					return PageMediaSizeName.JapanChou3Envelope;
				case PaperKind.JapaneseEnvelopeChouNumber4:
					return PageMediaSizeName.JapanChou4Envelope;
				case PaperKind.LetterRotated:
					return PageMediaSizeName.NorthAmericaLetterRotated;
				case PaperKind.A3Rotated:
					return PageMediaSizeName.ISOA3Rotated;
				case PaperKind.A4Rotated:
					return PageMediaSizeName.ISOA4Rotated;
				case PaperKind.A5Rotated:
					return PageMediaSizeName.ISOA5Rotated;
				case PaperKind.B4JisRotated:
					return PageMediaSizeName.JISB4Rotated;
				case PaperKind.B5JisRotated:
					return PageMediaSizeName.JISB5Rotated;
				case PaperKind.JapanesePostcardRotated:
					return PageMediaSizeName.JapanHagakiPostcardRotated;
				case PaperKind.JapaneseDoublePostcardRotated:
					return PageMediaSizeName.JapanHagakiPostcardRotated;
				case PaperKind.A6Rotated:
					return PageMediaSizeName.ISOA6Rotated;
				case PaperKind.JapaneseEnvelopeKakuNumber2Rotated:
					return PageMediaSizeName.JapanKaku2EnvelopeRotated;
				case PaperKind.JapaneseEnvelopeKakuNumber3Rotated:
					return PageMediaSizeName.JapanKaku3EnvelopeRotated;
				case PaperKind.JapaneseEnvelopeChouNumber3Rotated:
					return PageMediaSizeName.JapanChou3EnvelopeRotated;
				case PaperKind.JapaneseEnvelopeChouNumber4Rotated:
					return PageMediaSizeName.JapanChou4EnvelopeRotated;
				case PaperKind.B6Jis:
					return PageMediaSizeName.JISB6;
				case PaperKind.B6JisRotated:
					return PageMediaSizeName.JISB6Rotated;
				case PaperKind.Standard12x11:
					return PageMediaSizeName.Unknown;
				case PaperKind.JapaneseEnvelopeYouNumber4:
					return PageMediaSizeName.JapanYou4Envelope;
				case PaperKind.JapaneseEnvelopeYouNumber4Rotated:
					return PageMediaSizeName.JapanYou4EnvelopeRotated;
				case PaperKind.Prc16K:
					return PageMediaSizeName.PRC16K;
				case PaperKind.Prc32K:
					return PageMediaSizeName.PRC32K;
				case PaperKind.Prc32KBig:
					return PageMediaSizeName.PRC32KBig;
				case PaperKind.PrcEnvelopeNumber1:
					return PageMediaSizeName.PRC1Envelope;
				case PaperKind.PrcEnvelopeNumber2:
					return PageMediaSizeName.PRC2Envelope;
				case PaperKind.PrcEnvelopeNumber3:
					return PageMediaSizeName.PRC3Envelope;
				case PaperKind.PrcEnvelopeNumber4:
					return PageMediaSizeName.PRC4Envelope;
				case PaperKind.PrcEnvelopeNumber5:
					return PageMediaSizeName.PRC5Envelope;
				case PaperKind.PrcEnvelopeNumber6:
					return PageMediaSizeName.PRC6Envelope;
				case PaperKind.PrcEnvelopeNumber7:
					return PageMediaSizeName.PRC7Envelope;
				case PaperKind.PrcEnvelopeNumber8:
					return PageMediaSizeName.PRC8Envelope;
				case PaperKind.PrcEnvelopeNumber9:
					return PageMediaSizeName.PRC9Envelope;
				case PaperKind.PrcEnvelopeNumber10:
					return PageMediaSizeName.PRC10Envelope;
				case PaperKind.Prc16KRotated:
					return PageMediaSizeName.PRC16KRotated;
				case PaperKind.Prc32KRotated:
					return PageMediaSizeName.PRC32KRotated;
				case PaperKind.Prc32KBigRotated:
					return PageMediaSizeName.Unknown;
				case PaperKind.PrcEnvelopeNumber1Rotated:
					return PageMediaSizeName.PRC1EnvelopeRotated;
				case PaperKind.PrcEnvelopeNumber2Rotated:
					return PageMediaSizeName.PRC2EnvelopeRotated;
				case PaperKind.PrcEnvelopeNumber3Rotated:
					return PageMediaSizeName.PRC3EnvelopeRotated;
				case PaperKind.PrcEnvelopeNumber4Rotated:
					return PageMediaSizeName.PRC4EnvelopeRotated;
				case PaperKind.PrcEnvelopeNumber5Rotated:
					return PageMediaSizeName.PRC5EnvelopeRotated;
				case PaperKind.PrcEnvelopeNumber6Rotated:
					return PageMediaSizeName.PRC6EnvelopeRotated;
				case PaperKind.PrcEnvelopeNumber7Rotated:
					return PageMediaSizeName.PRC7EnvelopeRotated;
				case PaperKind.PrcEnvelopeNumber8Rotated:
					return PageMediaSizeName.PRC8EnvelopeRotated;
				case PaperKind.PrcEnvelopeNumber9Rotated:
					return PageMediaSizeName.PRC9EnvelopeRotated;
				case PaperKind.PrcEnvelopeNumber10Rotated:
					return PageMediaSizeName.PRC10EnvelopeRotated;
				default:
					throw new ArgumentOutOfRangeException("paperKind");
			}
		}
	}
}
