package net.demilich.metastone.gui.deckbuilder; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import net.demilich.metastone.utils.MetastoneProperties; import net.demilich.metastone.utils.ResourceInputStream; import net.demilich.metastone.utils.ResourceLoader; import net.demilich.metastone.utils.UserHomeMetastone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import net.demilich.metastone.GameNotification; import net.demilich.metastone.game.cards.Card; import net.demilich.metastone.game.cards.CardCatalogue; import net.demilich.metastone.game.cards.CardCollection; import net.demilich.metastone.game.cards.CardSet; import net.demilich.metastone.game.decks.Deck; import net.demilich.metastone.game.decks.DeckFormat; import net.demilich.metastone.game.decks.MetaDeck; import net.demilich.metastone.game.entities.heroes.HeroClass; import net.demilich.metastone.game.decks.validation.DefaultDeckValidator; import net.demilich.metastone.game.decks.validation.IDeckValidator; import net.demilich.nittygrittymvc.Proxy; public class DeckProxy extends Proxy<GameNotification> { private static Logger logger = LoggerFactory.getLogger(DeckProxy.class); public static final String NAME = "DeckProxy"; private static final String DECKS_FOLDER = "decks"; private static final String DECKS_FOLDER_PATH = UserHomeMetastone.getPath() + File.separator + DECKS_FOLDER; private static final String DECKS_COPIED_PROPERTY = "decks.copied"; private final List<Deck> decks = new ArrayList<Deck>(); private IDeckValidator activeDeckValidator = new DefaultDeckValidator(); private Deck activeDeck; public DeckProxy() { super(NAME); try { // ensure user's personal deck dir exists Files.createDirectories(Paths.get(DECKS_FOLDER_PATH)); // ensure decks have been copied to ~/metastone/decks copyDecksFromResources(); } catch (IOException e) { logger.error("Trouble creating " + Paths.get(DECKS_FOLDER_PATH)); e.printStackTrace(); } catch (URISyntaxException e) { e.printStackTrace(); } } public boolean addCardToDeck(Card card) { boolean result = activeDeckValidator.canAddCardToDeck(card, activeDeck); if (result) { activeDeck.getCards().add(card); } return result; } public Deck getActiveDeck() { return activeDeck; } public List<Card> getCards(HeroClass heroClass) { DeckFormat deckFormat = new DeckFormat(); for (CardSet cardSet : CardSet.values()) { deckFormat.addSet(cardSet); } CardCollection cardCollection; if (activeDeck.isArbitrary()) { cardCollection = CardCatalogue.query(deckFormat); } else { cardCollection = CardCatalogue.query(deckFormat, heroClass); // add neutral cards cardCollection.addAll(CardCatalogue.query(deckFormat, HeroClass.ANY)); } cardCollection.sortByName(); cardCollection.sortByManaCost(); return cardCollection.toList(); } public Deck getDeckByName(String deckName) { for (Deck deck : decks) { if (deck.getName().equals(deckName)) { return deck; } } return null; } public List<Deck> getDecks() { return decks; } public void deleteDeck(Deck deck) { decks.remove(deck); logger.debug("Trying to delete deck '{}' contained in file '{}'...", deck.getName(), deck.getFilename()); Path path = Paths.get(DECKS_FOLDER_PATH + File.separator + deck.getFilename()); try { Files.delete(path); } catch (NoSuchFileException x) { logger.error("Could not delete deck '{}' as the filename '{}' does not exist", deck.getName(), path); return; } catch (IOException e) { logger.error(e.getMessage()); logger.error("Could not delete file '{}'", path); return; } logger.info("Deck '{}' contained in file '{}' has been successfully deleted", deck.getName(), path.getFileName().toString()); getFacade().sendNotification(GameNotification.DECKS_LOADED, decks); } public void loadDecks() throws IOException, URISyntaxException { decks.clear(); // load decks from ~/metastone/decks on the filesystem loadStandardDecks(ResourceLoader.loadJsonInputStreams(DECKS_FOLDER_PATH, true), new GsonBuilder().setPrettyPrinting().create()); loadMetaDecks(ResourceLoader.loadJsonInputStreams(DECKS_FOLDER_PATH, true), new GsonBuilder().setPrettyPrinting().create()); } private void copyDecksFromResources() throws IOException, URISyntaxException { // if we have not copied decks to the USER_HOME_METASTONE decks folder, // then do so now if (!MetastoneProperties.getBoolean(DECKS_COPIED_PROPERTY)) { ResourceLoader.copyFromResources(DECKS_FOLDER, DECKS_FOLDER_PATH); // set a property to indicate that we have copied decks MetastoneProperties.setBoolean(DECKS_COPIED_PROPERTY, true); } } private void loadMetaDecks(Collection<ResourceInputStream> inputStreams, Gson gson) throws IOException { for (ResourceInputStream resourceInputStream : inputStreams) { Reader reader = new InputStreamReader(resourceInputStream.inputStream); HashMap<String, Object> map = gson.fromJson(reader, new TypeToken<HashMap<String, Object>>() { }.getType()); if (!map.containsKey("heroClass")) { logger.error("Deck {} does not specify a value for 'heroClass' and is therefor not valid", resourceInputStream.fileName); continue; } String deckName = (String) map.get("name"); Deck deck = null; if (!map.containsKey("decks")) { continue; } else { deck = parseMetaDeck(map); } deck.setName(deckName); deck.setFilename(resourceInputStream.fileName); decks.add(deck); } } private void loadStandardDecks(Collection<ResourceInputStream> inputStreams, Gson gson) throws FileNotFoundException { for (ResourceInputStream resourceInputStream : inputStreams) { Reader reader = new InputStreamReader(resourceInputStream.inputStream); HashMap<String, Object> map = gson.fromJson(reader, new TypeToken<HashMap<String, Object>>() { }.getType()); if (!map.containsKey("heroClass")) { logger.error("Deck {} does not speficy a value for 'heroClass' and is therefor not valid", resourceInputStream.fileName); continue; } HeroClass heroClass = HeroClass.valueOf((String) map.get("heroClass")); String deckName = (String) map.get("name"); Deck deck = null; // this one is a meta deck; we need to parse those after all other // decks are done if (map.containsKey("decks")) { continue; } else { deck = parseStandardDeck(deckName, heroClass, map); } deck.setName(deckName); deck.setFilename(resourceInputStream.fileName); decks.add(deck); } } public boolean nameAvailable(Deck deck) { for (Deck existingDeck : decks) { if (existingDeck != deck && existingDeck.getName().equals(deck.getName())) { return false; } } return true; } private Deck parseMetaDeck(Map<String, Object> map) { @SuppressWarnings("unchecked") List<String> referencedDecks = (List<String>) map.get("decks"); List<Deck> decksInMetaDeck = new ArrayList<>(); for (String deckName : referencedDecks) { Deck deck = getDeckByName(deckName); if (deck == null) { logger.error("Metadeck {} contains invalid reference to deck {}", map.get("name"), deckName); continue; } decksInMetaDeck.add(deck); } return new MetaDeck(decksInMetaDeck); } private Deck parseStandardDeck(String deckName, HeroClass heroClass, Map<String, Object> map) { boolean arbitrary = false; if (map.containsKey("arbitrary")) { arbitrary = (boolean) map.get("arbitrary"); } Deck deck = new Deck(heroClass, arbitrary); @SuppressWarnings("unchecked") List<String> cardIds = (List<String>) map.get("cards"); for (String cardId : cardIds) { Card card = CardCatalogue.getCardById(cardId); if (card == null) { logger.error("Deck {} contains invalid cardId '{}'", deckName, cardId); continue; } deck.getCards().add(card); } return deck; } public void removeCardFromDeck(Card card) { activeDeck.getCards().remove(card); } public void saveActiveDeck() { decks.add(activeDeck); saveToJson(activeDeck); activeDeck = null; } private void saveToJson(Deck deck) { Gson gson = new GsonBuilder().setPrettyPrinting().create(); HashMap<String, Object> saveData = new HashMap<String, Object>(); saveData.put("name", deck.getName()); saveData.put("description", deck.getDescription()); saveData.put("arbitrary", deck.isArbitrary()); saveData.put("heroClass", deck.getHeroClass()); if (deck.isMetaDeck()) { MetaDeck metaDeck = (MetaDeck) deck; List<String> referencedDecks = new ArrayList<>(); for (Deck referencedDeck : metaDeck.getDecks()) { referencedDecks.add(referencedDeck.getName()); } saveData.put("decks", referencedDecks); } else { List<String> cardIds = new ArrayList<String>(); for (Card card : deck.getCards()) { cardIds.add(card.getCardId()); } saveData.put("cards", cardIds); } String jsonData = gson.toJson(saveData); try { String filename = deck.getName().toLowerCase(); filename = filename.replaceAll(" ", "_"); filename = filename.replaceAll("\\W+", ""); filename = DECKS_FOLDER_PATH + File.separator + filename + ".json"; Path path = Paths.get(filename); Files.write(path, jsonData.getBytes()); deck.setFilename(path.getFileName().toString()); } catch (IOException e) { e.printStackTrace(); } } public void setActiveDeck(Deck activeDeck) { this.activeDeck = activeDeck; } public void setActiveDeckValidator(IDeckValidator deckValidator) { this.activeDeckValidator = deckValidator; } }