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 Windows.Data.Pdf;
using System.Reflection; // Assembly.

#if COMMENT
重要： デスクトップやコンソールアプリで、UWP APIを使う(PDF関連を含む）
https://codezine.jp/article/detail/10654

■追補：Windows 10 1803以降の場合
　	 以降で説明しているUwpDesktopではなく、Windows 10 WinRT API Packを使います。
	これは2019年9月にリリースされたものです。

■セットアップ
15063以降では、最小限必要なファイルは以下の2つです。
System.Runtime.WindowsRuntime.dll
	C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\｛.NETバージョン｝\
Windows.WinMD
	C:\Program Files (x86)\Windows Kits\10\UnionMetadata\｛SDKバージョン｝\

■WinMDファイルをビルドに含めないようにする

#endif

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


namespace CSRender {
	class Program {
		static void dispHelp() {
			var pgName = Path.GetFileName(Path.GetFileName(Assembly.GetExecutingAssembly().Location));// プログラム名
			string  msg = 
				 $"{pgName} /<Opts> <PDFPath>\n"
				+$"* Render of PDF file\n"
				+$"	/F <pdfPath> ; pdfファイル名。/Fは省略可能。明示的な指定"
				+"\n";
			Console.Write(msg);
		}
		static int Main(string[] args) {
			var pdfPath="";
			//static async Task Main(string[] args) {// おまじない 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.Dequeue();// next word.
				} else if (wdi == "/M") {
					mode =  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(); // 生成と計測開始を同時に行う

			Task<int> t = renderPDF(Path.GetFullPath(pdfPath));
			int ret = t.Result;// スレッドの終了まで「待つ」
							   // asyncメソッド内では await呼び出しができる。ここはasyncではない。 
			watch.Stop();
			Console.WriteLine( $"main end result={ret},time={ watch.ElapsedMilliseconds / 1000.0 } sec,ret={ret}");

			//Console.WriteLine("stop");
			//Console.ReadLine();
			return ret;//sucess.
		}
		//https://csharp.hotexamples.com/examples/-/PdfPageRenderOptions/-/php-pdfpagerenderoptions-class-examples.html#0x66958305801896177d9d64dbfb6e64ebd307d702f05b801316d258b19a8d91d6-70,,84,

		private static async Task<string> RenderPage(Windows.Data.Pdf.PdfPage page, string otPath, Windows.Storage.StorageFolder output) {

			var pagePath = string.Format("{0}.png", page.Index);
			//var pageFile = await output.CreateFileAsync(pagePath, Windows.Storage.CreationCollisionOption.ReplaceExisting);
			var dir = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(Directory.GetParent(otPath).FullName);
//			var imgFile = await dir.CreateFileAsync(Path.GetFileName(otPath), Windows.Storage.CreationCollisionOption.ReplaceExisting);

			Console.WriteLine($"task PageNum={page.Index},Tread={Thread.CurrentThread.ManagedThreadId}");
			var th = Thread.CurrentThread.ManagedThreadId;
			//using (var imageStream = await pageFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
			using (var memStrm = new Windows.Storage.Streams.InMemoryRandomAccessStream())
			{
				//var imageStream = await pageFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite);
				//var memStrm = new Windows.Storage.Streams.InMemoryRandomAccessStream();
				//Console.WriteLine($"[start render{th}]");
				//await page.RenderToStreamAsync(imageStream);
				//await imageStream.FlushAsync();

				await page.RenderToStreamAsync(memStrm);
				await memStrm.FlushAsync();

				//Console.WriteLine($"[end render:{th}]");

				{
					BitmapImage image = new BitmapImage();
					image.BeginInit();
					image.CacheOption = BitmapCacheOption.OnLoad;
					//Console.Write("[AsStrem ST]");
					image.StreamSource = memStrm.AsStream();
					//Console.Write("[AsStrem ED]");
					image.EndInit();
					BitmapEncoder enc = new JpegBitmapEncoder();
					if (enc == null) {
						Console.WriteLine("encode取得失敗"); return "NG";
					}
					enc.Frames.Add(BitmapFrame.Create(image));
					//var p = Path.Combine(otDir, $"{otBaseName}.{nPage + 1}.jpg");
					//var p = Path.ChangeExtension(pageFile.Path,"jpg");// otPath; 
					Console.WriteLine("ot=" + otPath);
					using (FileStream fs = new FileStream(otPath, System.IO.FileMode.Create)) {
						enc.Save(fs);
					}
				}
				//imageStream.Dispose();
				memStrm.Dispose();
			}
			Console.WriteLine($"save end:{pagePath}");
			return output.Path  + pagePath;
		}
		// これが並列がだめなもの
		public static async Task<string> renderPageNG(PdfPage page, string otPath) {
			Console.WriteLine($"task PageNum={page.Index},Tread={Thread.CurrentThread.ManagedThreadId}");
			//PdfPage page = doc.GetPage((uint)nPage);// using しない
			/*using (PdfPage page = doc.GetPage( (uint)nPage) )*/
			{
				BitmapImage image = new BitmapImage();
				using (var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream()) {
					Console.Write("[start stream}");
					//Console.Write("[GetPage ST]");
					//PdfPage page = doc.GetPage((uint)nPage);
					//Console.Write("[GetPage EN]");
					Console.Write("[Rendersync ST]");
					await page.RenderToStreamAsync(stream);
					Console.Write("[Rendersync EN]");
					image.BeginInit();
					image.CacheOption = BitmapCacheOption.OnLoad;
					Console.Write("[AsStrem ST]");
					image.StreamSource = stream.AsStream();
					Console.Write("[AsStrem ED]");
					image.EndInit();

					//  xamlへの返却 imgPdf.Source = image;
					// 画像BitmapImageの画像への保存 https://yoshiiz.blog.fc2.com/blog-entry-818.html
					// JPEG Q https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-set-jpeg-compression-level
					// https://water2litter.net/gin/?p=979


					BitmapEncoder enc = new JpegBitmapEncoder();
					if (enc == null) {
						Console.WriteLine("encode取得失敗"); return "NG";
					}
					enc.Frames.Add(BitmapFrame.Create(image));
					//var p = Path.Combine(otDir, $"{otBaseName}.{nPage + 1}.jpg");
					var p = otPath;
					Console.WriteLine("ot=" + p);
					using (FileStream fs = new FileStream(p, System.IO.FileMode.Create)) {
						enc.Save(fs);
					}
				}
			}
			return "SUCCESS";
		}

		private static async Task<string> RenderPageOrg(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);
			Console.WriteLine($"task PageNum={page.Index},Tread={Thread.CurrentThread.ManagedThreadId}");
			using (var imageStream = await pageFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
			{
				await page.RenderToStreamAsync(imageStream);
				PdfPageRenderOptions pdfPageRenderOptions = new PdfPageRenderOptions();
				pdfPageRenderOptions.BitmapEncoderId = Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId;// JPEG
				pdfPageRenderOptions.IsIgnoringHighContrast = false;
				await page.RenderToStreamAsync(imageStream, pdfPageRenderOptions);
				await imageStream.FlushAsync();
			}
			Console.WriteLine("save end" + pagePath);
			return output.Path + pagePath;
		}

		public static string mode = "";
		public static async Task<int>  renderPDF (string pdfPath) {
			var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfPath);
			var otDir = "";
			var otBaseName=Path.GetFileName(pdfPath);//*.pdf
			{  // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する
				otDir = Path.Combine(Directory.GetParent(pdfPath).FullName,"IMG");
				if ( !Directory.Exists(otDir)) {
					Directory.CreateDirectory(otDir);
				};
			}
			// PDFファイルを読み込む
			var pdfDoc = await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(file);
			if (pdfDoc == null) {
				Console.WriteLine("pdfDoc取得失敗"); return -1;
			}
			var n = pdfDoc.PageCount;
			if ( mode == "file" ) {
				Console.WriteLine("mode=file");
				var taskList = new List<Task<int>>();
				for ( int i = 0; i < n; i++ ) {
					taskList.Add( renderFILE(pdfPath, i,Path.Combine(otDir, $"{otBaseName}.{i + 1}.jpg")) );
					//renderPage(pdfDoc, i, Path.Combine(otDir, $"{otBaseName}.{i + 1}.jpg"));
					//var ret = await renderPage(pdfDoc, i,Path.Combine(otDir, $"{otBaseName}.{i + 1}.jpg"));
				}
				//int[] arrayInt =
				await Task.WhenAll(taskList);// すべてのタスクを待つ。
			} else if ( mode == "page" ) {
				Console.WriteLine("mode=page");
				var taskList = new List<Task<int>>();
				for (int i = 0; i < n; i++) {
					taskList.Add(renderPage(pdfDoc, pdfDoc.GetPage((uint)i),i, Path.Combine(otDir, $"{otBaseName}.{i + 1}.jpg"))); // docを渡す
				}
				//int[] arrayInt =
				await Task.WhenAll(taskList);// すべてのタスクを待つ。
			} else if (mode == "spl") {
				var dir = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(otDir);
				//private static async Task<string> RenderPage(Windows.Data.Pdf.PdfPage page, Windows.Storage.StorageFolder output) 
				Console.WriteLine("mode=spl");
				var taskList = new List<Task<string>>();
				for (int i = 0; i < n; i++) {
					taskList.Add(RenderPage(pdfDoc.GetPage((uint)i), Path.Combine(otDir, $"{otBaseName}.{i + 1}.jpg"), dir)); // docを渡す
					
				}
				//int[] arrayInt =
				await Task.WhenAll(taskList);// すべてのタスクを待つ。
			} else if (mode == "org") {
				// 並列で動くユイツのもの
				var dir = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(otDir);
				//private static async Task<string> RenderPage(Windows.Data.Pdf.PdfPage page, Windows.Storage.StorageFolder output) 
				Console.WriteLine("mode=org");
				var taskList = new List<Task<string>>();
				for (int i = 0; i < n; i++) {
					taskList.Add(RenderPageOrg(pdfDoc.GetPage((uint)i), dir)); // docを渡す

				}
				//int[] arrayInt =
				await Task.WhenAll(taskList);// すべてのタスクを待つ。

			} else {
				Console.WriteLine("mode=none");
				// スレッド無し
				for (int i = 0; i < n; i++) {// 1ページ目を読み込む
					using (Windows.Data.Pdf.PdfPage page = pdfDoc.GetPage((uint)i)) {
						BitmapImage image = new BitmapImage();
						using (var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream()) {
							await page.RenderToStreamAsync(stream);
							image.BeginInit();
							image.CacheOption = BitmapCacheOption.OnLoad;
							image.StreamSource = stream.AsStream();
							image.EndInit();
							BitmapEncoder enc = new JpegBitmapEncoder();
							if (enc == null) {
								Console.WriteLine("encode取得失敗"); return -2;
							}
							enc.Frames.Add(BitmapFrame.Create(image));
							var p = Path.Combine(otDir, $"{otBaseName}.{i + 1}.jpg");
							Console.WriteLine("ot=" + p);
							using (FileStream fs = new FileStream(p, System.IO.FileMode.Create)) {
								enc.Save(fs);
							}
						}
					}
				}
			}

			/*
			readner option sample: 
			https://csharp.hotexamples.com/examples/-/PdfPageRenderOptions/-/php-pdfpagerenderoptions-class-examples.html
			  
			for ( int i = 0; i < n; i++ ) {// 1ページ目を読み込む
				using (Windows.Data.Pdf.PdfPage page = pdfDoc.GetPage((uint)i)) {
					BitmapImage image = new BitmapImage();
					using (var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream()) {
						await page.RenderToStreamAsync(stream);
						image.BeginInit();
						image.CacheOption = BitmapCacheOption.OnLoad;
						image.StreamSource = stream.AsStream();
						image.EndInit();
						//  xamlへの返却 imgPdf.Source = image;
						// 画像BitmapImageの画像への保存 https://yoshiiz.blog.fc2.com/blog-entry-818.html
						// JPEG Q https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-set-jpeg-compression-level
						// https://water2litter.net/gin/?p=979
						
						// このソースじゃないかな？ https://gogowaten.hatenablog.com/entry/2020/01/02/125352

						BitmapEncoder enc = new JpegBitmapEncoder();
						if ( enc == null ) {
							Console.WriteLine("encode取得失敗");return -2;
						}
						enc.Frames.Add(BitmapFrame.Create(image));
						var p = Path.Combine(otDir, $"{otBaseName}.{i+1}.jpg");
						Console.WriteLine("ot="+p);
						using (FileStream fs = new FileStream(p, System.IO.FileMode.Create)) {
								enc.Save(fs);
						}
					}
				}
			}

		 */
			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> renderPage(PdfDocument doc, PdfPage page,int nPage, string otPath ) {
			//おまじない このダミーのawait入れないとスレッドにならない
			var dir = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(Directory.GetParent(otPath).FullName);
			//Console.WriteLine("これをかくとスレッド1 " + Thread.CurrentThread.ManagedThreadId);
			//Console.WriteLine($"task PageNum={nPage},Tread={Thread.CurrentThread.ManagedThreadId}");
			var th = Thread.CurrentThread.ManagedThreadId;

			//	
			//PdfPage page = doc.GetPage((uint)nPage);// using しない
			/*using (PdfPage page = doc.GetPage( (uint)nPage) )*/
			{

				using (var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream()) {
					//Console.Write("[start stream}");
					//Console.Write("[GetPage ST]");
					//PdfPage page = doc.GetPage((uint)nPage);
					//Console.Write("[GetPage EN]");
					//Console.Write("[Rendersync ST]");
					await page.RenderToStreamAsync(stream);

					//Console.Write("[Rendersync EN]");
					{
						BitmapImage image = new BitmapImage();
						image.BeginInit();
						image.CacheOption = BitmapCacheOption.OnLoad;
						//Console.Write("[AsStrem ST]");
						image.StreamSource = stream.AsStream();
						//Console.Write("[AsStrem ED]");
						image.EndInit();

						//  xamlへの返却 imgPdf.Source = image;
						// 画像BitmapImageの画像への保存 https://yoshiiz.blog.fc2.com/blog-entry-818.html
						// JPEG Q https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-set-jpeg-compression-level
						// https://water2litter.net/gin/?p=979
						BitmapEncoder enc = new JpegBitmapEncoder();
						if (enc == null) {
							Console.WriteLine("encode取得失敗"); return -2;
						}
						enc.Frames.Add(BitmapFrame.Create(image));
						//var p = Path.Combine(otDir, $"{otBaseName}.{nPage + 1}.jpg");
						var p = otPath;
						Console.WriteLine($"ot[{Thread.CurrentThread.ManagedThreadId}]=" + p );
						using (FileStream fs = new FileStream(p, System.IO.FileMode.Create)) {
							enc.Save(fs);
						}
					}
				}
			}
			return 0;//success
		}
		public static async Task<int> renderFILE(string inPath, int nPage, string otPath) {
			// ファイルから開いてみる
			var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(inPath);
			var doc = await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(file);
			if (doc == null) {
				Console.WriteLine("pdfDoc取得失敗 in renderFile"); return -1;
			}
			Console.WriteLine($"task PageNum={nPage},Tread={Thread.CurrentThread.ManagedThreadId}");
			using (PdfPage page = doc.GetPage((uint)nPage)) {
				BitmapImage image = new BitmapImage();
				using (var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream()) {
					await page.RenderToStreamAsync(stream);
					image.BeginInit();
					image.CacheOption = BitmapCacheOption.OnLoad;
					image.StreamSource = stream.AsStream();
					image.EndInit();
					//  xamlへの返却 imgPdf.Source = image;
					// 画像BitmapImageの画像への保存 https://yoshiiz.blog.fc2.com/blog-entry-818.html
					// JPEG Q https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-set-jpeg-compression-level
					// https://water2litter.net/gin/?p=979
					BitmapEncoder enc = new JpegBitmapEncoder();
					if (enc == null) {
						Console.WriteLine("encode取得失敗"); return -2;
					}
					enc.Frames.Add(BitmapFrame.Create(image));
					//var p = Path.Combine(otDir, $"{otBaseName}.{nPage + 1}.jpg");
					var p = otPath;
					Console.WriteLine("ot=" + p);
					using (FileStream fs = new FileStream(p, System.IO.FileMode.Create)) {
						enc.Save(fs);
					}
				}
			}
			return 0;//success
		}

	}
}
/* Tips:
		スレッド内のSleep Thread.Sleep(4560); // 何か重い処理をしている... 
		await Task....
*/

/*
	- メモ c# acync await
	https://qiita.com/rawr/items/5d49960a4e4d3823722f 

	https://qiita.com/4_mio_11/items/f9b19c04509328b1e5c1

	https://tech-lab.sios.jp/archives/15711

	http://www.ifelse.jp/blog/csharp-01
	async定義内で重い処理を直接記載せずに
			await Task<string>.Run(() => {} で呼び出す。重い通常関数を呼び出すとメインスレッドで動いてしまう（だめなわけじゃない）
*/
