public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);
				var deck = new Deck {Name = doc.DocumentNode.SelectSingleNode("//h1[@id='deck-title']").InnerText};

				var cardList = HttpUtility.HtmlDecode(doc.DocumentNode.SelectSingleNode("//table[@id='deck-guide']").Attributes["data-deck"].Value);
				cardList = cardList.Replace("\"", "").Replace("[", "").Replace("]", "").Replace("\\", "");

				foreach(var cardNode in cardList.Split(',').GroupBy(x => x))
				{
					var card = Database.GetCardFromId(cardNode.Key);
					card.Count = cardNode.Count();
					deck.Cards.Add(card);

					if (string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}

				return deck;
			}
			catch (Exception e)
			{
				Log.Error(e);
				return null;
			}
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				string json;
				using(var wc = new WebClient())
					json = await wc.DownloadStringTaskAsync(url + ".json");
				var wrapper = JsonConvert.DeserializeObject<IcyVeinsWrapper>(json);
				var deck = new Deck {Name = wrapper.deck_name};
				foreach(var cardObj in wrapper.deck_cards)
				{
					var cardName = cardObj.name;
					if(cardName.EndsWith(" Naxx"))
						cardName = cardName.Replace(" Naxx", "");
					if(cardName.EndsWith(" GvG"))
						cardName = cardName.Replace(" GvG", "");
					if(cardName.EndsWith(" BrM"))
						cardName = cardName.Replace(" BrM", "");
					var card = Database.GetCardFromName(cardName);
					card.Count = cardObj.quantity;
					deck.Cards.Add(card);
					if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}
				return deck;
			}
			catch(Exception e)
			{
				Log.Error(e);
				return null;
			}
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);
				var deck = new Deck
				{
					Name =
						HttpUtility.HtmlDecode(doc.DocumentNode.SelectSingleNode("//*[@id='content']/div[contains(@class, 'deck')]/h1").InnerText).Trim()
				};

				var nodes = doc.DocumentNode.SelectNodes("//a[@real_id]");

				foreach(var cardNode in nodes)
				{
					var id = cardNode.Attributes["real_id"].Value;
					var count = int.Parse(cardNode.Attributes["nb_card"].Value);

					var card = Database.GetCardFromId(id);
					card.Count = count;

					deck.Cards.Add(card);
					if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}

				return deck;
			}
			catch(Exception e)
			{
				Log.Error(e);
				return null;
			}
		}
		internal static void MoveGamesToOtherDeckWithoutConfirmation(Deck targetDeck, SerializableVersion targetVersion,
																	 params GameStats[] games)
		{
			if(games == null)
				return;
			foreach(var game in games)
			{
				var defaultDeck = DefaultDeckStats.Instance.DeckStats.FirstOrDefault(ds => ds.Games.Contains(game));
				if(defaultDeck != null)
				{
					defaultDeck.Games.Remove(game);
					DefaultDeckStats.Save();
				}
				else
				{
					var deck = DeckList.Instance.Decks.FirstOrDefault(d => game.DeckId == d.DeckId);
					deck?.DeckStats.Games.Remove(game);
				}
				game.PlayerDeckVersion = targetVersion;
				game.HearthStatsDeckVersionId = targetDeck.GetVersion(targetVersion).HearthStatsDeckVersionId;
				game.DeckId = targetDeck.DeckId;
				game.DeckName = targetDeck.Name;
				targetDeck.DeckStats.Games.Add(game);
				if(HearthStatsAPI.IsLoggedIn && Config.Instance.HearthStatsAutoUploadNewGames)
					HearthStatsManager.MoveMatchAsync(game, targetDeck, background: true).Forget();
			}
			DeckStatsList.Save();
			DeckList.Save();
			Core.MainWindow.DeckPickerList.UpdateDecks();
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);
				var deck = new Deck();

				var deckName = HttpUtility.HtmlDecode(doc.DocumentNode.SelectSingleNode("//h1[contains(@class,'page-title')]").FirstChild.InnerText);
				deck.Name = deckName;

				var cardNameNodes = doc.DocumentNode.SelectNodes("//div[contains(@class,'name')]");
				var cardCountNodes = doc.DocumentNode.SelectNodes("//div[contains(@class,'qty')]");

				var cardNames = cardNameNodes.Select(cardNameNode => HttpUtility.HtmlDecode(cardNameNode.InnerText));
				var cardCosts = cardCountNodes.Select(countNode => int.Parse(countNode.InnerText));

				var cardInfo = cardNames.Zip(cardCosts, (n, c) => new {Name = n, Count = c});
				foreach(var info in cardInfo)
				{
					var card = Database.GetCardFromName(info.Name);
					card.Count = info.Count;
					deck.Cards.Add(card);
					if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}

				return deck;
			}
			catch(Exception e)
			{
				Logger.WriteLine(e.ToString(), "DeckImporter");
				return null;
			}
		}
		private async void ExportDeck(Deck deck)
		{
			var export = true;
			if(Config.Instance.ShowExportingDialog)
			{
				var message =
					string.Format(
					              "1) create a new, empty {0}-Deck {1}.\n\n2) leave the deck creation screen open.\n\n3)do not move your mouse or type after clicking \"export\"",
					              deck.Class, (Config.Instance.AutoClearDeck ? "(or open an existing one to be cleared automatically)" : ""));

				if(deck.GetSelectedDeckVersion().Cards.Any(c => c.Name == "Stalagg" || c.Name == "Feugen"))
				{
					message +=
						"\n\nIMPORTANT: If you own golden versions of Feugen or Stalagg please make sure to configure\nOptions > Other > Exporting";
				}

				var settings = new MessageDialogs.Settings {AffirmativeButtonText = "export"};
				var result =
					await
					this.ShowMessageAsync("Export " + deck.Name + " to Hearthstone", message, MessageDialogStyle.AffirmativeAndNegative, settings);
				export = result == MessageDialogResult.Affirmative;
			}
			if(export)
			{
				var controller = await this.ShowProgressAsync("Creating Deck", "Please do not move your mouse or type.");
				Topmost = false;
				await Task.Delay(500);
				await DeckExporter.Export(deck);
				await controller.CloseAsync();

				if(deck.MissingCards.Any())
					this.ShowMissingCardsMessage(deck);
			}
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);
				var titleNode = doc.DocumentNode.SelectSingleNode("//div[contains(@class,'header__title internal')]/div[contains(@class,'container')]/h1");
				var cardNodes = doc.DocumentNode.SelectNodes("//ul[contains(@class,'list-unstyled cartas_list')]/li");

				var deck = new Deck();
				deck.Name = HttpUtility.HtmlDecode(titleNode.ChildNodes.FirstOrDefault(x => x.Name == "#text").InnerText);
				foreach (var node in cardNodes)
				{
					var nameNode = node.SelectSingleNode("span[contains(@class,'cartas__name')]/a");
					var countNode = node.SelectSingleNode("span[contains(@class,'cartas__qtd')]");
					var validChild = countNode?.ChildNodes.SingleOrDefault(c => c.Name == "#text");

					var id = nameNode.Attributes.FirstOrDefault(a => a.Name == "data-hcfw-card-id").Value;
					var count = validChild != null ? int.Parse(countNode.InnerText) : 1;

					var card = Database.GetCardFromId(id);
					card.Count = count;
					deck.Cards.Add(card);
					if (string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}

				return deck;
			}
			catch (Exception e)
			{
				Log.Error(e);
				return null;
			}
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);
				var deck = new Deck();

				var deckName = HttpUtility.HtmlDecode(doc.DocumentNode.SelectSingleNode("//span[contains(@class, 'deckName')]").InnerText).Trim();
				deck.Name = deckName;

				var cardNodes = doc.DocumentNode.SelectNodes("//table[@class='deck_card_list']/tbody/tr/td/a[@class='real_id']");

				foreach(var cardNode in cardNodes)
				{
					var id = cardNode.Attributes["real_id"].Value;
					var count = int.Parse(cardNode.Attributes["nb_card"].Value);

					var card = Database.GetCardFromId(id);
					card.Count = count;

					deck.Cards.Add(card);
					if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}

				return deck;
			}
			catch(Exception e)
			{
				Logger.WriteLine(e.ToString(), "DeckImporter");
				return null;
			}
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);
				var deck = new Deck();

				var deckName = HttpUtility.HtmlDecode(doc.DocumentNode.SelectSingleNode("//h2[contains(@class, 'dname')]").InnerText);
				deck.Name = deckName;

				var cardNodes = doc.DocumentNode.SelectNodes("//ul[@class='vminionslist' or @class='vspellslist']/li");

				foreach(var cardNode in cardNodes)
				{
					var count = int.Parse(cardNode.SelectSingleNode(".//span[@class='cantidad']").InnerText);
					var name =
						HttpUtility.HtmlDecode(
						                       cardNode.SelectSingleNode(
						                                                 ".//span[@class='nombreCarta rarity_legendary' or @class='nombreCarta rarity_epic' or @class='nombreCarta rarity_rare' or @class='nombreCarta rarity_common' or @class='nombreCarta rarity_basic']")
						                               .InnerText);
					var card = Database.GetCardFromName(name);
					card.Count = count;
					deck.Cards.Add(card);
					if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}

				return deck;
			}
			catch(Exception e)
			{
				Logger.WriteLine(e.ToString(), "DeckImporter");
				return null;
			}
		}
		public static async Task<Deck> TryFindDeck(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);
				var deck = new Deck();
				var metaNodes = doc.DocumentNode.SelectNodes("//meta");
				if(!metaNodes.Any())
					return null;
				deck.Name = GetMetaProperty(metaNodes, "x-hearthstone:deck");
				deck.Url = GetMetaProperty(metaNodes, "x-hearthstone:deck:url") ?? url;
				var heroId = GetMetaProperty(metaNodes, "x-hearthstone:deck:hero");
				if(!string.IsNullOrEmpty(heroId))
					deck.Class = Database.GetCardFromId(heroId).PlayerClass;
				var cardList = GetMetaProperty(metaNodes, "x-hearthstone:deck:cards").Split(',');
				foreach(var idGroup in cardList.GroupBy(x => x))
				{
					var card = Database.GetCardFromId(idGroup.Key);
					card.Count = idGroup.Count();
					deck.Cards.Add(card);
					if(deck.Class == null && card.IsClassCard)
						deck.Class = card.PlayerClass;
				}
				return deck;
			}
			catch(Exception e)
			{
				Log.Error(e);
				return null;
			}
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);
				var deck = new Deck {IsArenaDeck = true};

				var cardNodes = doc.DocumentNode.SelectSingleNode(".//ul[@class='deckList']");
				var nameNodes = cardNodes.SelectNodes(".//span[@class='name']");
				var countNodes = cardNodes.SelectNodes(".//span[@class='quantity']");
				var numberOfCards = nameNodes.Count;
				for(var i = 0; i < numberOfCards; i++)
				{
					var nameRaw = nameNodes.ElementAt(i).InnerText;
					var name = HttpUtility.HtmlDecode(nameRaw);
					var card = Database.GetCardFromName(name);
					card.Count = int.Parse(countNodes.ElementAt(i).InnerText);
					deck.Cards.Add(card);
					if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}
				if(DeckList.Instance.AllTags.Contains("Arena"))
					deck.Tags.Add("Arena");
				deck.Name = Helper.ParseDeckNameTemplate(Config.Instance.ArenaDeckNameTemplate, deck);
				return deck;
			}
			catch(Exception e)
			{
				Log.Error(e);
				return null;
			}
		}
		public void SubtractOperator001()
		{
			Deck d1 = new Deck();
			Deck d2 = new Deck();
			//just in d1
			d1.Cards.Add(new Card("ID_1", "", Rarity.FREE, "", "ID 1", 0, "", 0, 1, "", "", 0, 0, "", new string[] { }, 0, "", ""));
			//in both but diff count
			d1.Cards.Add(new Card("ID_2", "", Rarity.FREE, "", "ID 2", 0, "", 0, 2, "", "", 0, 0, "", new string[] { }, 0, "", ""));
			d2.Cards.Add(new Card("ID_2", "", Rarity.FREE, "", "ID 2", 0, "", 0, 3, "", "", 0, 0, "", new string[] { }, 0, "", ""));
			//just in d2
			d2.Cards.Add(new Card("ID_3", "", Rarity.FREE, "", "ID 3", 0, "", 0, 2, "", "", 0, 0, "", new string[] { }, 0, "", ""));
			//in bth and same cont
			d1.Cards.Add(new Card("ID_4", "", Rarity.FREE, "", "ID 4", 0, "", 0, 5, "", "", 0, 0, "", new string[] { }, 0, "", ""));
			d2.Cards.Add(new Card("ID_4", "", Rarity.FREE, "", "ID 4", 0, "", 0, 5, "", "", 0, 0, "", new string[] { }, 0, "", ""));

			IEnumerable<Card> result = d1 - d2;

			Assert.IsNotNull(result);
			foreach (Card c in result)
			{
				Console.Out.WriteLine(c.ToString());
			}
			Assert.AreEqual(3, result.Count());
			Assert.AreEqual(1, result.Where(c => c.Id == "ID_1" && c.Count == 1).Count(), "ID 1, expected count 1");
			Assert.AreEqual(1, result.Where(c => c.Id == "ID_2" && c.Count == -1).Count(), "ID 2, expected count -1");
			Assert.AreEqual(1, result.Where(c => c.Id == "ID_3" && c.Count == -2).Count(), "ID 3, expected count -2");
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);
				var deck = new Deck();

				var deckName = HttpUtility.HtmlDecode(doc.DocumentNode.SelectSingleNode("//div[contains(@class, 'deck-info')]//h1").InnerText);
				deck.Name = deckName;

				var cardNodes = doc.DocumentNode.SelectNodes("//ul[@class='listado mazo-cartas']/li");

				foreach(var cardNode in cardNodes)
				{
					var count = int.Parse(cardNode.SelectSingleNode(".//span[@class='cantidad']").InnerText);
					var name = HttpUtility.HtmlDecode(cardNode.SelectSingleNode(".//span[@class='nombreCarta']").InnerText);
					var card = Database.GetCardFromName(name);
					card.Count = count;
					deck.Cards.Add(card);
					if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}

				return deck;
			}
			catch(Exception e)
			{
				Log.Error(e);
				return null;
			}
		}
		private async void ExportDeck(Deck deck)
		{
			var export = true;
			if(Config.Instance.ShowExportingDialog)
			{
				var message = $"1) Create a new (or open an existing) {deck.Class} deck.\n\n2) Leave the deck creation screen open.\n\n3) Click 'Export' and do not move your mouse or type until done.";
				var settings = new MessageDialogs.Settings {AffirmativeButtonText = "Export"};
				var result = await this.ShowMessageAsync("Export " + deck.Name + " to Hearthstone", message, MessageDialogStyle.AffirmativeAndNegative, settings);
				export = result == MessageDialogResult.Affirmative;
			}
			if(export)
			{
				var controller = await this.ShowProgressAsync("Creating Deck", "Please do not move your mouse or type.");
				Topmost = false;
				await Task.Delay(500);
				var success = await DeckExporter.Export(deck);
				await controller.CloseAsync();

				if(success)
				{
					var hsDeck = HearthMirror.Reflection.GetEditedDeck();
					if(hsDeck != null)
					{
						var existingHsId = DeckList.Instance.Decks.Where(x => x.DeckId != deck.DeckId).FirstOrDefault(x => x.HsId == hsDeck.Id);
						if(existingHsId != null)
							existingHsId.HsId = 0;
						deck.HsId = hsDeck.Id;
						DeckList.Save();
					}
				}

				if(deck.MissingCards.Any())
					this.ShowMissingCardsMessage(deck);
			}
		}
		public DeckStats(Deck deck)
		{
			Name = deck.Name;
			Games = new List<GameStats>();
			HearthStatsDeckId = deck.HearthStatsId;
			DeckId = deck.DeckId;
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDocGzip(url);
				var deck = new Deck
				{
					Name =
						HttpUtility.HtmlDecode(doc.DocumentNode.SelectSingleNode("//header[@class='panel-heading']/h1[@class='panel-title']").InnerText)
						           .Trim()
				};
				var nodes = doc.DocumentNode.SelectNodes("//table[@class='table table-bordered table-hover table-db']/tbody/tr");

				foreach(var cardNode in nodes)
				{
					var name = HttpUtility.HtmlDecode(cardNode.SelectSingleNode(".//a").Attributes[3].Value);

					var temp = HttpUtility.HtmlDecode(cardNode.SelectSingleNode(".//a/small").InnerText[0].ToString());
					var count = int.Parse(temp);

					var card = Database.GetCardFromName(name);
					card.Count = count;
					deck.Cards.Add(card);
					if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}

				return deck;
			}
			catch(Exception e)
			{
				Logger.WriteLine(e.ToString(), "DeckImporter");
				return null;
			}
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);

				var deck = new Deck();

				var deckName =
					HttpUtility.HtmlDecode(doc.DocumentNode.SelectSingleNode("//header[@class='entry-header']/h1[@class='entry-title']").InnerText);
				deck.Name = deckName;


				var cardNodes = doc.DocumentNode.SelectNodes("//ul[contains(@class,'deck-class')]/li");

				foreach(var cardNode in cardNodes)
				{
					var name = HttpUtility.HtmlDecode(cardNode.SelectSingleNode(".//a/span[@class='card-name']").InnerText);
					var count = int.Parse(HttpUtility.HtmlDecode(cardNode.SelectSingleNode(".//a/span[@class='card-count']").InnerText));

					var card = Database.GetCardFromName(name);
					card.Count = count;
					deck.Cards.Add(card);
					if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
						deck.Class = card.PlayerClass;
				}

				return deck;
			}
			catch(Exception e)
			{
				Logger.WriteLine(e.ToString(), "DeckImporter");
				return null;
			}
		}
		public AddGameDialog(Deck deck)
		{
			InitializeComponent();
			_tcs = new TaskCompletionSource<GameStats>();
			_editing = false;
			var lastGame = deck.DeckStats.Games.LastOrDefault();
			if(deck.IsArenaDeck)
			{
				ComboBoxMode.SelectedItem = GameMode.Arena;
				ComboBoxMode.IsEnabled = false;
				TextBoxRank.IsEnabled = false;
			}
			else
			{
				ComboBoxMode.IsEnabled = true;
				TextBoxRank.IsEnabled = true;
				if(lastGame != null)
				{
					ComboBoxMode.SelectedItem = lastGame.GameMode;
					if(lastGame.GameMode == GameMode.Ranked)
						TextBoxRank.Text = lastGame.Rank.ToString();
				}
			}
			if(lastGame != null && lastGame.Region != Region.UNKNOWN)
				ComboBoxRegion.SelectedItem = lastGame.Region;
			_deck = deck;
			_game = new GameStats();
			BtnSave.Content = "add game";
		}
		public static async Task SetDeckName(Deck deck, ExportingInfo info)
		{
			if(Config.Instance.ExportSetDeckName && !deck.TagList.ToLower().Contains("brawl"))
			{
				var name = Regex.Replace(deck.Name, @"[\(\)\{\}]", "");
				if(name != deck.Name)
					Logger.WriteLine("Removed parenthesis/braces from deck name. New name: " + name, "DeckExporter");
				if(Config.Instance.ExportAddDeckVersionToName)
				{
					var version = " " + deck.SelectedVersion.ShortVersionString;
					if(name.Length + version.Length > MaxLengthDeckName)
						name = name.Substring(0, MaxLengthDeckName - version.Length);
					name += version;
				}

				Logger.WriteLine("Setting deck name...", "DeckExporter");
				var nameDeckPos = new Point((int)Helper.GetScaledXPos(Config.Instance.ExportNameDeckX, info.HsRect.Width, info.Ratio),
				                            (int)(Config.Instance.ExportNameDeckY * info.HsRect.Height));
				await ClickOnPoint(info.HsHandle, nameDeckPos);
				//send enter and second click to make sure the current name gets selected
				SendKeys.SendWait("{ENTER}");
				await ClickOnPoint(info.HsHandle, nameDeckPos);
				if(Config.Instance.ExportPasteClipboard)
				{
					Clipboard.SetText(name);
					SendKeys.SendWait("^v");
				}
				else
					SendKeys.SendWait(name);
				SendKeys.SendWait("{ENTER}");
			}
		}
		public static async Task SetDeckName(Deck deck, ExportingInfo info)
		{
			if(Config.Instance.ExportSetDeckName && !deck.TagList.ToLower().Contains("brawl"))
			{
				var name = deck.Name;
				if(Config.Instance.ExportAddDeckVersionToName)
					name += " " + deck.SelectedVersion.ShortVersionString;

				Logger.WriteLine("Setting deck name...", "DeckExporter");
				var nameDeckPos = new Point((int)Helper.GetScaledXPos(Config.Instance.ExportNameDeckX, info.HsRect.Width, info.Ratio),
				                            (int)(Config.Instance.ExportNameDeckY * info.HsRect.Height));
				await MouseActions.ClickOnPoint(info.HsHandle, nameDeckPos);
				//send enter and second click to make sure the current name gets selected
				SendKeys.SendWait("{ENTER}");
				await MouseActions.ClickOnPoint(info.HsHandle, nameDeckPos);
				if(Config.Instance.ExportPasteClipboard)
				{
					Clipboard.SetText(name);
					SendKeys.SendWait("^v");
				}
				else
					SendKeys.SendWait(name);
				SendKeys.SendWait("{ENTER}");
			}
		}
		public DeckPickerItem(Deck deck, Type deckPickerItemLayout)
		{
			InitializeComponent();
			DataContext = deck;
			Deck = deck;
			_deckPickerItem = deckPickerItemLayout;
			SetLayout();
		}
		public bool BelongsToDeck(Deck deck)
		{
			if(HasHearthStatsDeckId && deck.HasHearthStatsId)
				return HearthStatsDeckId.Equals(deck.HearthStatsIdForUploading);
			//if(HasHearthStatsDeckId && deck.HasHearthStatsId && HasHearthStatsDeckVersionId && deck.HasHearthStatsDeckVersionId)
			//	return HearthStatsDeckId.Equals(deck.HearthStatsId) && HearthStatsDeckVersionId.Equals(deck.HearthStatsDeckVersionId);
			return DeckId == deck.DeckId;
		}
		public void UpdateDeckList(Deck selected)
		{
			ListViewDeck.ItemsSource = null;
			if(selected == null)
				return;
			ListViewDeck.ItemsSource = selected.GetSelectedDeckVersion().Cards;
			Helper.SortCardCollection(ListViewDeck.Items, Config.Instance.CardSortingClassFirst);
		}
		public void SetDeck(Deck deck)
		{
			_deck = deck;
			ListViewDeck.Items.Clear();
			foreach(var card in deck.Cards)
				ListViewDeck.Items.Add(card);
			Helper.SortCardCollection(ListViewDeck.Items, false);
		}
		public static async Task<Deck> Import(string url)
		{
			try
			{
				var doc = await ImportingHelper.GetHtmlDoc(url);

				var deck = new Deck();
				deck.Name =
					HttpUtility.HtmlDecode(doc.DocumentNode.SelectSingleNode("/html/body/div/div[4]/div/div[2]/div/div[1]/h3").InnerText.Trim());

				var cards = doc.DocumentNode.SelectNodes("//div[contains(@class, 'cardname')]/span");

				var deckInfo = doc.DocumentNode.SelectSingleNode("//div[@id='subinfo']").SelectNodes("//span[contains(@class, 'midlarge')]/span");
				if(deckInfo.Count == 2)
				{
					deck.Class = HttpUtility.HtmlDecode(deckInfo[0].InnerText).Trim();

					var decktype = HttpUtility.HtmlDecode(deckInfo[1].InnerText).Trim();
					if(!string.IsNullOrEmpty(decktype) && decktype != "None" && Config.Instance.TagDecksOnImport)
					{
						if(!DeckList.Instance.AllTags.Contains(decktype))
						{
							DeckList.Instance.AllTags.Add(decktype);
							DeckList.Save();
							if(Helper.MainWindow != null) // to avoid errors when running tests
								Core.MainWindow.ReloadTags();
						}
						deck.Tags.Add(decktype);
					}
				}

				foreach(var cardNode in cards)
				{
					var nameString = HttpUtility.HtmlDecode(cardNode.InnerText);
					var match = Regex.Match(nameString, @"^\s*(\d+)\s+(.*)\s*$");

					if(match.Success)
					{
						var count = match.Groups[1].Value;
						var name = match.Groups[2].Value;

						var card = Database.GetCardFromName(name);
						card.Count = count.Equals("2") ? 2 : 1;
						deck.Cards.Add(card);
						if(string.IsNullOrEmpty(deck.Class) && card.PlayerClass != "Neutral")
							deck.Class = card.PlayerClass;
					}
				}

				return deck;
			}
			catch(Exception e)
			{
				Logger.WriteLine(e.ToString(), "DeckImporter");
				return null;
			}
		}
		public void Update(Deck deck)
		{
			RectIconOg.Visibility = deck?.ContainsSet("Whispers of the Old Gods") ?? false ? Visible : Collapsed;
			RectIconLoe.Visibility = deck?.ContainsSet("League of Explorers") ?? false ? Visible : Collapsed;
			RectIconTgt.Visibility = deck?.ContainsSet("The Grand Tournament") ?? false ? Visible : Collapsed;
			RectIconBrm.Visibility = deck?.ContainsSet("Blackrock Mountain") ?? false ? Visible : Collapsed;
			RectIconGvg.Visibility = deck?.ContainsSet("Goblins vs Gnomes") ?? false ? Visible : Collapsed;
			RectIconNaxx.Visibility = deck?.ContainsSet("Curse of Naxxramas") ?? false ? Visible : Collapsed;
		}
		public void SetDeck(Deck deck, bool showImportButton = true)
		{
			_deck = deck;
			ListViewDeck.Items.Clear();
			foreach(var card in deck.Cards.ToSortedCardList())
				ListViewDeck.Items.Add(card);
			Helper.SortCardCollection(ListViewDeck.Items, false);
			ButtonImport.Visibility = showImportButton ? Visibility.Visible : Visibility.Collapsed;
		}
		public ExistingDeck(Deck deck, HearthMirror.Objects.Deck newDeck)
		{
			Deck = deck;
			var tmp = new Deck { Cards = new ObservableCollection<Card>(newDeck.Cards.Select(x => new Card { Id = x.Id, Count = x.Count })) };
			MatchingCards = 30 - (deck - tmp).Count(x => x.Count > 0);
			NewVersion = MatchingCards == 30 ? new SerializableVersion(0, 0)
				: (MatchingCards < 26 ? SerializableVersion.IncreaseMajor(deck.Version)
					: SerializableVersion.IncreaseMinor(deck.Version));
		}
		public static async Task<bool> Export(Deck deck)
		{
			if(deck == null)
				return false;
			var currentClipboard = "";
			var altScreenCapture = Config.Instance.AlternativeScreenCapture;
			try
			{
				Log.Info("Exporting " + deck.GetDeckInfo());
				if(Config.Instance.ExportPasteClipboard && Clipboard.ContainsText())
					currentClipboard = Clipboard.GetText();

				var info = new ExportingInfo();
				LogDebugInfo(info);

				var inForeground = await ExportingHelper.EnsureHearthstoneInForeground(info.HsHandle);
				if(!inForeground)
					return false;
				Log.Info($"Waiting for {Config.Instance.ExportStartDelay} seconds before starting the export process");
				await Task.Delay(Config.Instance.ExportStartDelay * 1000);
				if(!altScreenCapture)
					Core.Overlay.ForceHide(true);

				await ClearDeck(info);
				await SetDeckName(deck, info);
				await ClearFilters(info);
				var lostFocus = await CreateDeck(deck, info);
				if(lostFocus)
					return false;
				await ClearSearchBox(info.HsHandle, info.SearchBoxPos);

				if(Config.Instance.ExportPasteClipboard)
					Clipboard.Clear();
				Log.Info("Success exporting deck.");
				return true;
			}
			catch(Exception e)
			{
				Log.Error("Error exporting deck: " + e);
				return false;
			}
			finally
			{
				if(!altScreenCapture)
					Core.Overlay.ForceHide(false);
				try
				{
					if(Config.Instance.ExportPasteClipboard && currentClipboard != "")
						Clipboard.SetText(currentClipboard);
				}
				catch(Exception ex)
				{
					Log.Error("Could not restore clipboard content after export: " + ex);
				}
			}
		}
		public Deck ToDeck(CardObject[] cards, string[] rawTags, DeckVersion[] versions, string version)
		{
			if(!klass_id.HasValue)
				return null;
			try
			{
				var url = "";
				bool archived = false;
				if(!string.IsNullOrEmpty(notes))
				{
					var match = Regex.Match(notes, noteUrlRegex);
					if(match.Success)
					{
						url = match.Groups["url"].Value;
						notes = Regex.Replace(notes, noteUrlRegex, "");
					}

					if(notes.Contains(noteArchived))
					{
						archived = true;
						notes = notes.Replace(noteArchived, "");
					}
					notes = notes.Trim();
				}


				//tags are returned all lowercase, find matching tag
				var tags =
					rawTags.Select(
					               tag =>
					               DeckList.Instance.AllTags.FirstOrDefault(t => string.Equals(t, tag, StringComparison.InvariantCultureIgnoreCase))
					               ?? tag);
				var deck = new Deck(name ?? "", Dictionaries.HeroDict[klass_id.Value],
				                    cards == null
					                    ? new List<Card>()
					                    : cards.Where(x => x != null && x.count != null && x.id != null)
					                           .Select(x => x.ToCard())
					                           .Where(x => x != null)
					                           .ToList(), tags, notes ?? "", url, DateTime.Now, archived, new List<Card>(),
				                    SerializableVersion.ParseOrDefault(version), new List<Deck>(), true, id.ToString(), Guid.NewGuid(),
				                    deck_version_id.ToString());
				deck.LastEdited = updated_at.ToLocalTime();
				if(versions.Length > 0)
					deck.Versions = versions.Where(v => v.version != version).Select(v => v.ToDeck(deck)).ToList();
				var current = versions.FirstOrDefault(v => v.version == version);
				if(current != null)
					deck.HearthStatsDeckVersionId = current.deck_version_id.ToString();
				deck.HearthStatsIdsAlreadyReset = true;
				return deck;
			}
			catch(Exception e)
			{
				Logger.WriteLine("error converting DeckObject: " + e, "HearthStatsAPI");
				return null;
			}
		}
