package magic.ui.widget.card.decks;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import magic.model.MagicCardDefinition;
import magic.model.MagicDeck;
import magic.translate.MText;
import magic.ui.FontsAndBorders;
import magic.ui.ScreenController;
import magic.utility.DeckUtils;
import magic.utility.MagicSystem;
import net.miginfocom.swing.MigLayout;
/**
* Displays a list of decks containing a given card (see {@code setCard()}).
*/
@SuppressWarnings("serial")
public class CardDecksPanel extends JPanel {
// translatable strings
private static final String _S1 = "Invalid Deck!";
public static final String CP_DECKS_UPDATED = "6f0c65a7-6485-4468-9d62-31a505d307a9";
private static final int TIMER_DELAY_MSECS = 500;
private static SwingWorker<File[], Void> worker;
private final JList<File> decksJList = new JList<>();
private final MigLayout miglayout = new MigLayout();
private final JScrollPane scroller = new JScrollPane();
private MagicCardDefinition card;
private Timer cooldownTimer;
public CardDecksPanel() {
decksJList.setOpaque(false);
decksJList.setBackground(new Color(0, 0, 0, 1));
decksJList.setForeground(Color.BLACK);
decksJList.setFocusable(true);
decksJList.setCellRenderer(new CardDecksListCellRenderer());
decksJList.setFont(FontsAndBorders.FONT1);
// scroll pane for deck names list
scroller.setViewportView(decksJList);
scroller.setBorder(null);
scroller.setOpaque(false);
scroller.getViewport().setOpaque(false);
scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
decksJList.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent evt) {
if (isDoubleClick(evt)) {
showSelectedDeck(decksJList.locationToIndex(evt.getPoint()));
}
}
});
miglayout.setLayoutConstraints("insets 0, flowy");
setLayout(miglayout);
// these constraints fix decksButton in CardPanel not resizing correctly
// when full screen and decksbutton is clicked to re-show image. Ensure you
// test if these constraints are updated especially if you add an 'h' constraint.
add(scroller, "w 100%, growy, pushy");
cooldownTimer = new Timer(TIMER_DELAY_MSECS, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
cooldownTimer.stop();
showDecksContainingCard(CardDecksPanel.this.card);
}
});
}
private void showSelectedDeck(int row) {
if (row >= 0) {
final File deckFile = decksJList.getModel().getElementAt(row);
final MagicDeck deck = DeckUtils.loadDeckFromFile(deckFile.toPath());
if (deck.isValid()) {
ScreenController.showDeckScreen(deck, card);
} else {
ScreenController.showWarningMessage(String.format("<html><b>%s</b><br>%s</html>",
MText.get(_S1),
deck.getDescription().replace("\n", "<br>"))
);
}
}
}
private boolean isDoubleClick(MouseEvent evt) {
return evt.getClickCount() == 2 && SwingUtilities.isLeftMouseButton(evt);
}
public void setCard(MagicCardDefinition aCard) {
if (worker != null && worker.isDone() == false && worker.isCancelled() == false) {
worker.cancel(true);
}
this.card = aCard;
decksJList.setListData(new File[]{});
cooldownTimer.setInitialDelay(TIMER_DELAY_MSECS);
cooldownTimer.restart();
}
private void showDecksContainingCard(final MagicCardDefinition card) {
worker = new SwingWorker<File[], Void>() {
@Override
protected File[] doInBackground() throws Exception {
final List<File> deckFiles = getDecksContainingCard(card);
sortDecksByFilename(deckFiles);
return deckFiles.toArray(new File[0]);
}
@Override
protected void done() {
try {
decksJList.setListData(get());
fireDecksUpdatedEvent();
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
} catch (InterruptedException ex) {
System.err.println("CardDecksPanel.SwingWorker.InterruptedException");
} catch (CancellationException ex) {
if (MagicSystem.isDevMode()) {
System.err.println("CardDecksPanel.SwingWorker.CancellationException");
}
}
}
private List<File> getDecksContainingCard(final MagicCardDefinition cardDef) {
final List<File> matchingDeckFiles = new ArrayList<>();
if (cardDef != null && !isCancelled()) {
for (File deckFile : DeckUtils.getDeckFiles()) {
if (isCancelled()) {
break;
}
try (final BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(deckFile), "UTF-8"))) {
String line;
while ((line = br.readLine()) != null && !isCancelled()) {
if (!line.startsWith("#") & !line.startsWith(">") & !line.trim().isEmpty()) {
if (line.substring(line.indexOf(" ")).trim().equals(cardDef.getName())) {
matchingDeckFiles.add(deckFile);
break;
}
}
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
return matchingDeckFiles;
}
/**
* In Windows, DirectoryStream returns a list of files already sorted by filename. In Linux it does not, so need to specifically sort list.
*/
private void sortDecksByFilename(final List<File> files) {
if (isCancelled() == false) {
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.getAbsolutePath().compareTo(o2.getAbsolutePath());
}
});
}
}
};
worker.execute();
}
private void fireDecksUpdatedEvent() {
firePropertyChange(CP_DECKS_UPDATED, -1, decksJList.getModel().getSize());
}
int getDecksCount() {
return decksJList.getModel().getSize();
}
}