/******************************************************************************
 ** $Id: Absprache.java 1682 2019-01-20 21:24:15Z wmh $
 ** Diese Datei ist Bestandteil der Java-Quelltexte des Würfelspiels JaFuffy.
 ******************************************************************************
 ** Copyright (C) Wolfgang Hauck <wolfgang.hauck@3kelvin.de>
 ******************************************************************************
 ** This program is free software: you can redistribute it and/or modify
 ** it under the terms of the GNU General Public License as published by
 ** the Free Software Foundation, either version 3 of the License, or
 ** (at your option) any later version.
 **
 ** This program is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ** GNU General Public License for more details.
 **
 ** You should have received a copy of the GNU General Public License
 ** along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************
 ** Die aktuellste Version von JaFuffy findet sich im Internet unter
 ** <http://jafuffy.3kelvin.de>.
 **
 ** Kommentare, Fehler oder Erweiterungswünsche bitte per E-Mail senden an
 ** <jafuffy@3kelvin.de>.
 ******************************************************************************/
package jafuffy.bedienung;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;

import jafuffy.Eigenschaften;
import jafuffy.bedienung.organisation.Organisation;
import jafuffy.logik.Beginner;
import jafuffy.logik.Bot;
import jafuffy.logik.Manager;
import jafuffy.logik.Spieler;
import jafuffy.logik.Turnier;
import jafuffy.logik.Variante;
import jafuffy.organisation.Leitung;

/**
 * Dialog zum Spielbeginn, Absprache der Spielbedingungen. Eingabe der Spielernamen, der Spielvariante und der Anzahl
 * der Spiele (optional). Start des Turniers.
 */
@SuppressWarnings("serial")
public class Absprache extends JDialog implements ActionListener, PropertyChangeListener {

    /** Ermöglicht Aktivierung und Deaktivierung eines Bots. */
    private static class Botselektion extends JCheckBox implements ActionListener {

        /** Schriftfarbe, falls Bot inaktiv ist. */
        private static final Color BOT_INAKTIV_FARBE = Color.GRAY;
        /** Schriftfarbe, falls Bot aktiv ist. */
        private static final Color BOT_AKTIV_FARBE = Color.ORANGE.darker();

        /** Benötigt, weil Bot nicht für alle Varianten zur Verfügung steht. */
        private final JComboBox<Variante> variantenauswahl;

        /**
         * Konstruiert das Auswahlfeld, welches den Bot aktivieren oder deaktivieren kann.
         *
         * @param kuenstlich
         *            Gibt an, ob ein Bot anfänglich aktiv ist.
         * @param nominierung
         *            Zugehöriges Nominierungsfeld.
         * @param variantenauswahl
         *            Variantenauswahl, welche die Verfügbarkeit des Bots beeinflusst.
         */
        Botselektion(boolean kuenstlich, Nominierung nominierung, JComboBox<Variante> variantenauswahl) {
            super("Bot");
            this.variantenauswahl = variantenauswahl;
            addActionListener(nominierung);
            setIcon(BOT_RUHT);
            setRolloverIcon(BOT_AKTIV);
            setSelectedIcon(BOT_AKTIV);
            setSelected(kuenstlich);
            setToolTipText("<html>" + "<p>Wenn angewählt, übernimmt <i>JaFuffy</i> die Rolle des Spielers.</p>"
                    + "<p><small>" + "Spielstärke im Menü: Einstellungen&rarr;Botverhalten&rarr;Spielstärke der Bots..."
                    + "</small></p>" + "<p><small>Bot steht bei erweiterten Regeln nicht zur Verfügung.</small></p>"
                    + "</html>");
            addActionListener(this);
            boolean botfaehig = variantenauswahl.getSelectedItem() != Variante.ERWEITERT;
            setEnabled(botfaehig);
            if (botfaehig) {
                if (isSelected()) {
                    setForeground(BOT_AKTIV_FARBE);
                } else {
                    setForeground(BOT_INAKTIV_FARBE);
                }
            } else {
                setSelected(false);
                setForeground(Color.LIGHT_GRAY);
            }
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            boolean botfaehig = variantenauswahl.getSelectedItem() != Variante.ERWEITERT;
            setEnabled(botfaehig);
            if (botfaehig) {
                if (isSelected()) {
                    setForeground(BOT_AKTIV_FARBE);
                } else {
                    setForeground(BOT_INAKTIV_FARBE);
                }
            } else {
                setSelected(false);
                setForeground(Color.LIGHT_GRAY);
            }
        }

    }

