/* ========================================================================
 * Copyright 2018 - Volian Enterprises, Inc. All rights reserved.          
 * Volian Enterprises - Proprietary Information - DO NOT COPY OR DISTRIBUTE
 * ------------------------------------------------------------------------
 * This program is to automate the Comparison of PDF output using the debug
 * files created during automated testing.  The SQL script is used to create
 * the debug files; DebugPagination.txt and DebugMeta.txt.  This comparison 
 * code then compares these files run with two versions of the PROMS code
 * and finds pages that have been impacted by the changes to the PROMS code.
 * 
 * By Identifying Procedures and Pages that have been changed the amount of
 * time required to perform comparisons is greatly reduced.
 * 
 * The Pagination Comparison simply looks for things that have impacted page
 * breaks.  This is a quick summary of the page breaks for all of the 
 * procedures in a folder.  Thus, the folders are listed which contain 
 * differences.  When a folder is selected, the pagination information is 
 * compared for all of the procedures in each folder.  If no changes exits,
 * then the folder is not contained in the list. after selecting a folder,
 * a comparison is shown of the pagination lines that are different.  
 * Pressing the UC button allows you to see ultracompare with the two
 * pagination files.  If you select a line that is different, two pdf
 * windows are opened to the relative pages.
 * 
 * The Meta Comparison looks at more detail including text and formatting
 * differences.  This information is organized by procedure and page.  It 
 * uses the procedure/page/line classes.  The structure of this file is 
 * described in 
 * V:\Proms Versions\Automated Testing\Baseline Metafile Key.docx
 * By Clicking on a folder a list of impacted procedures is provided.  
 * By clicking on a Procedure a list of pages and lines are provided.
 * By clicking on a line the PDF is displayed.
 * 
 * The Search feature performs a text search on the Meta File.  Looking 
 * for RCP will tell you where RCP can be found in the PDFs.  This may be 
 * of use if you want to see where certain text is contained within all of 
 * the PDFs.  Looking for [[ will find all of the special characters which
 * have not yet been handled in the PROMS meta code.  See FixText in 
 * DisplayText.cs in the PROMS code.
 * ======================================================================*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections.Specialized;
using System.IO;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;
using System.Runtime.InteropServices;
namespace Baseline
{
	enum LastWas
	{
		Pagination,
		Baseline,
		Search
	};
	public enum Relation
	{
		Contains,
		StartsWith,
		EndsWith,
		Regex
	}
	public partial class frmBaseline : Form
	{
		private IgnoreLines _MyIgnore = new IgnoreLines();
		public IgnoreLines MyIgnore
		{
			get { return _MyIgnore; }
			set { _MyIgnore = value; }
		}
		private LastWas myLast = LastWas.Search;
		private Settings MySettings;
		public string MyStatus
		{
			get { return tsslStatus.Text; }
			set
			{
				tsslStatus.Text = value;
				Application.DoEvents();
			}
		}
		public frmBaseline()
		{
			InitializeComponent();
			LoadSettings();
			SetupEventHandlers();
			MyStatus = "Ready";
		}
		#region Settings
		/// 
		/// Setup event handlers for changes to Settings
		/// 
		private void SetupEventHandlers()
		{
			this.Resize += frmBaseline_Resize;
			this.Move += frmBaseline_Move;
			this.splitContainer1.SplitterMoved += splitContainer1_SplitterMoved;
			this.splitContainer2.SplitterMoved += splitContainer2_SplitterMoved;
			this.splitContainer3.SplitterMoved += splitContainer3_SplitterMoved;
			cbFile1.TextChanged += cbFile1_TextChanged;
			cbFile2.TextChanged += cbFile2_TextChanged;
		}
		// Remember the location of splitters
		void splitContainer3_SplitterMoved(object sender, SplitterEventArgs e)
		{
			Properties.Settings.Default.Split3 = splitContainer3.SplitterDistance;
			Properties.Settings.Default.Save();
		}
		void splitContainer2_SplitterMoved(object sender, SplitterEventArgs e)
		{
			Properties.Settings.Default.Split2 = splitContainer2.SplitterDistance;
			Properties.Settings.Default.Save();
		}
		void splitContainer1_SplitterMoved(object sender, SplitterEventArgs e)
		{
			Properties.Settings.Default.Split1 = splitContainer1.SplitterDistance;
			Properties.Settings.Default.Save();
		}
		// Remember Form Location
		void frmBaseline_Move(object sender, EventArgs e)
		{
			Properties.Settings.Default.Location = this.Location;
			Properties.Settings.Default.Save();
		}
		// Remember Form Size
		void frmBaseline_Resize(object sender, EventArgs e)
		{
			if (this.WindowState == FormWindowState.Normal)
			{
				Properties.Settings.Default.Size = this.Size;
			}
			Properties.Settings.Default.WidnowState = this.WindowState;
			Properties.Settings.Default.Save();
		}
		// Load Settings
		private void LoadSettings()
		{
			this.Location = Properties.Settings.Default.Location;
			this.Size = Properties.Settings.Default.Size;
			this.WindowState = Properties.Settings.Default.WidnowState;
			if(Properties.Settings.Default.Ignore != null && Properties.Settings.Default.Ignore != "")
				MyIgnore = IgnoreLines.Get(Properties.Settings.Default.Ignore);
			MySettings= new Settings();
			MySettings.IgnoreLines = new BindingList();
			splitContainer1.SplitterDistance = Properties.Settings.Default.Split1;
			splitContainer2.SplitterDistance = Properties.Settings.Default.Split2;
			splitContainer3.SplitterDistance = Properties.Settings.Default.Split3;
			if (Properties.Settings.Default.MRU1 != null && Properties.Settings.Default.MRU1.Count > 0)
			{
				cbFile1.Items.Clear();
				foreach (string str in Properties.Settings.Default.MRU1)
					cbFile1.Items.Add(str);
				cbFile1.SelectedIndex = 0;
			}
			if (Properties.Settings.Default.MRU2 != null && Properties.Settings.Default.MRU2.Count > 0)
			{
				cbFile2.Items.Clear();
				foreach (string str in Properties.Settings.Default.MRU2)
					cbFile2.Items.Add(str);
				cbFile2.SelectedIndex = 0;
			}
		}
		#endregion
		/// 
		/// Use FolderBrowserDialog to find folder
		/// 
		/// 
		/// 
		private void btnBrowse1_Click(object sender, EventArgs e)
		{
			fbd.SelectedPath = cbFile1.Text;
			if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
				cbFile1.Text = fbd.SelectedPath;
		}
		private void btnBrowse2_Click(object sender, EventArgs e)
		{
			fbd.SelectedPath = cbFile2.Text;
			if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
				cbFile2.Text = fbd.SelectedPath;
		}
		/// 
		/// Open frmSettings to edit ignore list
		/// 
		/// 
		/// 
		private void btnSettings_Click(object sender, EventArgs e)
		{
			string saveOriginal = MyIgnore.ToString();
			frmSettings mySettings = new frmSettings(MyIgnore);
			if (mySettings.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				Properties.Settings.Default.Ignore = MyIgnore.ToString();
				Properties.Settings.Default.Save();
			}
			else
			{
				MyIgnore = IgnoreLines.Get(saveOriginal);
			}
		}
		/// 
		/// Process DebugPagination.txt in all sub-folders 
		/// 
		/// 
		/// 
		private void btnPagination_Click(object sender, EventArgs e)
		{
			myLast = LastWas.Pagination;
			// Initialize lbDifferent DocVersion comparison list
			lbDifferent.DataSource = null;
			lbDifferent.Items.Clear();
			// Initialize line differences for DebugPagination
			lbResults1.Items.Clear();
			lbResults2.Items.Clear();
			MyStatus = "Searching...";
			// Perform DebugPagination Comparison
			FindFiles fnd = new FindFiles(cbFile1.Text, cbFile2.Text, "DebugPagination.txt",MyIgnore);
			lbDifferent.DataSource = fnd;
			lbDifferent.DisplayMember = "File1";
			MyStatus = string.Format("{0} Differences Found", fnd.Count);
		}
		/// 
		/// Fill Appropriate List Boxes when an entry is selected in lbDifferent
		/// 
		/// 
		/// 
		private void lbDifferent_SelectedIndexChanged(object sender, EventArgs e)
		{
			//Initialize ListBoxes
			lbProcedures.Items.Clear();
			lbResults1.Items.Clear();
			lbResults2.Items.Clear();
			FindFile ff = lbDifferent.SelectedItem as FindFile;
			if (ff != null)
			{
				// Fill Procedure or Result ListBoxes
				switch (myLast)
				{
					case LastWas.Pagination:
						CompareContent(ff.File1, ff.File2);// Compare DebugPagination
						break;
					case LastWas.Baseline:
						CompareContent3(ff.File1, ff.File2);// Compare DebugMeta
						break;
					case LastWas.Search:
						ShowSearchResults(ff.File1, ff.File2);// Perform search on DebugMeta
						break;
					default:
						CompareContent(ff.File1, ff.File2);//Default DebugPagination
						break;
				}
				//CompareOneFile(ff.File1, ff.File2);
			}
		}
		Procedures MyProcs1;
		Procedures MyProcs2;
		/// 
		/// Use LINQ to find  a search string in the DebugMeta Files
		/// 
		/// 
		/// 
		private void ShowSearchResults(string file1, string file2)
		{
			IEnumerable lines1 = File.ReadLines(file1);// LINQ Read Lines
			IEnumerable lines2 = File.ReadLines(file2);// LINQ Read Lines
			lines1 = AddItemIDs(lines1);// LINQ Add ItemIDs to each line to make them unique
			lines2 = AddItemIDs(lines2);// LINQ Add ItemIDs to each line to make them unique
			// LINQ Lambda Expression - Only includes lines that contain the search text
			// Each line x such that local function Contains(tbsearch.Text, x) is true
			lines1 = lines1.Where(x => Contains(tbSearch.Text, x));// LINQ Search
			lines2 = lines2.Where(x => Contains(tbSearch.Text, x));// LINQ Search
			// Initialize Results
			lbResults1.Items.Clear();
			lbResults2.Items.Clear();
			// Initialize Procedure List
			lbProcedures.Items.Clear();
			// TODO: Are the following lines necessary
			string line1 = lines1.ElementAt(0);
			string line2 = lines2.ElementAt(0);
			// Load a List of Procedures
			MyProcs1 = FillProcedures(lines1);
			MyProcs2 = FillProcedures(lines2);
			// Populate the Procedure ListBox
			foreach (Procedure proc in MyProcs1)
			{
			// TODO: Is the following local variable necessary
				int i = lbProcedures.Items.Add(proc);
			}
			MyStatus = string.Format("{0} Procedures contain {1}",MyProcs1.Count(),tbSearch.Text);
		}
		/// 
		/// Fill the Procedure ListBox
		/// 
		/// 
		/// 
		private Procedures FillProcedures(IEnumerable lines)
		{
			Procedures myProcs = new Procedures();
			string lastProc = null;
			int pageNumber = 0;
			foreach (string line in lines)
			{
				if (line.StartsWith("!!")) // !! Lines contain Procedure Info
				{
					lastProc = line;
					//Remember last Procedure line
					pageNumber = 1;
				}
				else if (line.StartsWith("Page Change from ")) // These lines contain Page Number 
				{
					pageNumber = int.Parse(line.Substring(line.LastIndexOf(' ')));
					// Remember last page number
				}
				else
				{
					// use remembered procedure and page number to save line of text 
					myProcs.Add(lastProc, pageNumber, line);// Otherwise add Procedure, Page or Line
				}
			}
			return myProcs;
		}
		/// 
		/// LINQ add ItemID Prefix to each line
		/// 
		/// 
		/// 
		private static IEnumerable AddItemIDs(IEnumerable lines1)
		{
			List list1 = new List();
			string prefix = "";
			foreach (string line in lines1)
			{
				if (line.StartsWith("TX ")) //Get the ItemID
				{
					if (line.Contains("ItmID="))
					{
						prefix = line.Substring(line.IndexOf("ItmID=") + 6);
					}
					else
						prefix = "";
					list1.Add(line);
				}
				else if (line.Contains(" Atrbs: "))// Add the prefix to make the line unique
					list1.Add(prefix + line);
				else
				{
					prefix = "";
					list1.Add(line);
				}
			}
			lines1 = list1.AsEnumerable();// Convert back to Enumerable to work with LINQ
			return lines1;
		}
		private string GetProcNum(string line)
		{
			string retval =  line.Substring(3, line.IndexOf(" | ") - 3);
			if (retval.Contains("_"))
				retval = retval.Substring(0, retval.IndexOf("_") - 1);
			return retval;
		}
		/// 
		/// Include lines for Procedure or Page or Search is true
		/// Account for Case Insensitive CheckBox
		/// 
		/// 
		/// 
		/// 
		private bool Contains(string searchText, string x)
		{
			bool result;
			if (cbCaseSensitive.Checked)
			{
				result = x.Contains(searchText) || x.StartsWith("Page Change from ") || Regex.IsMatch(x, @"^!![^|]+\|[^|]+?$", RegexOptions.Compiled);
			}
			else
			{
				result = x.ToUpper().Contains(searchText.ToUpper()) || x.StartsWith("Page Change from ") || Regex.IsMatch(x, @"^!![^|]+\|[^|]+?$", RegexOptions.Compiled);
			}
			return result;
		}
		private void CompareContent(string file1, string file2)
		{
			// Enable the UltraCompare button
			// Collapse the Procedure Panel
			btnUC.Enabled = splitContainer3.Panel2Collapsed = true;
			IEnumerable lines1 = FindFiles.ReadFilteredLines(file1, true, MyIgnore);
			IEnumerable lines2 = FindFiles.ReadFilteredLines(file2, true, MyIgnore);
			IEnumerable missing1 = lines1.Except(lines2);
			IEnumerable missing2 = lines2.Except(lines1);
			lbResults1.Items.Clear();
			lbResults1.Items.AddRange(missing1.ToArray());
			lbResults2.Items.Clear(); 
			lbResults2.Items.AddRange(missing2.ToArray());
		}
		/// 
		/// LINQ Perform DebugMeta Comparison
		/// 
		/// 
		/// 
		private void CompareContent3(string file1, string file2)
		{
			// Disble the UltraCompare button
			// Expand the Procedure Panel
			btnUC.Enabled = splitContainer3.Panel2Collapsed = false;
			IEnumerable lines1 = FindFiles.ReadFilteredLines(file1, true,MyIgnore);// LINQ Read lines excluding lines to be ignored
			IEnumerable lines2 = FindFiles.ReadFilteredLines(file2, true, MyIgnore);// LINQ Read lines excluding lines to be ignored
			lines1 = AddItemIDs(lines1);// LINQ Add ItemID prefix to make lines unique
			lines2 = AddItemIDs(lines2);// LINQ Add ItemID prefix to make lines unique
			// The following lines create a list of lines without Page Numbers or procedures
			// They are removed from this list so that they will not be impacted by the intersect below
			IEnumerable noPageNumbers1 = lines1.Where(x => x.StartsWith("Page Change from ") == false);// LINQ get lines without Page Numbers
			IEnumerable noPageNumbers2 = lines2.Where(x => x.StartsWith("Page Change from ") == false);// LINQ get lines without Page Numbers
			noPageNumbers1 = noPageNumbers1.Where(x => x.StartsWith("!! ") == false);//LINQ remove lines with !!
			noPageNumbers2 = noPageNumbers2.Where(x => x.StartsWith("!! ") == false);//LINQ remove lines with !!
			IEnumerable intersect = noPageNumbers1.Intersect(noPageNumbers2);//LINQ find Matchhing lines
			List lIntersect = intersect.ToList();//LINQ Create a list of matching lines
			HashSet hs = new HashSet(intersect);// LINQ Create a HashSet of matching lines
			IEnumerable missing1 = lines1.Where(x => !hs.Contains(x));// LINQ Remove matching lines
			IEnumerable missing2 = lines2.Where(x => !hs.Contains(x));// LINQ Remove matching lines
			MyProcs1 = FillProcedures(missing1);// Fill Procedures from the lines that don't match (including Procedure and Page Number lines)
			MyProcs2 = FillProcedures(missing2);// Fill Procedures from the lines that don't match (including Procedure and Page Number lines)
			if (MyProcs1 != null && MyProcs1.Count > 0)// Fill Procedure ListBox with MyProcs1
			{
				foreach (Procedure proc in MyProcs1)
					lbProcedures.Items.Add(proc);
				MyStatus = string.Format("{0} Procedures found with differences", MyProcs1.Count());
			}
			else if (MyProcs2 != null && MyProcs2.Count > 0)// Fill Procedure ListBox with MyProcs1
			{
				foreach (Procedure proc in MyProcs2)
					lbProcedures.Items.Add(proc);
				MyStatus = string.Format("{0} Procedures found with differences", MyProcs2.Count());
			}
		}
		/// 
		/// Open One or more PDF's
		/// 
		/// 
		/// 
		private void lbResults1_SelectedIndexChanged(object sender, EventArgs e)
		{
			string line=null;
			if (lbResults1.SelectedItem is string)
				line = (string)lbResults1.SelectedItem;
			Line myLine = lbResults1.SelectedItem as Line;
				switch (myLast)
				{
					case LastWas.Pagination:
						line = OpenPDF(line);
						break;
					case LastWas.Baseline: // TODO: Need to add code here to open matching file
						OpenOnePDF(myLine,1);
						break;
					case LastWas.Search: // TODO: Need to add code here to open matching file
						OpenOnePDF(myLine,1);
						break;
					default:
						break;
				}
		}
		private void lbResults2_SelectedIndexChanged(object sender, EventArgs e)
		{
			string line=null;
			if(lbResults2.SelectedItem is string)
				line = (string)lbResults2.SelectedItem;
			Line myLine = lbResults2.SelectedItem as Line;
			switch (myLast)
			{
				case LastWas.Pagination:
					line = OpenPDF(line);
					break;
				case LastWas.Baseline: // TODO: Need to add code here to open matching file
					OpenOnePDF(myLine,2);
					break;
				case LastWas.Search: // TODO: Need to add code here to open matching file
					OpenOnePDF(myLine,2);
					break;
				default:
					break;
			}
		}
		string exePath;
		private string OpenPDF(string line)
		{
			int page = int.Parse(line.Substring(0, 6));
			// B2018-113 - Replace slashes and backslashes with underscores just as PROMS does when creating a PDF file.
			line = line.Substring(8, line.IndexOf(".S") - 8).Replace("/", "_").Replace("\\", "_");
			FindFile ff = lbDifferent.SelectedItem as FindFile;
			FileInfo fi1 = new FileInfo(ff.File1);
			FileInfo fi2 = new FileInfo(ff.File2);
			// If you don't know where the Reader executable is for PDFs Open a PDF and Check to see where the path points
			if (exePath == null)
			{
				System.Diagnostics.Process p = System.Diagnostics.Process.Start(fi1.DirectoryName + "\\" + line + ".pdf");
				exePath = TryToGetPath(p);
				p.Kill(); // No need to keep it open
			}
			// Open the first PDF on a Specific Page
			System.Diagnostics.ProcessStartInfo psi1 = new System.Diagnostics.ProcessStartInfo(exePath, string.Format("/A page={0} ", page) + fi1.DirectoryName + "\\" + line + ".pdf ");
			System.Diagnostics.Process p1 = System.Diagnostics.Process.Start(psi1);
			// Move the PDF Reader window to 0,0
			MoveProcess(p1, 0, 0);
			// Open the first PDF on a Specific Page
			System.Diagnostics.ProcessStartInfo psi2 = new System.Diagnostics.ProcessStartInfo(exePath, string.Format("/A page={0} ", page) + fi2.DirectoryName + "\\" + line + ".pdf ");
			System.Diagnostics.Process p2 = System.Diagnostics.Process.Start(psi2);
			// Move the PDF Reader window to 960,0
			// TODO: This Offset could be a Setting
			MoveProcess(p2, 960, 0);
			return line;
		}
		/// 
		/// Try to get the location of the PDF Reader executable
		/// 
		/// Process of PDF Reader
		/// 
		private string TryToGetPath(System.Diagnostics.Process p)
		{
			p.WaitForInputIdle();
			while (p.MainModule == null)
			{
				Console.WriteLine("{0} -  {1}", p.MainWindowTitle,p.ProcessName);
				p.WaitForInputIdle();
				Application.DoEvents();
			}
			return p.MainModule.FileName;
		}
		/// 
		/// Start UltraCompare of two files
		/// 
		/// 
		/// 
		private void CompareOneFile(string compareFile, string baseFile)
		{
			//Console.WriteLine("Compare {0} and {1}", compareFile, baseFile);
			string progname = string.Empty;
			if (System.IO.File.Exists(@"C:\Program Files\IDM Computer Solutions\UltraCompare\UC.exe"))
				progname = @"C:\Program Files\IDM Computer Solutions\UltraCompare\UC.exe";
			if (System.IO.File.Exists(@"C:\Program Files (x86)\IDM Computer Solutions\UltraCompare\UC.exe"))
				progname = @"C:\Program Files (x86)\IDM Computer Solutions\UltraCompare\UC.exe";
			System.Diagnostics.ProcessStartInfo psi =
				new System.Diagnostics.ProcessStartInfo(progname, string.Format(@" -t ""{0}"" ""{1}""", compareFile, baseFile));
			System.Diagnostics.Process prc = System.Diagnostics.Process.Start(psi);
		}
		private ProcessLocationQueue myQueue= new ProcessLocationQueue();
		private Timer queueTimer = null;
		/// 
		/// Move a Process to a specific screen location - This is done with a timer so 
		/// that it waits until windows are finished being initialized.
		/// 
		/// 
		/// 
		/// 
		private void MoveProcess(System.Diagnostics.Process proc, int x, int y)
		{
			if (queueTimer == null)
			{
				queueTimer = new Timer();
				queueTimer.Enabled = false;
				queueTimer.Tick += queueTimer_Tick;
				queueTimer.Interval = 1000;
			}
			myQueue.Add(proc, x, y);
			if (!queueTimer.Enabled) 
				queueTimer.Enabled = true;
		}
		/// 
		/// timer tick for moving a process window
		/// 
		/// 
		/// 
		void queueTimer_Tick(object sender, EventArgs e)
		{
			while (myQueue.Count > 0)
				myQueue.ProcessNext();
				queueTimer.Enabled = false;
		}
		/// 
		/// Open PDF associated with the selected line
		/// 
		/// 
		/// 
		private void OpenOnePDF(Line myLine, int list)
		{
			// B2018-113 - Replace slashes and backslashes with underscores just as PROMS does when creating a PDF file.
			string proc = myLine.MyProc.Number.Replace("/","_").Replace("\\","_");
			int pagenum = myLine.MyPage.Number;
			FindFile ff = lbDifferent.SelectedItem as FindFile;
			FileInfo fi1 = new FileInfo(ff.File1);
			FileInfo fi2 = new FileInfo(ff.File2);
			if (exePath == null)
			{
				System.Diagnostics.Process p = System.Diagnostics.Process.Start(fi1.DirectoryName + "\\" + proc + ".pdf");
				while (exePath == null)
				{
					try
					{
						exePath = p.MainModule.FileName;
					}
					catch (Exception ex)
					{
						Application.DoEvents();
						Console.WriteLine("{0} - {1}", ex.GetType().Name, ex.Message);
					}
				}
				p.Kill();
			}
			if (list == 1)
			{
				System.Diagnostics.ProcessStartInfo psi1 = new System.Diagnostics.ProcessStartInfo(exePath, string.Format("/A page={0} ", pagenum) + fi1.DirectoryName + "\\" + proc + ".pdf ");
				System.Diagnostics.Process p1 = System.Diagnostics.Process.Start(psi1);
			}
			else
			{
				System.Diagnostics.ProcessStartInfo psi2 = new System.Diagnostics.ProcessStartInfo(exePath, string.Format("/A page={0} ", pagenum) + fi2.DirectoryName + "\\" + proc + ".pdf ");
				System.Diagnostics.Process p1 = System.Diagnostics.Process.Start(psi2);
			}
		}
		/// 
		/// Perform Debug Meta file comparison for all of the folders within the automated testing folders 
		/// 
		/// 
		/// 
		private void btnBaseline_Click(object sender, EventArgs e)
		{
			myLast = LastWas.Baseline;
			lbDifferent.DataSource = null;
			// Initialize Differnce and Results ListBoxes
			lbDifferent.Items.Clear();
			lbResults1.Items.Clear();
			lbResults2.Items.Clear();
			MyStatus = "Searching...";
			// Perform code to find DebugMeta files with diffences
			FindFiles fnd = new FindFiles(cbFile1.Text, cbFile2.Text, "DebugMeta.txt",MyIgnore);
			lbDifferent.DataSource = fnd;
			lbDifferent.DisplayMember = "File1";
			MyStatus = string.Format("{0} Differences Found", fnd.Count);
		}
		/// 
		/// Perform search of all the DocVersions Debug.Meta Files
		/// 
		/// 
		/// 
		private void btnSearch_Click(object sender, EventArgs e)
		{
			// Disble the UltraCompare button
			// Expand the Procedure Panel
			btnUC.Enabled= splitContainer3.Panel2Collapsed = false;
			myLast = LastWas.Search;
			// Initialize ListBoxes
			lbDifferent.DataSource = null;
			lbDifferent.Items.Clear();
			lbResults1.Items.Clear();
			lbResults2.Items.Clear();
			MyStatus = "Searching...";
			//Perform Search
			FindFiles fnd = new FindFiles(cbFile1.Text, cbFile2.Text, "DebugMeta.txt", tbSearch.Text, SearchType.Contains, cbCaseSensitive.Checked);
			lbDifferent.DataSource = fnd;
			lbDifferent.DisplayMember = "File1";
			MyStatus = string.Format("{0} Differences Found", fnd.Count);
		}
		bool InHandler1 = false;
		// Track changes to the File Path 1 including Most Recent Used
		private void cbFile1_TextChanged(object sender, EventArgs e)
		{
			if (InHandler1) return;
			InHandler1 = true;
			string str = cbFile1.Text;
			while (cbFile1.Items.Contains(str))
				cbFile1.Items.Remove(str);
			cbFile1.Items.Insert(0, str);
			cbFile1.SelectedIndex = 0;
			Properties.Settings.Default.MRU1 = new System.Collections.Specialized.StringCollection();
			foreach (string str1 in cbFile1.Items)
				Properties.Settings.Default.MRU1.Add(str1);
			Properties.Settings.Default.Save();
			InHandler1 = false;
		}
		bool InHandler2 = false;
		// Track changes to the File Path 2 including Most Recent Used
		private void cbFile2_TextChanged(object sender, EventArgs e)
		{
			if (InHandler2) return;
			InHandler2 = true;
			string str = cbFile2.Text;
			while (cbFile2.Items.Contains(str))
				cbFile2.Items.Remove(str);
			cbFile2.Items.Insert(0, str);
			cbFile2.SelectedIndex = 0;
			Properties.Settings.Default.MRU2 = new System.Collections.Specialized.StringCollection();
			foreach (string str2 in cbFile2.Items)
				Properties.Settings.Default.MRU2.Add(str2);
			Properties.Settings.Default.Save();
			InHandler2 = false;
		}
		private void lbProcedures_SelectedIndexChanged(object sender, EventArgs e)
		{
			//Initialize Results List Box
			lbResults1.Items.Clear();
			Procedure myProc = lbProcedures.SelectedItem as Procedure;
			//TODO: May need to consider if there are duplicate procedure numers and titles
			Procedure myProc1 = MyProcs1.Find(x => x.Number == myProc.Number && x.Title == myProc.Title);
			// Build the results ListBox for the left window
			if (myProc1 != null)
			{
				foreach (Page myPage in myProc1.MyPages)
				{
					lbResults1.Items.Add(myPage);
					foreach (Line myLine in myPage.MyLines)
						lbResults1.Items.Add(myLine);
				}
			}
			lbResults2.Items.Clear();
			// Build the results ListBox for the right window
			Procedure myProc2 = MyProcs2.Find(x => x.Number == myProc.Number && x.Title == myProc.Title);
			if (myProc2 != null)
			{
				foreach (Page myPage in myProc2.MyPages)
				{
					lbResults2.Items.Add(myPage);
					foreach (Line myLine in myPage.MyLines)
						lbResults2.Items.Add(myLine);
				}
			}
		}
		/// 
		/// Open Ultra Compare for the currently selected DocVersion
		/// 
		/// 
		/// 
		private void btnUC_Click(object sender, EventArgs e)
		{
			if (myLast == LastWas.Pagination)
			{
				FindFile ff = lbDifferent.SelectedItem as FindFile;
				CompareOneFile(ff.File1, ff.File2);
			}
		}
	}
	public	enum SearchType
		{
			Contains,
			StartsWith,
			EndsWith
		};
	public class Settings
	{
		private BindingList _IgnoreLines;
		public BindingList IgnoreLines
		{
			get { return _IgnoreLines; }
			set { _IgnoreLines = value; }
		}
	}
	public partial class FindFile
	{
		private string _File1;
		public string File1
		{
			get { return _File1; }
			set { _File1 = value; }
		}
		private string _File2;
		public string File2
		{
			get { return _File2; }
			set { _File2 = value; }
		}
		public FindFile(string file1, string file2)
		{
			File1 = file1;
			File2 = file2;
		}
	}
	public partial class FindFiles : List
	{
		private string _FileName;
		public string FileName
		{
			get { return _FileName; }
		}
		/// 
		/// Build list of DocVersion Folders with differences
		/// 
		/// Base path
		/// Compare path
		/// filename
		/// Ignore list
		public FindFiles(string path1, string path2, string fileName,IgnoreLines myIgnore)
		{
			DirectoryInfo di1 = new DirectoryInfo(path1);
			DirectoryInfo di2 = new DirectoryInfo(path2);
			_FileName = fileName;
			FillByCompare(di1, di2, fileName, myIgnore);
		}
		/// 
		/// Fill for a search
		/// 
		/// 
		/// 
		/// 
		/// 
		/// 
		/// 
		public FindFiles(string path1, string path2, string fileName, string searchText, SearchType searchType, bool caseSensitive)
		{
			DirectoryInfo di1 = new DirectoryInfo(path1);
			DirectoryInfo di2 = new DirectoryInfo(path2);
			_FileName = fileName;
			FillByCompare(di1, di2, fileName,searchText,searchType, caseSensitive);
		}
		/// 
		/// Fill results by search
		/// 
		/// 
		/// 
		/// 
		/// 
		/// 
		/// 
		private void FillByCompare(DirectoryInfo di1, DirectoryInfo di2, string fileName, string searchText, SearchType searchType, bool caseSensitive)
		{
			foreach (DirectoryInfo diChild1 in di1.GetDirectories())
			{
				DirectoryInfo diChild2 = new DirectoryInfo(di2.FullName + "\\" + diChild1.Name);
				if (diChild2.Exists)
					FillByCompare(diChild1, diChild2, fileName,searchText,searchType,caseSensitive);
			}
			foreach (FileInfo fiChild1 in di1.GetFiles(fileName))
			{
				FileInfo fiChild2 = new FileInfo(di2.FullName + "\\" + fiChild1.Name);
				if (fiChild2.Exists)
				{
					if (CompareFile(fiChild1, fiChild2,searchText,searchType, caseSensitive))
						Add(new FindFile(fiChild1.FullName, fiChild2.FullName));
				}
			}
		}
		/// 
		///  LINQ Peform Searc on DebugMeta files in all DocVersion folders
		/// 
		/// 
		/// 
		/// 
		/// 
		/// 
		/// 
		private bool CompareFile(FileInfo fiChild1, FileInfo fiChild2, string searchText, SearchType searchType,bool caseSensitive)
		{
			IEnumerable lines1=null;
			IEnumerable lines2=null;
			switch (searchType)
			{
				case SearchType.Contains:
					if (caseSensitive)
					{
						lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.Contains(searchText));
						lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.Contains(searchText));
					}
					else
					{
						lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.ToUpper().Contains(searchText.ToUpper()));
						lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.ToUpper().Contains(searchText.ToUpper()));
					}
					break;
				case SearchType.StartsWith:
					if (caseSensitive)
					{
						lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.StartsWith(searchText));
						lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.StartsWith(searchText));
					}
					else
					{
						lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.ToUpper().StartsWith(searchText.ToUpper()));
						lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.ToUpper().StartsWith(searchText.ToUpper()));
					}
					break;
				case SearchType.EndsWith:
					if (caseSensitive)
					{
						lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.EndsWith(searchText));
						lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.EndsWith(searchText));
					}
					else
					{
						lines1 = File.ReadLines(fiChild1.FullName).Where(x => x.ToUpper().EndsWith(searchText.ToUpper()));
						lines2 = File.ReadLines(fiChild2.FullName).Where(x => x.ToUpper().EndsWith(searchText.ToUpper()));
					}
					break;
			}
			if (lines1 != null && lines2 != null)
			{
				if ((lines1.Count() > 0) || (lines2.Count() > 0))
				{
					return true;
				}
				return false;
			}
			return false;
		}
		private void FillByCompare(DirectoryInfo di1, DirectoryInfo di2, string fileName, IgnoreLines myIgnore)
		{
			foreach (DirectoryInfo diChild1 in di1.GetDirectories())
			{
				DirectoryInfo diChild2 = new DirectoryInfo(di2.FullName + "\\" + diChild1.Name);
				if (diChild2.Exists)
					FillByCompare(diChild1, diChild2, fileName,myIgnore);// Recursively work on Sub-Folders
			}
			foreach (FileInfo fiChild1 in di1.GetFiles(fileName))
			{
				FileInfo fiChild2 = new FileInfo(di2.FullName + "\\" + fiChild1.Name);
				if (fiChild2.Exists)
				{
					if (CompareFile(fiChild1, fiChild2,myIgnore))
						Add(new FindFile(fiChild1.FullName, fiChild2.FullName));// Process Each File
				}
			}
		}
		private bool CompareFile(FileInfo fiChild1, FileInfo fiChild2,IgnoreLines myIgnore)
		{
			if (fiChild1.Name == "DebugMeta.txt")
			{
				IEnumerable lines1 = ReadFilteredLines(fiChild1.FullName, false, myIgnore);//LINQ Read lines without ignored lines
				IEnumerable lines2 = ReadFilteredLines(fiChild2.FullName, false, myIgnore);//LINQ Read lines without ignored lines
				if (lines1.Except(lines2).Count() == 0 &&
					lines2.Except(lines1).Count() == 0) //Look for lines in one file that are not in the other
					return false;
				return true;
			}
			else
			{
				string file1Contents = ReadContents(fiChild1);// This is just a simple text comparison
				string file2Contents = ReadContents(fiChild2);
				return !file1Contents.Equals(file2Contents);
			}
		}
		/// 
		/// LINQ - Readlines and remove items based upon the ignore list
		/// 
		/// 
		/// 
		/// 
		/// 
		public static IEnumerable ReadFilteredLines(string child1, bool includeProcedures, IgnoreLines myIgnore)
		{
			IEnumerable lines = File.ReadLines(child1);
			lines = lines.Where(x => FindProcedure(x,includeProcedures));
			foreach (IgnoreLine ignore in myIgnore)
			{
				if (ignore.Active)
				{
					switch (ignore.SearchType)
					{
						case Relation.Contains:
							lines = lines.Where(x => x.Contains(ignore.Text) == false);
							break;
						case Relation.StartsWith:
							lines = lines.Where(x => x.StartsWith(ignore.Text) == false);
							break;
						case Relation.EndsWith:
							lines = lines.Where(x => x.EndsWith(ignore.Text) == false);
							break;
						case Relation.Regex:
							Regex myreg = new Regex(ignore.Text, RegexOptions.Compiled);
							lines = lines.Where(x => myreg.IsMatch(x) == false);
							break;
						default:
							break;
					}
				}
			}
			return lines;
		}
		/// 
		/// Find a procedure line in a meta file
		/// 
		/// 
		/// 
		/// 
		private static bool FindProcedure(string x, bool includeProcedures)
		{
			bool result = x.StartsWith("!!") == false;
			if(includeProcedures) result |= Regex.IsMatch(x, @"^!![^|]+\|[^|]+?$", RegexOptions.Compiled);
			return result;
		}
		/// 
		/// basic text read
		/// 
		/// 
		/// 
		private string ReadContents(FileInfo fiChild1)
		{
			StreamReader sr = fiChild1.OpenText();
			string buff = sr.ReadToEnd();
			return buff;
		}
	}
	// Classes for structured data storage used for meta file comparisons
	//
	// Procedure contains:
	// Number - Procedure Number
	// Title - Procedure Title
	// MyPages - The Page objects associated with this procedure
	//
	// Page contains:
	// Number - PageNumber
	// MyLines - The Line Objects associated with this Page
	//
	// Line contains:
	// MyProc - The containing procedure
	// MyPage - the containing page
	// Text - the line of text
	public partial class Procedure
	{
		private string _Number;
		public string Number
		{
			get { return _Number; }
			set { _Number = value; }
		}
		private string _Title;
		public string Title
		{
			get { return _Title; }
			set { _Title = value; }
		}
		private Pages _MyPages = new Pages();
		public Pages MyPages
		{
			get { return _MyPages; }
			set { _MyPages = value; }
		}
		public Procedure(string number, string title)
		{
			_Number = number;
			_Title = title;
		}
		public override string ToString()
		{
			return string.Format("{0} - {1}", Number, Title);
		}
	}
	public partial class Procedures : List
	{
		// Sample data for a Procedure Number line
		// !! E-0 | Reactor Trip Or Safety Injection
		// The First Group is the Procedure Number
		// The Second Group is the Procedure Title
		public Regex parseLine = new Regex("^!! (.*) \\| (.*)$", RegexOptions.Compiled);
		/// 
		/// This adds a procedure, page and line class as needed, If the procedure
		/// already exists it is used.  If the page already exists it is used.
		/// 
		/// Procedure Line matches Regular Expression above
		/// Page Number
		/// Text from the meta File
		public void Add(string lastProc, int pageNumber, string text)
		{
			if (lastProc == null) return;
			Match m = parseLine.Match(lastProc);
			if (m.Success)
			{
				Procedure myProc = this.Find(x => x.Number.Equals(m.Groups[1].ToString()) && x.Title.Equals(m.Groups[2].ToString()));
				if (myProc == null) this.Add(myProc = new Procedure(m.Groups[1].ToString(), m.Groups[2].ToString()));
				Page myPage = myProc.MyPages.Find(x => x.Number == pageNumber);
				if (myPage == null) myProc.MyPages.Add(myPage = new Page(pageNumber));
				myPage.MyLines.Add(new Line(text, myProc, myPage));
			}
		}
	}
	public partial class Page
	{
		private int _Number;
		public int Number
		{
			get { return _Number; }
			set { _Number = value; }
		}
		private Lines _MyLines = new Lines();
		public Lines MyLines
		{
			get { return _MyLines; }
			set { _MyLines = value; }
		}
		public Page(int number)
		{
			_Number = number;
		}
		public override string ToString()
		{
			return string.Format("Page {0}", Number);
		}
	}
	public partial class Pages : List
	{
		public void Add(int number)
		{
			Add(new Page(number));
		}
	}
	public partial class Line
	{
		private Procedure _MyProc;
		public Procedure MyProc
		{
			get { return _MyProc; }
			set { _MyProc = value; }
		}
		private Page _MyPage;
		public Page MyPage
		{
			get { return _MyPage; }
			set { _MyPage = value; }
		}
		private string _Text;
		public string Text
		{
			get { return _Text; }
			set { _Text = value; }
		}
		public Line(string text)
		{
			_Text = text;
		}
		public Line(string text, Procedure myProc, Page myPage)
		{
			_Text = text;
			_MyProc = myProc;
			_MyPage = myPage;
		}
		public override string ToString()
		{
			return Text;
		}
	}
	public partial class Lines : List
	{
		public void Add(string text)
		{
			Add(new Line(text));
		}
	}
	[Serializable]
	public partial class IgnoreLine
	{
		private bool _Active = true;
		public bool Active
		{
			get { return _Active; }
			set { _Active = value; }
		}
		private Relation _SearchType = Relation.Contains;
		public Relation SearchType
		{
			get { return _SearchType; }
			set { _SearchType = value; }
		}
		private string _Text;
		public string Text
		{
			get { return _Text; }
			set { _Text = value; }
		}
		public IgnoreLine(string text, Relation searchType, bool active)
		{
			Text = text;
			SearchType = searchType;
			Active = active;
		}
		public IgnoreLine()
		{
		}
	}
	[Serializable]
	public partial class IgnoreLines : BindingList
	{
		public IgnoreLines()
		{
		}
		public void Add(string text, Relation searchType, bool active)
		{
			Add(new IgnoreLine(text, searchType, active));
		}
		// Convert IgnoreLines to string (XML)
		public override string ToString()
		{
			return GenericSerializer.StringSerialize(this);
		}
		// Convert string to IgnoreLines
		public static IgnoreLines Get(string xml)
		{
			return GenericSerializer.StringDeserialize(xml);
		}
	}
	/// 
	/// This is a simple serializer that takes a class and converts it to and from string (XML)
	/// 
	/// 
	public static class GenericSerializer where T : class
	{
		public static string StringSerialize(T t)
		{
			string strOutput = string.Empty;
			XmlSerializer xs = new XmlSerializer(typeof(T));
			using (MemoryStream ms = new MemoryStream())
			{
				xs.Serialize(new NonXsiTextWriter(ms, Encoding.Unicode), t);
				//xs.Serialize(ms, t);
				ms.Position = 0;
				StreamReader sr = new StreamReader(ms);
				strOutput = sr.ReadToEnd();
				ms.Close();
			}
			return strOutput;
		}
		public static T StringDeserialize(string s)
		{
			T t;
			string ss = s.Replace("encoding=\"utf-16\"", "");
			XmlSerializer xs = new XmlSerializer(typeof(T));
			UTF8Encoding enc = new UTF8Encoding();
			Byte[] arrBytData = enc.GetBytes(ss);
			using (MemoryStream ms = new MemoryStream(arrBytData))
			{
				t = (T)xs.Deserialize(ms);
			}
			return t;
		}
	}
	/// 
	/// This XML Writer makes the XML more simple by excluding the XSI attributes from the serializer
	/// 
	public class NonXsiTextWriter : XmlTextWriter
	{
		public NonXsiTextWriter(TextWriter w) : base(w) { }
		public NonXsiTextWriter(Stream w, Encoding encoding)
			: base(w, encoding)
		{
			this.Formatting = Formatting.Indented;
		}
		public NonXsiTextWriter(string filename, Encoding encoding) : base(filename, encoding) { }
		bool _skip = false;
		public override void WriteStartAttribute(string prefix, string localName, string ns)
		{
			if ((prefix == "xmlns" && (localName == "xsd" || localName == "xsi")) || // Omits XSD and XSI declarations. 
				ns == XmlSchema.InstanceNamespace) // Omits all XSI attributes. 
			{
				_skip = true;
				return;
			}
			if (localName == "xlink_href")
				base.WriteStartAttribute(prefix, "xlink:href", ns);
			else
				base.WriteStartAttribute(prefix, localName, ns);
		}
		public override void WriteString(string text)
		{
			if (_skip) return;
			base.WriteString(text);
		}
		public override void WriteEndAttribute()
		{
			if (_skip)
			{ // Reset the flag, so we keep writing. 
				_skip = false;
				return;
			}
			base.WriteEndAttribute();
		}
	}
	/// 
	/// Class to support moving a process window
	/// 
	public class ProcessLocation
	{
		[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
		public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);
		public const short SWP_NOMOVE = 0X2;
		public const short SWP_NOSIZE = 1;
		public const short SWP_NOZORDER = 0X4;
		public const int SWP_SHOWWINDOW = 0x0040;
		private System.Diagnostics.Process _Process;
		public System.Diagnostics.Process Process
		{
			get { return _Process; }
			set { _Process = value; }
		}
		private int _X;
		public int X
		{
			get { return _X; }
			set { _X = value; }
		}
		private int _Y;
		public int Y
		{
			get { return _Y; }
			set { _Y = value; }
		}
		public ProcessLocation(System.Diagnostics.Process process, int x, int y)
		{
			Process = process;
			X = x;
			Y = y;
		}
		private static Boolean FoxitSettingInfo = true;
		/// 
		/// MoveIt() moves the window containing the PDF viewer to the right so the two pdf viewer windows will not overlap.
		/// 
		public void MoveIt()
		{
			try
			{
				SetWindowPos(Process.MainWindowHandle, 0, X, Y, 200, 400, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOZORDER);
			}
			catch // B2018-147 bring up message to inform user of the setting that will create separate windows of the pdf viewer
			{
				if (FoxitSettingInfo)
					MessageBox.Show("If you want to see the documents in separate windows,\nyou need to set Foxit to allow multiple instances. \nSelect File | preferences | Documents\n Check the Allow Multiple Instances", "Foxit Settings");
				FoxitSettingInfo = false;
			}
		}
	}
	public class ProcessLocationQueue: Queue
	{
		public void Add(System.Diagnostics.Process process, int x, int y)
		{
			Enqueue(new ProcessLocation(process,x,y));
		}
		public void ProcessNext()
		{
			ProcessLocation pl = Dequeue();
			pl.MoveIt();
		}
	}
}