package mage.client.deck.generator;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import mage.Mana;
import mage.cards.Card;
import mage.cards.Sets;
import mage.cards.decks.Deck;
import mage.cards.repository.CardCriteria;
import mage.cards.repository.CardInfo;
import mage.cards.repository.CardRepository;
import mage.cards.repository.ExpansionInfo;
import mage.cards.repository.ExpansionRepository;
import mage.client.MageFrame;
import mage.client.dialog.PreferencesDialog;
import mage.client.util.gui.ColorsChooser;
import mage.client.util.sets.ConstructedFormats;
import mage.constants.CardType;
import mage.constants.ColoredManaSymbol;
import mage.constants.Rarity;
import mage.interfaces.rate.RateCallback;
import mage.utils.DeckBuilder;


/**
 * Generates random card pool and builds a deck.
 *
 * @author nantuko
 */
public class DeckGenerator {

    private static JDialog dlg;
    private static String selectedColors;
    private static JComboBox cbSets;
    private static JComboBox cbDeckSize;

    private static final int SPELL_CARD_POOL_SIZE = 180;

    private static final int DECK_LANDS = 17;
    private static final int MAX_NON_BASIC_SOURCE = DECK_LANDS / 2;

    private static final int MAX_TRIES = 4096;

    private static Deck deck = new Deck();
    private static final int ADDITIONAL_CARDS_FOR_3_COLOR_DECKS = 20;

    /**
     * Opens color chooser dialog. Generates deck.
     * Saves generated deck and use it as selected deck to play.
     *
     * @return
     */
    public static String generateDeck() {
        JPanel p0 = new JPanel();
        p0.setLayout(new BoxLayout(p0, BoxLayout.Y_AXIS));

        JLabel text = new JLabel("Choose color for your deck: ");
        text.setAlignmentX(Component.CENTER_ALIGNMENT);
        p0.add(text);

        p0.add(Box.createVerticalStrut(5));
        String chosen = MageFrame.getPreferences().get("genDeckColor", "u");
        final ColorsChooser colorsChooser = new ColorsChooser(chosen);
        p0.add(colorsChooser);

        p0.add(Box.createVerticalStrut(5));
        JLabel text2 = new JLabel("(X - random color)");
        text2.setAlignmentX(Component.CENTER_ALIGNMENT);
        p0.add(text2);

        p0.add(Box.createVerticalStrut(5));
        JPanel jPanel = new JPanel();
        JLabel text3 = new JLabel("Choose sets:");
        cbSets = new JComboBox(ConstructedFormats.getTypes());
        cbSets.setSelectedIndex(0);
        cbSets.setPreferredSize(new Dimension(300, 25));
        cbSets.setMaximumSize(new Dimension(300, 25));
        cbSets.setAlignmentX(Component.LEFT_ALIGNMENT);
        jPanel.add(text3);
        jPanel.add(cbSets);
        p0.add(jPanel);

        String prefSet = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SET, null);
        if (prefSet != null) {
            cbSets.setSelectedItem(prefSet);
        }

        p0.add(Box.createVerticalStrut(5));
        JPanel jPanel2 = new JPanel();
        JLabel textDeckSize = new JLabel("Deck size:");
        cbDeckSize = new JComboBox(new String[]{"40","60"});
        cbDeckSize.setSelectedIndex(0);
        cbDeckSize.setPreferredSize(new Dimension(300, 25));
        cbDeckSize.setMaximumSize(new Dimension(300, 25));
        cbDeckSize.setAlignmentX(Component.LEFT_ALIGNMENT);
        jPanel2.add(textDeckSize);
        jPanel2.add(cbDeckSize);
        p0.add(jPanel2);