    /** Festlegung auf künstliche Intelligenz oder menschlichen Spieler mit Nameneingabe. */
    private static class Nominierung extends JPanel implements ActionListener {

        /** Karte für menschlichen Spieler. */
        private static final String MENSCH = "Mensch";
        /** Karte für künstliche Intelligenz. */
        private static final String BOT = "Bot";
        /** Eingabelängenbeschränkung für Spielernamen. */
        private static final int EINGABELAENGE = SPALTEN * 7 / 4;

        /** Benötigt zur Aktualisierung des Absprachedialogs aufgrund von Namenseingaben. */
        private final Absprache absprache;
        /** Nameneingabe für menschlichen Spieler. */
        private final JTextField spielernameneingabe = new JTextField(SPALTEN);
        /** Name der künstlichen Intelligenz. */
        private final JComboBox<String> botnamenauswahl = new JComboBox<>();

        /** Gibt an, ob die künstliche Intelligenz die Rolle als Spieler übernehmen soll. */
        private boolean kuenstlich;

        /**
         * Konstruiert ein Eingabefeld für die Nameneingabe oder Namenanzeige.
         *
         * @param kuenstlich
         *            Gibt an, ob ein Bot als Spieler als Vorbelegung bereitstehen soll.
         * @param name
         *            Spielername oder Botname.
         * @param botnamen
         *            Eine Liste aller Botnamen, die zusätzlich zur Auswahl stehen.
         * @param absprache
         *            Der Dialog, dessen Aussehen an dieses Objekt angepasst werden muss.
         */
        Nominierung(boolean kuenstlich, String name, List<String> botnamen, Absprache absprache) {
            super(new CardLayout());
            this.absprache = absprache;
            this.kuenstlich = kuenstlich;
            ((AbstractDocument) spielernameneingabe.getDocument()).setDocumentFilter(new DocumentFilter() {
                @Override
                public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)
                        throws BadLocationException {
                    if (gefiltert(fb, offset, 0, string, attr)) {
                        return;
                    }
                    super.insertString(fb, offset, string, attr);
                }

                @Override
                public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr)
                        throws BadLocationException {
                    if (gefiltert(fb, offset, length, string, attr)) {
                        return;
                    }
                    super.replace(fb, offset, length, string, attr);
                }

                /** Filtert auf Eingabelänge, meldet Filterung per Tonsignal und gibt zurück, ob gefiltert wurde. */
                private boolean gefiltert(FilterBypass fb, int offset, int laenge, String string, AttributeSet attr) {
                    boolean gefiltert = fb.getDocument().getLength() - laenge + string.length() > EINGABELAENGE
                            || offset == 0 && Character.isWhitespace(string.charAt(0));
                    if (gefiltert) {
                        Toolkit.getDefaultToolkit().beep();
                    }
                    return gefiltert;
                }
            });

