private static void VerifyConverterDecksAreEqual(ConverterDeck converterDeck, ConverterDeck otherConverterDeck)
 {
     for (int cs = 0; cs < converterDeck.ConverterSections.Count; cs++)
     {
         VerifyConverterSectionsAreIdentical(converterDeck.ConverterSections[cs], otherConverterDeck.ConverterSections[cs]);
     }
 }
        /// <summary>
        /// Converts a LoTR URL from cardgamedb.com into a ConverterDeck which is populated with all cards and deck name.
        /// </summary>
        /// <param name="url">The URL of the Deck</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <returns>A ConverterDeck which is populated with all cards and deck name</returns>
        private static ConverterDeck ConvertURL_cardgamedb_com_LoTR(string url, IEnumerable<string> deckSectionNames)
        {
            object htmlWebInstance = HtmlAgilityPackWrapper.HtmlWeb_CreateInstance();
            object htmlDocumentInstance = HtmlAgilityPackWrapper.HtmlWeb_InvokeMethod_Load(htmlWebInstance, url);
            object htmlDocument_DocumentNode = HtmlAgilityPackWrapper.HtmlDocument_GetProperty_DocumentNode(htmlDocumentInstance);

            // Find the div with class name 'category_block block_wrap clear'
            object deckDivHtmlNode = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectSingleNode(htmlDocument_DocumentNode, "//div[@class='category_block block_wrap clear']");

            // Find the table inside deckDiv, because one of the rows contains all of the cards
            object deckTableHtmlNode = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectSingleNode(deckDivHtmlNode, "table");

            ////Find all 'tr' nodes, because one of them contains all of the cards
            System.Collections.IEnumerable deckTRHtmlNodes = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectNodes(deckTableHtmlNode, "tr");

            foreach (object tableRowHtmlNode in deckTRHtmlNodes)
            {
                // Get the second td node
                object secondTDHtmlNode = HtmlAgilityPackWrapper.HtmlNode_InvokeMethod_SelectSingleNode(tableRowHtmlNode, @"td[2]");

                // Check if the TD contains 'Total Cards ('
                string innerText = HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(secondTDHtmlNode);

                if (innerText.Contains("Total Cards ("))
                {
                    Dictionary<string, IEnumerable<Tuple<string, string>>> deckSectionLinesAndSets = new Dictionary<string, IEnumerable<Tuple<string, string>>>();
                    List<Tuple<string, string>> currentSectionLinesAndSets = null;
                    string currentLineCardName = null;
                    string currentSetName = null;

                    foreach (object childNode in HtmlAgilityPackWrapper.HtmlNode_GetProperty_ChildNodes(secondTDHtmlNode))
                    {
                        string nodeName = HtmlAgilityPackWrapper.HtmlNode_GetProperty_Name(childNode);
                        if (nodeName.Equals("strong"))
                        {
                            string sectionText = HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(childNode);
                            string deckSectionName = deckSectionNames.FirstOrDefault(dsn => sectionText.ToLowerInvariant().Contains(dsn.ToLowerInvariant()));
                            if (deckSectionName != null)
                            {
                                currentSectionLinesAndSets = new List<Tuple<string, string>>();
                                deckSectionLinesAndSets.Add(deckSectionName, currentSectionLinesAndSets);
                            }
                        }
                        else if (nodeName.Equals("a"))
                        {
                            currentLineCardName = HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(childNode);
                            var htmlAttributeList = HtmlAgilityPackWrapper.HtmlNode_GetProperty_Attributes(childNode);
                            var hrefAttribute = htmlAttributeList.Cast<object>().First(attr => HtmlAgilityPackWrapper.HtmlAttribute_GetProperty_Name(attr).Equals(@"href", StringComparison.InvariantCultureIgnoreCase));
                            
                            // This is the entire string inside the href attribute of the 'a' link for this card
                            string hrefValue = HtmlAgilityPackWrapper.HtmlAttribute_GetProperty_Value(hrefAttribute);

                            string[] hrefValuesSplit = hrefValue.Split(new char[] { '/' });

                            // The set name should be the second to last group after splitting on '/'
                            string rawSetName = hrefValuesSplit[hrefValuesSplit.Length - 2];

                            // Special case - Sets for The Hobbit have "the-hobbit" in the parent directory
                            if (hrefValuesSplit[hrefValuesSplit.Length - 3].Equals("the-hobbit"))
                            {
                                rawSetName = hrefValuesSplit[hrefValuesSplit.Length - 3] + " " + rawSetName;
                            }

                            // Special case - 'core' should be renamed to 'core-set'
                            if (rawSetName.Equals("core"))
                            {
                                rawSetName = "core-set";
                            }

                            currentSetName = rawSetName.Replace('-', ' ');
                        }
                        else if (nodeName.Equals("#text"))
                        {
                            currentSectionLinesAndSets.Add(new Tuple<string, string>
                            (
                                currentLineCardName + " " + HtmlAgilityPackWrapper.HtmlNode_GetProperty_InnerText(childNode), 
                                currentSetName
                            ));

                            currentLineCardName = null;
                            currentSetName = null;
                        }
                    }
                    
                    ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);
                    foreach (KeyValuePair<string, IEnumerable<Tuple<string, string>>> section in deckSectionLinesAndSets)
                    {
                        ConverterSection converterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals(section.Key, StringComparison.InvariantCultureIgnoreCase));
                        foreach (Tuple<string, string> lineAndSection in section.Value)
                        {
                            ConverterMapping converterMapping = RegexMatch_RegularCardQuantityAfterName(lineAndSection.Item1);
                            converterMapping.CardSet = lineAndSection.Item2;
                            converterSection.AddConverterMapping(converterMapping);
                        }
                    }

                    return converterDeck;
                }
            }

            throw new NotImplementedException();
        }
        /// <summary>
        /// Reads each line in each section, and returns a ConverterDeck which is populated with all cards and deck name.
        /// </summary>
        /// <param name="sectionLines">A collection of deck sections with Item1 as Name, and Item2 as a collection of lines of text which are cards</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <returns>A ConverterDeck object populated with all cards and deck name</returns>
        private static ConverterDeck ConvertDeckWithSeparateSections(Dictionary<string, IEnumerable<string>> sectionLines, IEnumerable<string> deckSectionNames)
        {
            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            foreach (KeyValuePair<string, IEnumerable<string>> section in sectionLines)
            {
                ConverterSection converterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals(section.Key, StringComparison.InvariantCultureIgnoreCase));
                foreach (string line in section.Value)
                {
                    // The line is the Deck Name?  Record it.
                    string potentialDeckName = ConvertEngine.RegexMatch_DeckName(line);

                    if (potentialDeckName != null)
                    {
                        converterDeck.DeckName = potentialDeckName;
                    }
                    else
                    {
                        // The line is not a Comment?
                        if (ConvertEngine.RegexMatch_Comment(line) == null)
                        {
                            // The line is a regular main deck Card/Quantity entry, without any Set info?  Record it
                            ConverterMapping potentialRegularCard = RegexMatch_RegularCard(line);
                            ConverterMapping potentialRegularCardQuantityAfterName = RegexMatch_RegularCardQuantityAfterName(line);
                            if (potentialRegularCard != null)
                            {
                                converterSection.AddConverterMapping(potentialRegularCard);
                            }
                            else if (potentialRegularCardQuantityAfterName != null)
                            {
                                converterSection.AddConverterMapping(potentialRegularCardQuantityAfterName);
                            }
                            else
                            {
                                // The line is a MWS main deck Card/Quantity entry with Set info?  Record it
                                ConverterMapping potentialMWSMainDeckCard = RegexMatch_MWSMainDeckCard(line);
                                if (potentialMWSMainDeckCard != null)
                                {
                                    converterSection.AddConverterMapping(potentialMWSMainDeckCard);
                                }
                                else
                                {
                                    // The line is not a valid card entry
                                }
                            }
                        }
                    }
                }
            }

            converterDeck.ConversionSuccessful = true;
            return converterDeck;
        }
        /// <summary>
        /// Reads the file which has Sideboard cards denoted on each line, and returns a ConverterDeck which is populated with all cards and deck name.
        /// </summary>
        /// <param name="lines">An array of all the lines of text from a Deck file</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <returns>A ConverterDeck object populated with all cards and deck name</returns>
        private static ConverterDeck ConvertMTGDeckWithSideBoardCardsListedEachLine(IEnumerable<string> lines, IEnumerable<string> deckSectionNames)
        {
            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            ConverterSection mtgMainDeckConverterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals("Main", StringComparison.InvariantCultureIgnoreCase));
            ConverterSection mtgSideboardConverterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals("Sideboard", StringComparison.InvariantCultureIgnoreCase));

            foreach (string line in lines)
            {
                // The line is the Deck Name?  Record it.
                string potentialDeckName = ConvertEngine.RegexMatch_DeckName(line);

                if (potentialDeckName != null)
                {
                    converterDeck.DeckName = potentialDeckName;
                }
                else
                {
                    // The line is not a Comment?
                    if (ConvertEngine.RegexMatch_Comment(line) == null)
                    {
                        // Ordering: The most specific pattern is listed first, and each more generalized pattern follows
                        if (RegexMatch_MWSSideBoardCard(line) != null)
                        {
                            // The line is a MWS sideboard "SB: Quantity [SET] Card" entry
                            mtgSideboardConverterSection.AddConverterMapping(RegexMatch_MWSSideBoardCard(line));
                        }
                        else if (RegexMatch_MWSMainDeckCard(line) != null)
                        {
                            // The line is a MWS main deck "Quantity [SET] Card" entry
                            mtgMainDeckConverterSection.AddConverterMapping(RegexMatch_MWSMainDeckCard(line));
                        }
                        else if (RegexMatch_RegularMTGSideBoardCard(line) != null)
                        {
                            // The line is a regular sideboard "Quantity Card" entry, without any Set info
                            mtgSideboardConverterSection.AddConverterMapping(RegexMatch_RegularMTGSideBoardCard(line));
                        }
                        else if (RegexMatch_RegularCard(line) != null)
                        {
                            // The line is a regular main deck "Quantity Card" entry, without any Set info
                            mtgMainDeckConverterSection.AddConverterMapping(RegexMatch_RegularCard(line));
                        }
                        else if (RegexMatch_RegularCardQuantityAfterName(line) != null)
                        {
                            // The line is a regular main deck "Card Quantity" entry, without any Set info
                            mtgMainDeckConverterSection.AddConverterMapping(RegexMatch_RegularCardQuantityAfterName(line));
                        }
                        else
                        {
                            // The line is not a valid card entry
                        }
                    }
                }
            }

            converterDeck.ConversionSuccessful = true;
            return converterDeck;
        }
        /// <summary>
        /// Reads the OCTGN format file, and returns a ConverterDeck which is populated with all cards.
        /// </summary>
        /// <param name="fullPathName">The full path name of the OCTGN Deck file to convert</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <returns>A ConverterDeck object populated with all cards and deck name</returns>
        /// <remarks>This will purposely ignore the Guids, which may help importing from OCTGN2 or other unofficial sets</remarks>
        private static ConverterDeck ConvertOctgn(string fullPathName, IEnumerable<string> deckSectionNames)
        {
            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            XmlTextReader reader = new XmlTextReader(fullPathName);
            reader.WhitespaceHandling = WhitespaceHandling.None;

            XmlDocument xd = new XmlDocument();
            xd.Load(reader);

            XmlNode xnodDE = xd.DocumentElement;

            if (xnodDE.Name != "deck")
            {
                throw new InvalidOperationException("File is not a Octgn Deck");
            }

            foreach (XmlNode child in xnodDE.ChildNodes)
            {
                if (child.Name.Equals("section", StringComparison.InvariantCultureIgnoreCase))
                {
                    ConverterSection converterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals(child.Attributes.GetNamedItem("name").Value, StringComparison.InvariantCultureIgnoreCase));
                    foreach (XmlNode cardXmlNode in child.ChildNodes)
                    {
                        converterSection.AddConverterMapping
                        (
                            new ConverterMapping
                            (
                                cardXmlNode.InnerText,
                                string.Empty,
                                int.Parse(cardXmlNode.Attributes.GetNamedItem("qty").Value)
                            )
                        );
                    }
                }
            }

            converterDeck.ConversionSuccessful = true;
            return converterDeck;
        }
        /// <summary>
        /// Reads the Cockatrice format file, and returns a ConverterDeck which is populated with all cards and deck name.
        /// </summary>
        /// <param name="fullPathName">The full path name of the Cockatrice Deck file to convert</param>
        /// <param name="deckSectionNames">List of the name of each section for the deck being converted.</param>
        /// <returns>A ConverterDeck object populated with all cards and deck name</returns>
        private static ConverterDeck ConvertCockatrice(string fullPathName, IEnumerable<string> deckSectionNames)
        {
            ConverterDeck converterDeck = new ConverterDeck(deckSectionNames);

            XmlTextReader reader = new XmlTextReader(fullPathName);
            reader.WhitespaceHandling = WhitespaceHandling.None;

            XmlDocument xd = new XmlDocument();
            xd.Load(reader);

            XmlNode xnodDE = xd.DocumentElement;

            if (xnodDE.Name != "cockatrice_deck")
            {
                throw new InvalidOperationException("File is not a Cockatrice Deck");
            }

            ConverterSection mtgMainDeckConverterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals("Main", StringComparison.InvariantCultureIgnoreCase));
            ConverterSection mtgSideboardConverterSection = converterDeck.ConverterSections.First(cs => cs.SectionName.Equals("Sideboard", StringComparison.InvariantCultureIgnoreCase));

            foreach (XmlNode child in xnodDE.ChildNodes)
            {
                if (child.Name.Equals("deckname", StringComparison.InvariantCultureIgnoreCase))
                {
                    converterDeck.DeckName = child.InnerText;
                }
                else if (child.Name.Equals("zone", StringComparison.InvariantCultureIgnoreCase))
                {
                    if (child.Attributes.GetNamedItem("name").Value.Equals("main", StringComparison.InvariantCultureIgnoreCase))
                    {
                        foreach (XmlNode cardXmlNode in child.ChildNodes)
                        {
                            mtgMainDeckConverterSection.AddConverterMapping
                            (
                                new ConverterMapping
                                (
                                    cardXmlNode.Attributes.GetNamedItem("name").Value,
                                    string.Empty,
                                    int.Parse(cardXmlNode.Attributes.GetNamedItem("number").Value)
                                )
                            );
                        }
                    }
                    else if (child.Attributes.GetNamedItem("name").Value.Equals("side", StringComparison.InvariantCultureIgnoreCase))
                    {
                        foreach (XmlNode cardXmlNode in child.ChildNodes)
                        {
                            mtgSideboardConverterSection.AddConverterMapping
                            (
                                new ConverterMapping
                                (
                                    cardXmlNode.Attributes.GetNamedItem("name").Value,
                                    string.Empty,
                                    int.Parse(cardXmlNode.Attributes.GetNamedItem("number").Value)
                                )
                            );
                        }
                    }
                }
            }

            converterDeck.ConversionSuccessful = true;
            return converterDeck;
        }