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.
using System.Text.RegularExpressions;

using System.Security.Cryptography;

using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Collections.ObjectModel;

using System.Runtime.InteropServices;


// for Stream conv.
using System.Runtime.InteropServices.WindowsRuntime;

using Windows.Storage.Streams;

//using System.IO.WindowsRuntimeStreamExtensions;


//

//using Codeplex.Data; // DynamicJson


// UWP-APIの使用
using Windows.Data.Pdf;

// PDFiumの追加
using PdfiumViewer;

using static CSRender.RenderPDF;


//https://proself2.screen.co.jp/public/OcyIQAHPksNAnE4Bs3BxIPQApnAEmSCIBOxsCZ946Uur
//Screen8080
//1_A4縦_2×1_両面_可変長レコード.pdf
// TOshiba:
//#region アセンブリ Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime
// C:\UserData\GIT_JSH\WinRT\CSRender\CSRender\bin\Debug\Windows.winmd
// WindowsRuntime 1.3

//#endregion
// MacBookPro:
// WindowsRuntime 1.4
//CSRender, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null
//  mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine)
//  System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine)
//  Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime,Ver(255.255.255.255),VerComp(SameMachine)
//  System.Runtime.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine)
//  System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a,Ver(4.0.0.0),VerComp(SameMachine)
//  System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine)
//  System.Runtime.Serialization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine)
//  clrVer=4.0.30319.42000
//  参考： zoomパレメータなど
//  https://www.syncfusion.com/kb/8767/how-to-print-pdf-documents-in-xamarin-forms-platform
//var DisplayInformation = Windows.Graphics.Display.DisplayInformation.GetForCurrentView();
//var dpi = DisplayInformation.LogicalDpi / 96;

// https://elesynd.blogspot.com/2018/11/hDpiForm.html
//Program.csでDPIAwareする
//FormクラスのAutoScaleModeをUIを使って"Dpi"に変更する
//それでもズレるコントロールは、FontをUIを使って明示的に指定してみる
// https://qiita.com/felis_silv/items/efee4b1a397b0b95100a
// スケーリング grph.FromImage(bmp)後にスケール

namespace CSRender {
#pragma warning disable IDE1006 // 小文字のメソッド含む

	static public class Check
	{
		//https://gist.github.com/retorillo/4e0c4a3cf4c7096e05ac

		static public bool bDump = false;
		static public bool bThDump = false;

		const int LOGPIXELSX = 88;
		const int LOGPIXELSY = 90;
		//
		//
		const int HORZSIZE = 4;//物理画面の幅・高さ（ミリメートル単位）
		const int VERTSIZE = 5;
		const int HORZRES = 8; //画面の幅・高さ（ピクセル単位）
		const int VERZRES = 10;
		//
		const int RASTERCAPS = 38;
		// 返却値のマスク
		//1 (RC_BITBLT) ビットマップの転送
		//2 (RC_BANDING) バンド処理のサポートが必要
		//4 (RC_SCALING) スケーリング
		//8 (RC_BITMAP64)	64KB より大きいビットマップ
		//0x0080 (RC_DI_BITMAP) SetDIBits 関数と GetDIBits 関数
		//0x0100 (RC_PALETTE) デバイスはパレットベースのデバイスである
		//0x0200 (RC_DIBTODEV) SetDIBitsToDevice 関数
		//0x0800 (RC_STRETCHBLT) StretchBlt 関数
		//0x1000 (RC_FLOODFILL) 塗りつぶし
		//0x2000 (RC_STRETCHDIB) StretchDIBits 関数
		const int SCALINGFACTORX = 114;//x 軸・ y 軸のスケーリングファクター
		const int SCALINGFACTORY = 115;

	   [DllImport("user32.dll")]
		extern static bool SetProcessDPIAware();
		[DllImport("user32.dll")]
		extern static IntPtr GetWindowDC(IntPtr hwnd);
		[DllImport("gdi32.dll")]
		extern static int GetDeviceCaps(IntPtr hdc, int index);
		[DllImport("user32.dll")]
		extern static int ReleaseDC(IntPtr hwnd, IntPtr hdc);

		public static int GetDPI() {
			//return 96;
			SetProcessDPIAware();// もしくはdpiAwareをマニフェストで設定すればよい。
			var dc = GetWindowDC(IntPtr.Zero);
			var dpi = GetDeviceCaps(dc, LOGPIXELSX);
			if ( bDump ) {
				Console.WriteLine("DpiX: {0}", GetDeviceCaps(dc, LOGPIXELSX));
				Console.WriteLine("DpiY: {0}", GetDeviceCaps(dc, LOGPIXELSY));

				Console.WriteLine("SCALINGFACTORYX: {0}", GetDeviceCaps(dc, SCALINGFACTORX));
				Console.WriteLine("SCALINGFACTORY: {0}", GetDeviceCaps(dc, SCALINGFACTORY));
				//if ( false ) {// これは倍率で変更されない
				//	int width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width; // 幅(pixel)
				//	int height = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;   // 高さ(pixel)
				//	// 取得したスクリーンのサイズをコンソールに出力する
				//	string message = string.Format("This screen size is {0} x {1} (pixel)", width, height);
				//	System.Console.WriteLine(message);
				//}

				//if (false){// コンソールアプリでは呼び出せない
				//	var di = Windows.Graphics.Display.DisplayInformation.GetForCurrentView();
				//	Console.WriteLine($@"di.LogicalDpi: {di.LogicalDpi}");
				//	Console.WriteLine($@"di.RawDpiX: {di.RawDpiX}");
				//	Console.WriteLine($@"di.ResoSacle: {di.ResolutionScale.ToString()}");
				//	Console.WriteLine($@"di.StereoEnabled: {di.StereoEnabled}");
				//	Console.WriteLine($@"di: {di.ToString()}");
				//}
			}
			ReleaseDC(IntPtr.Zero, dc);
			return dpi;// 96;// dpi;
		}
	}
	public static class RenderPDF
	{
		/// <summary>
		/// Rendering Condition
		/// </summary>
		[DataContract]
		public class RenderConditionParams
		{
			[DataMember(Order = 0)]
			public double	Dpi			= 72.0;
			[DataMember(Order = 1)]
			public string	BoxType		= "Crop";
			[DataMember(Order = 2)]
			public string	ImageType	= "JPEG";
			[DataMember(Order = 3)]
			public int		JpegQ		= 91;
			// Method non
		}

