/*
 ** This file is part of Filius, a network construction and simulation software.
 ** 
 ** Originally created at the University of Siegen, Institute "Didactics of
 ** Informatics and E-Learning" by a students' project group:
 **     members (2006-2007): 
 **         André Asschoff, Johannes Bade, Carsten Dittich, Thomas Gerding,
 **         Nadja Haßler, Ernst Johannes Klebert, Michell Weyer
 **     supervisors:
 **         Stefan Freischlad (maintainer until 2009), Peer Stechert
 ** Project is maintained since 2010 by Christian Eibl <filius@c.fameibl.de>
 **         and Stefan Freischlad
 ** Filius 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 2 of the License, or
 ** (at your option) version 3.
 ** 
 ** Filius 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 Filius.  If not, see <http://www.gnu.org/licenses/>.
 */
package filius.software.dateiaustausch;

import java.util.LinkedList;
import java.util.ListIterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import filius.software.Anwendung;

/**
 * In dieser Klasse werden Anfragen an andere Teilnehmer im Peer-To-Peer-Netzwerk verschickt bzw. weitergeleitet und
 * dafuer gesorgt, dass die dazu gehoerigen eingehenden Antworten verarbeitet werden. Dazu wird ein neuer Thread mit
 * einem PeerToPeerClientMitarbeiter gestartet.
 * 
 */
class PeerToPeerClient extends Anwendung {
    private static Logger LOG = LoggerFactory.getLogger(PeerToPeerClient.class);

    /**
     * Die zugehoerige PeerToPeerAnwendung, die diesen Client zur Verarbeitung von eigenen Anfragen verwendet.
     */
    private PeerToPeerAnwendung peerToPeerAnwendung;

    /**
     * Eine Liste mit allen Mitarbeiter-Threads, die zur Verarbeitung der Antworten auf eigene Anfragen verwendet
     * werden.
     */
    private LinkedList<PeerToPeerClientMitarbeiter> mitarbeiterEigeneAnfragen = new LinkedList<PeerToPeerClientMitarbeiter>();

    /**
     * Eine Liste mit allen Mitarbeiter-Threads, die zur Verarbeitung von Antworten auf fremde, d. h. weitergeleitete,
     * Anfragen verwendet werden.
     */
    private LinkedList<PeerToPeerClientMitarbeiter> mitarbeiterFremdeAnfragen = new LinkedList<PeerToPeerClientMitarbeiter>();

    /**
     * Konstruktor, in dem die zugehoerige PeerToPeerAnwendung initialisiert und der Konstruktor der Oberklasse
     * aufgerufen wird.
     * 
     * @param peerToPeerAnwendung
     */
    PeerToPeerClient(PeerToPeerAnwendung peerToPeerAnwendung) {
        super();
        LOG.trace("INVOKED-2 (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerClient), constr: PeerToPeerClient(" + peerToPeerAnwendung + ")");

        this.peerToPeerAnwendung = peerToPeerAnwendung;
    }

    /** Zum beenden der Threads, die durch diesen Client gestartet wurden. */
    public void beenden() {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerClient), beenden()");
        ListIterator<?> it;
        PeerToPeerClientMitarbeiter m;

        super.beenden();

        it = mitarbeiterEigeneAnfragen.listIterator();
        while (it.hasNext()) {
            m = (PeerToPeerClientMitarbeiter) it.next();
            m.beenden();
        }

