using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Reflection; // Assembly.
// UWP-APIの使用
using Windows.Data.Pdf;


// ref:https://water2litter.net/rum/post/cs_pdf_wpf/

using EncType = System.Drawing.Imaging.Encoder;// 別名
using EncParamType = System.Drawing.Imaging.EncoderParameter;//別名

namespace ImageParams
{
	using EncType = System.Drawing.Imaging.Encoder;// 別名
	using EncParamType = System.Drawing.Imaging.EncoderParameter;//別名
	/// <summary>
	/// BitMapのSaveに使うImage Encodeパラメータの設定値の生成.
	/// var enc = new ImageParams.EncData("jpeg");
	/// //enc.SetImageType("PNG");
	/// bitmap.Save(xxx, enc.GetInfo(),enc.GetParams());
	/// 
	/// </summary>
	public class EncData {
		// SEE:https://dobon.net/vb/dotnet/graphics/encoderparameters.html
		private string ImageType = "jpeg";
		public EncData(string imageType = "jpeg") {
			SetImageType(imageType);
		}
		public long JpegQuality { get; set; } = 91;// 指定しないときの値は91と一致する
		/// <summary>
		/// イメージパラメータを設定する こんな感じで
		/// </summary>
		/// <param name="enc"></param>
		/// 
		public EncData SetImageType(string imageType) {
			imageType = imageType.ToLower();
			if (imageType == "jpeg" || imageType  == "jpg") {
				ImageType = "jpeg";
			} else if (imageType  == "png") {
				ImageType = "png";
			} else if (imageType == "tif" || imageType == "tiff") {
				ImageType = "tiff";
			} else {
				ImageType = imageType;// 小文字を設定
			}
			return this;
		}
		public int SaveImage(System.Drawing.Bitmap bitmap, string path, string imageType = null) {
			var p = GetParams();
			string imType = imageType?.ToLower();

			if (p != null) { 
				bitmap.Save(path, GetInfo(imType), GetParams());// imageTypeがnullならSetImageType()のものが使われる
			} else {
				// パラメータが空の場合
				var imageFormat = new System.Drawing.Imaging.ImageFormat(GetInfo(imType).FormatID);
				bitmap.Save(path, imageFormat);
			}
			return 0;
		}
		public System.Drawing.Imaging.EncoderParameters GetParams() {
			var encList = new List<System.Drawing.Imaging.EncoderParameter>();
			//encList.Clear();
			// 今はJPEGパラメータだけ
			encList.Add(new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, JpegQuality));
			//...
			// 結合
			var eps = new System.Drawing.Imaging.EncoderParameters(encList.Count);
			for (var i = 0; i < encList.Count; i++)
				eps.Param[i] = encList[i];
			return eps;
		}
		public System.Drawing.Imaging.ImageCodecInfo GetInfo(string it=null) {
			if ( it != null) {
				var ici = GetEncoderInfo($"image/{it}");
				return ici;
			} else {
				var ici = GetEncoderInfo($"image/{ImageType}");
				return ici;
			}
		}
		public static void GetSupportedParameters(System.Drawing.Bitmap bitmap1=null) {
			// https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-determine-the-parameters-supported-by-an-encoder
			// 現状Errorが発生して取得できない
			try {
				if (bitmap1 == null) {
					bitmap1 = new System.Drawing.Bitmap(100, 100);
					var destBitmapData = bitmap1.LockBits(
							new System.Drawing.Rectangle(0, 0, 100, 100),
							System.Drawing.Imaging.ImageLockMode.ReadOnly,
							bitmap1.PixelFormat
					);
					bitmap1.UnlockBits(destBitmapData);
				}

				var jpgEncoder = GetEncoderInfo(System.Drawing.Imaging.ImageFormat.Tiff);
				var paramList = bitmap1.GetEncoderParameterList(jpgEncoder.Clsid);
				var encParams = paramList.Param;

				for (int i = 0; i < encParams.Length; i++) {
					Console.WriteLine("Param " + i + " holds " + encParams[i].NumberOfValues +
						" items of type " +
						encParams[i].ValueType + "\r\n" + "Guid category: " + encParams[i].Encoder.Guid + "\r\n");
				}
			} catch (Exception e) {
				Console.WriteLine($"Ignore error ={e.ToString()}");
			}
		}
		// inner method.
		//ImageFormatで指定されたImageCodecInfoを探して返す
		private static System.Drawing.Imaging.ImageCodecInfo GetEncoderInfo(System.Drawing.Imaging.ImageFormat f) {
			var encs = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
			foreach (var enc in encs) {
				if (enc.FormatID == f.Guid) {
					return enc;
				}
			}
			return null;
		}
		private static System.Drawing.Imaging.ImageCodecInfo GetEncoderInfo(string mineType) {
			//GDI+ に組み込まれたイメージ エンコーダに関する情報をすべて取得
			System.Drawing.Imaging.ImageCodecInfo[] encs = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
			//指定されたMimeTypeを探して見つかれば返す
			foreach (System.Drawing.Imaging.ImageCodecInfo enc in encs) {
				if (enc.MimeType == mineType) {
					return enc;
				}
			}
			return null;
		}


	}
}