		// Verbose 
		public static void setEcho(bool b) {
			bEcho = b;
		}
		public static bool bEcho = false;
		public static void echo(params object[] args) {
			if (!bEcho) return;
			var s = "";
			foreach (object a in args) {
				s += a.ToString();
			}
			Console.WriteLine(s);
		}

		// 事前チェック  BashHash
		public static bool RenderPdfInitCheck( Stream stream /* PDFストリーム */) {
			Windows.Storage.Streams.InMemoryRandomAccessStream rmStrageStream=null;

			{// Stream->Memstream -> byte array -> IBuffer -> InMemmory...Stream()
				var ms = new MemoryStream();
				stream.CopyTo(ms);
				var bAray = ms.ToArray();
				IBuffer ib = bAray.AsBuffer();
				rmStrageStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
				//await s.WriteAsync(ib);
				async Task<uint> wa() => await rmStrageStream.WriteAsync(ib);
				var wa_ret = wa().Result;
			}
			async Task<Windows.Data.Pdf.PdfDocument> LoadS() => await Windows.Data.Pdf.PdfDocument.LoadFromStreamAsync(rmStrageStream);
			var pdfDoc = LoadS().Result;
			if (pdfDoc == null) {
				Console.WriteLine("error: get PdfDocument failed");
				return false;
			}

			//	var memStream = new MemoryStream();
			// memStream.AsInputStream();
			// WindowsRuntimeStreamExtensions.
			// var ras = WindowsRuntimeStreamExtensions.AsRandomAccessStream(stream);
			//https://blog.ch3cooh.jp/entry/20131207/1386342000
			//https://csharp.hotexamples.com/site/file?hash=0xe951daae5be57f9f35507c45eb09a1085819b0a42b846aaa211ac138a2d1b5af&fullName=src/Nutrition/Nutrition.WP/WPOCRService.cs&project=SamirHafez/Nutrition

			//なんだけどな。。。→ .NetFrameworkにAsRandomAccessStream(x)が存在しない。.Net Coreのみみたい。

			// exeと同一場所に"baseRender.jpg"
			string otPath = Path.Combine(Directory.GetParent(Assembly.GetEntryAssembly().Location).FullName,"baseRender.jpg");
			bool bSaveImage = false;// Debug時にtrueにする
			Console.WriteLine($"mode=Hash base check");
			var taskList = new List<Task<int>>();
			var hashValue = "";

			var pm = new RenderConditionParams();
			var ret = RenderPage(
					doc: pdfDoc,
					page: pdfDoc.GetPage(0),
					otPath: otPath,
					pm:pm,
					bSaveImage: bSaveImage,
					bHash: true,
					otHashCode: ref hashValue
				);
			Console.WriteLine($@"Hash={hashValue},Host={Environment.MachineName},{Environment.OSVersion}");
			return true;// true:OK, false:NG
		}

		// Sync version( no async -> Pallale Task ) **************************************************************************************

