/******************************************************************************
 ** $Id: Leitung.java 1686 2019-01-21 01:11:19Z 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.organisation;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;

import javax.swing.Timer;
import javax.swing.table.AbstractTableModel;

/** Übernimmt die Leitung der Organisation eines Turniers. */
@SuppressWarnings("serial")
public class Leitung extends AbstractTableModel implements ActionListener, PropertyChangeListener {

    /** Verschicken von Chat-Nachrichten. */
    public interface Chat {
        /** Anzeige der Bestandteile einer Chat-Nachricht. */
        void zeige(String identifikation, String teilnehmer, String text);
    }

    /** Die Spalten der Tabelle der Anmeldungen über das lokale Netzwerk. */
    private static enum Spalte {
        TEILNEHMER("Teilnehmer", 250), IDENTIFIKATION("Identifikation", 250);
        /** Spaltentitel für Anzeige in Tabelle. */
        private final String titel;
        /** Spaltenbreite für Anzeige in Tabelle. */
        final int breite;

        /**
         * Erstellt eine Aufzählungselement.
         * 
         * @param titel
         *            Spaltentitel.
         * @param breite
         *            Spaltenbreite.
         */
        Spalte(String titel, int breite) {
            this.titel = titel;
            this.breite = breite;
        }

        @Override
        public String toString() {
            return titel;
        }
    }

    /** Zur Überprüfung, ob die Anmeldung einer Austragungstätte noch aktuelle ist. */
    private class Ueberpruefung implements ActionListener {
        /** Identifikation der Austragungsstätte, die überprüft wird. */
        private final String identifikation;
        /** Wird bei jeder Anmeldungen auf wahr gesetzt, und regelmäßig auf falsch gesetzt. */
        private boolean vorhanden;

        /**
         * Erstellt die Überprüfung einer Austragungsstätte.
         * 
         * @param identifikation
         *            Identifikation der Austragungsstätte
         */
        Ueberpruefung(String identifikation) {
            this.identifikation = identifikation;
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            Ueberwachung ueberwachung = (Ueberwachung) event.getSource();
            if (!vorhanden) {
                ueberwachung.stop();
                ueberpruefungen.remove(identifikation);
                veranstaltungskandidaten.remove(identifikation);
                identifikationen = identifikationen();
                fireTableDataChanged();
            }
            vorhanden = false;
        }

        /** Bestätigt, dass die Anmeldung einer Austragungsstätte noch aktuell ist. */
        void bestaetige() {
            vorhanden = true;
        }
    }

    /** Regelmäßiger Anstoß der Überprüfung einer Anmeldung einer Austragungsstätte. */
    private static class Ueberwachung extends Timer {
        /** Periode des Anstoßes. */
        private static final int UEBERWACHUNGSPERIODE = 5000;

        /**
         * Erstellt die Überwachung einer Anmeldung.
         * 
         * @param ueberpruefung
         *            Die Überprüfung, die regelmäßig angestoßen werden soll.
         */
        Ueberwachung(Ueberpruefung ueberpruefung) {
            super(UEBERWACHUNGSPERIODE, ueberpruefung);
        }
    }

    /** Port-Nummer zwecks Organisation. */
    static final int PORT = 54815;
    /** IP-Adresse zwecks Organisation. */
    static final String ADRESSE = "239.255.255.136";

    /** Gruppe zwecks Organisation des anfänglichen Turnierbetriebs. */
    private final InetAddress abstimmungsgruppe;
    {
        InetAddress adresse = null;
        try {
            adresse = InetAddress.getByName(ADRESSE);
        } catch (UnknownHostException event) {
            event.printStackTrace();
        }
        abstimmungsgruppe = adresse;
    }
    /** Der Multicast-Socket zwecks Organisation des Turnierbetriebs zwischen den JaFuffy-Instanzen. */
    private final MulticastSocket abstimmungssocket;
    {
        MulticastSocket socket = null;
        try {
            socket = new MulticastSocket(PORT);
            socket.joinGroup(abstimmungsgruppe);
        } catch (IOException exception) {
            exception.printStackTrace();
        }
        abstimmungssocket = socket;
    }
    /** Der Multicast-Socket zur Kommunikation zwecks Turnierbetrieb zwischen den JaFuffy-Instanzen. */
    private final ServerSocket betriebssocket;
    {
        ServerSocket socket = null;
        try {
            socket = new ServerSocket(0);
        } catch (IOException exception) {
            exception.printStackTrace();
        }
        betriebssocket = socket;
    }
    /** Die vollständige Absenderadresse der Austragungsstätte, an dessen Gerät die Teilnehmer sitzen. */
    private final Kontakt kontakt = new Kontakt(InetAddress.getLocalHost(), betriebssocket.getLocalPort());
    /** Die Teilnehmer der Austragungsstätte, an dessen Gerät die Teilnehmer sitzen. */
    private final ArrayList<String> teilnehmer;
    /** Alle gefundenen Kandidaten der Austragungsstätten, welche über ein Netzwerk am Turnier teilnehmen möchten. */
    private final LinkedHashMap<String, ArrayList<String>> veranstaltungskandidaten = new LinkedHashMap<>();
    /** Überwachung-Threads für alle Kandidaten, ob diese während Abstimmung noch vorhanden ist. */
    private final HashMap<String, Ueberpruefung> ueberpruefungen = new HashMap<>();

