/*
 ** 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.List;

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

import filius.software.clientserver.ServerMitarbeiter;
import filius.software.system.Betriebssystem;
import filius.software.system.Datei;
import filius.software.transportschicht.Socket;
import filius.software.www.HTTPNachricht;

/**
 * In dieser Klasse wird ein Thread implementiert, der eingehende Anfragen an eine Peer-to-Peer-Anwendung verarbeitet.
 */
public class PeerToPeerServerMitarbeiter extends ServerMitarbeiter {
    private static Logger LOG = LoggerFactory.getLogger(PeerToPeerServerMitarbeiter.class);

    private PeerToPeerAnwendung peerToPeerAnwendung;

    /** die GUID der eingegangenen zu verarbeitenden Nachricht */
    private int guid;

    /**
     * Aufruf des Konstruktors der Oberklasse und Initialisierung der zugehoerigen Instanz von PeerToPeerAnwendung
     * 
     * @param server
     * @param socket
     * @param peerToPeerAnwendung
     */
    PeerToPeerServerMitarbeiter(PeerToPeerServer server, Socket socket, PeerToPeerAnwendung peerToPeerAnwendung) {
        super(server, socket);
        LOG.trace("INVOKED-2 (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerServerMitarbeiter), constr: PeerToPeerServerMitarbeiter(" + server + "," + socket + ","
                + peerToPeerAnwendung + ")");

        this.peerToPeerAnwendung = peerToPeerAnwendung;
    }

