/*
 * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of BetaSteward_at_googlemail.com.
 */
package mage.client.deckeditor;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.filechooser.FileFilter;

import mage.cards.Card;
import mage.cards.Sets;
import mage.cards.decks.Deck;
import mage.cards.decks.DeckCardLists;
import mage.cards.decks.importer.DeckImporter;
import mage.cards.decks.importer.DeckImporterUtil;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.client.MageFrame;
import mage.client.SessionHandler;
import mage.client.cards.BigCard;
import mage.client.cards.ICardGrid;
import mage.client.constants.Constants.DeckEditorMode;
import mage.client.deck.generator.DeckGenerator.DeckGeneratorException;
import mage.client.deck.generator.DeckGenerator;
import mage.client.dialog.AddLandDialog;
import mage.client.dialog.PreferencesDialog;
import mage.client.plugins.impl.Plugins;
import mage.client.util.Event;
import mage.client.util.Listener;
import mage.client.util.audio.AudioManager;
import mage.components.CardInfoPane;
import mage.game.GameException;
import mage.remote.Session;
import mage.view.CardView;
import mage.view.SimpleCardView;
import org.apache.log4j.Logger;

/**
 * @author BetaSteward_at_googlemail.com
 */
public class DeckEditorPanel extends javax.swing.JPanel {

    private static final Logger logger = Logger.getLogger(DeckEditorPanel.class);
    private final JFileChooser fcSelectDeck;
    private final JFileChooser fcImportDeck;
    private Deck deck = new Deck();
    private final Map<UUID, Card> temporaryCards = new HashMap<>(); // Cards dragged out of one part of the view into another
    private boolean isShowCardInfo = false;
    private UUID tableId;
    private DeckEditorMode mode;
    private int timeout;
    private Timer countdown;
    private UpdateDeckTask updateDeckTask;
    private int timeToSubmit = -1;