Exemple #31
0
        public async Task GetCardCounts(Deck deck)
        {
            var hsHandle = User32.GetHearthstoneWindow();

            if (!User32.IsHearthstoneInForeground())
            {
                //restore window and bring to foreground
                User32.ShowWindow(hsHandle, User32.SwRestore);
                User32.SetForegroundWindow(hsHandle);
                //wait it to actually be in foreground, else the rect might be wrong
                await Task.Delay(500);
            }
            if (!User32.IsHearthstoneInForeground())
            {
                Log.Error("Can't find Hearthstone window.");
                return;
            }
            await Task.Delay(1000);

            Core.Overlay.ForceHidden = true;
            Core.Overlay.UpdatePosition();
            const double xScale          = 0.013;
            const double yScale          = 0.017;
            const int    targetHue       = 53;
            const int    hueMargin       = 3;
            const int    numVisibleCards = 21;
            var          hsRect          = User32.GetHearthstoneRect(false);
            var          ratio           = (4.0 / 3.0) / ((double)hsRect.Width / hsRect.Height);
            var          posX            = (int)Helper.GetScaledXPos(0.92, hsRect.Width, ratio);
            var          startY          = 71.0 / 768.0 * hsRect.Height;
            var          strideY         = 29.0 / 768.0 * hsRect.Height;
            var          width           = (int)Math.Round(hsRect.Width * xScale);
            var          height          = (int)Math.Round(hsRect.Height * yScale);

            for (var i = 0; i < Math.Min(numVisibleCards, deck.Cards.Count); i++)
            {
                var posY    = (int)(startY + strideY * i);
                var capture = await ScreenCapture.CaptureHearthstoneAsync(new Point(posX, posY), width, height, hsHandle);

                if (capture == null)
                {
                    continue;
                }
                var yellowPixels = 0;
                for (var x = 0; x < width; x++)
                {
                    for (var y = 0; y < height; y++)
                    {
                        var pixel = capture.GetPixel(x, y);
                        if (Math.Abs(pixel.GetHue() - targetHue) < hueMargin)
                        {
                            yellowPixels++;
                        }
                    }
                }
                //Console.WriteLine(yellowPixels + " of " + width * height + " - " + yellowPixels / (double)(width * height));
                //capture.Save("arenadeckimages/" + i + ".png");
                var yellowPixelRatio = yellowPixels / (double)(width * height);
                if (yellowPixelRatio > 0.25 && yellowPixelRatio < 50)
                {
                    deck.Cards[i].Count = 2;
                }
            }

            if (deck.Cards.Count > numVisibleCards)
            {
                const int scrollClicksPerCard = 4;
                const int scrollDistance      = 120;
                var       clientPoint         = new Point(posX, (int)startY);
                var       previousPos         = System.Windows.Forms.Cursor.Position;
                User32.ClientToScreen(hsHandle, ref clientPoint);
                System.Windows.Forms.Cursor.Position = new Point(clientPoint.X, clientPoint.Y);
                for (var j = 0; j < scrollClicksPerCard * (deck.Cards.Count - numVisibleCards); j++)
                {
                    User32.mouse_event((uint)User32.MouseEventFlags.Wheel, 0, 0, -scrollDistance, UIntPtr.Zero);
                    await Task.Delay(30);
                }
                System.Windows.Forms.Cursor.Position = previousPos;
                await Task.Delay(100);

                var remainingCards = deck.Cards.Count - numVisibleCards;
                startY = 76.0 / 768.0 * hsRect.Height + (numVisibleCards - remainingCards) * strideY;
                for (var i = 0; i < remainingCards; i++)
                {
                    var posY    = (int)(startY + strideY * i);
                    var capture = await ScreenCapture.CaptureHearthstoneAsync(new Point(posX, posY), width, height, hsHandle);

                    if (capture == null)
                    {
                        continue;
                    }
                    var yellowPixels = 0;
                    for (var x = 0; x < width; x++)
                    {
                        for (var y = 0; y < height; y++)
                        {
                            var pixel = capture.GetPixel(x, y);
                            if (Math.Abs(pixel.GetHue() - targetHue) < hueMargin)
                            {
                                yellowPixels++;
                            }
                        }
                    }
                    var yellowPixelRatio = yellowPixels / (double)(width * height);
                    if (yellowPixelRatio > 0.25 && yellowPixelRatio < 50)
                    {
                        deck.Cards[numVisibleCards + i].Count = 2;
                    }
                }

                System.Windows.Forms.Cursor.Position = new Point(clientPoint.X, clientPoint.Y);
                for (var j = 0; j < scrollClicksPerCard * (deck.Cards.Count - 21); j++)
                {
                    User32.mouse_event((uint)User32.MouseEventFlags.Wheel, 0, 0, scrollDistance, UIntPtr.Zero);
                    await Task.Delay(30);
                }
                System.Windows.Forms.Cursor.Position = previousPos;
            }

            Core.Overlay.ForceHidden = false;
            Core.Overlay.UpdatePosition();

            ActivateWindow();
        }
 public void SelectVersion(Deck deck)
 {
     SelectVersion(deck.Version);
 }
        private static async Task AutoSelectDeck(Deck currentDeck, string heroClass, GameMode mode, Format?currentFormat, List <IGrouping <string, Entity> > cardEntites = null)
        {
            if (mode == GameMode.Battlegrounds)
            {
                Log.Info("Switching to no-deck mode for battlegrounds");
                Core.MainWindow.SelectDeck(null, true);
                return;
            }
            _waitingForDraws++;
            await Task.Delay(500);

            _waitingForDraws--;
            if (_waitingForDraws > 0)
            {
                return;
            }
            var validDecks = DeckList.Instance.Decks.Where(x => x.Class == heroClass && !x.Archived && !x.IsDungeonDeck && !x.IsDuelsDeck).ToList();

            if (currentDeck != null)
            {
                validDecks.Remove(currentDeck);
            }
            validDecks = validDecks.FilterByMode(mode, currentFormat);
            if (cardEntites != null)
            {
                validDecks = validDecks.Where(x => cardEntites.All(ce => x.GetSelectedDeckVersion().Cards.Any(c => c.Id == ce.Key && c.Count >= ce.Count()))).ToList();
            }
            if (_autoSelectCount > 1)
            {
                Log.Info("Too many auto selects. Showing dialog.");
                ShowDeckSelectionDialog(validDecks);
                return;
            }
            if (validDecks.Count == 0)
            {
                if (cardEntites == null || !AutoSelectDeckVersion(heroClass, mode, currentFormat, cardEntites))
                {
                    Log.Info("No matching deck found, using no-deck mode");
                    Core.MainWindow.SelectDeck(null, true);
                }
                return;
            }
            if (validDecks.Count == 1)
            {
                var deck = validDecks.Single();
                Log.Info("Found one matching deck: " + deck);
                Core.MainWindow.SelectDeck(deck, true);
                _autoSelectCount++;
                return;
            }
            var lastUsed = DeckList.Instance.LastDeckClass.FirstOrDefault(x => x.Class == heroClass);

            if (lastUsed != null)
            {
                var deck = validDecks.FirstOrDefault(x => x.DeckId == lastUsed.Id);
                if (deck != null)
                {
                    Log.Info($"Last used {heroClass} deck matches!");
                    Core.MainWindow.SelectDeck(deck, true);
                    _autoSelectCount++;
                    return;
                }
            }
            if (cardEntites == null || !AutoSelectDeckVersion(heroClass, mode, currentFormat, cardEntites))
            {
                ShowDeckSelectionDialog(validDecks);
            }
        }
 private static List <IGrouping <string, Entity> > GetMissingCards(List <IGrouping <string, Entity> > revealed, Deck deck) =>
 revealed.Where(x => !deck.GetSelectedDeckVersion().Cards.Any(c => c.Id == x.Key && c.Count >= x.Count())).ToList();
        public static List <Deck> ImportDecksTo(ICollection <Deck> targetList, IEnumerable <ImportedDeck> decks, bool brawl, bool importNew, bool updateExisting)
        {
            var importedDecks = new List <Deck>();

            foreach (var deck in decks)
            {
                if (deck.SelectedImportOption is NewDeck)
                {
                    if (!importNew)
                    {
                        continue;
                    }
                    Log.Info($"Saving {deck.Deck.Name} as new deck.");
                    var newDeck = new Deck
                    {
                        Class = deck.Class,
                        Name  = deck.Deck.Name,
                        HsId  = deck.Deck.Id,
                        Cards = new ObservableCollection <Card>(deck.Deck.Cards.Select(x =>
                        {
                            var card   = Database.GetCardFromId(x.Id);
                            card.Count = x.Count;
                            return(card);
                        })),
                        LastEdited  = DateTime.Now,
                        IsArenaDeck = false
                    };
                    if (brawl)
                    {
                        newDeck.Tags.Add("Brawl");
                        newDeck.Name = Helper.ParseDeckNameTemplate(Config.Instance.BrawlDeckNameTemplate, newDeck);
                    }

                    var existingWithId = targetList.FirstOrDefault(d => d.HsId == deck.Deck.Id);
                    if (existingWithId != null)
                    {
                        existingWithId.HsId = 0;
                    }

                    targetList.Add(newDeck);
                    importedDecks.Add(newDeck);
                }
                else
                {
                    if (!updateExisting)
                    {
                        continue;
                    }
                    var existing = deck.SelectedImportOption as ExistingDeck;
                    if (existing == null)
                    {
                        continue;
                    }
                    var target = existing.Deck;
                    target.HsId = deck.Deck.Id;
                    if (brawl && !target.Tags.Any(x => x.ToUpper().Contains("BRAWL")) &&
                        (target.DeckStats.Games.Count == 0 ||
                         target.DeckStats.Games.All(g => g.GameMode == GameMode.Brawl)))
                    {
                        target.Tags.Add("Brawl");
                    }
                    if (target.Archived)
                    {
                        target.Archived = false;
                        Log.Info($"Unarchiving deck: {deck.Deck.Name}.");
                    }
                    if (existing.NewVersion.Major == 0)
                    {
                        Log.Info($"Assinging id to existing deck: {deck.Deck.Name}.");
                    }
                    else
                    {
                        Log.Info(
                            $"Saving {deck.Deck.Name} as {existing.NewVersion.ShortVersionString} (prev={target.Version.ShortVersionString}).");
                        targetList.Remove(target);
                        var oldDeck = (Deck)target.Clone();
                        oldDeck.Versions = new List <Deck>();
                        if (!brawl)
                        {
                            target.Name = deck.Deck.Name;
                        }
                        target.LastEdited = DateTime.Now;
                        target.Versions.Add(oldDeck);
                        target.Version         = existing.NewVersion;
                        target.SelectedVersion = existing.NewVersion;
                        target.Cards.Clear();
                        var cards = deck.Deck.Cards.Select(x =>
                        {
                            var card   = Database.GetCardFromId(x.Id);
                            card.Count = x.Count;
                            return(card);
                        });
                        foreach (var card in cards)
                        {
                            target.Cards.Add(card);
                        }
                        var clone = (Deck)target.Clone();
                        targetList.Add(clone);
                        importedDecks.Add(clone);
                    }
                }
            }
            return(importedDecks);
        }
 public bool BelongsToDeckVerion(Deck deck) => PlayerDeckVersion == deck.Version ||
 !IsAssociatedWithDeckVersion && deck.Version == new SerializableVersion(1, 0);
        public static void ImportDecks(IEnumerable <ImportedDeck> decks, bool brawl, bool importNew = true, bool updateExisting = true, bool select = true)
        {
            Deck toSelect = null;

            foreach (var deck in decks)
            {
                if (deck.SelectedImportOption is NewDeck)
                {
                    if (!importNew)
                    {
                        continue;
                    }
                    Log.Info($"Saving {deck.Deck.Name} as new deck.");
                    var newDeck = new Deck {
                        Class = deck.Class,
                        Name  = deck.Deck.Name,
                        HsId  = deck.Deck.Id,
                        Cards = new ObservableCollection <Card>(deck.Deck.Cards.Select(x =>
                        {
                            var card   = Database.GetCardFromId(x.Id);
                            card.Count = x.Count;
                            return(card);
                        })),
                        LastEdited  = DateTime.Now,
                        IsArenaDeck = false
                    };
                    if (brawl)
                    {
                        newDeck.Tags.Add("Brawl");
                        newDeck.Name = Helper.ParseDeckNameTemplate(Config.Instance.BrawlDeckNameTemplate, newDeck);
                    }
                    DeckList.Instance.Decks.Add(newDeck);
                    toSelect = newDeck;
                }
                else
                {
                    if (!updateExisting)
                    {
                        continue;
                    }
                    var existing = deck.SelectedImportOption as ExistingDeck;
                    if (existing == null)
                    {
                        continue;
                    }
                    var target = existing.Deck;
                    target.HsId = deck.Deck.Id;
                    if (brawl && !target.Tags.Any(x => x.ToUpper().Contains("BRAWL")))
                    {
                        target.Tags.Add("Brawl");
                    }
                    if (existing.NewVersion.Major == 0)
                    {
                        Log.Info($"Assinging id to existing deck: {deck.Deck.Name}.");
                    }
                    else
                    {
                        Log.Info($"Saving {deck.Deck.Name} as {existing.NewVersion.ShortVersionString} (prev={target.Version.ShortVersionString}).");
                        DeckList.Instance.Decks.Remove(target);
                        var oldDeck = (Deck)target.Clone();
                        oldDeck.Versions = new List <Deck>();
                        if (!brawl)
                        {
                            target.Name = deck.Deck.Name;
                        }
                        target.LastEdited = DateTime.Now;
                        target.Versions.Add(oldDeck);
                        target.Version         = existing.NewVersion;
                        target.SelectedVersion = existing.NewVersion;
                        target.Cards.Clear();
                        var cards = deck.Deck.Cards.Select(x =>
                        {
                            var card   = Database.GetCardFromId(x.Id);
                            card.Count = x.Count;
                            return(card);
                        });
                        foreach (var card in cards)
                        {
                            target.Cards.Add(card);
                        }
                        var clone = (Deck)target.Clone();
                        DeckList.Instance.Decks.Add(clone);
                        toSelect = clone;
                    }
                }
            }
            DeckList.Save();
            Core.MainWindow.DeckPickerList.UpdateDecks();
            Core.MainWindow.UpdateIntroLabelVisibility();
            if (select && toSelect != null)
            {
                Core.MainWindow.SelectDeck(toSelect, true);
            }
            Core.UpdatePlayerCards(true);
        }
 public void SetPlayerCards(Deck deck, List <Card> revealedCards) => SetPlayerCards(deck?.Cards, revealedCards);