        String prefSize = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, "60");
        if (prefSet != null) {
            cbDeckSize.setSelectedItem(prefSize);
        }

        final JButton btnGenerate = new JButton("Ok");
        btnGenerate.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                btnGenerate.setEnabled(false);
                colorsChooser.setEnabled(false);
                selectedColors = (String) colorsChooser.getSelectedItem();
                dlg.setVisible(false);
                MageFrame.getPreferences().put("genDeckColor", selectedColors);
            }
        });
        final JButton btnCancel = new JButton("Cancel");
        btnCancel.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                dlg.setVisible(false);
                selectedColors = null;
            }
        });
        Object[] options = {btnGenerate, btnCancel};
        JOptionPane optionPane = new JOptionPane(p0, JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[1]);
        dlg = optionPane.createDialog("Generating deck");
        dlg.setVisible(true);
        dlg.dispose();

        if (selectedColors != null) {
            // save values to prefs
             PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_DECK_SIZE, cbDeckSize.getSelectedItem().toString());
             PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_DECK_GENERATOR_SET, cbSets.getSelectedItem().toString());

            // build deck
            buildDeck();
            try {
                File tmp = File.createTempFile("tempDeck" + UUID.randomUUID().toString(), ".dck");
                tmp.createNewFile();
                deck.setName("Generated-Deck-" + UUID.randomUUID());
                Sets.saveDeck(tmp.getAbsolutePath(), deck.getDeckCardLists());
                deck = null;
                //JOptionPane.showMessageDialog(null, "Deck has been generated.");
                DeckGenerator.cleanUp(btnGenerate, btnCancel);
                return tmp.getAbsolutePath();
            } catch (Exception e) {
                JOptionPane.showMessageDialog(null, "Couldn't generate deck. Try again.");
            }
        }
        DeckGenerator.cleanUp(btnGenerate, btnCancel);
        return selectedColors;
    }

    private static void cleanUp(JButton btnGenerate, JButton btnCancel) {
        for (ActionListener al: btnGenerate.getActionListeners()) {
            btnGenerate.removeActionListener(al);
        }
        for (ActionListener al: btnCancel.getActionListeners()) {
            btnCancel.removeActionListener(al);
        }        
        deck = null;
    }
    
    /**
     * Generates card pool
     */
    protected static void buildDeck() {
        List<ColoredManaSymbol> allowedColors = new ArrayList<ColoredManaSymbol>();
        selectedColors = selectedColors != null ? selectedColors.toUpperCase() : getRandomColors("X");

        String format = (String) cbSets.getSelectedItem();
        List<String> setsToUse = ConstructedFormats.getSetsByFormat(format);
        if (setsToUse.isEmpty()) {
            // use all
            setsToUse = ExpansionRepository.instance.getSetCodes();
        }

        int deckSize = Integer.parseInt(cbDeckSize.getSelectedItem().toString());
        if (deckSize < 40) {
            deckSize = 40;
        }
        if (selectedColors.contains("X")) {
            selectedColors = getRandomColors(selectedColors);
        }

        for (int i = 0; i < selectedColors.length(); i++) {
            char c = selectedColors.charAt(i);
            allowedColors.add(ColoredManaSymbol.lookup(c));
        }

        int cardPoolSize = SPELL_CARD_POOL_SIZE;
        if (selectedColors.length() > 2) {
            cardPoolSize += ADDITIONAL_CARDS_FOR_3_COLOR_DECKS;
        }
        List<Card> spellCardPool = generateSpellCardPool(cardPoolSize, allowedColors, setsToUse);
        List<Card> landCardPool = generateNonBasicLandCardPool(MAX_NON_BASIC_SOURCE, allowedColors, setsToUse);

        // System.out.println("deck generator card pool: spells=" + spellCardPool.size() + ", lands=" + landCardPool.size());

        final List<String> setsToUseFinal = setsToUse;

        deck = DeckBuilder.buildDeck(spellCardPool, allowedColors, setsToUseFinal, landCardPool, deckSize, new RateCallback() {
            @Override
            public int rateCard(Card card) {
                return 6;
            }

            @Override
            public Card getBestBasicLand(ColoredManaSymbol color, List<String> setsToUse) {
                return DeckGenerator.getBestBasicLand(color, setsToUseFinal);
            }
        });
    }

    private static String getRandomColors(String _selectedColors) {
        Random random = new Random();
        List<Character> availableColors = new ArrayList();
        availableColors.add('R');
        availableColors.add('G');
        availableColors.add('B');
        availableColors.add('U');
        availableColors.add('W');

        StringBuilder generatedColors = new StringBuilder();
        int randomColors = 0;
        for (int i = 0; i < _selectedColors.length(); i++) {
            char currentColor = _selectedColors.charAt(i);
            if (currentColor != 'X') {
                generatedColors.append(currentColor);
                availableColors.remove(new Character(currentColor));
            } else {
                randomColors++;
            }
        }

        for (int i = 0; i < randomColors && !availableColors.isEmpty(); i++) {
            int index = random.nextInt(availableColors.size());
            generatedColors.append(availableColors.remove(index));
        }

        return generatedColors.toString();
    }

    /**
     * Generates card pool of cardsCount cards that have manacost of allowed colors.
     *
     * @param cardsCount
     * @param allowedColors
     * @return
     */
    private static List<Card> generateSpellCardPool(int cardsCount, List<ColoredManaSymbol> allowedColors, List<String> setsToUse) {
        List<Card> spellCardPool = new ArrayList<Card>();

        CardCriteria spellCriteria = new CardCriteria();
        spellCriteria.setCodes(setsToUse.toArray(new String[0]));
        spellCriteria.notTypes(CardType.LAND);

        List<CardInfo> cardPool = CardRepository.instance.findCards(spellCriteria);
        int cardPoolCount = cardPool.size();
        Random random = new Random();
        if (cardPoolCount > 0) {
            int tries = 0;
            int count = 0;
            while (count < cardsCount) {
                Card card = cardPool.get(random.nextInt(cardPoolCount)).getMockCard();
                if (cardFitsChosenColors(card, allowedColors)) {
                    spellCardPool.add(card);
                    count++;
                }
                tries++;
                if (tries > MAX_TRIES) { // to avoid infinite loop
                    throw new IllegalStateException("Not enough cards for chosen colors to generate deck: " + selectedColors);
                }
            }
        } else {
            throw new IllegalStateException("Not enough cards to generate deck.");
        }

        return spellCardPool;
    }

    /**
     * Check that card can be played using chosen (allowed) colors.
     *
     * @param card
     * @param allowedColors
     * @return
     */
    private static boolean cardFitsChosenColors(Card card, List<ColoredManaSymbol> allowedColors) {
        for (String symbol : card.getManaCost().getSymbols()) {
            boolean found = false;
            symbol = symbol.replace("{", "").replace("}", "");
            if (isColoredMana(symbol)) {
                for (ColoredManaSymbol allowed : allowedColors) {
                    if (symbol.contains(allowed.toString())) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Generates card pool of land cards that can produce allowed colors.
     *
     * @param landsCount
     * @param allowedColors
     * @return
     */
    private static List<Card> generateNonBasicLandCardPool(int landsCount, List<ColoredManaSymbol> allowedColors, List<String> setsToUse) {
        List<Card> nonBasicLandCardPool = new ArrayList<Card>();

        CardCriteria landCriteria = new CardCriteria();
        landCriteria.setCodes(setsToUse.toArray(new String[0]));
        landCriteria.types(CardType.LAND);
        landCriteria.notSupertypes("Basic");
        List<CardInfo> landCards = CardRepository.instance.findCards(landCriteria);

        int allCount = landCards.size();
        Random random = new Random();
        if (allCount > 0) {
            int tries = 0;
            int count = 0;
            while (count < landsCount) {
                Card card = landCards.get(random.nextInt(allCount)).getMockCard();
                if (cardCardProduceChosenColors(card, allowedColors)) {
                    nonBasicLandCardPool.add(card);
                    count++;
                }
                tries++;
                if (tries > MAX_TRIES) { // to avoid infinite loop
                    // return what have been found
                    return nonBasicLandCardPool;
                }
            }
        }

        return nonBasicLandCardPool;
    }

    /**
     * Checks that chosen card can produce mana of specific color.
     *
     * @param card
     * @param allowedColors
     * @return
     */
    private static boolean cardCardProduceChosenColors(Card card, List<ColoredManaSymbol> allowedColors) {
        int score = 0;
        for (Mana mana : card.getMana()) {
            for (ColoredManaSymbol color : allowedColors) {
                score += mana.getColor(color);
            }
        }
        if (score > 1) {
            return true;
        }
        return false;
    }

    /**
     * Get random basic land that can produce specified color mana.
     * Random here means random set and collector id for the same mana producing land.
     *
     * @param color
     * @return
     */
    private static Card getBestBasicLand(ColoredManaSymbol color, List<String> setsToUse) {
        String cardName = "";
        switch(color) {
            case G:
                cardName = "Forest";
                break;
            case W:
                cardName = "Plains";
                break;
            case R:
                cardName = "Mountain";
                break;
            case B:
                cardName = "Swamp";
                break;
            case U:
                cardName = "Island";
                break;
        }

        List<String> landSets = new LinkedList<String>();

        // decide from which sets basic lands are taken from
        for (String setCode :setsToUse) {
            ExpansionInfo expansionInfo = ExpansionRepository.instance.getSetByCode(setCode);
            if (expansionInfo.hasBasicLands()) {
                landSets.add(expansionInfo.getCode());
            }
        }

        // if sets have no basic land, take land from block
        if (landSets.isEmpty()) {
            for (String setCode :setsToUse) {
                ExpansionInfo expansionInfo = ExpansionRepository.instance.getSetByCode(setCode);
                ExpansionInfo [] blockSets = ExpansionRepository.instance.getSetsFromBlock(expansionInfo.getBlockName());
                for (ExpansionInfo blockSet: blockSets) {
                    if (blockSet.hasBasicLands()) {
                        landSets.add(blockSet.getCode());
                    }
                }
            }
        }
        // if still no set with lands found, take one by random
        if (landSets.isEmpty()) {
            // if sets have no basic lands and also it has no parent or parent has no lands get last set with lands
            // select a set with basic lands by random
            Random generator = new Random();
            List<ExpansionInfo> basicLandSets = ExpansionRepository.instance.getSetsWithBasicLandsByReleaseDate();
            if (basicLandSets.size() > 0) {
                landSets.add(basicLandSets.get(generator.nextInt(basicLandSets.size())).getCode());
            }
        }

        if (landSets.isEmpty()) {
            throw new IllegalArgumentException("No set with basic land was found");
        }

        CardCriteria criteria = new CardCriteria();
        if (!landSets.isEmpty()) {
            criteria.setCodes(landSets.toArray(new String[landSets.size()]));
        }
        criteria.rarities(Rarity.LAND).name(cardName);
        List<CardInfo> cards = CardRepository.instance.findCards(criteria);

        if (cards.isEmpty() && !setsToUse.isEmpty()) {
            cards = CardRepository.instance.findCards(cardName);
        }

        int randomInt = new Random().nextInt(cards.size());
        return cards.get(randomInt).getMockCard();

    }

    protected static boolean isColoredMana(String symbol) {
        return symbol.equals("W") || symbol.equals("G") || symbol.equals("U") || symbol.equals("B") || symbol.equals("R") || symbol.contains("/");
    }
}