    /**
     * Creates new form DeckEditorPanel
     */
    public DeckEditorPanel() {
        initComponents();
        fcSelectDeck = new JFileChooser();
        fcSelectDeck.setAcceptAllFileFilterUsed(false);
        fcSelectDeck.addChoosableFileFilter(new DeckFilter());
        fcImportDeck = new JFileChooser();
        fcImportDeck.setAcceptAllFileFilterUsed(false);
        fcImportDeck.addChoosableFileFilter(new ImportFilter());

        deckArea.setOpaque(false);
        jPanel1.setOpaque(false);
        jSplitPane1.setOpaque(false);
        restoreDividerLocationsAndDeckAreaSettings();
        countdown = new Timer(1000,
                e -> {
                    if (--timeout > 0) {
                        setTimeout(timeout);
                    } else {
                        if (updateDeckTask != null) {
                            updateDeckTask.cancel(true);
                        }
                        setTimeout(0);
                        countdown.stop();
                        removeDeckEditor();
                    }
                });

        // Set up tracking to save the deck editor settings when the deck editor is hidden.
        addHierarchyListener((HierarchyEvent e) -> {
            if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
                if (!isShowing()) {
                    saveDividerLocationsAndDeckAreaSettings();
                }
            }
        });
    }

    /**
     * Free resources so GC can remove unused objects from memory
     */
    public void cleanUp() {
        saveDividerLocationsAndDeckAreaSettings();
        if (updateDeckTask != null) {
            updateDeckTask.cancel(true);
        }
        if (countdown != null) {
            if (countdown.isRunning()) {
                countdown.stop();
            }
            for (ActionListener al : countdown.getActionListeners()) {
                countdown.removeActionListener(al);
            }
        }
        this.cardSelector.cleanUp();
        this.deckArea.cleanUp();

        this.remove(bigCard);
        this.bigCard = null;
    }

    private void saveDividerLocationsAndDeckAreaSettings() {
        PreferencesDialog.saveValue(PreferencesDialog.KEY_EDITOR_HORIZONTAL_DIVIDER_LOCATION, Integer.toString(jSplitPane1.getDividerLocation()));
        PreferencesDialog.saveValue(PreferencesDialog.KEY_EDITOR_DECKAREA_SETTINGS, this.deckArea.saveSettings().toString());
    }

    private void restoreDividerLocationsAndDeckAreaSettings() {
        // Load horizontal split position setting
        String dividerLocation = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_EDITOR_HORIZONTAL_DIVIDER_LOCATION, "");
        if (!dividerLocation.isEmpty()) {
            jSplitPane1.setDividerLocation(Integer.parseInt(dividerLocation));
        }

        // Load deck area settings
        this.deckArea.loadSettings(
                DeckArea.Settings.parse(
                        PreferencesDialog.getCachedValue(PreferencesDialog.KEY_EDITOR_DECKAREA_SETTINGS, "")));
    }

    public void changeGUISize() {
        this.cardSelector.changeGUISize();
        this.deckArea.changeGUISize();
    }

    public void showDeckEditor(DeckEditorMode mode, Deck deck, UUID tableId, int time) {
        if (deck != null) {
            this.deck = deck;
        }
        this.tableId = tableId;
        this.mode = mode;
        this.btnAddLand.setVisible(false);

        switch (mode) {
            case LIMITED_BUILDING:
                this.btnAddLand.setVisible(true);
                this.txtTimeRemaining.setVisible(true);
                // Fall through to sideboarding
            case SIDEBOARDING:
                this.btnSubmit.setVisible(true);
                this.btnSubmitTimer.setVisible(true);
                if (mode == DeckEditorMode.SIDEBOARDING) {
                    this.deckArea.setOrientation(/*limitedBuildingOrientation = */false);
                } else /*(if (mode == LIMITED_BUILDING)*/ {
                    this.deckArea.setOrientation(/*limitedBuildingOrientation = */true);
                }
                this.cardSelector.setVisible(false);
                this.btnExit.setVisible(false);
                this.btnImport.setVisible(false);
                this.btnGenDeck.setVisible(false);
                if (!SessionHandler.isTestMode()) {
                    this.btnLoad.setVisible(false);
                }
                this.deckArea.showSideboard(true);
                countdown.stop();
                this.timeout = time;
                setTimeout(timeout);
                if (timeout != 0) {
                    countdown.start();
                    if (updateDeckTask == null || updateDeckTask.isDone()) {
                        updateDeckTask = new UpdateDeckTask(SessionHandler.getSession(), tableId, deck);
                        updateDeckTask.execute();
                    }
                }
                break;
            case FREE_BUILDING:
                this.deckArea.setOrientation(/*limitedBuildingOrientation = */false);
                this.btnSubmit.setVisible(false);
                this.btnSubmitTimer.setVisible(false);
                this.btnAddLand.setVisible(true);
                this.cardSelector.setVisible(true);
                this.cardSelector.loadCards(this.bigCard);
                //this.cardTableSelector.loadCards(this.bigCard);
                this.btnExit.setVisible(true);
                this.btnImport.setVisible(true);
                this.btnGenDeck.setVisible(true);
                if (!SessionHandler.isTestMode()) {
                    this.btnLoad.setVisible(true);
                }
                this.deckArea.showSideboard(true);
                this.txtTimeRemaining.setVisible(false);
                break;
        }
        init();
        this.deckArea.setDeckEditorMode(mode);
    }

    private Card retrieveTemporaryCard(SimpleCardView cardView) {
        Card card = temporaryCards.get(cardView.getId());
        if (card == null) {
            // Need to make a new card
            Logger.getLogger(DeckEditorPanel.class).info("Retrieve " + cardView.getCardNumber() + " Failed");
            card = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber()).getCard();
        } else {
            // Only need a temporary card once
            temporaryCards.remove(cardView.getId());
        }
        return card;
    }

    private void storeTemporaryCard(Card card) {
        temporaryCards.put(card.getId(), card);
    }

    private void init() {
        //this.cardSelector.setVisible(true);
        this.jPanel1.setVisible(true);
        for (ICardGrid component : this.cardSelector.getCardGridComponents()) {
            component.clearCardEventListeners();
            component.addCardEventListener(
                    (Listener<Event>) event -> {
                        switch (event.getEventName()) {
                            case "double-click":
                                moveSelectorCardToDeck(event);
                                break;
                            case "alt-double-click":
                                if (mode == DeckEditorMode.FREE_BUILDING) {
                                    moveSelectorCardToSideboard(event);
                                } else {
                                    // because in match mode selector is used as sideboard the card goes to deck also for shift click
                                    moveSelectorCardToDeck(event);
                                }
                                break;
                            case "remove-main":
                                DeckEditorPanel.this.deckArea.getDeckList().removeSelection();
                                break;
                            case "remove-sideboard":
                                DeckEditorPanel.this.deckArea.getSideboardList().removeSelection();
                                break;
                        }
                        refreshDeck();
                    });
        }
        this.deckArea.clearDeckEventListeners();
        this.deckArea.addDeckEventListener(
                (Listener<Event>) event -> {
                    if (mode == DeckEditorMode.FREE_BUILDING) {
                        switch (event.getEventName()) {
                            case "double-click": {
                                SimpleCardView cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getCards()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getCards().remove(card);
                                        break;
                                    }
                                }
                                hidePopup();
                                refreshDeck();
                                break;
                            }
                            case "alt-double-click": {
                                SimpleCardView cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getCards()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getCards().remove(card);
                                        deck.getSideboard().add(card);
                                        break;
                                    }
                                }
                                hidePopup();
                                refreshDeck();
                                break;
                            }
                            case "set-number": {
                                setCardNumberToCardsList(event, deck.getCards());
                                break;
                            }
                            case "remove-specific-card": {
                                SimpleCardView cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getCards()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getCards().remove(card);
                                        storeTemporaryCard(card);
                                        break;
                                    }
                                }
                                break;
                            }
                            case "add-specific-card": {
                                SimpleCardView cardView = (CardView) event.getSource();
                                deck.getCards().add(retrieveTemporaryCard(cardView));
                                break;
                            }
                        }
                    } else {
                        // constructing phase or sideboarding during match -> card goes always to sideboard
                        switch (event.getEventName()) {
                            case "double-click":
                            case "alt-double-click": {
                                SimpleCardView cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getCards()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getCards().remove(card);
                                        deck.getSideboard().add(card);
                                        cardSelector.loadSideboard(new ArrayList<>(deck.getSideboard()), this.bigCard);
                                        break;
                                    }
                                }
                                hidePopup();
                                refreshDeck();
                                break;
                            }
                            case "remove-specific-card": {
                                SimpleCardView cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getCards()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getCards().remove(card);
                                        storeTemporaryCard(card);
                                        break;
                                    }
                                }
                                break;
                            }
                            case "add-specific-card": {
                                SimpleCardView cardView = (CardView) event.getSource();
                                deck.getCards().add(retrieveTemporaryCard(cardView));
                                break;
                            }
                        }
                    }
                });
        this.deckArea.clearSideboardEventListeners();
        this.deckArea.addSideboardEventListener(
                (Listener<Event>) event -> {
                    if (mode == DeckEditorMode.FREE_BUILDING) {
                        // normal edit mode
                        switch (event.getEventName()) {
                            case "double-click":
                                // remove card from sideboard (don't add it to deck)
                                SimpleCardView cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getSideboard()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getSideboard().remove(card);
                                        break;
                                    }
                                }
                                hidePopup();
                                refreshDeck();
                                break;
                            case "alt-double-click":
                                // remove card from sideboard
                                cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getSideboard()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getSideboard().remove(card);
                                        deck.getCards().add(card);
                                        break;
                                    }
                                }
                                hidePopup();
                                refreshDeck();
                                break;
                            case "set-number": {
                                setCardNumberToCardsList(event, deck.getSideboard());
                                break;
                            }
                            case "remove-specific-card": {
                                cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getSideboard()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getSideboard().remove(card);
                                        storeTemporaryCard(card);
                                        break;
                                    }
                                }
                                break;
                            }
                            case "add-specific-card": {
                                cardView = (CardView) event.getSource();
                                deck.getSideboard().add(retrieveTemporaryCard(cardView));
                                break;
                            }
                        }
                    } else {
                        // construct phase or sideboarding during match
                        switch (event.getEventName()) {
                            case "remove-specific-card": {
                                SimpleCardView cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getSideboard()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getSideboard().remove(card);
                                        storeTemporaryCard(card);
                                        break;
                                    }
                                }
                                break;
                            }
                            case "add-specific-card": {
                                SimpleCardView cardView = (CardView) event.getSource();
                                deck.getSideboard().add(retrieveTemporaryCard(cardView));
                                break;
                            }
                            case "double-click":
                            case "alt-double-click":
                                SimpleCardView cardView = (SimpleCardView) event.getSource();
                                for (Card card : deck.getSideboard()) {
                                    if (card.getId().equals(cardView.getId())) {
                                        deck.getSideboard().remove(card);
                                        deck.getCards().add(card);
                                        break;
                                    }
                                }
                                hidePopup();
                                refreshDeck();
                                break;
                        }
                    }
                });
        refreshDeck();

        this.setVisible(true);
        this.repaint();
    }

    private void setCardNumberToCardsList(Event event, Set<Card> cards) {
        CardView cardView = (CardView) event.getSource();
        int numberToSet = event.getNumber();
        int cardsFound = 0;
        List<Card> toDelete = new ArrayList<>();
        for (Card card : cards) {
            if (card.getName().equals(cardView.getName())
                    && Objects.equals(card.getCardNumber(), cardView.getCardNumber())
                    && card.getExpansionSetCode().equals(cardView.getExpansionSetCode())) {
                cardsFound++;
                if (cardsFound > numberToSet) {
                    toDelete.add(card);

                }
            }
        }
        if (toDelete.isEmpty()) {
            // add cards
            CardInfo cardInfo = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber());
            for (int i = cardsFound; i < numberToSet; i++) {
                cards.add(cardInfo.getMockCard());
            }
        } else {
            // remove cards
            for (Card card : toDelete) {
                cards.remove(card);
            }
        }
        hidePopup();
        refreshDeck();
    }

    private void moveSelectorCardToDeck(Event event) {
        SimpleCardView cardView = (SimpleCardView) event.getSource();
        CardInfo cardInfo = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber());
        Card card = null;
        if (mode == DeckEditorMode.SIDEBOARDING || mode == DeckEditorMode.LIMITED_BUILDING) {
            for (Object o : deck.getSideboard()) {
                card = (Card) o;
                if (card.getId().equals(cardView.getId())) {
                    break;
                }
            }
        } else {
            card = cardInfo != null ? cardInfo.getMockCard() : null;
        }
        if (card != null) {
            deck.getCards().add(card);
            if (mode == DeckEditorMode.SIDEBOARDING || mode == DeckEditorMode.LIMITED_BUILDING) {
                deck.getSideboard().remove(card);
                cardSelector.removeCard(card.getId());
                cardSelector.setCardCount(deck.getSideboard().size());
                cardSelector.refresh();
            }
            if (cardInfoPane instanceof CardInfoPane) {
                ((CardInfoPane) cardInfoPane).setCard(new CardView(card), null);
            }
            hidePopup();
        }
    }

    private void moveSelectorCardToSideboard(Event event) {
        SimpleCardView cardView = (SimpleCardView) event.getSource();
        CardInfo cardInfo = CardRepository.instance.findCard(cardView.getExpansionSetCode(), cardView.getCardNumber());
        Card card = cardInfo != null ? cardInfo.getMockCard() : null;
        if (card != null) {
            deck.getSideboard().add(card);
        }
        if (cardInfoPane instanceof CardInfoPane) {
            ((CardInfoPane) cardInfoPane).setCard(new CardView(card), null);
        }
        hidePopup();
    }

    private void hidePopup() {
        Plugins.instance.getActionCallback().mouseExited(null, null);
    }

    public void removeDeckEditor() {
        hidePopup();
        this.cleanUp();

        Component c = this.getParent();
        while (c != null && !(c instanceof DeckEditorPane)) {
            c = c.getParent();
        }
        if (c != null) {
            ((DeckEditorPane) c).removeFrame();
        }
    }

    public DeckEditorMode getDeckEditorMode() {
        return mode;
    }

    private BigCard getBigCard() {
        return this.bigCard;
    }

    private void refreshDeck() {
        refreshDeck(false);
    }

    private void refreshDeck(boolean useLayout) {
        try {
            setCursor(new Cursor(Cursor.WAIT_CURSOR));
            this.txtDeckName.setText(deck.getName());
            deckArea.loadDeck(deck, useLayout, bigCard);
        } finally {
            setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
        }
    }

    private void setTimeout(int s) {
        int minute = s / 60;
        int second = s - (minute * 60);
        String text;
        if (minute < 10) {
            text = '0' + Integer.toString(minute) + ':';
        } else {
            text = Integer.toString(minute) + ':';
        }
        if (second < 10) {
            text = text + '0' + Integer.toString(second);
        } else {
            text = text + Integer.toString(second);
        }
        this.txtTimeRemaining.setText(text);
        if (s == 60) {
            AudioManager.playOnCountdown1();
        }
        if (timeToSubmit > 0) {
            timeToSubmit--;
            btnSubmitTimer.setText("Submit (" + timeToSubmit + ')');
            btnSubmitTimer.setToolTipText("Submit your deck in " + timeToSubmit + " seconds!");
        }
    }

    private void initComponents() {

        jSplitPane1 = new javax.swing.JSplitPane();
        cardSelector = new mage.client.deckeditor.CardSelector();
        deckArea = new mage.client.deckeditor.DeckArea();
        jPanel1 = new javax.swing.JPanel();
        bigCard = new mage.client.cards.BigCard();
        txtDeckName = new javax.swing.JTextField();
        lblDeckName = new javax.swing.JLabel();
        btnSave = new javax.swing.JButton();
        btnLoad = new javax.swing.JButton();
        btnNew = new javax.swing.JButton();
        btnExit = new javax.swing.JButton();
        btnImport = new javax.swing.JButton();
        btnSubmit = new javax.swing.JButton();
        btnSubmitTimer = new javax.swing.JButton();
        btnAddLand = new javax.swing.JButton();
        btnGenDeck = new javax.swing.JButton();
        txtTimeRemaining = new javax.swing.JTextField();

        jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
        jSplitPane1.setResizeWeight(0.5);
        jSplitPane1.setTopComponent(cardSelector);
        jSplitPane1.setBottomComponent(deckArea);

        bigCard.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));

        cardInfoPane = Plugins.instance.getCardInfoPane();
        if (cardInfoPane != null && System.getProperty("testCardInfo") != null) {
            cardInfoPane.setPreferredSize(new Dimension(170, 150));
            cardInfoPane.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(200, 0, 0)));
            isShowCardInfo = true;
        } else {
            cardInfoPane = new JLabel();
            cardInfoPane.setVisible(false);
        }

        lblDeckName.setLabelFor(txtDeckName);
        lblDeckName.setText("Deck Name:");

        btnSave.setText("Save");
        btnSave.addActionListener(evt -> btnSaveActionPerformed(evt));

        btnLoad.setText("Load");
        btnLoad.addActionListener(evt -> btnLoadActionPerformed(evt));

        btnNew.setText("New");
        btnNew.addActionListener(evt -> btnNewActionPerformed(evt));

        btnExit.setText("Exit");
        btnExit.addActionListener(evt -> btnExitActionPerformed(evt));

        btnImport.setText("Import");
        btnImport.setName("btnImport"); // NOI18N
        btnImport.addActionListener(evt -> {
            Object[] options = {"File", "Clipboard", "Append from Clipboard"};

            int n = JOptionPane.showOptionDialog(MageFrame.getDesktop(),
                    "Where would you like to import from?",
                    "Deck import",
                    JOptionPane.YES_NO_CANCEL_OPTION,
                    JOptionPane.QUESTION_MESSAGE,
                    null,
                    options,
                    options[0]
            );

            logger.info(n);

            switch (n) {
                case 0:
                    btnImportActionPerformed(evt);
                    break;
                case 1:
                    btnImportFromClipboardActionPerformed(evt);
                case 2:
                    btnImportFromClipboardActionWAppendPerformed(evt);
            }
        });

        btnSubmit.setText("Submit");
        btnSubmitTimer.setToolTipText("Submit your deck now!");
        btnSubmit.setName("btnSubmit"); // NOI18N
        btnSubmit.addActionListener(evt -> btnSubmitActionPerformed(evt));

        btnSubmitTimer.setText("Submit (60s)");
        btnSubmitTimer.setToolTipText("Submit your deck in one minute!");
        btnSubmitTimer.setName("btnSubmitTimer");
        btnSubmitTimer.addActionListener(evt -> btnSubmitTimerActionPerformed(evt));

        btnAddLand.setText("Add Land");
        btnAddLand.setName("btnAddLand"); // NOI18N
        btnAddLand.addActionListener(evt -> btnAddLandActionPerformed(evt));

        btnGenDeck.setText("Generate");
        btnGenDeck.setName("btnGenDeck");
        btnGenDeck.addActionListener(evt -> btnGenDeckActionPerformed(evt));

        txtTimeRemaining.setEditable(false);
        txtTimeRemaining.setForeground(java.awt.Color.red);
        txtTimeRemaining.setHorizontalAlignment(javax.swing.JTextField.CENTER);
        txtTimeRemaining.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED));

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
                jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(jPanel1Layout.createSequentialGroup()
                                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                                /*.addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel1Layout.createSequentialGroup()
                                 .addContainerGap()
                                 .addComponent(jLayeredPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 256, Short.MAX_VALUE))*/
                                                .addGroup(jPanel1Layout.createSequentialGroup()
                                                        .addGap(6, 6, 6)
                                                        .addComponent(lblDeckName)
                                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                        .addComponent(txtDeckName, javax.swing.GroupLayout.DEFAULT_SIZE, 189, Short.MAX_VALUE))
                                                .addComponent(cardInfoPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                                .addComponent(bigCard, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                                .addGroup(jPanel1Layout.createSequentialGroup()
                                                        .addContainerGap()
                                                        .addComponent(btnSave)
                                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                        .addComponent(btnLoad)
                                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                        .addComponent(btnNew)
                                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                                        .addComponent(btnExit))
                                                .addGroup(jPanel1Layout.createSequentialGroup()
                                                        .addContainerGap()
                                                        .addComponent(btnImport)
                                                        .addContainerGap()
                                                        .addComponent(btnGenDeck)
                                                        .addContainerGap()
                                                        .addComponent(btnAddLand)
                                                        .addContainerGap()
                                                        .addComponent(btnSubmit)
                                                        .addContainerGap()
                                                        .addComponent(btnSubmitTimer))
                                                .addGroup(jPanel1Layout.createSequentialGroup()
                                                        .addContainerGap()
                                                        .addComponent(txtTimeRemaining))
                                )
                                .addContainerGap()));
        jPanel1Layout.setVerticalGroup(
                jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(jPanel1Layout.createSequentialGroup()
                                .addContainerGap()
                                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(txtDeckName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                        .addComponent(lblDeckName))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(btnSave)
                                        .addComponent(btnLoad)
                                        .addComponent(btnNew)
                                        .addComponent(btnExit))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(btnImport)
                                        .addComponent(btnGenDeck)
                                        .addComponent(btnAddLand)
                                        .addComponent(btnSubmit)
                                        .addComponent(btnSubmitTimer))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                        .addComponent(txtTimeRemaining))
                                //.addComponent(jLayeredPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, isShowCardInfo ? 30 : 159, Short.MAX_VALUE)
                                .addComponent(cardInfoPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 104, Short.MAX_VALUE)
                                .addComponent(bigCard, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)));

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(layout.createSequentialGroup()
                                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addGap(0, 0, 0)
                                .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 604, Short.MAX_VALUE)));
        layout.setVerticalGroup(
                layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(jSplitPane1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 615, Short.MAX_VALUE));
    }

    /**
     * @param evt ActionEvent
     */
    private void btnImportFromClipboardActionPerformed(ActionEvent evt) {
        final DeckImportFromClipboardDialog dialog = new DeckImportFromClipboardDialog();
        dialog.pack();
        dialog.setVisible(true);

        dialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                try {
                    deck = Deck.load(DeckImporterUtil.importDeck(dialog.getTmpPath()), true, true);
                    refreshDeck();
                } catch (GameException e1) {
                    JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE);
                }
            }
        });
    }

    /**
     * @param evt ActionEvent
     */
    private void btnImportFromClipboardActionWAppendPerformed(ActionEvent evt) {
        final DeckImportFromClipboardDialog dialog = new DeckImportFromClipboardDialog();
        dialog.pack();
        dialog.setVisible(true);

        dialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                Deck deckToAppend = null;
                try {
                    deckToAppend = Deck.load(DeckImporterUtil.importDeck(dialog.getTmpPath()), true, true);
                    if (deckToAppend != null) {
                        deck = Deck.append(deckToAppend, deck);
                        refreshDeck();
                    }
                } catch (GameException e1) {
                    JOptionPane.showMessageDialog(MageFrame.getDesktop(), e1.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE);
                }

            }
        });
    }


    private void btnLoadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLoadActionPerformed
        //fcSelectDeck.setCurrentDirectory(new File());
        String lastFolder = MageFrame.getPreferences().get("lastDeckFolder", "");
        if (!lastFolder.isEmpty()) {
            fcSelectDeck.setCurrentDirectory(new File(lastFolder));
        }
        int ret = fcSelectDeck.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION) {
            File file = fcSelectDeck.getSelectedFile();
            {
                /**
                 * Work around a JFileChooser bug on Windows 7-10 with JRT 7+
                 * In the case where the user selects the exact same file as was previously
                 * selected without touching anything else in the dialog, getSelectedFile()
                 * will erroneously return null due to some combination of our settings.
                 *
                 * We manually sub in the last selected file in this case.
                 */
                if (file == null) {
                    if (!lastFolder.isEmpty()) {
                        file = new File(lastFolder);
                    }
                }
            }
            try {
                setCursor(new Cursor(Cursor.WAIT_CURSOR));
                deck = Deck.load(DeckImporterUtil.importDeck(file.getPath()), true, true);
            } catch (GameException ex) {
                JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Error loading deck", JOptionPane.ERROR_MESSAGE);
            } finally {
                setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            }
            refreshDeck(true);
            try {
                if (file != null) {
                    MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath());
                }
            } catch (IOException ex) {
            }
        }
        fcSelectDeck.setSelectedFile(null);
    }//GEN-LAST:event_btnLoadActionPerformed

    private void btnSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSaveActionPerformed
        String lastFolder = MageFrame.getPreferences().get("lastDeckFolder", "");
        if (!lastFolder.isEmpty()) {
            fcSelectDeck.setCurrentDirectory(new File(lastFolder));
        }
        deck.setName(this.txtDeckName.getText());
        int ret = fcSelectDeck.showSaveDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION) {
            File file = fcSelectDeck.getSelectedFile();
            {
                /**
                 * Work around a JFileChooser bug on Windows 7-10 with JRT 7+
                 * In the case where the user selects the exact same file as was previously
                 * selected without touching anything else in the dialog, getSelectedFile()
                 * will erroneously return null due to some combination of our settings.
                 *
                 * We manually sub in the last selected file in this case.
                 */
                if (file == null) {
                    if (!lastFolder.isEmpty()) {
                        file = new File(lastFolder);
                    }
                }
            }
            try {
                String fileName = file.getPath();
                if (!fileName.endsWith(".dck")) {
                    fileName += ".dck";
                }
                setCursor(new Cursor(Cursor.WAIT_CURSOR));
                DeckCardLists cardLists = deck.getDeckCardLists();
                cardLists.setCardLayout(deckArea.getCardLayout());
                cardLists.setSideboardLayout(deckArea.getSideboardLayout());
                Sets.saveDeck(fileName, cardLists);
            } catch (FileNotFoundException ex) {
                JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage() + "\nTry ensuring that the selected directory is writable.", "Error saving deck", JOptionPane.ERROR_MESSAGE);
            } finally {
                setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            }
            try {
                MageFrame.getPreferences().put("lastDeckFolder", file.getCanonicalPath());
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }//GEN-LAST:event_btnSaveActionPerformed

    private void btnNewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnNewActionPerformed
        if (mode == DeckEditorMode.SIDEBOARDING || mode == DeckEditorMode.LIMITED_BUILDING) {
            for (Card card : deck.getCards()) {
                deck.getSideboard().add(card);
            }
            deck.getCards().clear();
            cardSelector.loadSideboard(new ArrayList<>(deck.getSideboard()), this.bigCard);
        } else {
            deck = new Deck();
        }
        refreshDeck();
    }//GEN-LAST:event_btnNewActionPerformed

    private void btnExitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExitActionPerformed
        removeDeckEditor();
    }//GEN-LAST:event_btnExitActionPerformed

    private void btnImportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnImportActionPerformed
        String lastFolder = MageFrame.getPreferences().get("lastImportFolder", "");
        if (!lastFolder.isEmpty()) {
            fcImportDeck.setCurrentDirectory(new File(lastFolder));
        }
        int ret = fcImportDeck.showOpenDialog(this);
        if (ret == JFileChooser.APPROVE_OPTION) {
            File file = fcImportDeck.getSelectedFile();
            try {
                setCursor(new Cursor(Cursor.WAIT_CURSOR));
                DeckImporter importer = DeckImporterUtil.getDeckImporter(file.getPath());
                if (importer != null) {
                    deck = Deck.load(importer.importDeck(file.getPath()));
                    String errors = importer.getErrors();
                    if (!errors.isEmpty()) {
                        JOptionPane.showMessageDialog(MageFrame.getDesktop(), errors, "Error importing deck", JOptionPane.ERROR_MESSAGE);
                    }
                } else {
                    JOptionPane.showMessageDialog(MageFrame.getDesktop(), "Unknown deck format", "Error importing deck", JOptionPane.ERROR_MESSAGE);
                }
            } catch (Exception ex) {
                logger.fatal(ex);
            } finally {
                setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            }
            refreshDeck();
            try {
                if (file != null) {
                    MageFrame.getPreferences().put("lastImportFolder", file.getCanonicalPath());
                }
            } catch (IOException ex) {
            }
        }
        fcImportDeck.setSelectedFile(null);
    }//GEN-LAST:event_btnImportActionPerformed

    private void btnSubmitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSubmitActionPerformed
        if (updateDeckTask != null) {
            updateDeckTask.cancel(true);
        }

        if (SessionHandler.submitDeck(tableId, deck.getDeckCardLists())) {
            removeDeckEditor();
        }
    }//GEN-LAST:event_btnSubmitActionPerformed

    private void btnSubmitTimerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSubmitTimerActionPerformed

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
        timeToSubmit = 60;
        this.btnSubmitTimer.setEnabled(false);

        ScheduledFuture scheduledFuture = scheduledExecutorService.schedule((Callable) () -> {
            if (updateDeckTask != null) {
                updateDeckTask.cancel(true);
            }

            if (SessionHandler.submitDeck(tableId, deck.getDeckCardLists())) {
                removeDeckEditor();
            }
            return null;
        }, 60, TimeUnit.SECONDS);
    }//GEN-LAST:event_btnSubmitTimerActionPerformed

    private void btnAddLandActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAddLandActionPerformed
        AddLandDialog addLand = new AddLandDialog();
        addLand.showDialog(deck, mode);
        refreshDeck();
    }//GEN-LAST:event_btnAddLandActionPerformed

    private void btnGenDeckActionPerformed(ActionEvent evt) {
        try {
            setCursor(new Cursor(Cursor.WAIT_CURSOR));
            String path = DeckGenerator.generateDeck();
            deck = Deck.load(DeckImporterUtil.importDeck(path), true, true);
        } catch (GameException ex) {
            JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Error loading generated deck", JOptionPane.ERROR_MESSAGE);
        } catch (DeckGeneratorException ex) {
            JOptionPane.showMessageDialog(MageFrame.getDesktop(), ex.getMessage(), "Generator error", JOptionPane.ERROR_MESSAGE);
        } finally {
            setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
        }
        refreshDeck();
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private mage.client.cards.BigCard bigCard;
    private javax.swing.JButton btnExit;
    private javax.swing.JButton btnImport;
    private javax.swing.JButton btnLoad;
    private javax.swing.JButton btnNew;
    private javax.swing.JButton btnSave;
    private mage.client.deckeditor.CardSelector cardSelector;
    private mage.client.deckeditor.DeckArea deckArea;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JSplitPane jSplitPane1;
    private javax.swing.JLabel lblDeckName;
    private javax.swing.JTextField txtDeckName;
    private javax.swing.JButton btnSubmit;
    private javax.swing.JButton btnSubmitTimer;
    private javax.swing.JButton btnAddLand;
    private javax.swing.JButton btnGenDeck;
    private JComponent cardInfoPane;
    private javax.swing.JTextField txtTimeRemaining;
    // End of variables declaration//GEN-END:variables
}

