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.Threading;
using System.Threading.Tasks;

using System.Runtime.InteropServices;

using System.Runtime.Serialization;
using System.ServiceModel;				// WCF
using System.Diagnostics;               // for Process


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

// 作成クラス
using CSRender;
using CSRender.UtHash;

//  *重要* デスクトップアプリで UWP Api を呼び出す  プロジェクトの設定方法が記載されている
// https://docs.microsoft.com/ja-jp/windows/apps/desktop/modernize/desktop-to-uwp-enhance
// 参照はwindows. winmd	:C:\Program Files (x86) \Windows Kits\10\UnionMetadata\<sdk バージョン>/ファサード

namespace xChangeWCFPipe
{

}

namespace CSRenderMain {
	[DataContract]
	//[Serializable()] // これでもいけそう
	public class ParamData {
		[DataMember]
		public string pdfPath = "";     //対象ファイル
		[DataMember]
		public string pdfPath2 = "";       //比較ファイル
		[DataMember]
		public string outuptImageDir = "";      // /O
		[DataMember]
		public string dpi = "72.0";   // /D 
		[DataMember]
		public string boxSelect = "Crop";   // /B<x>
		[DataMember]
		public string pageRange = "1-*";    // /P
		[DataMember]
		public string mode = "page";   // /MODE
		[DataMember]
		public string imageType = "JPG";    // /JPG or /PNG
		[DataMember]
		public string jpegQ = "91";     // JPEGQ 
		[DataMember]
		public bool bHash = false;    // ハッシュ値を生成する
		[DataMember]
		public bool bMkHash = false;    // ハッシュファイルを作成する
		[DataMember]
		public bool bFC = false;
		[DataMember]
		public string resultPath = "";
		[DataMember]
		public bool bPDFium = true;
		//
		[DataMember]
		public bool bExeSepa = true;// 実行分離
		[DataMember]
		public string subExe = null;// "Sub"が指定されたら、処理をExe分離する。内部コマンド
									// データはWCFの通信データを使う。引数はPortAddressNameとする
		public ParamData Clone() {
			return (ParamData)MemberwiseClone();
		}
	}
	public class Program {
		static void DispHelp() {
			var pgName = Path.GetFileName(Path.GetFileName(Assembly.GetExecutingAssembly().Location));// プログラム名
			var pgNameFull = Assembly.GetExecutingAssembly().Location;// プログラム名
			string msg =
				 $"{pgName} [/<Opts>] <PDFPath or PDFDir>\n"
				+ $"* Render of PDF file. available 3 command mode:[Basic Rendering] [Make Hash command] [Compare command], and [Render Options]\n"
				+ $"\tPDFの画像化は[Basic Rendering]。\n"
				+ $"\t比較は/MkHash([Make Hash command])後に、/FC([Compare command])で高速実行できます。\n"
				+ $"\n"
				+ $"[Basic Rendering] 基本的なレンダリング\n"
				+ $"\t{pgName} [/<Render Options>] <PDFPath|PDFDir>\n"
				+ $"\t/F <pdfPath|pdfDir> : pdfPath(pdfファイル名|ディレクトリ) /Fは省略可能\n"
				+ $"\t/O <output directory> : 出力ディレクトリ。省略時は\"IMG\"フォルダ\n"
				+ $"\n"
				+ $"[Render Options] レンダリングオプション\n"
				+ $"\t/D <解像度> : 解像度指定 9 - 300dpi(default=72dpi)\n"
				+ $"\t/BM,/BT,/BA,/BA,/BC: Select one box.(default=/BC:CrobBox): Boxies:MediaBox/BleedBox/TrimBox/ArtBox/CropBox\n"
				+ $"\t/JPG,/JPEG,/PNG,/TIF,/TIFF,/GIF,/BMP: Select one output format.(default=/JPG)\n"
				+ $"\t/JPEGQ <quality>: Jpegの品質指定1-100(default=91)\n"
				+ $"\n"
				+ $"\t/P <PageRange> : ページの範囲を指定する(省略時は全ページ)\n"
				+ $"\t\t連続した範囲を指定する場合は、ハイフン（'-'）を用いる。終了側を省略すると最終pageまで。\n"
				+ $"\t\t複数のページを指定する場合は、カンマ（','）を用いる\n"
				+ $"\t\tEx. /P \"1,2,30-100\" //1,2pages and 30-100pages.\n"
				+ $"\t[unsupport] 未対応\n"
				+ $"\t/L <input List text> : 入力PDFファイルリスト(*unsupport)\n"
				+ $"\t/T  <tempPath> : テンポラリフォルダを指定（省略時は出力先フォルダと同じ(*unsupport no need)\n"
				+ $"\t/OP <0|1> : オーバープリントのOn/Off (省略時は1)(*unsupport allways on[1])\n"
				+ $"\t/U <0|1> : 同名上書き設定 0:上書きしない 1:上書き(*unsupport allways overwrite[1])\n"
				+ $"\t/OFFSET <X> <Y> : ミリ単位でオフセットを指定する（省略時は共に0mm)(*unsupport)\n"
				+ $"\t/PDFium <0or1>: GoogoleのPDFiumViewerエンジンを使用する(default=1>\n"

				+ $"\n"
				+ $"[Make Hash command] 比較用ハッシュ値作成コマンド\n"
				+ $"\t{pgName} /MkHash ...<Render Options>... <PDF|PDF dir>\n"
				+ $"\t/MKHash : ハッシュ値を出力する。前記の[Render Options]を指定すること\n"
				+ $"\t[HASHファイル作成]\n"

				+ $"\n"
				+ $"[Compare command] 比較コマンド\n"
				+ $"\t{pgName} /FC ...<Render Options>... <Target PDF|PDF dir> <Reference PDF|PDF dir>\n"
				+ $"\t/FC : 2つのPDFを比較する。前記の[Render Options]を指定すること。無名引数が2つ必要です\n"
				+ $"\t 事前に/MkHashを実行しておくことで高速に処理できる\n"
				+ $"\t/RESULT <result file> : 比較結果を格納するファイルパス\n"
				+ $"\t\t/FCコマンドを指定すると一致したら0，不一致なら1を返却するようになる\n"
				+ $"\t\t<result file>は、<pageNum(1始まり)>,<OK or NG>の行で構成される\n"
				
				+ $"\n"
				+ $"[ELSE ] その他のオプション\n"
				+ $"\t/PDFium <0|1>:PDFiumライブラリを使う,デフォルト=1\n"
				+ $"\t/NoExeSepa :実行分離しない(遅い)\n"
				+ $"\t内部コマンド：/SubExe <WCF_PipeAddress> :実行分離,PDF単位で別Processで処理\n"
				+ $"\n"
				+ $"/H or /?  : This help\n"


				/* PDFと同じフォルダに
					data.<出力条件>.jsonファイルを作成する
					出力条件：<dpi>+<Box>+<IMFormat>+<JQ>
							data.72_BT_JPG_Q34.json
					既に存在したら、それを読んでから、追記（もしくは書き換える＞。
				 */

				//  Remove TEST
				//+$"\t[for TEST]\n"
				//+$"\t/M Sync(default) or ASync\n"
				//+$"\t/M <mode> : pageBitMap(default), pageBitMapImage, page(same as pageBitMap), org(orginal source)\n"
				+ "\n";

			Console.Write(msg);
		}
#if COMENT
// 設計の見直し

* ハッシュ作成のみのコマンド
		→ /MkHashをつけると ハッシュファイルのみ作成。ただし、ディレクトリ指定に限る
		→ CSRender /MkHash <PDFディレクトリ> <各種Renderパラメータ>

* 単純なRenderを維持。それに同時にハッシュファイルのOn/Off
		→ /MkHashをつけなければよい。
		→ CSRender <PDFディレクトリor PDFファイル> <各種Renderパラメータ>

* 比較モードは、Autoハッシュで、存在しなければ最初に作成。Target/Refともに。比較しながら作成してよし
  Manualでハッシュなしで、遅いけどできるようにする。ハッシュ動作の検証用に
		→ /FCをつけると、デフォルトでハッシュ優先で検査(Auto相当)、ハッシュ検査を無効にしたければ
		→ /NoHash追加でハッシュを無視してTarget/Refとも再計算。ハッシュを使いたければ、事前に/MkHashで
		  作成しておけばよい。
* ヘルプは <単純なRender> <ハッシュ作成> <比較>にわける
		* <単純なレンダラ>: 1ファイルおよびディレクトリの比較。 Renderingパラメータの一覧(項目分ける）まで.
		* <ハッシュ作成>: /MkHashで高速比較のための前準備。ディレクトリモードでハッシュファイルのみを作成する。Renderingパラメータは合わせること
		* <比較>:/FCコマンドで比較の説明

* ここにAsiccDocを埋められないの？ → 情報なし*
// 遅くなるので
* 比較モード時の差異があったとき、Diffを出す上限値をLimitDiffNumがほしい(デフォルトは各PDFで1pageとしたい）
* 比較NGになって、フルでDIFF画像を出したいときはLimitDiffを解除すべき。単独ファイル指定のとき。
			 
// 設計の見直し(END)
#endif

		/// <summary>
		/// PDFレンダラーメイン
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		static int Main(string[] args) {

			// 引数の保持
			var pm = new ParamData();

			bool bDirMode = false;      // ディレクト指定の場合

			//var pdfPathLst = new List<string>();
			string[] pdfPathLst = null;
			string[] pdfPathLst2 = null;
			var pdfPathLstBoth = new List<string>();
			var pdfPathLstNoBoth = new List<string>();
			//ハッシュ関係
			string hashBaseName = "RenderHash";
			//
			CSRender.Check.GetDPI();


			{// ベースハッシュ値の計算テスト
				var streamPDF = ResData.GetBaseHashPDF();
				RenderPDF.RenderPdfInitCheck(streamPDF);
				//return 0;

			}

			var qu = new Queue<string>(args); // 引数をQueに登録 (qu.Enqueue(a)でも可能)
			while (qu.Count > 0) {
				// 引数のパース 
				var wd = qu.Dequeue();  // 取り出しqu.Dequeue()で次の要素を取得する
				if (wd.First() == '-') {
					// 先頭-(ハイフンもオプション扱いにする)→"/"に置換
					wd = Regex.Replace(wd, @"^\-", "/");
				}
				//大文字小文字無視でオプションチェック
				var eIgnoreCase = StringComparer.OrdinalIgnoreCase;
				// オプションチェックローカル関数
				bool isOpt(params string[] opts) => opts.Contains<string>(wd, eIgnoreCase);
				// ボックスオプション辞書
				var BoxSelOptDic = new Dictionary<string, string>(eIgnoreCase) { ["/BM"] = "Media", ["/BT"] = "Trim",
					["/BB"] = "Bleed", ["/BC"] = "Crop",
					["/BA"] = "Art"
				};
				if (isOpt("/?", "/H")) {
					DispHelp();
					return -1;
				} else if (isOpt("/F")) {
					pm.pdfPath = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
				} else if (isOpt("/O")) {
					pm.outuptImageDir = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
				} else if (isOpt("/D")) {
					pm.dpi = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
					if (!double.TryParse(pm.dpi, out double dmy)) {
						Console.WriteLine($"解像度が不正です:/D {pm.dpi}");
						DispHelp();
						return -1;
					}
				} else if (isOpt("/P")) {
					pm.pageRange = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
				} else if (isOpt("/JPG", "/JPEG")) {
					pm.imageType = "JPG";
				} else if (isOpt("/PNG")) {
					pm.imageType = "PNG";
				} else if (isOpt("/TIF", "/TIFF")) {
					pm.imageType = "TIFF";
				} else if (isOpt("/GIF", "/GIFF")) {
					pm.imageType = "GIF";
				} else if (isOpt("/BMP")) {
					pm.imageType = "BMP";
				} else if (isOpt("/JPEGQ")) {
					pm.jpegQ = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
					if (!int.TryParse(pm.jpegQ, out int dmy)) {
						Console.WriteLine($"JPEG Qualityが不正です:/JPEGQ {pm.jpegQ}");
						DispHelp();
						return -1;
					}
					if (!(0 < dmy && dmy <= 100)) {
						Console.WriteLine($"JPEG Qualityが不正です(not 1-100):/JPEGQ {dmy}");
						return -1;
					}
				} else if (isOpt("/M")) {
					pm.mode = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
				} else if (BoxSelOptDic.ContainsKey(wd)) {
					pm.boxSelect = BoxSelOptDic[wd];// "/BT" -> "Trim",...
				} else if (isOpt("/HASH")) {
					pm.bHash = true;
				} else if (isOpt("/MKHASH")) {
					pm.bMkHash = true;
					pm.bHash = true;
				} else if (isOpt("/FC")) {
					pm.bFC = true;
				} else if (isOpt("/PDFium")) {
					pm.bPDFium = true;
					var flgStr = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
					if (!int.TryParse(flgStr, out int dmy)) {
						Console.WriteLine($"PDFiumフラグが不正です:/PDFium {flgStr}");
						return -1;
					}
					if (dmy == 0) {
						pm.bPDFium = false;
					} else if (dmy == 1) {
						pm.bPDFium = true;
					} else {
						Console.WriteLine($"PDFiumフラグが不正です:/PDFium {flgStr}");
						return -1;
					}
				} else if (isOpt("/SubExe")) {
					pm.subExe = (qu.Count > 0) ? qu.Dequeue() : null;// next word.
				} else if (isOpt("/NoExeSepa")) {
					pm.bExeSepa = false;
				} else if (isOpt("/RESULT")) {
					pm.resultPath = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
				} else if (wd.First() == '/') {
					// 処理の無いオプションを明示的に無視する
					Console.WriteLine($"Warning::Ignore opt:{wd}");
				} else {
					// 引数をファイル名として取得する
					if (pm.pdfPath == "") {
						pm.pdfPath = wd;
						continue;
					}
					if (pm.bFC && (pm.pdfPath2 == "")) {
						pm.pdfPath2 = wd;// 比較時のみ取得
						continue;
					}
				}
			}

			//
			bool bSubExe = (pm.subExe != null);// SubExeで起動されている場合。

			xChangeWCFPipe.IXData cltSrv = null;

			if (!bSubExe) {
				/// サーバー側のセットアップ
				Console.WriteLine("++MainProcess start");
			} else {
				// SubExe側の動作に差し替える
				Console.WriteLine("++SubProcess start:"+pm.subExe);

				//cltSrv = xChangeWCFPipe.CLT.makeCLT(pipeName:"pName",pipeAddress:pm.subExe);
				cltSrv = xChangeWCFPipeGEN.CLT_T.makeCLT<xChangeWCFPipe.IXData>(
													pipeName:"pName",pipeAddress:pm.subExe);

				Console.WriteLine("[S]:End makeCLT");

				var pm2 = cltSrv.GetParam();
				Console.WriteLine($@"[S]:pm2.pdfPath={pm2.pdfPath}");


				var ppp = cltSrv.GetData();
				cltSrv.SetMessage("Message");
				Console.WriteLine("[S]:End GetData");

				pm = ppp.pm;               // パラメータを呼び出し元のものに置き換え
				Console.WriteLine("[S]:End GetData pm");
				Console.WriteLine($@"[S]:pm.pdfPath={ppp.pm.pdfPath}");
				Console.WriteLine($@"[S]:ppp.dataB.b={ppp.dataB.b}");


			}
			if (pm.pdfPath == "") {
				Console.WriteLine("pdfファイルが指定されてません");
				DispHelp();
				return -1;
			}
			if (!(File.Exists(pm.pdfPath) || Directory.Exists(pm.pdfPath))) {
				// ファイルもしくはディレクトも見つからない場合
				Console.WriteLine($"ファイルが存在しません:{pm.pdfPath}");
				return -1;
			}
			var rdCond = new RenderPDF.RenderConditionParams {
				Dpi = double.Parse(pm.dpi),
				ImageType = pm.imageType,
				BoxType = pm.boxSelect,
				JpegQ = int.Parse(pm.jpegQ)
			};
			bool bDir = File.GetAttributes(pm.pdfPath).HasFlag(FileAttributes.Directory);
			if (bDir) {
				// Directoryが指定されたのでファイルリストアップ
				bDirMode = true;
				pdfPathLst = System.IO.Directory.GetFiles(pm.pdfPath, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/);
				pdfPathLst = Array.ConvertAll(pdfPathLst, f => Path.GetFileName(f)); // 配列書き換え（ファイル名のみにする
																					 // var enumLst  = pdfPathLst.Select(f => Path.GetFileName(f)); LINQ式に置き換えることも可能(返り値は配列ではない）
																					 //foreach ( var f in pdfPathLst) { 
																					 //	Console.WriteLine($@"path1={f}");
																					 //}
				Console.WriteLine($@"path1.Len={pdfPathLst.Count()}");

			}
			if ((!pm.bFC) && bDir) {
				// 単純レンダリング時 かつ ディレクト時に 対象リストに追加
				foreach (var f in pdfPathLst) {
					pdfPathLstBoth.Add(f);
				}
			}
			if (pm.bFC) {// 比較モード時
				if (pm.pdfPath2 == "") {
					Console.WriteLine("比較モードで2つ目のpdfファイルが指定されてません");
					return -1;
				}
				if (!(File.Exists(pm.pdfPath2) || Directory.Exists(pm.pdfPath2))) {
					Console.WriteLine($"比較ファイルが存在しません:{pm.pdfPath2}");
					return -1;
				}
				bool bDir2 = File.GetAttributes(pm.pdfPath2).HasFlag(FileAttributes.Directory);
				if (bDir2) {
					if (!bDir) {
						Console.WriteLine($"比較対象はファイルパスでないといけません:{pm.pdfPath2}");
						return -1;
					}
					// 2つ目のファイルリストアップ
					pdfPathLst2 = System.IO.Directory.GetFiles(pm.pdfPath2, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/);
					pdfPathLst2 = Array.ConvertAll(pdfPathLst2, f => Path.GetFileName(f));// 配列の書き換え

					//  共通のファイルを見つける。Lstの要素がLst2に含まれているかどうか
					foreach (var f in pdfPathLst) {
						if (pdfPathLst2.Contains(f)) {
							pdfPathLstBoth.Add(f);
						} else {
							pdfPathLstNoBoth.Add(f);// LstがLst2に含まれていない
						}
					}
					// Lst2のみファイルをNoBothに登録
					foreach (var f in pdfPathLst2) {
						if (!pdfPathLstBoth.Contains(f)) {
							pdfPathLstNoBoth.Add(f);
						}
					}
					// pdfPathBoth,pdfPathNoBothが作成済み
					if (pdfPathLstNoBoth.Count() != 0) {
						Console.WriteLine($@"不一致のファイル={pdfPathLstNoBoth.Count()}");
						foreach (var f in pdfPathLstNoBoth) {
							Console.WriteLine($@"warning [no match]={f}");
						}
					}
				}
			}
			var watch = System.Diagnostics.Stopwatch.StartNew(); // 時間の生成と計測開始を同時に行う
			var otDir = pm.outuptImageDir;
			if (otDir == "") {
				// 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する
				otDir = Path.Combine(Directory.GetParent(pm.pdfPath).FullName, "IMG");
			}

			if (!Directory.Exists(otDir) /* && (!bMkHash)*/ ) { // MkHashのとき不要→必要
				Directory.CreateDirectory(otDir);
			}
			var otHashPath = "";
			var otHashPath2 = "";
			int ret = -1;

			Console.WriteLine($@"bPDFium={pm.bPDFium}");

			if (!bDirMode) {
				if (pm.bFC) {
					//比較モード
					// ハッシュデータの読み込み
					Console.WriteLine($@"pre pdfPath={pm.pdfPath}");
					Console.WriteLine($@"pre pdfPath2={pm.pdfPath2}");

					var hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath);
					var hashDataRef = HashData.load(pm.pdfPath2, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2);
					//Console.WriteLine("<Compare Sync(Paralles) Version!>");

					ret = RenderPDF.RenderPdfDocCompare(
							pdfPath		: Path.GetFullPath(pm.pdfPath),
							refPdfPath	: Path.GetFullPath(pm.pdfPath2),
							inDir		: otDir,
							pm			: rdCond,
							inPageRange	: pm.pageRange,
							inHashDataTgt: hashDataTgt,
							inHashDataRef: hashDataRef,
							bPDFium		: pm.bPDFium
					);
				} else {
					// Sync version.
					// Console.WriteLine("<Render Sync(Paralles) Version!>");
					// シングル指定では既存のハッシュファイルを読みださない → 単独名のハッシュファイル<pdfFName+".hash")
					// 空のハッシュデータを作成
					var hashDataTgt = new HashData();
					hashDataTgt.Head.SetRenderInfo(pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ);
					//retはページ数
					// rdCond

					ret = RenderPDF.RenderPdfDoc(
							pdfPath			: Path.GetFullPath(pm.pdfPath),
							inDir			: otDir,
							pm				: rdCond,
							inPageRange		: pm.pageRange,
							bSaveImage		: pm.bMkHash ? false : true,// ハッシュ値生成ではイメージ保存しない。
							bHash			: pm.bHash,
							inHashData		: hashDataTgt,
							bPDFium			: pm.bPDFium
					);
					if (pm.bMkHash) {
						/// ここでMain側に通信データを返せばよい
						if (bSubExe) {
							cltSrv.SetHashData(hashDataTgt);
						} else {
							// 個別のファイル名＋"hash"拡張子で保存します。
							var othashPath = Path.Combine(otDir, Path.GetFileName(pm.pdfPath) + ".hash");
							hashDataTgt.save(othashPath);//ハッシュモードで保存する(Dutyチェックもいるでしょう
														 //不要:hashDataTgt.save(otHashPath);//比較モード,MakeHashに限定すること
														 //表示のみ まだやめておく：
						}
					}
					// retはページ数です。
				}
			} else {
				// 対象がディレクトリ
				Console.WriteLine("For Directory mode");
				HashData hashDataTgt = null, hashDataRef = null;
				hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath);
				if (pm.bMkHash) {
					hashDataTgt = null;// MKHashの時にはLoadしない → 一旦nullで除去して、otHashPathを利用する
				}
				if (hashDataTgt == null) {
					// 空のハッシュデータを作成
					hashDataTgt = new HashData();
					hashDataTgt.Head.SetRenderInfo(pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ);
				}
				if (pm.bFC) {
					// リファレンス側のハッシュの読み込み
					hashDataRef = HashData.load(pm.pdfPath2, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2);
					if (hashDataRef == null) {
						// 空のハッシュデータを作成
						hashDataRef = new HashData();
						hashDataRef.Head.SetRenderInfo(pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ);
					}
				}
				// ハッシュデータが適切に作成済みがどうかを確認。もしハッシュデータが存在しない場合は
				// ハッシュデータを渡してはいけない。ハッシュのFilesで確認するか？
				// ハッシュデータの作成プロセスをもっと簡単にすべき
				Console.WriteLine($@"Start render:{pdfPathLstBoth.Count}");
				//Console.WriteLine("<Render Sync(Paralles) Version!>");[
				var count = pdfPathLstBoth.Count;

				//ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 6 };
				//Parallel.For(0,pdfPathLstBoth.Count, options, index => {
				//逆に遅くなる UWPコール(render）は対応していない？
				if (pm.bExeSepa) {
					var tokenSource = new CancellationTokenSource();
					ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 4, CancellationToken=tokenSource.Token };
					var loopResult = Parallel.For(0,pdfPathLstBoth.Count, options, (index,lpState) => {
						//for (var index = 0; index < pdfPathLstBoth.Count; index++) {
						var tgt = Path.Combine(pm.pdfPath, pdfPathLstBoth[index]);
						Console.WriteLine($@"**remain({count})****tgt({index}/{pdfPathLstBoth.Count})={tgt}");
						if (pm.bFC) {
							//比較モード
							var reff = Path.Combine(pm.pdfPath2, pdfPathLstBoth[index]);
						} else { // Sync version(Paralles).
							var svrData = new xChangeWCFPipe.XData();
							var pmClone = pm.Clone();
							pmClone.pdfPath = tgt;// 引数のターゲットのみを書き換える
							svrData.SetParam(pmClone);
							var svrHost = xChangeWCFPipeGEN.SVR_T.makeSVR(
											baseIf		: typeof(xChangeWCFPipe.IXData), 
											obj			: svrData,
											pipeName	: "pName",
											pipeAddress	: $"PortName{index}"
										);
							//var svrHost = xChangeWCFPipe.SVR.makeSVR(x: svrData, pipeName: "pName", pipeAddress:$"PortName{index}");
							if ( svrHost == null) {
								Console.WriteLine("WCF cannot make");
								//tokenSource.Cancel();// 処理のキャンセル( throwされる）
								lpState.Stop();
								return;

							} else {
								var pgFullName = Assembly.GetExecutingAssembly().Location;
								var proc = Ut.Ut.DoCmd(
												cmdAndArgs: new string[] { $@"{pgFullName}", "/SubExe", $"PortName{index}" }
												, bEcho: false
												, bSameConsole: true
											);
								while (!proc.HasExited) {
									// DoCmd()が終了するまで監視
									//var dc = svrData.GetData();
									System.Threading.Thread.Sleep(2 * 1000);
								}
								var tmpH = svrData.GetHashData();
								if (tmpH == null) {
									Console.WriteLine("tmph is null");
								}
								if ((hashDataTgt != null) && (tmpH != null)) {
									//hashDataTgt.Files += tmpH.Files;
									Console.WriteLine($@"tempF.Count={tmpH.Files.Count()}");
									var merged = hashDataTgt.Files
										  .Concat(tmpH.Files.Where(pair =>
											!hashDataTgt.Files.ContainsKey(pair.Key))
										  ).ToDictionary(
											pair => pair.Key,
											pair => pair.Value
										  );
									hashDataTgt.Files = new SortedDictionary<string, HashFile>(merged);// hashを合成した。
								}
								proc.Dispose();
								svrHost.Close();
								svrData = null;
							}
						}
						//GC.Collect();// これでメモリリークが解決した
						count--;
					});
					if ( !loopResult.IsCompleted ) {
						Console.WriteLine("中断");
						pm.bMkHash = false;// Hash値保存抑制
					}
					////tokenSource.Cancel();// 処理のキャンセル
					//if ( tokenSource.IsCancellationRequested ) {
					//	pm.bMkHash = false;// Hash値保存抑制
					//}

					//};
				} else { // NoExeSepa
					for (var index = 0; index < pdfPathLstBoth.Count; index++) {
						var tgt = Path.Combine(pm.pdfPath, pdfPathLstBoth[index]);
						Console.WriteLine($@"tgt={tgt}:{index}/{pdfPathLstBoth.Count}");
						if (pm.bFC) {
							//比較モード
							var reff = Path.Combine(pm.pdfPath2, pdfPathLstBoth[index]);
							//Console.WriteLine("<Compare Sync(Paralles) Version!>");
							ret = RenderPDF.RenderPdfDocCompare(
								pdfPath: Path.GetFullPath(tgt),
								refPdfPath: Path.GetFullPath(reff),
								inDir: otDir,
								pm: rdCond,
								inPageRange: pm.pageRange,
								inHashDataTgt: hashDataTgt,
								inHashDataRef: hashDataRef,
								bPDFium: pm.bPDFium
							);
						} else { // Sync version(Paralles).
							ret = RenderPDF.RenderPdfDoc(
								pdfPath: Path.GetFullPath(tgt),
								inDir: otDir,
								pm: rdCond,
								inPageRange: pm.pageRange,
								bSaveImage: pm.bMkHash ? false : true,// ハッシュ値生成ではイメージ保存しない。
								bHash: pm.bHash,// bHash,dumy
								inHashData: pm.bMkHash ? hashDataTgt : null, //ハッシュコマンドモードのみDataを渡す
								bPDFium: pm.bPDFium
							);
						}
						count--;
						GC.Collect();// これでメモリリークが解決した
					};
				}
				if (pm.bMkHash) {
					hashDataTgt.save(otHashPath);//ハッシュモードで保存する(Dutyチェックもいるでしょう
				}
			}
			watch.Stop();
			Console.WriteLine($"bPDFium={pm.bPDFium},result={ret},time={ watch.ElapsedMilliseconds / 1000.0 }[sec]");
#if COMMNET
			Assembly assm = Assembly.GetExecutingAssembly();
			Console.WriteLine(assm.FullName);
			// 参照しているすべてのアセンブリを取得し、表示する
			foreach (AssemblyName refassm in assm.GetReferencedAssemblies()) {
				Console.WriteLine($@"  {refassm.FullName},Ver({refassm.Version}),VerComp({refassm.VersionCompatibility})" );
			}
			string clrVersion = System.Environment.Version.ToString();
			Console.WriteLine($@"clrVer={clrVersion}");
#endif
			return ret;//success.
		}