    /** Methode zum versenden von Antwortnachrichten */
    void senden(String nachricht) {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerServerMitarbeiter), senden(" + nachricht + ")");
        if (socket != null && socket.istVerbunden()) {
            try {
                socket.senden(nachricht);
            } catch (Exception e) {
                LOG.debug("", e);
            }
        }
    }

    /**
     * Zugriff auf die GUID der Anfrage-Nachricht, die durch diesen Mitarbeiter-Thread verarbeitet wird.
     * 
     * @return
     */
    int holeGuid() {
        return guid;
    }

    /**
     * Diese Operation verarbeitet eine eingegangene HTTP-Anfrage:
     * <ol>
     * <li>Lesen der gesuchten Datei aus lokalem Dateisystem</li>
     * <li>Wenn die Datei vorhanden ist, Versenden der Datei; wenn die Datei nicht vorhanden ist, verschicken einer
     * HTTP-Antwort mit dem Fehler-Status-Code 404</li>
     * <li>Versenden der Antwort ueber den geoeffneten Socket</li>
     * </ol>
     * 
     * @param element
     *            die zu verarbeitende HTTP Anfrage in Form eines TcpPufferElements
     */
    private void httpAnfrageVerarbeiten(String nachricht) {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerServerMitarbeiter), httpAnfrageVerarbeiten(" + nachricht + ")");
        HTTPNachricht http, antwort;
        Datei datei;

        http = new HTTPNachricht(nachricht);
        antwort = new HTTPNachricht(HTTPNachricht.SERVER);
        datei = peerToPeerAnwendung.holeDatei(http.getPfad());

        if (datei != null) {
            antwort.setStatusCode(200);
            antwort.setContentType(datei.getDateiTyp());
            antwort.setDaten(datei.getDateiInhalt());
        } else {
            antwort.setStatusCode(404);
        }

        try {
            socket.senden(antwort.toString());
        } catch (Exception e) {
            LOG.debug("", e);
        }
    }

    /**
     * Methode zur Verarbeitung einer eingehenden Ping-Nachricht.
     * <ol>
     * <li>Absender des Ping-Pakets wird der Liste der bekannten Teilnehmer im Peer-to-Peer-Netzwerk hinzugefuegt.</li>
     * <li>Erzeugen einer entsprechenden Pong-Nachricht, wenn auf diese Ping-Nachricht nicht schon geantwortet
     * wurde.</li>
     * <li>Erhoehung des Hop-Zahlers und Dekrementierung des TTL-Zaehlers der Ping-Nachricht.</li>
     * <li>Weiterleitung des Ping-Pakets</li>
     * </ol>
     * 
     * @param pingPaket
     */
    private void verarbeitePing(PingPaket pingPaket) {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerServerMitarbeiter), verarbeitePing(" + pingPaket + ")");
        String pongNachricht;

        peerToPeerAnwendung.hinzuTeilnehmer(pingPaket.getIp());

        pongNachricht = peerToPeerAnwendung.erstellePong(pingPaket).toString();
        if (pongNachricht != null) {
            try {
                socket.senden(pongNachricht);
            } catch (Exception e) {
                LOG.debug("", e);
            }
        }

        pingPaket.setHops(pingPaket.getHops() + 1);
        pingPaket.setTtl(pingPaket.getTtl() - 1);

        guid = pingPaket.getGuid();
        peerToPeerAnwendung.sendePing(pingPaket, socket.holeZielIPAdresse());
    }

    /**
     * Methode zur Verarbeitung einer eingehenden Suchanfrage (Query).
     * <ol>
     * <li>Erstellen der Ergebnisliste fuer eine Suchanfrage. Wenn auf die Query-Nachricht bereits geantwortet wurde,
     * wird keine Liste erzeugt. Ausserdem wird damit zugleich veranlasst, dass die Suchanfrage ggf. weitergeleitet
     * wird.</li>
     * <li>
     * <ul>
     * <li>Wenn eine Liste mit mindestens einem Eintrag vorliegt, wird fuer jede Datei eine Antwortnachricht ueber den
     * Socket verschickt (Query-Hit).</li>
     * </ul>
     * </li>
     * </ol>
     * 
     * @param queryPaket
     */
    private void verarbeiteQuery(QueryPaket queryPaket) {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerServerMitarbeiter), verarbeiteQuery(" + queryPaket + ")");
        List<Datei> dateien;
        Betriebssystem bs;
        QueryHitPaket antwortPaket;

        guid = queryPaket.getGuid();

        dateien = peerToPeerAnwendung.verarbeiteAnfrage(socket.holeZielIPAdresse(), queryPaket);

        if (dateien != null && dateien.size() > 0) {
            bs = (Betriebssystem) peerToPeerAnwendung.getSystemSoftware();

            for (Datei aktuelleDatei : dateien) {
                antwortPaket = new QueryHitPaket(dateien.size(), 6346, bs.primaryIPAdresse(), "2", "", " ");
                antwortPaket.setGuid(queryPaket.getGuid());
                antwortPaket.setHops(0);
                antwortPaket.setTtl(8);
                antwortPaket.setErgebnis(aktuelleDatei.getName() + ": " + aktuelleDatei.holeGroesse() + " B");

                try {
                    socket.senden(antwortPaket.toString());
                } catch (Exception e) {
                    LOG.debug("", e);
                }
            }
            socket.schliessen();
        }
    }

    /**
     * Wenn eine Nachricht auf dem zu ueberwachenden Socket eintrifft, wird die Verarbeitung an diese Methode delegiert.
     * <br />
     * Unterschieden wird eine HTTP-GET-Anfrage, eine eingehende Ping-Nachricht und eine eingehende Suchanfrage (Query).
     */
    protected void verarbeiteNachricht(String nachricht) {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (PeerToPeerServerMitarbeiter), verarbeiteNachricht(" + nachricht + ")");
        PeerToPeerPaket paket;
        PingPaket pingPaket;
        QueryPaket queryPaket;

        if (nachricht != null) {
            if (nachricht.startsWith("GET")) {
                httpAnfrageVerarbeiten(nachricht);
            } else {
                paket = new PeerToPeerPaket(nachricht);
                guid = paket.getGuid();

                if (paket.getPayload().equals("0x00")) {
                    pingPaket = new PingPaket(nachricht);
                    verarbeitePing(pingPaket);
                } else if (paket.getPayload().equals("0x80")) {
                    queryPaket = new QueryPaket(nachricht);
                    verarbeiteQuery(queryPaket);
                }
            }
        }
    }
}