namespace CSRender {

	class Program {
		static string outuptImageDir = "";// /O
		static string dpi = "72.0";// /D
#if non
		static string boxSelect = "C";	//B<x>
		static string pageRange = "";	//P
		static string inputList = "";	//L
		static string offset = "";		//OFFSET
		static string overprint = "";	//OP
		static string tempDir	= "";	//T
		static string fileUpdate = "";	//U
#endif
		static string mode		= "page";	//MODE
		static string imageType = "JPG";	//JPG or /PNG
		static string jpegQ		= "91";		//JPEGQ
		static void DispHelp() {
			var pgName = Path.GetFileName(Path.GetFileName(Assembly.GetExecutingAssembly().Location));// プログラム名
			string  msg = 
				 $"{pgName} [/<Opts>] <PDFPath>\n"
				+$"* Render of PDF file\n"
				+$"\t/F <pdfPath> : pdfPath(pdfファイル名) /Fは省略可能\n"
				+$"\t/O <output directory> : 出力ディレクトリ\n"
				+$"\t/D <解像度> : 解像度指定 9 - 300dpi\n"

				+ $"\t/B<x> : ボックス指定 x:M/B/T/A/C(default)  ex. /BM (MediaBox/BleedBox/TrimBox/ArtBox/CropBox(*unsupport)\n"

				+$"\t/P <PageRange> : ページの範囲を指定する(省略時は全ページ)(*unsupport)\n"
				+$"\t\t連続した範囲を指定する場合は、ハイフン（'-'）を用いる\n"
				+$"\t\t複数のページ範囲を指定する場合は、カンマ（','）を用いる\n"

				+$"\t/L <input List text> : 入力PDFファイルリスト(*unsupport)\n"
				+$"\t/OFFSET <X> <Y> : ミリ単位でオフセットを指定する（省略時は共に0mm)(*unsupport)\n"
				+$"\t/OP <0 or 1> : オーバープリントのOn/Off (省略時は1)(*unsupport allready 1)\n"
				+$"\t/T  <tempPath> : テンポラリフォルダを指定（省略時は出力先フォルダと同じ(*unsupport no need)\n"
				+$"\t/U <0 or 1> : 同名上書き設定 0:上書きしない 1:上書き(*unsupport)\n"
				+$"\t/H or /?  : Help\n"

				+$"\t[Type of Image format]\n"
				+$"\t/JPG: JPEGファイル出力\n"
				+$"\t/PNG: PNGファイル出力\n"
				+$"\t/TIF: TIFファイル出力\n"
				+$"\t/GIF: GIFファイル出力\n"
				+$"\t/BMP: BMPファイル出力\n"
				+$"\t[JPEG Param]\n"
				+$"\t/JPEGQ <quality>: Jpegの品質指定1-100(default=91)\n"

				+$"\t[for TEST]\n"
				+$"\t/M <mode> : pageBitMap(default), pageBitMapImage, page(same as pageBitMap), org(orginal source)\n"
				+ "\n";
			Console.Write(msg);
		}
		/// <summary>
		/// PDFレンダラーメイン
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		static int Main(string[] args) {
			var pdfPath="";
			//static async Task Main(string[] args) {// async Task Main:おまじない Mainでawaitが書けるようになる C# 7.1からじゃないと書けない
			var qu = new Queue<string>(args); // 引数をQueに登録 (qu.Enqueue(a)でも可能)
			while (qu.Count > 0) {
				// 引数のパース qu.Dequeue()で次の要素を取得する
				var wd = qu.Dequeue();  // 取り出し
				var wdi = wd.ToUpper(); // 大文字で比較
				if (wdi == "/?" || wdi == "/H") {
					DispHelp();
					return -1;
				} else if (wdi == "/F") {
					pdfPath = (qu.Count > 0) ? qu.Dequeue() :"";// next word.
				} else if (wdi == "/O") {
					outuptImageDir = (qu.Count > 0) ? qu.Dequeue() :"";// next word.
				} else if (wdi == "/D") {
					dpi = (qu.Count > 0) ? qu.Dequeue():"";// next word.
					if ( !double.TryParse(dpi, out double dmy)) {
						Console.WriteLine($"解像度が不正です:/D {dpi}");
						DispHelp();
						return -1;
					}
				} else if (wdi == "/JPG" || wdi == "/JPEG") {
					imageType = "JPG";
				} else if (wdi == "/PNG") {
					imageType = "PNG";
				} else if (wdi == "/TIF") {
					imageType = "TIFF";
				} else if (wdi == "/GIF") {
					imageType = "GIF";
				} else if (wdi == "/BMP") {
					imageType = "BMP";
				} else if (wdi == "/JPEGQ") {
					jpegQ = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
					if (!int.TryParse(jpegQ, out int dmy)) {
						Console.WriteLine($"JPEG Qualityが不正です:/JPEGQ {jpegQ}");
						DispHelp();
						return -1;
					}
					if (  !(0 < dmy && dmy <= 100) ) {
						Console.WriteLine($"JPEG Qualityが不正です(not 1-100):/JPEGQ {dmy}");
						return -1;
					}

				} else if (wdi == "/M") {
					mode = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
				} else {
					// 引数をファイル名として取得する
					if (pdfPath == "") {
						pdfPath = wd;
					} 
				}
			}
			if (pdfPath=="") {
				Console.WriteLine("pdfファイルが指定されてません");
				DispHelp();
				return -1;
			}
			if ( !File.Exists(pdfPath) ) {
				Console.WriteLine($"ファイルが存在しません:{pdfPath}");
				return -1;
			}
			var watch = System.Diagnostics.Stopwatch.StartNew(); // 時間の生成と計測開始を同時に行う
			var otDir = "";
			if ( outuptImageDir == "" ) {  // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する
				otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG");
				if (!Directory.Exists(otDir))
					Directory.CreateDirectory(otDir);
			}
			var ret = RenderPdfDocAsync(
				pdfPath	:Path.GetFullPath(pdfPath),
				inDir	:otDir,
				inDpi	:double.Parse(dpi), 
				imageType:imageType,
				inJpegQ	:int.Parse(jpegQ)
			).Result;// スレッドの終了まで「待つ」
			watch.Stop();
			Console.WriteLine( $"result={ret},time={ watch.ElapsedMilliseconds/1000.0 }[sec]");
			return ret;//success.
		}