		// リソースからの取得
		public static class ResData {
			public static Stream GetBaseHashPDF(string resName = "CSRender.RES.BaseHash.pdf") {
				System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
				var sel = from x in asm.GetManifestResourceNames() select resName;
				if (sel.Count() == 1) {
					return asm.GetManifestResourceStream(sel.First());
				}
				return null;
			}
		}

	}
}
/// <summary>
/// WCF通信モジュールのラッパクラス。
/// ２つのExe間をWCFパイプモードで通信を行う
/// </summary>
namespace xChangeWCFPipe
{
	/// シリアライズ可能にすること
	/// </summary>
	[ServiceContract]
	public interface IXData
	{
		[OperationContract(IsOneWay = true)]
		void Execute();
		[OperationContract]
		bool SetMessage(string msg);
		[OperationContract]
		bool SetProgress(int per);//0-100.
		[OperationContract]
		DataContainer GetData();
		[OperationContract]
		void SetData(DataContainer d);

		[OperationContract]
		HashData GetHashData();
		[OperationContract]
		void SetHashData(HashData d);

		[OperationContract]
		CSRenderMain.ParamData GetParam();
		//CSRenderMain.Program.ParamData GetParam();
	}
	// ServiceContract https://tnakamura.hatenablog.com/entry/20080606/1220023868
	// https://devlights.hatenablog.com/entry/20111023/p2