class DeckFilter extends FileFilter {

    @Override
    public boolean accept(File f) {
        if (f.isDirectory()) {
            return true;
        }

        String ext = null;
        String s = f.getName();
        int i = s.lastIndexOf('.');

        if (i > 0 && i < s.length() - 1) {
            ext = s.substring(i + 1).toLowerCase();
        }
        return (ext == null) ? false : ext.equals("dck");
    }

    @Override
    public String getDescription() {
        return "Deck Files";
    }
}

class ImportFilter extends FileFilter {

    @Override
    public boolean accept(File f) {
        if (f.isDirectory()) {
            return true;
        }

        String ext = null;
        String s = f.getName();
        int i = s.lastIndexOf('.');

        if (i > 0 && i < s.length() - 1) {
            ext = s.substring(i + 1).toLowerCase();
        }
        if (ext != null) {
            if (ext.toLowerCase().equals("dec") || ext.toLowerCase().equals("mwdeck") || ext.toLowerCase().equals("txt") || ext.toLowerCase().equals("dek")) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String getDescription() {
        return "*.dec | *.mwDeck | *.txt | *.dek";
    }
}

class UpdateDeckTask extends SwingWorker<Void, Void> {

    private static final Logger logger = Logger.getLogger(UpdateDeckTask.class);
    private final Session session;
    private final UUID tableId;
    private final Deck deck;

    UpdateDeckTask(Session session, UUID tableId, Deck deck) {
        this.session = session;
        this.tableId = tableId;
        this.deck = deck;
    }

    @Override
    protected Void doInBackground() throws Exception {
        while (!isCancelled()) {
            SessionHandler.updateDeck(tableId, deck.getDeckCardLists());
            Thread.sleep(5000);
        }
        return null;
    }

    @Override
    protected void done() {
        try {
            get();
        } catch (InterruptedException | ExecutionException ex) {
            logger.fatal("Update Matches Task error", ex);
        } catch (CancellationException ex) {
        }
    }
}