		public static async Task<int> RenderPdfDocAsync(string pdfPath, string inDir = "", double inDpi = 9.0, string imageType = "JPG", int inJpegQ = 0) {
			var otDir = inDir;
			var otBaseName = Path.GetFileName(pdfPath);//*.pdf
			var otExtention = imageType.ToLower();
			{  // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する
				otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG");
				if (!Directory.Exists(otDir))
					Directory.CreateDirectory(otDir);
			}

			// PDFファイルを読み込む
			var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfPath);
			var pdfDoc = await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(file);
			if (pdfDoc == null) {
				Console.WriteLine("error: get PdfDocument failed");
				return -1;
			}
			var n = pdfDoc.PageCount;
			if (mode == "page" || mode == "pageBitMap" || mode == "pageBitMapImage") {
				if  (mode == "page") {
					mode = "pageBitMap";
				}
				Console.WriteLine($"mode={mode}");
				var taskList = new List<Task<int>>();
				for (int i = 0; i < n; i++) {
					taskList.Add(
						RenderPageAync(
							doc		:pdfDoc, 
							page	:pdfDoc.GetPage((uint)i), 
							nPage	:i, 
							otPath	:Path.Combine(otDir, $"{otBaseName}.{i + 1}.{otExtention}"),
							inDpi	:inDpi, 
							encName	:imageType, 
							mode	:mode,
							inJpegQ	:inJpegQ
						)
					); // docを渡す
				}
				await Task.WhenAll(taskList);// すべてのタスクを待つ。//int[] arrayInt =
			} else if (mode == "org") {
				// 並列で動く唯一のもの(Test)
				var dir = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(otDir);
				Console.WriteLine("mode=org");
				var taskList = new List<Task<int>>();
				for (int i = 0; i < n; i++) {
					taskList.Add(RenderPageOrgASync(pdfDoc.GetPage((uint)i), dir)); // docを渡す
				}
				await Task.WhenAll(taskList);// すべてのタスクを待つ。//int[] arrayInt =
			}
			return 0;// success.
		}
		/// <summary>
		/// pdfのページを画像に出力する
		/// </summary>
		/// <param name="doc">PdfDocument</param>
		/// <param name="nPage">ページ番号</param>
		/// <param name="otPath">出力ファイル名</param>
		/// <returns>正常:0 </returns>
		public static async Task<int> RenderPageAync(PdfDocument doc, PdfPage page,int nPage, string otPath, double inDpi=72.0, string encName="JPG",string mode = "pageBitMap", int inJpegQ = 91) {
			//おまじない このダミーのawait入れないとスレッドにならない
			var dir = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(Directory.GetParent(otPath).FullName);
			var th = Thread.CurrentThread.ManagedThreadId;
			// パラメータ
			const double dpiWin = 96.0;/* , dpiPDF = 72.0*/
			double resoScale = (inDpi / dpiWin);
			var opt = new PdfPageRenderOptions() {
				// SEE:https://github.com/microsoft/Windows-universal-samples/blob/master/Samples/PdfDocument/cs/Scenario1_Render.xaml.cs
				//BitmapEncoderId = Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId,// JPEG
				IsIgnoringHighContrast = false
			};
			opt.DestinationHeight = (uint)(page.Size.Height * resoScale + 0.5);
			opt.DestinationWidth = (uint)(page.Size.Width * resoScale + 0.5);
			//Console.WriteLine($"dpi={inDpi},scale={resoScale},height(org)={page.Size.Height}->{opt.DestinationHeight}");
			using (var memStrm = new Windows.Storage.Streams.InMemoryRandomAccessStream()) {
				await page.RenderToStreamAsync(memStrm,opt);
				await memStrm.FlushAsync();
				if (mode == "pageBitMap") {
					var bmp = new System.Drawing.Bitmap(memStrm.AsStream());
					//Console.WriteLine($"OrgReso({bmp.HorizontalResolution},{bmp.VerticalResolution})");
					var imEnc = new ImageParams.EncData(encName);
					imEnc.JpegQuality = inJpegQ;
					bmp.SetResolution((float)inDpi, (float)inDpi);
					Console.WriteLine($"ot[{Thread.CurrentThread.ManagedThreadId}]={Path.GetFileName(otPath)}({Directory.GetParent(otPath)})");
					imEnc.SaveImage(bmp, otPath);
					bmp.Dispose();
					memStrm.Seek(0);
				} else if (mode == "pageBitMapImage") {
					var image = new BitmapImage();
					image.BeginInit();
					image.CacheOption = BitmapCacheOption.OnLoad;
					image.StreamSource = memStrm.AsStream();
					image.EndInit();
					BitmapEncoder enc = null;
					switch (encName) {
						case "JPG":	enc = new JpegBitmapEncoder();	break;
						case "PNG": enc = new PngBitmapEncoder();	break;
						case "TIF":
						case "TIFF": enc = new TiffBitmapEncoder(); break;
						case "GIF": enc = new GifBitmapEncoder();	break;
						case "BMP": enc = new BmpBitmapEncoder();	break;
						default: Console.WriteLine("failed encode"); return -2;
					}
					enc.Frames.Add(BitmapFrame.Create(image));
					Console.WriteLine($"ot[{Thread.CurrentThread.ManagedThreadId}]={otPath}");
					using (var fs = new FileStream(otPath, System.IO.FileMode.Create)) {
						enc.Save(fs);
					}
				}
			}
			return 0;//success
		}
		/// <summary>
		/// PDF Render サンプル
		/// ref: https://csharp.hotexamples.com/examples/-/PdfPageRenderOptions/-/php-pdfpagerenderoptions-class-examples.html#0x66958305801896177d9d64dbfb6e64ebd307d702f05b801316d258b19a8d91d6-70,,84,
		/// * コード量は少ないが、以下ができない
		///		- Built-in のEncodeIdしか指定できない（パラメータが渡せない）
		///		- 遅い：PDFレンダリングと同時にファイル保存するから？
		/// </summary>
		/// <param name="page"></param>
		/// <param name="output"></param>
		/// <returns></returns>
		// ref: https://csharp.hotexamples.com/examples/-/PdfPageRenderOptions/-/php-pdfpagerenderoptions-class-examples.html#0x66958305801896177d9d64dbfb6e64ebd307d702f05b801316d258b19a8d91d6-70,,84,
		private static async Task<int> RenderPageOrgASync(Windows.Data.Pdf.PdfPage page, Windows.Storage.StorageFolder output) {
			var pagePath = string.Format("{0}.jpeg", page.Index);
			var pageFile = await output.CreateFileAsync(pagePath, Windows.Storage.CreationCollisionOption.ReplaceExisting);
			using (var imageStream = await pageFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite)) {
				await page.RenderToStreamAsync(imageStream);
				var renderOpt = new PdfPageRenderOptions() {
					BitmapEncoderId = Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId,// JPEG
					IsIgnoringHighContrast = false
				};
				await page.RenderToStreamAsync(imageStream, renderOpt);
				await imageStream.FlushAsync();
			}
			Console.WriteLine($">{Thread.CurrentThread.ManagedThreadId} save {pagePath}");
			return 0;
		}
	}
}