	/// <summary>
	/// 通信用複雑データ。スカラー型(int,double,,,.)以外は[DataCOntract]属性を
	/// つけて、通知用Interfaceの引数や、返値で利用する
	/// </summary>
	// 複雑データ
	[DataContract]
	public class DataContainer
	{
		[DataMember]
		public DataA dataA;
		[DataMember]
		public DataB dataB;
		[DataMember]
		public CSRenderMain.ParamData pm;
		[DataMember]
		public HashData hdata;
	}
	public class DataA
	{
		public int a;
	}
	public class DataB : Inheritance
	{
		public string b;
	}
	public abstract class Inheritance
	{
		public byte[] c;
	}
}

namespace xChangeWCFPipe
{
	/// <summary>
	/// Main(サーバー)側 データ定義
	/// </summary>
	[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
	public class XData : IXData
	{
		private readonly Object LockObj = new object();// 排他制御用
		public delegate void callFunc();
		public callFunc callDelegate;
		public XData() {
			_dc.dataA = new DataA();
			_dc.dataA.a = 1;
			_dc.dataB = new DataB();
			_dc.dataB.b = "OK!!!!x";
			_dc.dataB.c = new byte[] { 0, 1, 2, 3 };
			_dc.pm = new CSRenderMain.ParamData();
			_dc.pm.pdfPath = "hoge init path";
		}
		public void Execute() {
			lock (LockObj) {
				callDelegate();    // 実装はデリゲートします
			}
		}
		public bool SetMessage(string msg) {
			lock (LockObj) {
				Console.WriteLine($"SVR:called setMessage({msg}) from CLT");
				_msg = msg;
			}
			return true;
		}
		public bool SetProgress(int per) {//0-100.
			lock (LockObj) {
				Console.WriteLine($"SVR:called setProgress({per}) from CLT");
				_progress = per;
			}
			return true;
		}

		public DataContainer GetData() {
			lock (LockObj) {
				return _dc;
			}
		}
		public void SetData(DataContainer dc) {
			lock (LockObj) {
				_dc = dc;
			}
		}
		public void SetHashData(HashData h) {
			lock (LockObj) {
				_dc.hdata = h;
			}
		}
		public HashData GetHashData() {
			lock (LockObj) {
				return _dc.hdata;
			}
		}

		//public void SetParam(ParamData pm) {
		public void SetParam(CSRenderMain.ParamData pm) {
				lock (LockObj) {
				Console.WriteLine("w: ParamData");
				Console.WriteLine($@"w1:path:{pm.pdfPath}");
				_dc.pm = pm;
				Console.WriteLine($@"w2:path:{_dc.pm.pdfPath}");

			}
		}

		public CSRenderMain.ParamData GetParam() {
		//public CSRenderMain.Program.ParamData  GetParam() {
				lock (LockObj) {
				Console.WriteLine("getParam call");
				Console.WriteLine($@"getParam pdfPath={_dc.pm.pdfPath}");
				return _dc.pm;
			}
		}
		// private data
		private string _msg = "init msg";
		private int _progress = 0;// 0-100 
		public  DataContainer _dc = new DataContainer();
	}
	/// <summary>
	///  Main(サーバー)側クラス
	/// </summary>
	public class SVR
	{
		// - static -
		/// <summary>
		/// 通信用のインスタンスを作成する
		/// </summary>
		/// <param name="x"></param>
		/// <param name="pipeName"></param>
		/// <param name="pipeAddress"></param>
		/// <returns></returns>
		static public SVR makeSVR(XData x, string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") {
			return (new SVR(x, pipeName, pipeAddress));
		}
		// - public I/F -
		public void Close() {
			Console.WriteLine($"Close Service:{_service!=null},{_serviceHost != null}");
			_service = null;
			Console.WriteLine("1");
			_serviceHost.Close();
			Console.WriteLine("2");

		}
		// - private -
		private XData _service = null;// 要らないかも
		private ServiceHost _serviceHost = null;
		private SVR() { }
		private SVR(XData x, string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") {
			const string pipeBase = "net.pipe://localhost";
			var uri = pipeBase + "/" + pipeName;
			_service = x;
			// デリゲート登録
			//callDelegate = () => { Console.WriteLine("svr:Delegate func called"); }
#if false
			{
				// インターフェースを求める
				// AddServiceEndpoint()にわたすIXDataはXData.Interfaces()から求められるが
				// インタフェースは一つとは限らないのでやめておく。
				Type interfType = null;
				Type type = _service.GetType();
				Type[] interfaces = type.GetInterfaces();
				Console.WriteLine($@"GetInterfaces***********************************");
				foreach (Type t in interfaces) {
					Console.WriteLine(t.ToString());
					interfType = t;
				};
				Console.WriteLine($@"End of GetInterfaces***********************************");
			}
#endif
			_serviceHost = new ServiceHost(_service, new Uri(uri));
			try {
				_serviceHost.AddServiceEndpoint(typeof(IXData)/*interfType*/, new NetNamedPipeBinding(), pipeAddress);
				_serviceHost.Open();
			} catch (AddressAlreadyInUseException) {
				Console.WriteLine("既にサービスは起動しています。");
			} catch (Exception e) {
				Console.WriteLine(e.ToString());
			}
		}
	}
}


namespace xChangeWCFPipe
{
	/// <summary>
	///  Sub(クライアント)側クラス
	/// </summary>
	public class CLT
	{
		/// <summary>
		/// Sub側の初期化処理。Main側とpipeName,pipeAddressを同じにすること
		/// </summary>
		/// <param name="pipeName"></param>
		/// <param name="pipeAddress"></param>
		/// <returns></returns>
		static public IXData makeCLT(string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") {
			const string pipeBase = "net.pipe://localhost";
			var address = pipeBase + "/" + pipeName + "/" + pipeAddress;
			//try {
				var factory = (new ChannelFactory<IXData>(new NetNamedPipeBinding(), new EndpointAddress(address)));
				IXData toMain = factory.CreateChannel();
				return toMain;
			//} catch (Exception e) {
			//	Console.WriteLine("makeCLT failed:" + e.ToString());
			//	return null;
			//}
		}
	}
}























/// <summary>
/// ジェネリック版
/// </summary>
/// 

#if COMMENT
	// インターフェースクラスはサーバーとクライアント両方で参照可能なところに定義する