        it = mitarbeiterFremdeAnfragen.listIterator();
        while (it.hasNext()) {
            m = (PeerToPeerClientMitarbeiter) it.next();
            m.beenden();
        }
    }

    /**
     * Verschicken eines eigenden oder weitersenden einer fremden Ping-Nachricht. Wenn eine IP-Adresse angegeben wird,
     * zu dem diese Nachricht verschickt werden soll, wird die Ping-Nachricht als eigene Anfrage behandelt. Eine
     * Ping-Nachricht wird zum Beitreten zu einem Peer-To-Peer-Netzwerk verschickt.
     * 
     * @param wohinZuerst
     *            die IP-Adresse des Servents, mit welchem man sich zuerst verbinden moechte
     * @param pingPaket
     *            das Ping-Paket, welches verschickt wird
     * @param peerToPeerServerMitarbeiter
     *            der peerToPeerServerMitarbeiter, ueber den eine moegliche Antwort zurueck geschickt wird
     * @param nichtIP
     *            die IP, an die das Paket nicht weitergeleitet werden darf (weil es daher kommt)
     */
    void sendePing(String wohinZuerst, PingPaket pingPaket, String nichtIP) {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerClient), sendePing(" + wohinZuerst + "," + pingPaket + "," + nichtIP + ")");
        // der erste Schritt => nur an den senden, den man angegeben hat, der
        // kuemmert sich um das weitere fluten
        if (!wohinZuerst.equals("")) {
            PeerToPeerClientMitarbeiter lauscher = new PeerToPeerClientMitarbeiter(peerToPeerAnwendung, wohinZuerst,
                    pingPaket);
            mitarbeiterEigeneAnfragen.add(lauscher);
            lauscher.start();
        }
        // Ping weiter fluten (an alle, die in der Liste stehen, ausser an den,
        // von dem es aktuell kam und
        // den, wo es urspruenglich her kam
        else {
            for (int i = 0; i < peerToPeerAnwendung.holeBekanntePeerToPeerTeilnehmer().size(); i++) {
                String aktuelle = (String) peerToPeerAnwendung.holeBekanntePeerToPeerTeilnehmer().get(i);
                if (aktuelle.equals(nichtIP) == false && aktuelle.equals(pingPaket.getIp()) == false) {
                    PeerToPeerClientMitarbeiter lauscher = new PeerToPeerClientMitarbeiter(peerToPeerAnwendung,
                            aktuelle, pingPaket);
                    mitarbeiterFremdeAnfragen.add(lauscher);
                    lauscher.start();
                }
            }
        }
    }

    /**
     * Damit wird eine initiierte Suchanfrage abgebrochen. D. h., dass alle Threads, die auf eingehende Antworten auf
     * eine Suchanfrage warten, beendet werden.
     */
    void abbrechenSuche() {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerClient), abbrechenSuche()");
        ListIterator<?> it;
        PeerToPeerClientMitarbeiter mitarbeiter;

        it = mitarbeiterEigeneAnfragen.listIterator();
        while (it.hasNext()) {
            mitarbeiter = (PeerToPeerClientMitarbeiter) it.next();
            mitarbeiter.beenden();
        }
    }

    /**
     * erstellt eine HTTP Nachricht und schickt sie an den Teilnehmer, auf welchem die entsprechende Datei liegt. Auch
     * dazu wird ein Mitarbeiter-Thread gestartet und dieser in die Liste der Threads fuer eigene Anfragen eingefuegt.
     * 
     * @param teilnehmerIp
     *            der Teilnehmer, auf welchem die Datei liegt
     * @param dateiName
     *            der Name der gesuchten Datei
     */
    void dateiVomTeilnehmerAnfordern(String teilnehmerIp, String dateiName) {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerClient), dateiVomTeilnehmerAnfordern(" + teilnehmerIp + "," + dateiName + ")");
        PeerToPeerClientMitarbeiter m;

        m = new PeerToPeerClientMitarbeiter(peerToPeerAnwendung, teilnehmerIp, dateiName);
        m.start();
        mitarbeiterEigeneAnfragen.add(m);
    }

    /**
     * Initiierung und weiterleitung einer Suchanfrage (Query). Zur weiteren Verarbeitung wird ein neuer
     * Mitarbeiter-Thread erzeugt. Der Thread wird der Liste fuer eigene Anfragen hinzugefuegt, wenn die GUID in der
     * Liste der eigenen Anfragen der PeerToPeerAnwendung vorhanden ist. Die Anfrage wird immer an alle bekannten
     * Teilnehmer im Peer-To-Peer-Netzwerk verschickt.
     * 
     * @param anfragePaket
     *            das weiterzusendende Paket
     * @param absenderIP
     *            die IP-Adresse des Absenders
     * @param peerToPeerServerMitarbeiter
     *            der PeerToPeerServermitarbeiter, an den moegliche Antworten zurueck uebergeben werden muessen
     */
    void sendeAnfrage(QueryPaket anfragePaket, String absenderIP) {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerClient), sendeAnfrage(" + anfragePaket + "," + absenderIP + ")");
        LinkedList<String> dieNachbarn;
        PeerToPeerClientMitarbeiter lauscher;
        String nachbar;

        dieNachbarn = peerToPeerAnwendung.holeBekanntePeerToPeerTeilnehmer();
        /* Anfrage an ALLE direkten Nachbarn */
        for (int i = 0; i < dieNachbarn.size(); i++) {
            nachbar = (String) dieNachbarn.get(i);
            if (!nachbar.equals(absenderIP)) {
                lauscher = new PeerToPeerClientMitarbeiter(peerToPeerAnwendung, nachbar, anfragePaket);
                if (peerToPeerAnwendung.holeEigeneAnfragen().contains(anfragePaket.getGuid())) {
                    mitarbeiterEigeneAnfragen.add(lauscher);
                } else {
                    mitarbeiterFremdeAnfragen.add(lauscher);
                }
                lauscher.start();
            }
        }
    }
}
