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;


// 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 CSRenderMain {
	class Program {
		static void DispHelp() {
			var pgName = Path.GetFileName(Path.GetFileName(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"

				+ $"\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"
				+ $"/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) {

			string outuptImageDir = "";	// /O
			string dpi = "72.0";		// /D
			string boxSelect = "Crop";  //B<x>
			string pageRange = "1-*";   //P
			string mode = "page";		//MODE
			string imageType = "JPG";   //JPG or /PNG
			string jpegQ = "91";		//JPEGQ
			string pdfPath ="";
			bool bHash = false;         //ハッシュ値を生成する
			bool bMkHash = false;		//ハッシュファイルを作成する
			bool bFC = false;
			bool bDirMode = false;		// ディレクト指定の場合
			string pdfPath2 = "";		//比較ファイル
			string resultPath = "";

			//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")) {
					pdfPath = (qu.Count > 0) ? qu.Dequeue() :"";// next word.
				} else if (isOpt("/O")) {
					outuptImageDir = (qu.Count > 0) ? qu.Dequeue() :"";// next word.
				} else if (isOpt("/D")) {
					dpi = (qu.Count > 0) ? qu.Dequeue():"";// next word.
					if (!double.TryParse(dpi, out double dmy)) {
						Console.WriteLine($"解像度が不正です:/D {dpi}");
						DispHelp();
						return -1;
					}
				} else if (isOpt("/P")) {
						pageRange = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
				} else if (isOpt("/JPG","/JPEG")) {
					imageType = "JPG";
				} else if (isOpt("/PNG")) {
					imageType = "PNG";
				} else if (isOpt("/TIF","/TIFF")) {
					imageType = "TIFF";
				} else if (isOpt("/GIF", "/GIFF")) {
					imageType = "GIF";
				} else if (isOpt("/BMP")) {
					imageType = "BMP";
				} else if (isOpt("/JPEGQ")) {
					jpegQ = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
					if (!int.TryParse(jpegQ, out int dmy)) {
						Console.WriteLine($"JPEG Qualityが不正です:/JPEGQ {jpegQ}");
						DispHelp();
						return -1;
					}
					if (  !(0 < dmy && dmy <= 100) ) {
						Console.WriteLine($"JPEG Qualityが不正です(not 1-100):/JPEGQ {dmy}");
						return -1;
					}
				} else if (isOpt("/M") ) {
					mode = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
				} else if ( BoxSelOptDic.ContainsKey(wd) ) {
					boxSelect = BoxSelOptDic[wd];// "/BT" -> "Trim",...
				} else if (isOpt("/HASH")) {
					bHash = true;
				} else if (isOpt("/MKHASH")) {
					bMkHash = true;
					bHash = true;
				} else if (isOpt("/FC")) {
					bFC = true;
				} else if (isOpt("/RESULT")) {
					resultPath = (qu.Count > 0) ? qu.Dequeue() : "";// next word.
				} else if (wd.First()=='/') {
					// 処理の無いオプションを明示的に無視する
					Console.WriteLine($"Warning::Ignore opt:{wd}");
				} else {
					// 引数をファイル名として取得する
					if (pdfPath == "") {
						pdfPath = wd;
						continue;
					}
					if (bFC && (pdfPath2 == "") ) {
						pdfPath2 = wd;// 比較時のみ取得
						continue;
					} 
				}
			}
			if (pdfPath=="") {
				Console.WriteLine("pdfファイルが指定されてません");
				DispHelp();
				return -1;
			}
			if ( !(File.Exists(pdfPath)|| Directory.Exists(pdfPath)) ) {
				// ファイルもしくはディレクトも見つからない場合
				Console.WriteLine($"ファイルが存在しません:{pdfPath}");
				return -1;
			}

			var rdCond = new RenderPDF.RenderConditionParams {
					Dpi = double.Parse(dpi),
					ImageType = imageType,
					BoxType = boxSelect,
					JpegQ= int.Parse(jpegQ)
			};
			bool bDir = File.GetAttributes(pdfPath).HasFlag(FileAttributes.Directory);
			if ( bDir ) {
				bDirMode = true;
				// Directoryが指定されたのでファイルリストアップ
				pdfPathLst = System.IO.Directory.GetFiles(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}");
				}
			}
			if ((!bFC) && bDir ) {
				// 単純レンダリング時およびディレクト時に対象リストに追加
				foreach (var f in pdfPathLst) {
					pdfPathLstBoth.Add(f);
				}
			}
			if (bFC) {// 比較モード時
				if (pdfPath2 == "") {
					Console.WriteLine("比較モードで2つ目のpdfファイルが指定されてません");
					return -1;
				}
				if (!(File.Exists(pdfPath2) || Directory.Exists(pdfPath2))) {
					Console.WriteLine($"比較ファイルが存在しません:{pdfPath2}");
					return -1;
				}
				bool bDir2 = File.GetAttributes(pdfPath2).HasFlag(FileAttributes.Directory);
				if (bDir2) {
					if (!bDir) {
						Console.WriteLine($"比較対象はファイルパスでないといけません:{pdfPath2}");
						return -1;
					}
					// 2つ目のファイルリストアップ
					pdfPathLst2 = System.IO.Directory.GetFiles(pdfPath2, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/);
					pdfPathLst2 = Array.ConvertAll( pdfPathLst2, f => Path.GetFileName(f) );// 配列の書き換え
					foreach( var f in pdfPathLst2) {
						Console.WriteLine($@"path2={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が作成済み
				}
			}
			var watch = System.Diagnostics.Stopwatch.StartNew(); // 時間の生成と計測開始を同時に行う
			var otDir = outuptImageDir;
			if (otDir == "" ) {  
				// 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する
				otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG");
			}

			if (!Directory.Exists(otDir) /* && (!bMkHash)*/ ) { // MkHashのとき不要→必要
				Directory.CreateDirectory(otDir);
			}
			var otHashPath = "";
			var otHashPath2 = "";
			int ret = -1;
			if (!bDirMode) {
				if (bFC) {
					//比較モード
					// ハッシュデータの読み込み
					Console.WriteLine($@"pre pdfPath={pdfPath}");
					Console.WriteLine($@"pre pdfPath2={pdfPath2}");

					var hashDataTgt = HashData.load(pdfPath, hashBaseName, dpi, boxSelect, imageType, jpegQ, ref otHashPath);
					var hashDataRef = HashData.load(pdfPath2, hashBaseName, dpi, boxSelect, imageType, jpegQ, ref otHashPath2);
					Console.WriteLine("<Compare Sync(Paralles) Version!>");
						ret = RenderPDF.RenderPdfDocCompare(
							pdfPath: Path.GetFullPath(pdfPath),
							refPdfPath: Path.GetFullPath(pdfPath2),
							inDir: otDir,
							pm:rdCond,
							inPageRange: pageRange,
							inHashDataTgt: hashDataTgt,
							inHashDataRef: hashDataRef
						);
					//} else if (mode.ToLower() == "async") {
					//	// これはもはや不要
					//	ret = RenderPDF.RenderPdfDocAsync(
					//		...
					//	).Result;// スレッドの終了まで「待つ」
				} else {
					// Sync version.
					// Console.WriteLine("<Render Sync(Paralles) Version!>");
					// シングル指定では既存のハッシュファイルを読みださない → 単独名のハッシュファイル<pdfFName+".hash")
					// 空のハッシュデータを作成
					var hashDataTgt = new HashData();
					hashDataTgt.Head.SetRenderInfo(dpi, boxSelect, imageType, jpegQ);
					//retはページ数
					// rdCond

					ret = RenderPDF.RenderPdfDoc(
						pdfPath: Path.GetFullPath(pdfPath),
						inDir: otDir,
						pm	:rdCond,
						inPageRange: pageRange,
						bSaveImage: bMkHash?false:true,// ハッシュ値生成ではイメージ保存しない。
						bHash: bHash,
						inHashData: hashDataTgt
					);
					if (bMkHash) {
						// 個別のファイル名＋"hash"拡張子で保存します。
						var othashPath = Path.Combine(otDir,Path.GetFileName(pdfPath)+".hash");
						hashDataTgt.save(othashPath);//ハッシュモードで保存する(Dutyチェックもいるでしょう
						//不要:hashDataTgt.save(otHashPath);//比較モード,MakeHashに限定すること
						//表示のみ まだやめておく：
					}
					// retはページ数です。
				}
			} else {
				// 対象がディレクトリ
				Console.WriteLine("For Directory mode");

				HashData hashDataTgt = null, hashDataRef = null;
				//if ( !bMkHash) {// MKHashの時にはLoadしない 。ファイルがなくても、otHashPathは帰ってくる
					hashDataTgt = HashData.load(pdfPath, hashBaseName, dpi, boxSelect, imageType, jpegQ, ref otHashPath);
				//}
				if (hashDataTgt == null) {
					// 空のハッシュデータを作成
					hashDataTgt = new HashData();
					hashDataTgt.Head.SetRenderInfo(dpi, boxSelect, imageType, jpegQ);
				}
				if ( bFC) {
					hashDataRef = HashData.load(pdfPath2, hashBaseName, dpi, boxSelect, imageType, jpegQ, ref otHashPath2);
					if (hashDataRef == null) {
						// 空のハッシュデータを作成
						hashDataRef = new HashData();
						hashDataRef.Head.SetRenderInfo(dpi, boxSelect, imageType, 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）は対応していない？

				for (var index=0; index < pdfPathLstBoth.Count; index++) {
					GC.Collect();// これでメモリリークが解決した
					var tgt  = Path.Combine(pdfPath, pdfPathLstBoth[index]);
					Console.WriteLine($@"tgt={tgt}:{index}/{pdfPathLstBoth.Count}");
					if (bFC) {
						//比較モード
						var reff = Path.Combine(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: pageRange,
							inHashDataTgt: hashDataTgt,
							inHashDataRef: hashDataRef
						);
					} else { // Sync version(Paralles).
						ret = RenderPDF.RenderPdfDoc(
							pdfPath: Path.GetFullPath(tgt),
							inDir: otDir,
							pm: rdCond,
							inPageRange: pageRange,
							bSaveImage: bMkHash ? false:true,// ハッシュ値生成ではイメージ保存しない。
							bHash: bHash,// bHash,dumy
							inHashData : bMkHash ? hashDataTgt:null //ハッシュコマンドモードのみDataを渡す
						);
					}
					count--;
				};

				if (bMkHash) {
					hashDataTgt.save(otHashPath);//ハッシュモードで保存する(Dutyチェックもいるでしょう
				}
			}
			watch.Stop();
			Console.WriteLine( $"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;
			}
		}

	}
}