            uebernehmeVorbelegung(name, botnamen);
            erstelleKarten();
            verbindeBeobachter();
            setToolTipText("FooBar");
        }

        /** Bearbeitet die Entscheidung zur künstlichen Intelligenz. */
        @Override
        public void actionPerformed(ActionEvent event) {
            String kommando = event.getActionCommand();
            if (kommando.equals("Bot")) {
                botnamenauswahl.removeAllItems();
                JCheckBox entscheidung = (JCheckBox) event.getSource();
                kuenstlich = entscheidung.isSelected();
                if (kuenstlich) {
                    for (String botname : Bot.BOTNAMEN) {
                        if (absprache.istBotnameFrei(botname)) {
                            botnamenauswahl.addItem(botname);
                        }
                    }
                    CardLayout layout = (CardLayout) getLayout();
                    layout.show(this, BOT);
                } else {
                    CardLayout layout = (CardLayout) getLayout();
                    layout.show(this, MENSCH);
                }
                absprache.aktualisiere();
            } else if (kommando.equals("Var")) {
                @SuppressWarnings("unchecked")
                JComboBox<Variante> variantenauswahl = (JComboBox<Variante>) event.getSource();
                boolean botfaehig = variantenauswahl.getSelectedItem() != Variante.ERWEITERT;
                if (!botfaehig) {
                    kuenstlich = false;
                    CardLayout layout = (CardLayout) getLayout();
                    layout.show(this, MENSCH);
                }
            }
        }

        @Override
        public String toString() {
            return kuenstlich ? (String) botnamenauswahl.getSelectedItem() : spielernameneingabe.getText().trim();
        }

        /** Fügt die Karten für Spieler oder Bot ein an bei gleichzeitiger passender Kartenauswahl. */
        private void erstelleKarten() {
            add(spielernameneingabe, MENSCH);
            add(botnamenauswahl, BOT);
            CardLayout layout = (CardLayout) getLayout();
            layout.show(this, kuenstlich ? BOT : MENSCH);
        }

        /**
         * Übernimmt die Voreinstellungen für die Namensauswahlen aus den abgespeicherten Eigenschaften.
         *
         * @param name
         *            Spielername oder Botname.
         * @param botnamen
         *            Liste von Botnamen, die außer dem Namen zur Verfügung stehen.
         */
        private void uebernehmeVorbelegung(String name, List<String> botnamen) {
            if (kuenstlich) {
                botnamenauswahl.addItem(name);
                for (String botname : botnamen) {
                    botnamenauswahl.addItem(botname);
                }
            } else {
                spielernameneingabe.setText(name);
            }
        }

        /** Installiert die Beobachter für die Eingabe der Spielernamen (inklusive Bot). */
        private void verbindeBeobachter() {
            spielernameneingabe.getDocument().addDocumentListener(new DocumentListener() {

                @Override
                public void changedUpdate(DocumentEvent event) {
                    absprache.aktualisiere();
                }

                @Override
                public void insertUpdate(DocumentEvent event) {
                    absprache.aktualisiere();
                }

                @Override
                public void removeUpdate(DocumentEvent event) {
                    absprache.aktualisiere();
                }
            });
            botnamenauswahl.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent event) {
                    if (event.getStateChange() == ItemEvent.SELECTED) {
                        absprache.entferne(Nominierung.this, event.getItem().toString());
                    } else {
                        absprache.erweitere(Nominierung.this, event.getItem().toString());
                    }
                }
            });
        }

        /**
         * Entfernt einen Botnamen aus der Namensauswahl.
         *
         * @param botname
         *            Der zu entfernende Botname.
         */
        void entferne(String botname) {
            botnamenauswahl.removeItem(botname);
        }

        /**
         * Erweitert die Namenauswahl um einen Botnamen.
         *
         * @param botname
         *            Der hinzuzufügende Name.
         */
        void erweitere(String botname) {
            botnamenauswahl.addItem(botname);
        }

        /** Gibt an, ob künstliche intelligenz Rolle des Spielers übernehmen soll. */
        boolean kuenstlich() {
            return kuenstlich;
        }
    }

    /** Auswahlicon für aktive Bots. */
    private static final ImageIcon BOT_AKTIV = Grafik.icon("Aktivbot.png");
    /** Auswahlicon für ruhenden Bot. */
    private static final ImageIcon BOT_RUHT = Grafik.icon("Ruhebot.png");
    /** Maximale Anzahl der Spiele pro Turnier. */
    private static final int ANZAHL = 20;
    /** Abstand vom Feldrahmen zum Inhalt. */
    private static final int FELDRAHMENABSTAND = 4;
    /** Abstand vom Dialograhmen zum Inhalt. */
    private static final int DIALOGRAHMENABSTAND = 8;
    /** Abstand Feldrahmen zu Buttons. */
    private static final int BUTTONABSTAND = 2 * FELDRAHMENABSTAND;
    /** Abstand der Feinticks (Spielanzahlauswahl). */
    private static final int FEINTICKS = 1;
    /** Abstand der Orientierungsticks (Spielanzahlauswahl). */
    private static final int ORIENTIERUNGSTICKS = 5;
    /** Anzahl der Spalten im Texteingabefeld. */
    private static final int SPALTEN = 8;
    /** Anzahl der Tick-Labels (Spielanzahlauswahl). */
    private static final int TICKLABELS = 5;

    /** Einstellung der Spieleanzahl pro Turnier per Schieberegler. */
    private final JSlider anzahlschieberegler = new JSlider(0, ANZAHL);
    /** Beginnmodusauswahl. */
    private final JComboBox<Beginner> beginnmodusauswahl = new JComboBox<>(Beginner.values());
    /** Programm- und Turniereigenschaften. */
    private final Eigenschaften eigenschaften;
    /** Der Manager, welcher das aus der Absprache entstandene Turnier betreut. */
    private final Manager manager;
    /** Regelvariantenauswahl. */
    private final JComboBox<Variante> variantenauswahl = new JComboBox<>(Variante.values());
    /** Nominierungen der Mitspieler, sowohl menschliche Spieler und künstliche Intelligenzen. */
    private final Nominierung[] nominierungen = new Nominierung[Spieler.SPIELER];
    /** Auswahlboxen für künstliche Intelligenzen. */
    private final Botselektion[] botselektionen = new Botselektion[Spieler.SPIELER];
    /** Organisation der Durchführung eines Turniers über ein Netzwerk. */
    private final JButton netzwerkorganisation = new JButton("Organisation über Netzwerk");
    /** OK-Button. */
    private final JButton ok = new JButton("OK");
    /** Abbruch-Button. */
    private final JButton abbruch = new JButton("Abbruch");

    /**
     * Konstruktor.
     *
     * @param eigenschaften
     *            Programm- und Turniereigenschaften.
     * @param manager
     *            Der betreuende Manager, welcher sich um Turnier kümmert, welche aus der Absprache entstehen.
     */
    public Absprache(Eigenschaften eigenschaften, Manager manager) {
        this.eigenschaften = eigenschaften;
        this.manager = manager;
        UIManager.addPropertyChangeListener(this);
        baue();
        installiere();
        aktualisiere();
        setTitle("JaFuffy (Daten für neues Turnier)");
        setIconImages(Oberflaeche.LOGOS);
        setModal(true);
        pack();
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        String kommmando = event.getActionCommand();
        switch (kommmando) {
        case "Abbruch":
            aktualisiere();
            ok.requestFocus();
            setVisible(false);
            break;
        case "OK":
            ArrayList<Spieler> teilnehmer = uebernehmeTeilnehmer();
            manager.betreibe(new Turnier(uebernehmeVariante(), teilnehmer, erster(teilnehmer), uebernehmeBeginnmodus(),
                    uebernehmeAnzahl()));
            ok.requestFocus();
            setVisible(false);
            break;
        case "Abstimmung":
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        Leitung leitung;
                        leitung = new Leitung(teilnehmer());
                        netzwerkorganisation.addActionListener(leitung);
                        Organisation organisation = new Organisation(leitung);
                        organisation.setLocationRelativeTo(Absprache.this);
                        organisation.setVisible(true);
                        netzwerkorganisation.removeActionListener(leitung);
                    } catch (UnknownHostException exception) {
                        JOptionPane.showMessageDialog(Absprache.this,
                                "Kein Turnier über Netzwork möglich!\nKein Netzwerk verfügbar?\nJava-Exception:\n"
                                        + exception,
                                "JaFuffy (Fehler)", JOptionPane.ERROR_MESSAGE);
                    }
                }

                private ArrayList<String> teilnehmer() {
                    ArrayList<String> teilnehmer = new ArrayList<>();
                    for (Nominierung nominierung : nominierungen) {
                        String name = nominierung.toString();
                        if (!name.isEmpty()) {
                            teilnehmer.add(name);
                        }
                    }
                    return teilnehmer;
                }
            });
            break;
        default:
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        if (event.getPropertyName().equals("Beginnen")) {
            setLocationRelativeTo((JRootPane) event.getNewValue());
            setVisible(true);
        } else if (event.getPropertyName().equals("lookAndFeel")) {
            SwingUtilities.updateComponentTreeUI(this);
            pack();
        }
    }

    /**
     * Anzahl der Spiele pro Turnier.
     *
     * @return Anzahlauswahlfeld.
     */
    private JPanel anzahlauswahl() {
        JLabel label;
        label = new JLabel("Anzahl der Spiele im Turnier");
        label.setAlignmentX(Component.CENTER_ALIGNMENT);
        int anzahl = Integer.parseInt(eigenschaften.getProperty("Anzahl"));
        anzahlschieberegler.setValue(anzahl);
        anzahlschieberegler.setMinorTickSpacing(FEINTICKS);
        anzahlschieberegler.setMajorTickSpacing(ORIENTIERUNGSTICKS);
        anzahlschieberegler.setPaintTicks(true);
        anzahlschieberegler.setPaintLabels(true);
        anzahlschieberegler.setSnapToTicks(true);
        @SuppressWarnings("unchecked")
        Hashtable<Integer, JLabel> hashtable = anzahlschieberegler.createStandardLabels(TICKLABELS);
        hashtable.put(0, new JLabel("beliebig"));
        anzahlschieberegler.setLabelTable(hashtable);
        JPanel anzahlfeld = new JPanel();
        anzahlfeld.setLayout(new BoxLayout(anzahlfeld, BoxLayout.Y_AXIS));
        anzahlfeld.add(label);
        anzahlfeld.add(Box.createVerticalStrut(FELDRAHMENABSTAND / 2));
        anzahlfeld.add(Box.createVerticalGlue());
        anzahlfeld.add(Box.createVerticalStrut(FELDRAHMENABSTAND / 2));
        anzahlfeld.add(anzahlschieberegler);
        return anzahlfeld;
    }

    /** Aufbau des Gesamtdialogs. */
    private void baue() {
        JPanel regelnfeld = new JPanel(new BorderLayout());
        regelnfeld.add(regelnfeld(), BorderLayout.CENTER);
        JPanel buttonspalte = new JPanel(new BorderLayout());
        buttonspalte.add(buttonspalte(), BorderLayout.CENTER);
        JPanel feld = new JPanel();
        feld.setBorder(
                new EmptyBorder(DIALOGRAHMENABSTAND, DIALOGRAHMENABSTAND, DIALOGRAHMENABSTAND, DIALOGRAHMENABSTAND));
        feld.setLayout(new BoxLayout(feld, BoxLayout.X_AXIS));
        feld.add(nominierungsfeld());
        feld.add(Box.createHorizontalStrut(FELDRAHMENABSTAND));
        feld.add(regelnfeld);
        feld.add(Box.createHorizontalStrut(FELDRAHMENABSTAND));
        feld.add(buttonspalte);
        setContentPane(feld);
    }

    /**
     * OK-Abbruch-Leiste.
     *
     * @return Buttonspalte.
     */
    private JPanel buttonspalte() {
        JPanel buttonspalte = new JPanel(new GridLayout(0, 1, 0, BUTTONABSTAND));
        buttonspalte.setBorder(BorderFactory.createEmptyBorder(2 * BUTTONABSTAND, FELDRAHMENABSTAND, 2 * BUTTONABSTAND,
                FELDRAHMENABSTAND));
        buttonspalte.add(ok);
        buttonspalte.add(abbruch);
        return buttonspalte;
    }

    /**
     * Bestimmt den ersten Spieler, der das Turnier eröffnet.
     *
     * @param teilnehmer
     *            Alle teilnehmenden Spieler.
     * @return Der eröffnende Spieler.
     */
    private Spieler erster(ArrayList<Spieler> teilnehmer) {
        Spieler erster = teilnehmer.get(0);
        if (teilnehmer.size() > 1) {
            Eroeffnung eroeffnung = new Eroeffnung(teilnehmer);
            eroeffnung.addPropertyChangeListener(this);
            eroeffnung.setLocationRelativeTo(this);
            eroeffnung.setVisible(true);
            eroeffnung.dispose();
            erster = eroeffnung.erster();
        }
        return erster;
    }

    /** Installiert die Reaktionen der Buttonleiste auf Abbruch und OK, sowie die Netzwerkkontrolle. */
    private void installiere() {
        netzwerkorganisation.setActionCommand("Abstimmung");
        netzwerkorganisation.addActionListener(this);
        netzwerkorganisation.setToolTipText("<html>" + "<p>Organisiert ein Turnier über ein lokales Netzwerk.</p>"
                + "<p>Erlaubt Hinzufügen von Mitspielern, die über das Netzwerk teilnehmen.</p>"
                + "<br><p><small><em>Experimentell! Volle Funktionalität steht nicht zur Verfügung.</em></small></p>"
                + "</html>");
        abbruch.addActionListener(this);
        ok.addActionListener(this);
        getRootPane().setDefaultButton(ok);
    }

    /** Baut das Feld für die Auswahl des Beginnmodus auf. */
    private void konfiguriereBeginnmodusauswahl() {
        beginnmodusauswahl.setAlignmentX(Component.CENTER_ALIGNMENT);
        beginnmodusauswahl.setSelectedIndex(eigenschaften.beginnmodus());
        beginnmodusauswahl.setToolTipText("Auswahl, welcher Mitspieler jedes neue Spiel beginnt");
    }

    /** Baut das Feld zur Variantenauswahl auf. */
    private void konfiguriereVariantenauswahl() {
        variantenauswahl.setActionCommand("Var");
        variantenauswahl.setAlignmentX(Component.CENTER_ALIGNMENT);
        variantenauswahl.setSelectedIndex(eigenschaften.variante());
        variantenauswahl.setToolTipText("Nach welchen Spielregeln soll gespielt werden?");
    }

    /**
     * Erstellt das Feld zum Zugang zur Netzwerkkontrolle für ein Turnier, das über ein Netzwerk gespielt wird.
     * 
     * @return Netzwerkkontrollfeld.
     */
    private JPanel netzwerkorganisationsfeld() {
        JPanel netzwerkorganisationsfeld = new JPanel(new BorderLayout());
        netzwerkorganisationsfeld
                .setBorder(BorderFactory.createEmptyBorder(BUTTONABSTAND, 0, FELDRAHMENABSTAND / 2, 0));
        netzwerkorganisationsfeld.add(netzwerkorganisation, BorderLayout.CENTER);
        return netzwerkorganisationsfeld;
    }

    /**
     * Baut das Nominierungsfeld zusammen, in dem Spieler menschlicher oder künstlicher Natur angemeldet werden.
     *
     * @return Nominierungsfeld.
     */
    private JPanel nominierungsfeld() {
        JPanel nominierungsfeld = new JPanel(new BorderLayout());
        nominierungsfeld.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Namen"),
                BorderFactory.createEmptyBorder(FELDRAHMENABSTAND / 2, FELDRAHMENABSTAND, FELDRAHMENABSTAND / 2,
                        FELDRAHMENABSTAND)));
        nominierungsfeld.add(spielerfestlegung(), BorderLayout.PAGE_START);
        nominierungsfeld.add(netzwerkorganisationsfeld(), BorderLayout.CENTER);
        return nominierungsfeld;
    }

    /**
     * Erstellt das Feld, in dem die Spielregeln festgelegt werden.
     *
     * @return Regelnfeld.
     */
    private JPanel regelnfeld() {
        konfiguriereVariantenauswahl();
        konfiguriereBeginnmodusauswahl();
        JPanel regelnfeld = new JPanel();
        regelnfeld.setLayout(new BoxLayout(regelnfeld, BoxLayout.Y_AXIS));
        regelnfeld.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("Regeln"),
                BorderFactory.createEmptyBorder(FELDRAHMENABSTAND / 2, FELDRAHMENABSTAND, FELDRAHMENABSTAND / 2,
                        FELDRAHMENABSTAND)));
        regelnfeld.add(beginnmodusauswahl);
        regelnfeld.add(Box.createVerticalStrut(1));
        regelnfeld.add(Box.createVerticalGlue());
        regelnfeld.add(Box.createVerticalStrut(1));
        regelnfeld.add(variantenauswahl);
        regelnfeld.add(Box.createVerticalStrut(FELDRAHMENABSTAND));
        regelnfeld.add(Box.createVerticalGlue());
        regelnfeld.add(Box.createVerticalStrut(FELDRAHMENABSTAND));
        regelnfeld.add(anzahlauswahl());
        regelnfeld.add(Box.createVerticalGlue());
        return regelnfeld;
    }

    private JPanel spielerfestlegung() {
        JPanel spielerfestlegung = new JPanel(new GridBagLayout());
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridwidth = 1;
        constraints.weighty = 1;
        constraints.insets = new Insets(1, 0, 1, 0);
        LinkedList<String> botnamen = new LinkedList<>(Arrays.asList(Bot.BOTNAMEN));
        for (int i = 0; i < Spieler.SPIELER; i++) {
            String botname = eigenschaften.getProperty("Bot" + i);
            if (!botname.isEmpty()) {
                botnamen.remove(botname);
            }
        }
        for (int i = 0; i < Spieler.SPIELER; i++) {
            constraints.anchor = GridBagConstraints.LINE_START;
            constraints.gridx = 0;
            constraints.gridy = i;
            constraints.ipadx = 2 * FELDRAHMENABSTAND;
            constraints.fill = GridBagConstraints.VERTICAL;
            constraints.weightx = 0;
            String name = eigenschaften.getProperty("Bot" + i);
            boolean kuenstlich = !name.isEmpty();
            if (!kuenstlich) {
                name = eigenschaften.getProperty("Spieler" + i);
            }
            nominierungen[i] = new Nominierung(kuenstlich, name, botnamen, this);
            variantenauswahl.addActionListener(nominierungen[i]);
            botselektionen[i] = new Botselektion(kuenstlich, nominierungen[i], variantenauswahl);
            variantenauswahl.addActionListener(botselektionen[i]);
            spielerfestlegung.add(botselektionen[i], constraints);
            constraints.anchor = GridBagConstraints.CENTER;
            constraints.gridx = 1;
            constraints.ipadx = FELDRAHMENABSTAND;
            constraints.weightx = 0;
            JLabel kopf = new JLabel(i + 1 + ". Spieler: ", SwingConstants.CENTER);
            kopf.setToolTipText("Eingabe der Spielernamen, bis zu " + Nominierung.EINGABELAENGE + " Zeichen.");
            spielerfestlegung.add(kopf, constraints);
            constraints.anchor = GridBagConstraints.LINE_END;
            constraints.gridx = 2;
            constraints.ipadx = 0;
            constraints.fill = GridBagConstraints.BOTH;
            constraints.weightx = 1;
            spielerfestlegung.add(nominierungen[i], constraints);
        }
        return spielerfestlegung;
    }

    /**
     * Bestimmt und übernimmt die Anzahl.
     *
     * @return Anzahl der Spiele pro Turnier.
     */
    private int uebernehmeAnzahl() {
        eigenschaften.setProperty("Anzahl", Integer.toString(anzahlschieberegler.getValue()));
        return anzahlschieberegler.getValue();
    }

    /**
     * Bestimmt und übernimmt den Beginnmodus.
     *
     * @return Beginnmodus.
     */
    private Beginner uebernehmeBeginnmodus() {
        Beginner beginnmodus = Beginner.values()[beginnmodusauswahl.getSelectedIndex()];
        eigenschaften.setProperty("Beginnmodus", beginnmodus.name());
        return beginnmodus;
    }

    /**
     * Bestimmt und übernimmt die teilnehmenden Spieler.
     *
     * @return Alle teilnehmenden Spieler.
     */
    private ArrayList<Spieler> uebernehmeTeilnehmer() {
        ArrayList<Spieler> teilnehmer = new ArrayList<>(Spieler.SPIELER);
        for (int index = 0; index < Spieler.SPIELER; index++) {
            eigenschaften.setProperty("Spieler" + index, "");
            eigenschaften.setProperty("Bot" + index, "");
        }
        for (Nominierung nominierung : nominierungen) {
            String name = nominierung.toString();
            if (!name.isEmpty()) {
                if (nominierung.kuenstlich()) {
                    eigenschaften.setProperty("Bot" + teilnehmer.size(), name);
                    teilnehmer.add(new Bot(eigenschaften, name, teilnehmer.size()));
                } else {
                    eigenschaften.setProperty("Spieler" + teilnehmer.size(), name);
                    teilnehmer.add(new Spieler(name, teilnehmer.size()));
                }
                nominierung.spielernameneingabe.setText(name);
            }
        }
        return teilnehmer;
    }

    /**
     * Bestimmt und übernimmt die Regelvariante.
     *
     * @return Regelvariante.
     */
    private Variante uebernehmeVariante() {
        Variante variante = (Variante) variantenauswahl.getSelectedItem();
        eigenschaften.setProperty("Variante", variante.name());
        return variante;
    }

    /** Verteilt die Namen der künstlichen Intelligenzen und passt Elemente des Dialogs an den aktuellen Zustand an. */
    void aktualisiere() {
        int spieleranzahl = 0;
        for (Nominierung nominierung : nominierungen) {
            if (!nominierung.toString().isEmpty()) {
                spieleranzahl++;
            }
        }
        beginnmodusauswahl.setEnabled(spieleranzahl > 1);
        ok.setEnabled(spieleranzahl > 0);
        netzwerkorganisation.setEnabled(spieleranzahl > 0);
    }

    /**
     * Entfernt einen künftig benötigten Namen eines Verursachers von den Namensauswahlen der anderen Bots.
     *
     * @param verursacher
     *            Der verursachende Bot.
     * @param botname
     *            Der zu entfernende Name.
     */
    void entferne(Nominierung verursacher, String botname) {
        for (Nominierung nominierung : nominierungen) {
            if (nominierung != verursacher && nominierung.kuenstlich()) {
                nominierung.entferne(botname);
            }
        }
    }

    /**
     * Fügt einen freiwerdenden Namen des Verursachers zur Namensauswahl anderer Bots hinzu.
     *
     * @param verursacher
     *            Der verursachende Bot.
     * @param botname
     *            Der hinzuzufügende Name.
     */
    void erweitere(Nominierung verursacher, String botname) {
        for (Nominierung nominierung : nominierungen) {
            if (nominierung != verursacher && nominierung.kuenstlich()) {
                nominierung.erweitere(botname);
            }
        }
    }

    /**
     * Ermittelt, ob der gewünschte Botname noch zur Verfügung steht.
     * 
     * @param botname
     *            Zu überprüfender Name.
     * @return Gibt an, ob der Botname noch zur Verfügung steht.
     */
    boolean istBotnameFrei(String botname) {
        for (Nominierung nominierung : nominierungen) {
            if (botname.equals(nominierung.toString())) {
                return false;
            }
        }
        return true;
    }

}