		/// <summary>
		/// PDFのレンダリング
		/// </summary>
		/// <param name="pdfPath"></param>
		/// <param name="inDir"></param>
		/// <param name="pm">レンダリングの条件</param>
		/// <param name="inPageRange"></param>
		/// <param name="inMode"></param>
		/// <param name="bSaveImage"></param>
		/// <param name="bHash">未使用,常にtrueで使用</param>
		/// <param name="inHashData"></param>
		/// <returns></returns>
		public static int RenderPdfDoc(
			string pdfPath,
			string inDir = "",
			RenderConditionParams pm = null,
			string inPageRange = "1-*",
			string inMode = "pageBitMap",
			bool bSaveImage = true,         //ハッシュ値のみ計算するときにfalseにする
			bool bHash = false,
			UtHash.HashData inHashData = null,
			bool bPDFium = false
		) {
			if (pm == null) {
				pm = new RenderConditionParams();
			}
			var otDir = inDir;
			var otBaseName = Path.GetFileName(pdfPath);//*.pdf
			var otExtention = pm.ImageType.ToLower();
			if (bSaveImage && otDir == "") {
				// 出力ディレクトリが空→元PDFの同一フォルダにIMGフォルダを作成する
				otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG");
				if (!Directory.Exists(otDir))
					Directory.CreateDirectory(otDir);
			}
			// PDFファイルを読み込む
			async Task<Windows.Storage.StorageFile> GetStrageFilePath(string path)
									=> await Windows.Storage.StorageFile.GetFileFromPathAsync(path);
			var file = GetStrageFilePath(pdfPath).Result;

		
			async Task<Windows.Data.Pdf.PdfDocument> Load(Windows.Storage.StorageFile path)
									=> await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(file);
			var pdfDoc = Load(file).Result;

			// 2つを合わせることができそう
			//async Task<Windows.Data.Pdf.PdfDocument> GetLoadFromPath(string path)
			//{
			//		var a1 = await Windows.Storage.StorageFile.GetFileFromPathAsync(path);
			//		var a2 = await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(a1);
			//		return a2;
			//};
			//var pdfDoc2 = GetLoadFromPath(pdfPath).Result;


			if (pdfDoc == null) {
				Console.WriteLine("error: get PdfDocument failed");
				return -1;
			}
			var n = pdfDoc.PageCount;
			inMode = "pageBitMap";
			//Console.WriteLine($"mode={inMode},bPDFium={bPDFium}");
			var taskList = new List<Task<int>>();
			uint[] rNo = makePageRange(inPageRange, pdfDoc.PageCount);

			if (inHashData != null) {
				// ハッシュ対象の最大ページ数の設定必須。
				inHashData.SetFile(pdfPath, (int)pdfDoc.PageCount);
			}
			//  PDFiumViewer
			PdfiumViewer.PdfDocument docG = null;
			if (bPDFium) {
				docG = PdfiumViewer.PdfDocument.Load(pdfPath);
			}
			//Console.WriteLine($@"dpi={pm.Dpi}");

			/////並行処理するスレッド数を指定（2-4ぐらいが穏便な数値）
			ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
			Parallel.ForEach(rNo, options, i => {
				//Console.WriteLine($"*****{i}*****/{rNo.Length}");
				var hashValue = "";
				var ret = RenderPage(
						doc: pdfDoc,
						page: pdfDoc.GetPage((uint)(i - 1)),
						otPath: Path.Combine(otDir, $"{otBaseName}.{i}.{otExtention}"),
						pm: pm,
						bSaveImage: bSaveImage, // ファイル保存
						bHash: bHash,
						otHashCode: ref hashValue,
						docG : docG
					);
				if (inHashData != null) {
					lock (inHashData) {
						inHashData.AddHashCode(pdfPath, (int)i, hashValue);
					}
				}
			});
			if ( docG != null)
				docG.Dispose();

			return (int)pdfDoc.PageCount;// ページ数を返却します
			//return 0;// success.
		}
		/// <summary>
		/// pdfのページを画像に出力する
		/// </summary>
		/// <param name="doc">PdfDocument</param>
		/// <param name="nPage">ページ番号</param>
		/// <param name="otPath">出力ファイル名</param>
		/// <returns>正常:0 </returns>
		public static int RenderPage(
				// Need:
				Windows.Data.Pdf.PdfDocument doc,    // Need GetPage() only. (Total page number);
				PdfPage page,   // Target Page Data(page.index:0-x)
				string otPath,
				ref string otHashCode,
				// Optional:
				RenderConditionParams pm = null,
				bool bSaveImage = true,// ファイル保存有無。ハッシュ値計算のみのときにfalseにする
				bool bHash = false,
				PdfiumViewer.PdfDocument docG = null // 
		){
			if( pm == null) {
				pm = new RenderConditionParams();
			}
			double dpiWin = Check.GetDPI(); //96.0;/* , dpiPDF = 72.0*/
			//double resoScale = (96.0 / dpiWin);
			double resoScale = (pm.Dpi / 96.0);
			//double resoScale = (inDpi / dpiWin);

			var bDump = Check.bDump;
			var bThDump = Check.bThDump;

			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
			};
			var boxDic = new Dictionary<string, Windows.Foundation.Rect>() {
				// Init
				["Media"] = page.Dimensions.MediaBox,
				["Trim"] = page.Dimensions.TrimBox,
				["Bleed"] = page.Dimensions.BleedBox,
				["Crop"] = page.Dimensions.CropBox,
				["Art"] = page.Dimensions.ArtBox
			};
			//Dump:
			if ( bDump){
				Console.WriteLine($@"PageZoom={page.PreferredZoom}");
				Console.WriteLine($@"PageRotate={page.Rotation.ToString()}");

				Console.WriteLine($"{"Size",-5}=({page.Size.Width,8:f2},{page.Size.Height,8:f2})");
				foreach (var b in boxDic.Keys) {
					var r = boxDic[b];
					Console.WriteLine($"{b,-5}=({r.X,8:f2},{r.Y,8:f2}) W({r.Width,8:f2},{r.Height,8:f2})");
					Console.WriteLine($"{b,-5}=L {r.Left,-8:f2} R {r.Right,-8:f2}) T {r.Top,-8:f2} B {r.Bottom,-8:f2})");
				}
			}

			opt.SourceRect = boxDic.ContainsKey(pm.BoxType) ? boxDic[pm.BoxType] : boxDic["Crop"];// Cannot find -> "Crop";
			//Console.WriteLine($"Select {inBoxType} box.");

			// 何も変倍しないとどうなる
			opt.DestinationWidth = (uint)(opt.SourceRect.Width * resoScale + 0.5);
			opt.DestinationHeight = (uint)(opt.SourceRect.Height * resoScale + 0.5);

