/// <summary>
        ///
        /// </summary>
        /// <param name="xlWorksheetMLS"></param>
        /// <param name="xlWorksheetAIM"></param>
        /// <param name="xlRangeMLS"></param>
        /// <param name="xlRangeAIM"></param>
        /// <param name="rangeCount"></param>
        /// <param name="relevantCols"></param>
        /// <param name="thresholds"></param>
        /// <param name="progress"></param>
        public void determinerDoWork(Excel._Worksheet xlWorksheetMLS, Excel._Worksheet xlWorksheetAIM,
                                     Excel.Range xlRangeMLS, Excel.Range xlRangeAIM, Dictionary <string, int> rangeCount,
                                     Dictionary <string, int> relevantCols, Dictionary <string, double> thresholds, IProgress <int> progress,
                                     mainForm form)
        {
            // loop through the files and do the main work
            for (int currentMLSFile = 2; currentMLSFile <= rangeCount["rowCountMLS"]; currentMLSFile++)
            {
                // initialize the variables that will determine if file in row closed
                //
                // For addressMatch and ownerMatch: 0 = no match,
                // 1 = likely a match, 2 = definitely a match
                bool       dateClosedMatch   = false;
                int        addressMatch      = 0;
                int        ownerMatch        = 0;
                int        closedGFNumRow    = 2;
                List <int> consideredRowsAIM = new List <int>();

                /// determine if date closed is a match
                if (xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSCloseDateCol"]].Value2 != null) // check that the next MLS close date cell is not empty
                {
                    // parse MLS close date into a DateTime struct
                    string   MLSRawCloseDate = xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSCloseDateCol"]].Value.ToString();
                    DateTime MLSCloseDate    = DateTime.Parse(MLSRawCloseDate);

                    for (int currentAIMFile = 2; currentAIMFile <= rangeCount["rowCountAIM"]; currentAIMFile++)
                    {
                        if (xlRangeAIM.Cells[currentAIMFile, relevantCols["AIMCloseDateCol"]].Value2 != null) // check that the next AIM close date cell is not empty
                        {
                            // parse AIM close date into a DateTime struct
                            string   AIMRawCloseDate = xlRangeAIM.Cells[currentAIMFile, relevantCols["AIMCloseDateCol"]].Value.ToString();
                            DateTime AIMCloseDate    = DateTime.Parse(AIMRawCloseDate);

                            if ((MLSCloseDate - AIMCloseDate).TotalDays <= 15) // check that the two close dates are within 15 days of each other
                            {
                                consideredRowsAIM.Add(currentAIMFile);         // add the current AIM file row to the list of considered AIM rows
                                dateClosedMatch = true;
                                //Console.WriteLine("Found match in days between row " + currentMLSFile
                                //    + " in MLS xl file and row " + currentAIMFile + " in AIM xl file");
                            }
                        }
                    }
                }

                /// determine if owner/seller name are a match only if date closed and addrees are already a match
                if (dateClosedMatch && xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSOwnerCol"]].Value2 != null)
                {
                    for (int currentAIMFile = 2; currentAIMFile <= rangeCount["rowCountAIM"]; currentAIMFile++)
                    {
                        if (xlRangeAIM.Cells[currentAIMFile, relevantCols["AIMSellerCol"]].Value2 != null &&
                            consideredRowsAIM.Contains(currentAIMFile))
                        {
                            string   owner        = xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSOwnerCol"]].Value2.ToString();
                            string   seller       = xlRangeAIM.Cells[currentAIMFile, relevantCols["AIMSellerCol"]].Value2.ToString();
                            string[] parsedOwner  = owner.ToLower().Split(' ');
                            string[] parsedSeller = seller.ToLower().Split(' ');

                            // Check that the first 3 (if applicable) words
                            // in the owner string reasonably match with a
                            // word in the seller string
                            bool fnLnMatch = false;
                            if (parsedOwner.Length == 1)
                            {
                                for (int i = 0; i < parsedSeller.Length; i++)
                                {
                                    if (StringDistance.GetStringDistance(parsedOwner[0], parsedSeller[i]) <= 1)
                                    {
                                        fnLnMatch = true;
                                        break;
                                    }
                                }
                            }
                            else if (parsedOwner.Length == 2)
                            {
                                int numMatches = 0; // number of owner words that matched with seller words

                                for (int i = 0; i < parsedSeller.Length; i++)
                                {
                                    if (StringDistance.GetStringDistance(parsedOwner[0], parsedSeller[i]) <= 1)
                                    {
                                        numMatches++;
                                    }
                                }
                                for (int i = 0; i < parsedSeller.Length; i++)
                                {
                                    if (StringDistance.GetStringDistance(parsedOwner[1], parsedSeller[i]) <= 1)
                                    {
                                        numMatches++;
                                    }
                                }

                                if (numMatches >= 1)
                                {
                                    fnLnMatch = true;
                                }
                            }
                            else if (parsedOwner.Length == 3)
                            {
                                int numMatches = 0; // number of owner words that matched with seller words

                                for (int i = 0; i < parsedSeller.Length; i++)
                                {
                                    if (StringDistance.GetStringDistance(parsedOwner[0], parsedSeller[i]) <= 1)
                                    {
                                        numMatches++;
                                        break;
                                    }
                                }
                                for (int i = 0; i < parsedSeller.Length; i++)
                                {
                                    if (StringDistance.GetStringDistance(parsedOwner[1], parsedSeller[i]) <= 1)
                                    {
                                        numMatches++;
                                        break;
                                    }
                                }
                                for (int i = 0; i < parsedSeller.Length; i++)
                                {
                                    if (StringDistance.GetStringDistance(parsedOwner[2], parsedSeller[i]) <= 1)
                                    {
                                        numMatches++;
                                        break;
                                    }
                                }

                                if (numMatches >= 2)
                                {
                                    fnLnMatch = true;
                                }
                            }
                            else
                            {
                                int numMatches = 0; // number of owner words that matches with seller words
                                for (int i = 0, j = 1; i < parsedOwner.Length && j <= 3; i++)
                                {
                                    if (!((parsedOwner[i].Length == 1) || (parsedOwner[i].Length == 2) ||
                                          (parsedOwner[i].ToLower() == "and"))) // if not a word that should be ignored
                                    {                                           // words that should be ignored: "and" and words with length 1 or 2
                                        // go through parsedSeller and check that the current parsedOwner word is contained
                                        for (int k = 0; k < parsedSeller.Length; k++)
                                        {
                                            if (StringDistance.GetStringDistance(parsedOwner[i], parsedSeller[k]) <= 1)
                                            {
                                                numMatches++;
                                                break; // break out of inner for loop
                                            }
                                        }
                                        j++;
                                    }
                                }

                                if (numMatches >= 2)
                                {
                                    fnLnMatch = true;
                                }
                            }

                            // check ownerDistance against the percentage threshold of the longer test string to see if it is a match
                            if (fnLnMatch)
                            {
                                ownerMatch = 2;
                                break;
                            }
                            else
                            {
                                ownerMatch = 1;
                                consideredRowsAIM.Remove(currentAIMFile);
                            }
                        }
                        else
                        {
                            consideredRowsAIM.Remove(currentAIMFile);
                        }
                    }
                }

                /// determine if property addresses are a match only if date closed is already a match
                if (dateClosedMatch && ownerMatch > 0 && xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSAddressCol"]].Value2 != null)
                {
                    for (int currentAIMFile = 2; currentAIMFile <= rangeCount["rowCountAIM"]; currentAIMFile++)
                    {
                        if (xlRangeAIM.Cells[currentAIMFile, relevantCols["AIMAddressCol"]].Value2 != null &&
                            consideredRowsAIM.Contains(currentAIMFile))
                        {
                            // get the address strings from the xl files and parse them by the space character
                            string   addressMLS       = xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSAddressCol"]].Value2.ToString();
                            string   addressAIM       = xlRangeAIM.Cells[currentAIMFile, relevantCols["AIMAddressCol"]].Value2.ToString();
                            string[] parsedAddressMLS = addressMLS.Split(' ');
                            string[] parsedAddressAIM = addressAIM.Split(' ');

                            if (parsedAddressMLS[0] == parsedAddressAIM[0]) // check that the address numbers match
                            {
                                // account for condominium, private road, and country road
                                if (addressMLS.ToLower().Contains("condominium"))
                                {
                                    addressMLS = addressMLS.ToLower().Replace("condominium", "");
                                    addressAIM = addressAIM.ToLower();
                                }
                                else if (addressAIM.ToLower().Contains("condominium"))
                                {
                                    addressMLS = addressMLS.ToLower();
                                    addressAIM = addressAIM.ToLower().Replace("condominium", "");
                                }
                                if (addressMLS.ToLower().Contains("pr") && addressAIM.ToLower().Contains("private road"))
                                {
                                    addressMLS = addressMLS.ToLower().Replace("pr", "");
                                    addressAIM = addressAIM.ToLower().Replace("private road", "");
                                }
                                else if (addressMLS.ToLower().Contains("private road") && addressAIM.ToLower().Contains("pr"))
                                {
                                    addressMLS = addressMLS.ToLower().Replace("private road", "");
                                    addressAIM = addressAIM.ToLower().Replace("pr", "");
                                }
                                if (addressMLS.ToLower().Contains("cr") && addressAIM.ToLower().Contains("county road"))
                                {
                                    addressMLS = addressMLS.ToLower().Replace("cr", "");
                                    addressAIM = addressAIM.ToLower().Replace("county road", "");
                                }
                                else if (addressMLS.ToLower().Contains("county road") && addressAIM.ToLower().Contains("cr"))
                                {
                                    addressMLS = addressMLS.ToLower().Replace("county road", "");
                                    addressAIM = addressAIM.ToLower().Replace("cr", "");
                                }

                                int addressDistance = StringDistance.GetStringDistance(addressMLS, addressAIM); // get distance between the two strings

                                // compute updated thresholds
                                int addressThresholdUpdated     = (int)Math.Ceiling(thresholds["addressThreshold"] * Math.Max(addressMLS.Length, addressAIM.Length));
                                int addressThresholdWeakUpdated = (int)Math.Ceiling(thresholds["addressThresholdWeak"] * Math.Max(addressMLS.Length, addressAIM.Length));

                                // check addressDistance against the percentage threshold of the longer test string to see if it is a match
                                if (addressDistance <= addressThresholdUpdated)
                                {
                                    addressMatch   = 2;
                                    closedGFNumRow = currentAIMFile;
                                    Console.WriteLine("Found match in address between row " + currentMLSFile
                                                      + " in MLS xl file and row " + currentAIMFile + " in AIM xl file");
                                    break; // if a match is found, there's no need to search any further
                                }
                                else if (addressDistance <= addressThresholdWeakUpdated && addressDistance > addressThresholdUpdated)
                                {
                                    addressMatch   = 1;
                                    closedGFNumRow = currentAIMFile;
                                    Console.WriteLine("Found likely match in address between row " + currentMLSFile
                                                      + " in MLS xl file and row " + currentAIMFile + " in AIM xl file");
                                }
                            }
                        }
                    }
                }

                /// determine whether the file was closed with hatco or not and print to xl file
                if (dateClosedMatch && addressMatch == 2 && ownerMatch == 2)
                {
                    string closedGF = xlRangeAIM.Cells[closedGFNumRow, relevantCols["AIMFileNoCol"]].Value.ToString();
                    string escrOff  = xlRangeAIM.Cells[closedGFNumRow, relevantCols["AIMEscrowCol"]].Value.ToString();
                    xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSGFCol"]].Value            = closedGF;
                    xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSEscrowOfficerCol"]].Value = escrOff;
                    Console.WriteLine("File on row " + currentMLSFile + " of MLS xl file closed with GF #"
                                      + closedGF);
                }
                else if (dateClosedMatch && addressMatch > 0 && ownerMatch > 0)
                {
                    string closedGF = xlRangeAIM.Cells[closedGFNumRow, relevantCols["AIMFileNoCol"]].Value.ToString();
                    string escrOff  = xlRangeAIM.Cells[closedGFNumRow, relevantCols["AIMEscrowCol"]].Value.ToString();
                    xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSGFCol"]].Value            = closedGF;
                    xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSLikelyCloseCol"]].Value   = "true";
                    xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSEscrowOfficerCol"]].Value = escrOff;
                    Console.WriteLine("File on row " + currentMLSFile + " likely closed with GF #" + closedGF);
                }
                else
                {
                    xlRangeMLS.Cells[currentMLSFile, relevantCols["MLSGFCol"]].Value = "did not close";
                    Console.WriteLine("File on row " + currentMLSFile + " did not close");
                }

                // update progress bar after each row of MLS file
                if (progress != null)
                {
                    progress.Report(100 / rangeCount["rowCountMLS"]);
                }

                string        progressDetailedUpdate = (currentMLSFile - 1).ToString() + "/" + (rangeCount["rowCountMLS"] - 1).ToString();
                MethodInvoker inv = delegate
                {
                    form.progressDetailed.Text = progressDetailedUpdate;
                };
                form.Invoke(inv);
            }
        }
        /// <summary>
        /// run through each file in MLSFileName and check against AIMFileName files
        /// to determine whether the files in MLSFileName closed with hatco.
        ///
        /// addressThreshold, addressThresholdWeak,
        /// ownerThreshold, and ownerThresholdWeak are percentages (between 0 and 1)
        ///
        /// progress is used to update the windowsform's progress bar
        /// </summary>
        /// <param name="MLSFileName"></param>
        /// <param name="AIMFileName"></param>
        /// <param name="addressThreshold"></param>
        /// <param name="ownerThreshold"></param>
        public void mainDeterminer(string MLSFileName, string AIMFileName, double addressThreshold,
                                   double addressThresholdWeak, double ownerThreshold, double ownerThresholdWeak, IProgress <int> progress,
                                   mainForm form)
        {
            Application.UseWaitCursor = true; // set the cursor to waiting symbol

            // open all excel files for use
            Excel.Application xlApp         = new Excel.Application();
            Excel.Workbook    xlWorkbookMLS = null;
            Excel.Workbook    xlWorkbookAIM = null;
            try
            {
                xlWorkbookMLS = xlApp.Workbooks.Open(MLSFileName);
                xlWorkbookAIM = xlApp.Workbooks.Open(AIMFileName);
            }
            catch (Exception ex) // catch possible "file could not open" exception
            {
                throw ex;
            }

            if (xlWorkbookAIM != null && xlWorkbookMLS != null) // check that excel files opened properly
            {
                // open worksheets and range in excel files for use
                Excel._Worksheet xlWorksheetMLS = xlWorkbookMLS.Sheets[1];
                Excel._Worksheet xlWorksheetAIM = xlWorkbookAIM.Sheets[1];
                Excel.Range      xlRangeMLS     = xlWorksheetMLS.UsedRange;
                Excel.Range      xlRangeAIM     = xlWorksheetAIM.UsedRange;

                Dictionary <string, int>    rangeCount   = new Dictionary <string, int>();
                Dictionary <string, int>    relevantCols = new Dictionary <string, int>();
                Dictionary <string, double> thresholds   = new Dictionary <string, double>();
                thresholds.Add("addressThreshold", addressThreshold);
                thresholds.Add("addressThresholdWeak", addressThresholdWeak);
                thresholds.Add("ownerThreshold", ownerThreshold);
                thresholds.Add("ownerThresholdWeak", ownerThresholdWeak);

                try
                {
                    // do the main processing on the excel files and catch any exceptions that are thrown
                    // get the range of rows and columns for AIM excel file
                    rangeCount.Add("rowCountMLS", xlRangeMLS.Rows.Count);
                    rangeCount.Add("colCountMLS", xlRangeMLS.Columns.Count);
                    rangeCount.Add("rowCountAIM", xlRangeAIM.Rows.Count);
                    rangeCount.Add("colCountAIM", xlRangeAIM.Columns.Count);

                    // relevant columns indeces
                    relevantCols.Add("MLSOwnerCol", 0);
                    relevantCols.Add("MLSAddressCol", 0);
                    relevantCols.Add("MLSCloseDateCol", 0);
                    relevantCols.Add("MLSGFCol", 0);
                    relevantCols.Add("AIMFileNoCol", 0);
                    relevantCols.Add("AIMCloseDateCol", 0);
                    relevantCols.Add("AIMAddressCol", 0);
                    relevantCols.Add("AIMSellerCol", 0);
                    relevantCols.Add("AIMEscrowCol", 0);

                    // determine the columns in MLS file that have relevant information
                    for (int i = 1; i <= rangeCount["colCountMLS"]; i++)
                    {
                        if (xlRangeMLS.Cells[1, i].Value2 != null) // check that the cell is not empty
                        {
                            if (xlRangeMLS.Cells[1, i].Value2.ToString().Contains("Owner"))
                            {
                                relevantCols["MLSOwnerCol"] = i;
                            }
                            else if (xlRangeMLS.Cells[1, i].Value2.ToString().Contains("Address"))
                            {
                                relevantCols["MLSAddressCol"] = i;
                            }
                            else if (xlRangeMLS.Cells[1, i].Value2.ToString().Contains("Close Date"))
                            {
                                relevantCols["MLSCloseDateCol"] = i;
                            }
                            else if (xlRangeMLS.Cells[1, i].Value2.ToString().Contains("GF"))
                            {
                                relevantCols["MLSGFCol"] = i;
                            }
                        }
                    }

                    // determine the columns in AIM file that have relevant information
                    for (int i = 1; i <= rangeCount["colCountAIM"]; i++)
                    {
                        if (xlRangeAIM.Cells[1, i].Value2 != null) // check that the cell is not empty
                        {
                            if (xlRangeAIM.Cells[1, i].Value2.ToString().Contains("File Number"))
                            {
                                relevantCols["AIMFileNoCol"] = i;
                            }
                            else if (xlRangeAIM.Cells[1, i].Value2.ToString().Contains("Date"))
                            {
                                relevantCols["AIMCloseDateCol"] = i;
                            }
                            else if (xlRangeAIM.Cells[1, i].Value2.ToString().Contains("Property Address"))
                            {
                                relevantCols["AIMAddressCol"] = i;
                            }
                            else if (xlRangeAIM.Cells[1, i].Value2.ToString().Contains("Seller"))
                            {
                                relevantCols["AIMSellerCol"] = i;
                            }
                            else if (xlRangeAIM.Cells[1, i].Value2.ToString().Contains("Escrow"))
                            {
                                relevantCols["AIMEscrowCol"] = i;
                            }
                        }
                    }

                    // add new columns to MLS file
                    relevantCols.Add("MLSLikelyCloseCol", rangeCount["colCountMLS"] + 1);
                    relevantCols.Add("MLSEscrowOfficerCol", rangeCount["colCountMLS"] + 2);
                    xlRangeMLS.Cells[1, relevantCols["MLSLikelyCloseCol"]].Value   = "Likely Closed";
                    xlRangeMLS.Cells[1, relevantCols["MLSEscrowOfficerCol"]].Value = "Escrow Officer";

                    // set the progress bar to the first little tick
                    if (progress != null)
                    {
                        progress.Report(100 / rangeCount["rowCountMLS"]);
                    }

                    DeterminerWork det = new DeterminerWork();
                    det.determinerDoWork(xlWorksheetMLS, xlWorksheetAIM, xlRangeMLS, xlRangeAIM,
                                         rangeCount, relevantCols, thresholds, progress, form);
                }
                catch (Exception ex) // if an exception is caught, close the excel files so they aren't held hostage
                {
                    Console.WriteLine("Problem with determiner processing. Closing excel files.");

                    // cleanup
                    GC.Collect();
                    GC.WaitForPendingFinalizers();

                    // release com objects so the excel processes are
                    // fully killed from running in the background
                    Marshal.ReleaseComObject(xlRangeMLS);
                    Marshal.ReleaseComObject(xlRangeAIM);
                    Marshal.ReleaseComObject(xlWorksheetMLS);
                    Marshal.ReleaseComObject(xlWorksheetAIM);

                    // save, close, and release workbooks
                    xlWorkbookMLS.Close();
                    Console.WriteLine("closed MLS workbook");
                    xlWorkbookAIM.Close();
                    Console.WriteLine("closed AIM workbook");
                    Marshal.ReleaseComObject(xlWorkbookMLS);
                    Marshal.ReleaseComObject(xlWorkbookAIM);

                    // quit and release excel app
                    xlApp.Quit();
                    Marshal.ReleaseComObject(xlApp);

                    throw ex;
                }

                // cleanup
                GC.Collect();
                GC.WaitForPendingFinalizers();

                // release com objects so the excel processes are
                // fully killed from running in the background
                Marshal.ReleaseComObject(xlRangeMLS);
                Marshal.ReleaseComObject(xlRangeAIM);
                Marshal.ReleaseComObject(xlWorksheetMLS);
                Marshal.ReleaseComObject(xlWorksheetAIM);

                // save, close, and release workbooks
                xlWorkbookMLS.Save();
                Console.WriteLine("saved MLS workbook");
                xlWorkbookMLS.Close();
                Console.WriteLine("closed MLS workbook");
                xlWorkbookAIM.Close();
                Console.WriteLine("closed AIM workbook");
                Marshal.ReleaseComObject(xlWorkbookMLS);
                Marshal.ReleaseComObject(xlWorkbookAIM);

                // quit and release excel app
                xlApp.Quit();
                Marshal.ReleaseComObject(xlApp);
            }

            Application.UseWaitCursor = false; // set cursor back to default
        }