	// 実装データクラスはサーバー内で定義する
	[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
	public class XData : IXData
	{
		...
	}
#endif

namespace xChangeWCFPipeGEN
{
	/// <summary>
	/// Main(サーバー)側 データ定義
	/// </summary>
	//[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
	//public class XData : IXData
	//{
	//	...
	//}
	/// <summary>
	///  Main(サーバー)側クラス
	/// </summary>
	/// 

	public class SVR_T
	{
		// - static -

		/// <summary>
		/// 
		/// </summary>
		/// <param name="baseIf"> 公開するインターフェースクラス </param>
		/// <param name="x">実装インスタンス（baseIfを含むこと)</param>
		/// <param name="pipeName"></param>
		/// <param name="pipeAddress"></param>
		/// <returns></returns>
		static public SVR_T makeSVR(Type baseIf, object obj/*XData*/ , string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") {

			Type oType = obj.GetType();
			bool hasBaseIf = oType.GetInterfaces().Any(t => (t == baseIf) );
			if ( !hasBaseIf) {
				Console.WriteLine($@"Error:'{oType.FullName}' have not the interface '{baseIf.FullName}'");//A have not  the interface B.
				return null;
			}
			return (new SVR_T(baseIf, obj, pipeName, pipeAddress));
		}
		// - public I/F -
		public void Close() {
			Console.WriteLine($"Close Service:{_service != null},{_serviceHost != null}");
			_service = null;
			_serviceHost.Close();
		}
		// - private -
		private object _service = null;// 要らないかも
		private Type _baseIf = null;
		private ServiceHost _serviceHost = null;
		private SVR_T() { }
		private SVR_T(Type baseIf, object oInstance, string pipeName, string pipeAddress) {
			const string pipeBase = "net.pipe://localhost";
			var uri = pipeBase + "/" + pipeName;
			_baseIf = baseIf;
			_service = oInstance;
			// デリゲート登録
			//callDelegate = () => { Console.WriteLine("svr:Delegate func called"); }
#if false
			{
				// インターフェースを求める
				// AddServiceEndpoint()にわたすIXDataはXData.Interfaces()から求められるが
				// インタフェースは一つとは限らないのでやめておく。
				Type interfType = null;
				Type type = _service.GetType();
				Type[] interfaces = type.GetInterfaces();
				Console.WriteLine($@"GetInterfaces***********************************");
				foreach (Type t in interfaces) {
					Console.WriteLine(t.ToString());
					interfType = t;
				};
				Console.WriteLine($@"End of GetInterfaces***********************************");
			}
#endif
			_serviceHost = new ServiceHost(_service, new Uri(uri));
			try {
				_serviceHost.AddServiceEndpoint(_baseIf/*typeof(Ti)*/, new NetNamedPipeBinding(), pipeAddress);
				_serviceHost.Open();
			} catch (AddressAlreadyInUseException) {
				Console.WriteLine("既にサービスは起動しています。");
			} catch (Exception e) {
				Console.WriteLine(e.ToString());
			}
		}
	}
	/// <summary>
	///  Sub(クライアント)側クラス
	/// </summary>
	public class CLT_T
	{
		/// <summary>
		/// Sub側の初期化処理。Main側とpipeName,pipeAddressを同じにすること
		/// </summary>
		/// <param name="pipeName"></param>
		/// <param name="pipeAddress"></param>
		/// <returns></returns>
		static public Ti makeCLT<Ti>(string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") {
			const string pipeBase = "net.pipe://localhost";
			var address = pipeBase + "/" + pipeName + "/" + pipeAddress;
			//try {
			var factory = (new ChannelFactory<Ti>(new NetNamedPipeBinding(), new EndpointAddress(address)));
			Ti toMain = factory.CreateChannel();
			return toMain;
			//} catch (Exception e) {
			//	Console.WriteLine("makeCLT failed:" + e.ToString());
			//	return null;
			//}
		}
	}
}
















namespace Ut
{
	public class Ut
	{
		// Util
		// --------------------------------------------------------------
		/// <summary>
		/// 親プロセスが終了したら、自身を終了させる
		/// </summary>
		/// <param name="bEnable"></param>
		public static void SetAutoSelfKillByMainProcEnd(bool bEnable = true) {
			if (_SingletonProc != null)
				return;//一度しか呼び出せない
			if (bEnable == false)
				return;
			const string fname = "SetAutoSelfKillByMainProcEnd";
			// 自動的に親のプロセスがいなくなったら自動的にキルモードを設定する
			// メインプロセスの終了チェック
			// 親プロセスIDでProcessハンドルを取得、そのExitedイベントに自身の終了関数を設定(Environment.Exit())
			int paProcID = GetParentProcessId();
			var paProc = Process.GetProcessById(paProcID);
			paProc.EnableRaisingEvents = true;
			Console.WriteLine($@"{fname}:ParrentProcessName ={paProc.ProcessName}({paProcID})");
			paProc.Exited += new EventHandler(
				(object s, EventArgs a) => {
					Console.WriteLine($@"{fname}:Exited Event!!!!");
					var ss = s as Process;
					Console.WriteLine($@"Sure ProcName is {ss.ProcessName}({ss.Id}) {ss.StartInfo.Arguments}"); ;
					System.Threading.Thread.Sleep(10 * 1000);
					ss.Close();
					ss.Dispose();
					Console.WriteLine($@"{fname}:Exited Event!!!!(afer10sec)");
				//Environment.Exit(-1);
			});
			//
			_SingletonProc = paProc;
		}
		private static System.Diagnostics.Process _SingletonProc = null;
		/// <summary>
		/// コマンドライン引数複数個をエンコードして、スペースで結合
		/// </summary>
		/// <param name="values">string[] コマンドライン引数</param>
		/// <returns>コマンドライン文字列(Escaped)</returns>
		public static string makeCmdLine(IEnumerable<string> args) {
			if (args == null)
				throw new ArgumentNullException("args");
			string EscapeCmdLineArg(string v) {
				if (string.IsNullOrEmpty(v)) return "";
				var containsSpace = v.IndexOfAny(new[] { ' ', '\t' }) != -1;
				v = ReCommandLineEscapePattern.Replace(v, @"$1\$&");//「\…\"」をエスケープ.「"」直前の「\」の数を 2倍+1
				if (containsSpace) {
					v = "\"" + ReLastBackSlashPattern.Replace(v, "$1$1") + "\"";
				}
				return v;
			}
			return string.Join(" ", args.Select(v => EscapeCmdLineArg(v)));
		}
		private static Regex ReCommandLineEscapePattern = new Regex("(\\\\*)\"");
		private static Regex ReLastBackSlashPattern = new Regex(@"(\\+)$");

		/// <summary>
		/// 親のプロセスIDを取得する
		/// </summary>
		/// <returns>親ProcessID</returns>
		static int GetParentProcessId() {
			var myProcId = GetCurrentProcessId();
			var query = string.Format($@"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {myProcId}");
			//クエリから結果を取得
			using (var search = new System.Management.ManagementObjectSearcher(@"root\CIMV2", query))
			using (var results = search.Get().GetEnumerator()) {
				if (!results.MoveNext())
					throw new ApplicationException("Couldn't Get ParrentProcessId.");
				var queryResult = results.Current;
				//親プロセスのPIDを取得
				uint pa = (uint)(queryResult["ParentProcessId"]);
				return (int)(pa);
			}
		}
		/// <summary>
		/// 自身のプロセスIDを取得する
		/// </summary>
		/// <returns>ProcessID</returns>
		static int GetCurrentProcessId() { return Process.GetCurrentProcess().Id; }
		/// <summary>
		/// 外部プログラムを起動する
		/// </summary>
		/// <param name="cmdAndArgs">引数文字列の配列</param>
		/// <param name="bSameConsole">true:コンソールを呼び出しと共有 false:別Console Windows</param>
		/// <param name="bEcho">標準出力(未実装）</param>
		/// <param name="bWait">コマンドが終了するまで待つ（false:未実装,常に待つ）</param>
		/// <param name="nWaitLimitMSec">タイムアウト時間ms(未実装)</param>
		/// <returns></returns>
		public static Process DoCmd(IEnumerable<string> cmdAndArgs, bool bSameConsole = true, bool bEcho = true, bool bWait = false, int nWaitLimitMSec = -1) {
			var cmdName = cmdAndArgs.First();//[0];
			var argStr = makeCmdLine(cmdAndArgs.Skip(1));// 先頭を除外してコピー new ArraySegment<string>(cmdAndArgs.ToArray(), 1, cmdAndArgs.Count()-1)
			Console.WriteLine($@"DoCmd:{cmdName}:args[{argStr}]");
			//using (var p = new Process()) {
			var p = new Process();
			{
				var info = p.StartInfo;
				p.StartInfo.FileName = cmdName;
				p.StartInfo.Arguments = argStr;
				if (bSameConsole) {
					Console.WriteLine("same windows mode");
					p.StartInfo.UseShellExecute = false; // Shell経由で実行しない（必須） true: 別ウインドウ。false:コンソールを共有
					p.StartInfo.CreateNoWindow = false;   // ウィンドウを作成しない
				} else {
					Console.WriteLine("new windows mode");
					p.StartInfo.UseShellExecute = true;
					p.StartInfo.CreateNoWindow = true;
				}
				p.Start();// 実行
				if (bWait == false) {
					return p;
				}
				if (nWaitLimitMSec == -1) {
					p.WaitForExit();// タイムアウト無し
				} else {
					bool bExit = p.WaitForExit(nWaitLimitMSec);
					if (!bExit) {// 処理の終了を待つ
						Console.WriteLine("処理が終了しないので強制終了");
						p.Kill(); // 強制終了する
						return p;
					}
				}
				return p;
			}
		}
	}
}