package com.cardshifter.client;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import com.cardshifter.api.config.DeckConfig;
import net.zomis.cardshifter.ecs.usage.CardshifterIO;
import com.cardshifter.api.outgoing.CardInfoMessage;
import com.cardshifter.client.buttons.GenericButton;
import com.cardshifter.client.buttons.SavedDeckButton;
import com.cardshifter.client.views.CardHandDocumentController;
import com.cardshifter.client.views.DeckCardController;
public class DeckBuilderWindow {
@FXML private AnchorPane rootPane;
@FXML private FlowPane cardListBox;
@FXML private VBox activeDeckBox;
@FXML private VBox deckListBox;
@FXML private AnchorPane previousPage;
@FXML private AnchorPane nextPage;
@FXML private AnchorPane saveDeckButton;
@FXML private AnchorPane loadDeckButton;
@FXML private AnchorPane clearDeckButton;
@FXML private TextField deckNameBox;
@FXML private AnchorPane exitButton;
@FXML private AnchorPane deleteButton;
@FXML private Label cardCountLabel;
@FXML private AnchorPane deckInfoBox;
@FXML private AnchorPane deckInfoButton;
@FXML private VBox deckInfoLabelBox;
private static final int CARDS_PER_PAGE = 12;
private int currentPage = 0;
private Map<Integer, CardInfoMessage> cardList = new HashMap<>();
private List<List<CardInfoMessage>> pageList = new ArrayList<>();
private DeckConfig activeDeckConfig;
private CardInfoMessage cardBeingDragged;
private Consumer<DeckConfig> configCallback;
private String deckToLoad;
private File deckDirectory;
public void acceptDeckConfig(DeckConfig deckConfig, String modName, Consumer<DeckConfig> configCallback) {
this.configCallback = configCallback;
this.deckDirectory = new File("decks", modName);
deckDirectory.mkdirs();
this.activeDeckConfig = deckConfig;
this.cardList = deckConfig.getCardData();
}
public void configureWindow() {
this.previousPage.setOnMouseClicked(this::goToPreviousPage);
this.nextPage.setOnMouseClicked(this::goToNextPage);
this.exitButton.setOnMouseClicked(this::startGame);
this.deckInfoButton.setOnMouseClicked(this::toggleDeckInfoBox);
this.saveDeckButton.setOnMouseClicked(this::saveDeck);
this.loadDeckButton.setOnMouseClicked(this::loadDeck);
this.deleteButton.setOnMouseClicked(this::deleteDeck);
this.clearDeckButton.setOnMouseClicked(this::clickedClearDeckButton);
this.activeDeckBox.setOnDragDropped(e -> this.completeDragToActiveDeck(e, true));
this.activeDeckBox.setOnDragOver(e -> this.completeDragToActiveDeck(e, false));
List<CardInfoMessage> sortedCardList = new ArrayList<>(this.cardList.values());
Collections.sort(sortedCardList, Comparator.comparingInt(msg -> msg.getId()));
this.pageList = listSplitter(sortedCardList, CARDS_PER_PAGE);
this.displaySavedDecks();
this.clearDeck();
}
public void disableGameStart() {
this.exitButton.setVisible(false);
}
private void startGame(MouseEvent event) {
if (this.activeDeckConfig.total() == this.activeDeckConfig.getMaxSize()) {
this.configCallback.accept(this.activeDeckConfig);
this.closeWindow();
}
}
private void displayCurrentPage() {
this.cardListBox.getChildren().clear();
for (CardInfoMessage message : this.pageList.get(this.currentPage)) {
CardHandDocumentController card = new CardHandDocumentController(message, null);
Pane cardPane = card.getRootPane();
Rectangle numberOfCardsBox = new Rectangle(cardPane.getPrefWidth()/3, cardPane.getPrefHeight()/10);
numberOfCardsBox.setFill(Color.BLUE);
numberOfCardsBox.setStroke(Color.BLACK);
int numChosenCards = 0;
if (this.activeDeckConfig.getChosen().get(message.getId()) != null) {
numChosenCards = this.activeDeckConfig.getChosen().get(message.getId());
}
Label numberOfCardsLabel = new Label(String.format("%d / %d", numChosenCards, this.activeDeckConfig.getMaxFor(message.getId())));
numberOfCardsLabel.setTextFill(Color.WHITE);
numberOfCardsBox.relocate(cardPane.getPrefWidth()/2.6, cardPane.getPrefHeight() - cardPane.getPrefHeight()/18);
numberOfCardsLabel.relocate(cardPane.getPrefWidth()/2.3, cardPane.getPrefHeight() - cardPane.getPrefHeight()/18);
cardPane.getChildren().add(numberOfCardsBox);
cardPane.getChildren().add(numberOfCardsLabel);
cardPane.setOnMouseClicked(e -> {this.addCardToActiveDeck(e, message);});
cardPane.setOnDragDetected(e -> this.startDragToActiveDeck(e, cardPane, message));
this.cardListBox.getChildren().add(cardPane);
}
}
private void displaySavedDecks() {
this.deckListBox.getChildren().clear();
if (deckDirectory.listFiles() != null) {
for (File file : deckDirectory.listFiles()) {
try {
if ((CardshifterIO.mapper().readValue(file, DeckConfig.class) instanceof DeckConfig)) {
SavedDeckButton deckButton = new SavedDeckButton(this.deckListBox.getPrefWidth(), 40, cleanName(file.getName()), this);
this.deckListBox.getChildren().add(deckButton);
}
} catch (Exception e) {
//swallowing this exception because it clogs the logs
//happens every time the file is not the proper format
//System.out.println("File failed to load");
}
}
}
}
private String cleanName(String name) {
final String extension = ".deck";
if (name.endsWith(extension)) {
return name.substring(0, name.length() - extension.length());
}
return name;
}
private void displayActiveDeck() {
this.activeDeckBox.getChildren().clear();
List<Integer> sortedKeys = new ArrayList<>(this.activeDeckConfig.getChosen().keySet());
Collections.sort(sortedKeys, Comparator.comparingInt(key -> key));
for (Integer cardId : sortedKeys) {
if (!cardList.containsKey(cardId)) {
activeDeckConfig.setChosen(cardId, 0);
continue;
}
DeckCardController card = new DeckCardController(this.cardList.get(cardId), this.activeDeckConfig.getChosen().get(cardId));
Pane cardPane = card.getRootPane();
cardPane.setOnMouseClicked(e -> {this.removeCardFromDeck(e, cardId);});
this.activeDeckBox.getChildren().add(cardPane);
}
this.cardCountLabel.setText(String.format("%d / %d", this.activeDeckConfig.total(), this.activeDeckConfig.getMaxSize()));
}
private void addCardToActiveDeck(MouseEvent event, CardInfoMessage message) {
if (this.activeDeckConfig.total() < this.activeDeckConfig.getMaxSize()) {
if(this.activeDeckConfig.getChosen().get(message.getId()) == null) {
this.activeDeckConfig.setChosen(message.getId(), 1);
} else {
if (this.activeDeckConfig.getChosen().get(message.getId()) < this.activeDeckConfig.getMaxFor(message.getId())) {
this.activeDeckConfig.add(message.getId());
}
}
}
this.displayActiveDeck();
this.displayCurrentPage();
}
private void removeCardFromDeck(MouseEvent event, int cardId) {
if (this.activeDeckConfig.getChosen().get(cardId) != null) {
this.activeDeckConfig.removeChosen(cardId);
}
this.displayActiveDeck();
this.displayCurrentPage();
}
private void startDragToActiveDeck(MouseEvent event, Pane pane, CardInfoMessage message) {
this.cardBeingDragged = message;
Dragboard db = pane.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
content.putString(message.toString());
db.setContent(content);
event.consume();
}
private void completeDragToActiveDeck(DragEvent event, boolean dropped) {
event.acceptTransferModes(TransferMode.MOVE);
if (dropped) {
this.addCardToActiveDeck(null, this.cardBeingDragged);
}
event.consume();
}
public void setDeckToLoad(String deckName) {
this.deckToLoad = deckName;
}
private void clickedClearDeckButton(MouseEvent event) {
this.clearDeck();
}
private void toggleDeckInfoBox(MouseEvent event) {
if (this.deckInfoBox.isVisible()) {
this.deckInfoBox.setVisible(false);
} else {
this.deckInfoBox.setVisible(true);
this.buildDeckInfoBox();
}
}
private void buildDeckInfoBox() {
this.deckInfoLabelBox.getChildren().clear();
Map<Integer, Integer> manaCostValues = new HashMap<>();
Map<Integer, Integer> scrapCostValues = new HashMap<>();
Map<String, Integer> creatureTypes = new HashMap<>();
for (int cardId : this.activeDeckConfig.getChosen().keySet()) {
CardInfoMessage card = this.cardList.get(cardId);
int cardCount = this.activeDeckConfig.getChosen().get(cardId);
Optional.ofNullable(card.getProperties().get("creatureType"))
.ifPresent(obj -> increase(creatureTypes, (String) obj, cardCount));
Optional.ofNullable(card.getProperties().get("MANA_COST"))
.ifPresent(obj -> increase(manaCostValues, (Integer) obj, cardCount));
Optional.ofNullable(card.getProperties().get("SCRAP_COST"))
.ifPresent(obj -> increase(scrapCostValues, (Integer) obj, cardCount));
}
for (int manaCost : manaCostValues.keySet()) {
Label manaCostLabel = new Label();
manaCostLabel.setText(String.format("Mana Cost = %d, Count = %d", manaCost, manaCostValues.get(manaCost)));
this.deckInfoLabelBox.getChildren().add(manaCostLabel);
}
for (int scrapCost : scrapCostValues.keySet()) {
Label scrapCostLabel = new Label();
scrapCostLabel.setText(String.format("Scrap Cost = %d, Count = %d", scrapCost, scrapCostValues.get(scrapCost)));
this.deckInfoLabelBox.getChildren().add(scrapCostLabel);
}
for (String creatureType : creatureTypes.keySet()) {
Label creatureTypeLabel = new Label();
creatureTypeLabel.setText(String.format("Creature Type %s, Count = %d", creatureType, creatureTypes.get(creatureType)));
this.deckInfoLabelBox.getChildren().add(creatureTypeLabel);
}
}
private <T> void increase(Map<T, Integer> map, T key, int cardCount) {
map.merge(key, cardCount, (a, b) -> a + b);
}
private void clearDeck() {
this.activeDeckConfig.clearChosen();
this.displayActiveDeck();
this.displayCurrentPage();
}
private void saveDeck(MouseEvent event) {
if(!this.deckNameBox.getText().isEmpty()) {
try {
File file = deckFile(deckNameBox.getText());
if (file.isFile()) {
System.out.println("Deck already exists");
} else {
CardshifterIO.mapper().writeValue(deckFile(deckNameBox.getText()), this.activeDeckConfig);
}
} catch (Exception e) {
System.out.println("Deck failed to save");
}
}
this.displaySavedDecks();
}
private void loadDeck(MouseEvent event) {
if (this.deckToLoad != null) {
try {
this.activeDeckConfig = CardshifterIO.mapper().readValue(deckFile(this.deckToLoad), DeckConfig.class);
String truncatedDeckName = this.deckToLoad.substring(0, this.deckToLoad.length()- 5);
this.deckNameBox.setText(truncatedDeckName);
} catch (Exception e) {
System.out.println("Deck failed to load");
}
}
this.displayActiveDeck();
this.displayCurrentPage();
}
private void deleteDeck(MouseEvent event) {
if(this.deckToLoad != null) {
try {
File deckToDelete = deckFile(this.deckToLoad);
System.out.println("deck " + deckToDelete + " exists? " + deckToDelete.exists());
if (deckToDelete.exists()) {
deckToDelete.delete();
}
} catch (Exception e) {
System.out.println("Failed to load deck for delete");
}
}
this.displaySavedDecks();
}
private File deckFile(String deckName) {
return new File(deckDirectory, deckName + ".deck");
}
public void clearSavedDeckButtons() {
for (Object button : this.deckListBox.getChildren()) {
((GenericButton)button).unHighlightButton();
}
}
private void goToPreviousPage(MouseEvent event) {
if (this.currentPage > 0) {
this.currentPage--;
this.displayCurrentPage();
}
}
private void goToNextPage(MouseEvent event) {
if (this.currentPage < this.pageList.size() - 1) {
this.currentPage++;
this.displayCurrentPage();
}
}
public void closeWindow() {
Node source = this.rootPane;
Stage stage = (Stage)source.getScene().getWindow();
stage.close();
}
private static <T> List<List<T>> listSplitter(List<T> originalList, int resultsPerList) {
if (resultsPerList <= 0) {
throw new IllegalArgumentException("resultsPerList must be positive");
}
List<List<T>> listOfLists = new ArrayList<>();
List<T> latestList = new ArrayList<>();
Iterator<T> iterator = originalList.iterator();
while (iterator.hasNext()) {
T next = iterator.next();
if (latestList.size() >= resultsPerList) {
listOfLists.add(latestList);
latestList = new ArrayList<>();
}
latestList.add(next);
}
if (!latestList.isEmpty()) {
listOfLists.add(latestList);
}
return listOfLists;
}
}