    /** Zur Delegation der Anzeige von Chat-Nachrichten an das zuständige Element der Benutzeroberfläche. */
    private Chat chat;
    /** Meldet einen Austragungsstätte zur Teilnahme an. */
    private Anmeldung anmeldung;
    /** Ermöglicht die Einschreibung in ein ausgeschriebenes Turnier. */
    private Annahme annahme;
    /**
     * Die Identifikationen der Austragungsstätten, für die eine Anmeldung gefunden wurde und laufend bestätigt wird.
     */
    private String[] identifikationen;

    /**
     * Turnierleitung nimmt Arbeit auf.
     * 
     * @param teilnehmer
     *            Die Teilnehmer des Turniers der Austragungsstätten, an dessen Gerät die Teilnehmer direkt sitzen.
     */
    public Leitung(ArrayList<String> teilnehmer) throws UnknownHostException {
        this.teilnehmer = teilnehmer;
        veranstaltungskandidaten.put(kontakt.identifikation(), teilnehmer);
        starte();
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        String kommando = event.getActionCommand();
        if (kommando == "Aufnahme") {
            System.out.println(this + ": " + event);
        } else if (kommando == "Abbruch") {
            stoppe();
        }
    }

    /** Liefert Spaltenbreite für Anzeige in einer Tabelle. */
    public int breite(int spalte) {
        return Spalte.values()[spalte].breite;
    }

    @Override
    public int getColumnCount() {
        return Spalte.values().length;
    }

    @Override
    public String getColumnName(int spalte) {
        return Spalte.values()[spalte].toString();
    }

    @Override
    public int getRowCount() {
        return veranstaltungskandidaten.size();
    }

    @Override
    public Object getValueAt(int zeile, int spalte) {
        String identifikation = identifikationen[zeile];
        switch (Spalte.values()[spalte]) {
        case IDENTIFIKATION:
            return identifikationen[zeile];
        case TEILNEHMER:
            return veranstaltungskandidaten.get(identifikation);
        default:
            return null;
        }
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        String eigenschaft = event.getPropertyName();
        if (eigenschaft == "Senden") {
            anmeldung.sende(new Nachricht(kontakt, teilnehmer, event.getNewValue().toString()));
        } else if (eigenschaft == "Nachricht") {
            verarbeite((Nachricht) event.getNewValue());
        }
    }

    /** Setzt zur Ausgabe der Nachrichten die Ausgabefunktion des zuständigen Elements der Benutzeroberfläche. */
    public void verfolge(Chat chat) {
        this.chat = chat;
    }

    /** Liefert die Identifikationen der Austragungsstätten als Feld zurück. */
    private String[] identifikationen() {
        return (String[]) veranstaltungskandidaten.keySet().toArray(new String[veranstaltungskandidaten.size()]);
    }

    /** Started die Organisation des Turnierbetriebs über ein lokales Netzwerk. */
    private void starte() {
        anmeldung = new Anmeldung(kontakt, teilnehmer, abstimmungssocket);
        anmeldung.starte();
        annahme = new Annahme(abstimmungssocket);
        annahme.addPropertyChangeListener("Nachricht", this);
        new Thread(annahme).start();
    }

    /** Stoppt die Organisation des Turnierbetriebs über ein lokales Netzwerk. */
    private void stoppe() {
        try {
            abstimmungssocket.leaveGroup(abstimmungsgruppe);
        } catch (IOException exception) {
            exception.printStackTrace();
        }
        abstimmungssocket.close();
        annahme.removePropertyChangeListener("Nachricht", this);
    }

    /**
     * Bearbeitet eine Nachricht, die zwecks Organisation des Turnierbetriebs verschickt wurde.
     * 
     * @param nachricht
     *            Zu bearbeitende Nachricht.
     */
    private void verarbeite(Nachricht nachricht) {
        if (nachricht.text() == null) {
            String identifikation = nachricht.kontakt().identifikation();
            veranstaltungskandidaten.put(identifikation, nachricht.teilnehmer());
            identifikationen = identifikationen();
            if (ueberpruefungen.containsKey(identifikation)) {
                ueberpruefungen.get(identifikation).bestaetige();
            } else {
                Ueberpruefung ueberpruefung = new Ueberpruefung(identifikation);
                ueberpruefungen.put(identifikation, ueberpruefung);
                Ueberwachung ueberwachung = new Ueberwachung(ueberpruefung);
                ueberwachung.start();
            }
            fireTableDataChanged();
        } else {
            chat.zeige(nachricht.kontakt().identifikation(), nachricht.teilnehmer().toString(), nachricht.text());
        }
    }

}