			// 150%の時は * 0.5358を設定するとよい解像度になるが、この数値の計算方法がわからない
			//opt.DestinationWidth = (uint)(opt.SourceRect.Width * 0.5358/*resoScale*/ + 0.5);
			//opt.DestinationHeight = (uint)(opt.SourceRect.Height * 0.5358/*resoScale*/ + 0.5);

			if (bDump) {
				Console.WriteLine($"dpi={pm.Dpi},scale={resoScale},width(org)={page.Size.Width}->{opt.DestinationWidth}");
				Console.WriteLine($"dpi={pm.Dpi},scale={resoScale},height(org)={page.Size.Height}->{opt.DestinationHeight}");
			}
			using (var memStrm = new Windows.Storage.Streams.InMemoryRandomAccessStream()) {

				if (docG == null) {
					async Task RenderToStream(PdfPage p) => await p.RenderToStreamAsync(memStrm, opt);
					RenderToStream(page).Wait();
				} else {
					PdfRenderFlags flg = (PdfRenderFlags.ForPrinting | PdfRenderFlags.CorrectFromDpi);
					System.Drawing.Image img = docG.Render((int)(page.Index), (float)pm.Dpi, (float)pm.Dpi, flg);
					img.Save(memStrm.AsStream(), System.Drawing.Imaging.ImageFormat.Jpeg);
					// [注意] PDFuim用に直接 AsStream()に書き込んでいるので、AsStream().Flush()しないとだめです
				}

				async Task<bool> FlushX() => await memStrm.FlushAsync();
				FlushX().Wait();

				var bmp = new System.Drawing.Bitmap(memStrm.AsStream());
				if (bDump) {
					Console.WriteLine($"bmp=w:{bmp.Size.Width},h:{bmp.Size.Height}");
				}
				//Console.WriteLine($"OrgReso({bmp.HorizontalResolution},{bmp.VerticalResolution})");
				if (bHash) {
					var h =GetHashValue(memStrm);
					//Console.WriteLine($"HashString:{h}");
					otHashCode = h;
				}
				var th = Thread.CurrentThread.ManagedThreadId;
				if ( !bSaveImage ) { //ファイル保存しない.ハッシュ計算のみ

					if ( bThDump ) {
						Console.WriteLine($"ot[th{th},{(page.Index + 1)}/{doc.PageCount}]=,{Path.GetFileName(otPath)},hash:{otHashCode}");
					}
					return 0;
				} 
	
				var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers.
				bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi);
				if (bThDump) {
					Console.WriteLine($"ot[th{th},{(page.Index + 1)}/{doc.PageCount}]={Path.GetFileName(otPath)}({Directory.GetParent(otPath)})");
				}
				// 
				imEnc.SaveImage(bmp, otPath);
				//
				bmp.Dispose();
				//memStrm.Seek(0);
			}
			return 0;//success
		}

		// Sync version( 比較モード(/FC ) **************************************************************************************

		/// <summary>
		/// レンダリング（比較モード)
		/// </summary>
		/// <param name="pdfPath"></param>
		/// <param name="refPdfPath"></param>
		/// <param name="inDir"></param>
		/// <param name="pm">レンダリング条件</param>
		/// <param name="inPageRange"></param>
		/// <param name="inMode"></param>
		/// <param name="bHash">未使用常にtrueで利用する</param>
		/// <param name="inHashDataTgt"></param>
		/// <param name="inHashDataRef"></param>
		/// <returns>一致した場合は0、不一致の場合は!0を返却する</returns>
		public static int RenderPdfDocCompare(
			string pdfPath,
			string refPdfPath,
			string resultDataPath,
			string inDir = "",
			RenderConditionParams pm = null,
			string inPageRange = "1-*",
			string inMode = "pageBitMap",
			bool bHash = false,
			UtHash.HashData inHashDataTgt = null,
			UtHash.HashData inHashDataRef = null,
			bool bPDFium = false
			// 比較結果を返す ページ番号とメッセージ
		) {


			var bDump = Check.bDump;
			var bThDump = Check.bThDump;

			if(bDump)
				Console.WriteLine("RenderPdfDocCompare & diff Image! & non Para");
			if (pm == null)
				pm = new RenderConditionParams();

			var otDir = inDir;// 今は未使用
			var otBaseName = Path.GetFileName(pdfPath);//xxx.pdf
			var otExtention = pm.ImageType.ToLower();
			if (otDir == "") {
				// 出力ディレクトリが空→元PDFの同一フォルダにIMGフォルダを作成する
				otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG");
				if (!Directory.Exists(otDir))
					Directory.CreateDirectory(otDir);
			}
			// PDFファイルを読み込む



			// 以下のコードで良いみたい。
			// [変更待ち] var file2 = Windows.Storage.StorageFile.GetFileFromPathAsync(pdfPath).GetAwaiter().GetResult();

			async Task<Windows.Storage.StorageFile> GetStrageFilePath(string path)
									=> await Windows.Storage.StorageFile.GetFileFromPathAsync(path);
			var file = GetStrageFilePath(pdfPath).Result;
			async Task<Windows.Data.Pdf.PdfDocument> Load(Windows.Storage.StorageFile path)
									=> await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(path);
			var pdfDoc = Load(file).Result;
			if (pdfDoc == null) {
				Console.WriteLine("error: get PdfDocument failed");
				return -1;
			}

			// ref
			var fileRef = GetStrageFilePath(refPdfPath).Result;
			var pdfDocRef = Load(fileRef).Result;
			if (pdfDocRef == null) {
				Console.WriteLine("error: get PdfDocument(ref) failed");
				return -1;
			}

			var n = pdfDoc.PageCount;
			if ( n != pdfDocRef.PageCount) {
				Console.WriteLine($"error: differ page length:{n}:{pdfDocRef.PageCount}");
				return -1;
			}
			uint[] rNo = makePageRange(inPageRange, n);

			int nRetAll = 0;// 一致(def)

			// ハッシュ情報の確認
			/*
				- 双方のハッシュ値が存在するか確認
				- tgtののみなら、refのrender
				- refのみなら、tgtのみrender
				- とう感じ
			 */
			UtHash.HashFile tgtHf=null;
			if (inHashDataTgt!=null) {
				var f = Path.GetFileName(pdfPath);
				if (inHashDataTgt.Files.ContainsKey(f)) {
					tgtHf = inHashDataTgt.Files[f];
				}
			}
			UtHash.HashFile refHf = null;
			if (inHashDataRef != null) {
				var f = Path.GetFileName(refPdfPath);
				if ( inHashDataRef.Files.ContainsKey(f) ) {
					refHf = inHashDataRef.Files[f];
				}
			}
			Console.WriteLine($@"UsingHashFile:tgt({(tgtHf==null?0:1)}),ref({(refHf == null ? 0 : 1)})");
			// tgtHf,refHfを構築完了

			var fcResultMsg = new SortedDictionary<int,string>();// 比較結果を返す ページ番号とメッセージ

			//  PDFiumViewer
			PdfiumViewer.PdfDocument docG = null;
			PdfiumViewer.PdfDocument docGRef = null;
			if (bPDFium) {
				docG = PdfiumViewer.PdfDocument.Load(pdfPath);
				docGRef = PdfiumViewer.PdfDocument.Load(refPdfPath);
			}


			/////並行処理するスレッド数を指定（2-4ぐらいが穏便な数値）
			ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
			Parallel.ForEach(rNo, options, i => {
				Windows.Storage.Streams.InMemoryRandomAccessStream tgtStrm = null, refStrm = null;
				string tgtHash = "", refHash = "";
				// ターゲット レンダー
				InMemoryRandomAccessStream RendPageTGT() {
					var strm = RenderPageStream(doc:pdfDoc,page:pdfDoc.GetPage((uint)(i-1)), pm:pm,docG:docG);
					return strm;
				}
				// リファレンス レンダー
				InMemoryRandomAccessStream RendPageREF() {
					var strm = RenderPageStream(doc:pdfDocRef,page:pdfDocRef.GetPage((uint)(i-1)), pm:pm,docG:docGRef);
					return strm;
				}
				//tgtStrm = RendPageTGT();

				if (tgtHf == null) {
					tgtStrm = RendPageTGT();
					tgtHash = GetHashValue(tgtStrm);
				} else {
					lock (inHashDataTgt) {
						tgtHash = tgtHf.GetHashValue((int)(i - 1));
					}
				}
				// リファレンス
				if (refHf == null) {
					refStrm = RendPageREF();
					refHash = GetHashValue(refStrm);
				} else {
					lock (inHashDataRef) {
						refHash = refHf.GetHashValue((int)(i - 1));
					}
				}
				// compare stream;
				int nRet = 1;//異なる(def).
				var dateStr = DateTime.Now.ToString();
				//Console.WriteLine($@"tgt={i}:{tgtHash}");
				//Console.WriteLine($@"ref={i}:{refHash}");

				if (tgtHash != refHash) {
					nRet = 1;
					nRetAll = nRet;//全体の不一致設定
					echo($"{i}:NotMatch:{i},tgt[{tgtHash}],ref[{refHash}]");
					//2020年3月2日 15:23:55:[@Difference]:<fullpath>.diff.jpg
					lock (fcResultMsg) {
						fcResultMsg.Add((int)i, $@"{dateStr}:[@Difference]:{pdfPath}.diff.jpg");
					}

					//if ( true && tgtStrm != null && bSave) {
					{
						echo("***** save diff image");
						// diffのjpegを書き出してみる(tgt)
						if (tgtStrm == null) tgtStrm = RendPageTGT();
						var otPath = Path.Combine(otDir, $"{otBaseName}.{i}.tgt.{otExtention}");
						var bmp = new System.Drawing.Bitmap(tgtStrm.AsStream());
						var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers.
						bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi);
						imEnc.SaveImage(bmp, otPath);
						bmp.Dispose();
					}
					//if (true && refStrm != null && bSave) {
					{ 
						// diffのjpegを書き出してみる(ref)
						if (refStrm == null) refStrm = RendPageREF();
						var otPath = Path.Combine(otDir, $"{otBaseName}.{i}.ref.{otExtention}");
						var bmp = new System.Drawing.Bitmap(refStrm.AsStream());
						var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers.
						bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi);
						imEnc.SaveImage(bmp, otPath);
						bmp.Dispose();
					}

				} else {
					nRet = 0;// match
							 //Console.WriteLine($"{i}:Match:{i}");
							 // 2019年12月18日 19:31:38:[OK]:fname.pdf.1.png
					lock (fcResultMsg) {
						fcResultMsg.Add((int)i, $@"{dateStr}:[OK]:{Path.GetFileName(pdfPath)}.{i}.png");
					}
				}
				// dispose:
				if (refStrm != null) refStrm.Dispose();
				if (tgtStrm != null) tgtStrm.Dispose();
			});
			if ( docG != null)
				docG.Dispose();
			if (docGRef != null)
				docGRef.Dispose();

			// まとめて表示
			foreach ( var v  in fcResultMsg){
				Console.WriteLine("@CMP@"+v.Value);
			}
			if (resultDataPath != "") {
				// まとめて書き出し
				var resCont =($"{pdfPath}\n");
				foreach (var v in fcResultMsg) {
					resCont += ("@CMP@" + v.Value) + "\n";
				}
				File.AppendAllText(resultDataPath, resCont);
			}
			return nRetAll;// success.
		}

		/// <summary>
		/// 比較モード
		/// </summary>
		/// <param name="doc">PdfDocument</param>
		/// <param name="nPage">ページ番号</param>
		/// <param name="otPath">出力ファイル名</param>
		/// <returns>正常:0 </returns>
		public static Windows.Storage.Streams.InMemoryRandomAccessStream RenderPageStream(
				// Need:
				Windows.Data.Pdf.PdfDocument doc,    // Need GetPage() only. (Total page number);
				PdfPage page,   // Target Page Data(page.index:0-x)
								//string otPath,
								// Optional:
				RenderConditionParams pm = null,
				PdfiumViewer.PdfDocument docG = null // 
		) {
			var bDump = Check.bDump;
			var bThDump = Check.bThDump;

			if (pm == null)
				pm = new RenderConditionParams();
			double dpiWin = Check.GetDPI(); //96.0;/* , dpiPDF = 72.0*/
			double resoScale = (pm.Dpi / 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
			};
			//
			//async Task PreparePage(PdfPage p) => await p.PreparePageAsync();//ページの完成を待つ
			//PreparePage(page).Wait();
			//page.PreparePageAsync

			var boxDic = new Dictionary<string, Windows.Foundation.Rect>() {
				// Init
				["Media"] = page.Dimensions.MediaBox,
				["Trim"] = page.Dimensions.TrimBox,
				["Bleed"] = page.Dimensions.BleedBox,
				["Crop"] = page.Dimensions.CropBox,
				["Art"] = page.Dimensions.ArtBox
			};
			opt.SourceRect = boxDic.ContainsKey(pm.BoxType) ? boxDic[pm.BoxType] : boxDic["Crop"];// Cannot find -> "Crop";

			opt.DestinationWidth = (uint)(opt.SourceRect.Width * resoScale + 0.5);
			opt.DestinationHeight = (uint)(opt.SourceRect.Height * resoScale + 0.5);
			if (bDump)
				Console.WriteLine($"dpi={pm.Dpi},scale={resoScale},height(org)={page.Size.Height}->{opt.DestinationHeight}");
			var memStrm = new Windows.Storage.Streams.InMemoryRandomAccessStream();
			if(true)  {
				if (docG == null) {
					async Task RenderToStream(PdfPage p) => await p.RenderToStreamAsync(memStrm, opt);
					RenderToStream(page).Wait();
				} else {
					PdfRenderFlags flg = (PdfRenderFlags.ForPrinting | PdfRenderFlags.CorrectFromDpi);
					System.Drawing.Image img = docG.Render((int)(page.Index), (float)pm.Dpi, (float)pm.Dpi, flg);
					img.Save(memStrm.AsStream(), System.Drawing.Imaging.ImageFormat.Jpeg);
					// [注意] PDFuim用に直接 AsStream()に書き込んでいるので、AsStream().Flush()しないとだめです
					memStrm.AsStream().Flush();
				}
				async Task<bool> FlushX() => await memStrm.FlushAsync();
				//var dmy =FlushX().Result;
				FlushX().Wait();
			}
			//page.Dispose();

			return memStrm;//
		}

		/// <summary>
		/// ページ１から始まるページ範囲の配列を返す
		/// </summary>
		/// <param name="pageRange"></param>
		/// <param name="maxPage"></param>
		/// <returns></returns>
		private static uint[] makePageRange(string pageRange = "1", uint endPage = 100) {
			var range = new SortedSet<uint>();
			var vec = pageRange.Split(',');//カンマで分割
			var reRange = new Regex($@"([\d]+)[\s]*\-[\s]*([\d]*|\*)");// n-m or n- or n-*
			var reOne = new Regex($@"([\d]+)");                     // n
			bool isRange(uint x) => (0 < x && x <= endPage); // 範囲チェック
			void swap<T>(ref T a, ref T b) { T tmp = a; a = b; b = tmp; }
			foreach (var s in vec) {
				//範囲指定
				var m = reRange.Match(s);
				if (m.Success) {
					uint st = uint.Parse(m.Groups[1].Value);
					uint en = 0;
					if (!isRange(st))
						continue;//開始値が範囲外で無効
					uint.TryParse(m.Groups[2].Value, out en);
					if (!isRange(en))
						en = endPage;// 最終値が無効なため最終ページ
					if (st > en) swap(ref st, ref en);
					// 範囲登録
					foreach (var g in Enumerable.Range((int)st, (int)(en - st + 1)))
						range.Add((uint)g);
					continue;
				}
				//ページ指定
				m = reOne.Match(s);
				if (m.Success) {
					var n = uint.Parse(m.Groups[1].Value);
					if (isRange(n))
						range.Add(n);
				}
			}
			return range.ToArray<uint>();
		}
		public static string GetHashValue(Windows.Storage.Streams.InMemoryRandomAccessStream strm) {
			var alg = new SHA256CryptoServiceProvider();
			strm.Seek(0);
			var bin = alg.ComputeHash(strm.AsStream());
			alg.Clear();
			// バイト配列をUTF8エンコードで文字列化
			var hashedText = new StringBuilder();
			foreach (var b in bin) {
				hashedText.AppendFormat("{0:X2}", b);
			}
			return hashedText.ToString();
		}
	}

	/// <summary>
	/// ファイルのハッシュ値のIO
	/// </summary>
	namespace UtHash
	// https://qiita.com/Akasaki/items/dee137b24aea4b7e2bcb
	// http://mokake.hatenablog.com/entry/2017/09/28/234433
	// https://lifetime-engineer.com/csharp-create-json-indent/
	// DataMemberでOrderを指定することで順序を確定。
	// JsonReaderWriterFactory.CreateJsonWriterでindent=trueにすることでjsonの可視性向上
	{
		[DataContract]
		public class HashHead
		{
			public bool Dirty = false;              // 書き換えられたらTrue 保存対象外
			[DataMember(Order = 0)]
			public string HashBaseName;             // 保存時にベース名を設定する
			[DataMember(Order=1)]
			public string HashFilePath;
			[DataMember(Order=2)]
			public string Dpi;
			[DataMember(Order=3)]
			public string Box;
			[DataMember(Order=4)]
			public string ImType;
			[DataMember(Order=5)]
			public string JQ;
			// メソッド
			public HashHead() {
				SetRenderInfo("0", "Bx", "IMx", "xx");
			}
			public HashHead SetRenderInfo(string dpi, string box, string imType, string jq) {
				Dpi = dpi; Box = box; ImType = imType; JQ = jq;

				return this;
			}
			public string GetRenderInfoStr() { return $@"{Dpi}_{Box}_{ImType}_{JQ}"; }

		}
		[DataContract]
		public class HashFile
		{

			[DataMember(Order=0)]
			public DateTime UpdateTime;       // ファイルの更新日
			[DataMember(Order=1)]
			public string UpdateTimeStr;       // ファイルの更新日(Json目視用)
			[DataMember(Order=2)]
			public List<string> PageHashCode = new List<string>(); // ページ毎のハッシュ値の配列
			// メソッド
			public string GetHashValue(int i) {
				// 範囲チェック
				if ( !(0  <= i && i < PageHashCode.Count) ){
					return "";
				}
				return PageHashCode[i];
			}
		}
		[DataContract]
		public class HashData
		{
			[DataMember(Order=0)]
			public HashHead Head = new HashHead();

			[DataMember(Order=1)]
			public SortedDictionary<string, HashFile> Files = new SortedDictionary<string, HashFile>();

			// メソッド
			static public string GetHashFileName(string baseName, string dpi, string box, string imType, string jq) {
				var h = new HashHead();
				var f = baseName + "_" + h.SetRenderInfo(dpi, box, imType, jq).GetRenderInfoStr() + ".json";
				return f;
			}
			static public HashData load(string pdfPath/*or Dir*/, string baseName, string dpi, string box, string imType, string jq, ref string otPath) {
				var hashPath = "";
				if (Path.GetExtension(pdfPath).ToLower() == ".pdf") {
					var paPath = Directory.GetParent(Path.GetFullPath(pdfPath)).ToString();
					var hashFName = GetHashFileName(baseName, dpi, box, imType, jq);
					hashPath = Path.Combine(paPath, hashFName);
				} else if (File.GetAttributes(pdfPath).HasFlag(FileAttributes.Directory)) {
					var hashFName = GetHashFileName(baseName, dpi, box, imType, jq);
					hashPath = Path.Combine(pdfPath, hashFName);
				}
				//Console.WriteLine($@"hashPath={hashPath}");
				if (otPath != null) {
					otPath = hashPath;
				}
				var hd = HashData.load(hashPath); //ファイルの存在はこっちに任す
				return hd;
			}

			static public HashData load(string hashPath) {
				if (!File.Exists(hashPath)) {
					echo("not find load hash data file");
					return null;
				}
				DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HashData));
				using (var rd = new FileStream(hashPath, FileMode.Open,FileAccess.Read,FileShare.ReadWrite)) {
					var a = (HashData)serializer.ReadObject(rd);
					a.Head.Dirty = false;// clear
					return a;
				}
			}
			static public HashData loadFromString(string s) {
				DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HashData));
				using (var rd = new MemoryStream( Encoding.UTF8.GetBytes(s) )) {
					var a = (HashData)serializer.ReadObject(rd);
					a.Head.Dirty = false;// clear
					return a;
				}
			}
			public override string ToString() {
				//文字列に書き出す
				using (var m = new MemoryStream())
				using (var writer = JsonReaderWriterFactory.CreateJsonWriter(m, Encoding.UTF8, true, true, "  ")) {
					var serializer = new DataContractJsonSerializer(typeof(HashData));
					serializer.WriteObject(writer, this);
					var x=  Encoding.UTF8.GetString(m.ToArray());// 文字列に変換
					return x;
				}
			}

			public bool save(string path) {
				// JSONに変換するデータを作る。
				// 再保存の抑制
				//this.Head.Dirtyがfalseなら更新されていないことになる。
				//またかつ保存パスが同じ this.Head.HashFilePath = path;

				echo($@"HashData.save:{path}");
				this.Head.HashFilePath = Path.GetFullPath(path);
				this.Head.HashBaseName = this.Head.GetRenderInfoStr();
				try {
					using (var fs = new FileStream(path, FileMode.Create))
					using (var writer = JsonReaderWriterFactory.CreateJsonWriter(fs, Encoding.UTF8, true, true, "  ")) {
						var serializer = new DataContractJsonSerializer(typeof(HashData));
						serializer.WriteObject(writer, this);
						return true;
					}
				} catch (Exception e) {
					Console.WriteLine($@"error Save json anything error:{e}");
					return false;
				}
			}
			public bool SetFile(string pathPDF, int pageNum) {
				var fi = new FileInfo(pathPDF);
				var fName = fi.Name;
				HashFile hf;
				if (!Files.ContainsKey(fName)) {
					this.Head.Dirty = true;// 変更あり
					hf = new HashFile() {
						UpdateTime = fi.LastWriteTimeUtc,
						UpdateTimeStr= fi.LastWriteTime.ToString(),
						PageHashCode = new List<string>(pageNum)
					};

					// Console.WriteLine($@"update={hf.UpdateTime}");

					for (var i = 0; i < pageNum; i++) {
						hf.PageHashCode.Add("");
					}
					Files.Add(fName,hf);
				} else {
					// 既に存在、pageNumがかわらないことだけチェック
					var pageNumOrg = Files[fName].PageHashCode.Count;
					if (Files[fName].UpdateTime.ToString() != fi.LastWriteTimeUtc.ToString()) {
						// 日付が一致しなければ更新(ToStringで比較しないとだめです
						this.Head.Dirty = true;// 変更あり
						Files[fName].UpdateTime = fi.LastWriteTimeUtc;
						Files[fName].UpdateTimeStr = fi.LastWriteTime.ToString();
						Files[fName].PageHashCode = new List<string>(pageNum);
						for (var i = 0; i < pageNum; i++) {
							Files[fName].PageHashCode.Add("");
						}
					}
					// ありえないけど、同一日付でページ数が異なる
					if (pageNumOrg != pageNum) {
						Console.WriteLine($@"Mismatch pageNum:setP:{pageNum},DbP:{Files[fName].PageHashCode.Count}");
						this.Head.Dirty = true;// 変更あり
						return false; // Page数が一致しない
					}
				}
				return true;
			}
			public bool AddHashCode(string pathPDF, int pageNo/*1開始*/, string hashCode) {
				//Console.WriteLine("call addHashCode:" + pathPDF + ":" + pageNo);

				var fi = new FileInfo(pathPDF);
				var fName = fi.Name;
				var n = pageNo - 1;//0始まり
				if (!Files.ContainsKey(fName)) {
					this.Head.Dirty = true;// 変更あり
					return false;
				}

				if (Files[fName].PageHashCode[n] != hashCode) {
					this.Head.Dirty = true;// 変更あり
					Files[fName].PageHashCode[n] = hashCode;
				}
				return true;
			}
			public string GetHashCode(string pathPDF, int pageNo/*1開始*/) {
				var fi = new FileInfo(pathPDF);
				var fName = fi.Name;
				var n = pageNo - 1;//0始まり
				if (!Files.ContainsKey(fName)) {
					return "";
				}
				var v = Files[fName].PageHashCode;
				return (n < v.Count) ? v[n] : "";
			}
		}

	}

	namespace UtImage // Image保存のユーティリティー
	{
		// ref:https://water2litter.net/rum/post/cs_pdf_wpf/
		//using EncType = System.Drawing.Imaging.Encoder;// 別名
		//using EncParamType = System.Drawing.Imaging.EncoderParameter;//別名

		/// <summary>
		/// BitMapのSaveに使うImage Encodeパラメータの設定値の生成と 画像保存
		/// var enc = new UtImage.Enc("jpeg");
		/// //enc.SetImageType("PNG"); // 後で変更できる
		/// //enc.JJpegQuality = 100; JpegのQuarty変更
		/// enc.SaveImage(BitMap,path);
		/// // enc.SaveImage(BitMap,path,"PNG");
		/// </summary>
		/// 
		public class Enc
		{
			// SEE:https://dobon.net/vb/dotnet/graphics/encoderparameters.html
			public Enc(string imageType = "jpeg") { SetImageType(imageType); }
			public long JpegQuality { get; set; } = 91;// 指定しないときの値は91と一致する
			private string ImageType = "jpeg";

			public Enc 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;
			}
			/// <summary>
			/// サポートするEncodeパラメータ一覧
			/// 現状Errorが発生して取得できない
			/// </summary>
			/// <param name="bitmap1">対象のBitMap</param>
			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
				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.
			private 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;
			}
			private System.Drawing.Imaging.ImageCodecInfo GetInfo(string it = null) {
				if (it == null) {// SetImageType()で指定したものを使う
					it = ImageType;
				}
				return GetEncoderInfo($"image/{it}");
			}
			//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) {
				var encs = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
				//指定されたMimeTypeを探して見つかれば返す
				foreach (System.Drawing.Imaging.ImageCodecInfo enc in encs) {
					if (enc.MimeType == mineType)
						return enc;
				}
				return null;
			}
		}

	}
}
