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

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

import filius.rahmenprogramm.I18n;
import filius.software.transportschicht.TCPSocket;

/**
 * <p>
 * In dieser Klasse wird das Client-Programm einer einfachen Client-Server-Anwendung implementiert. Nachrichten an die
 * graphische Benutzungsoberflaeche werden durch den Aufruf banachrichtigeBeobachter(Object) versendet.
 * </p>
 * <p>
 * Aufrufe folgender Methoden des Sockets blockieren:
 * </p>
 * <ul>
 * <li>verbinden()</li>
 * <li>senden()</li>
 * <li>empfangen()</li>
 * <li>schliessen()</li>
 * </ul>
 * <p>
 * Deshalb muessen Methoden dieser Klasse, die Aufrufe dieser Methoden enthalten, ueber die Methode
 * <code>ausfuehren(String, Object[])</code> aufgerufen werden. Das bewirkt, dass diese Methoden in einem eigenen Thread
 * ausgefuehrt werden und damit der aufrufende Thread nicht blockiert. Das ist wichtig, wenn die Aufrufe von der
 * graphischen Benutzungsoberflaeche ausgeloest werden.
 * </p>
 * <p>
 * Ein Beispiel fuer die Verwendung von <code>ausfuehren()</code> ist folgendes: <br />
 * <code> public void verbinden(String zielAdresse, Integer port) { <br />
 * &nbsp;&nbsp; Object[] args; <br /> <br />
 * &nbsp;&nbsp; args = new Object[2]; <br />
 * &nbsp;&nbsp; args[0] = zielAdresse; <br />
 * &nbsp;&nbsp;	args[1] = port; <br /> <br />
 * &nbsp;&nbsp; ausfuehren("initialisiereSocket", args); <br />
 * }
 * </p>
 * <p>
 * Dabei wird als erstes Argument der auszufuehrenden blockierenden Methode
 * uebergeben (hier: <code> initialisiereSocket</code>) und dann in einem Array die zu uebergebenden Parameter (hier:
 * <code>zielAdresse</code> und <code>port</code>). Der Aufruf der Methode <code>verbinden(zielAdresse, port)
 * </code> bewirkt also das Ausfuehren der Methode <code>initialisiereSocket(zielAdresse, port)</code> in einem anderen
 * Thread. Damit blockiert die Methode <code>verbinden</code> nicht.
 * </p>
 * <p>
 * <b> Achtung:</b> Die indirekt aufgerufene Methode (d. h. ueber <code>
 * ausfuehren(String, Object[])</code>) muss als <code>public</code> deklariert sein!
 * </p>
 */
public class ClientBaustein extends ClientAnwendung implements I18n {
    private static Logger LOG = LoggerFactory.getLogger(ClientBaustein.class);

    /** Port-Nummer des Servers, an dem Verbindungsanfragen angenommen werden */
    private int zielPort = 55555;

    /**
     * Adresse des Rechners, auf dem der Server laeuft als Domainname oder IP-Adresse.
     */
    private String zielIPAdresse;

    /**
     * Methode zum Verbindungsaufbau zu einem Server. Hier wird der Client-Socket als TCP/IP-Socket erzeugt. Wenn UDP
     * verwendet werden soll, muss diese Methode ueberschrieben werden. <br />
     * Diese Methode ist <b>nicht blockierend</b>. Diese Methode veranlasst den Aufruf von
     * <code>initialisiereSocket</code> in einem anderen Thread.
     */
    public void verbinden() {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (ClientBaustein), verbinden()");
        Object[] args;

        args = new Object[2];
        args[0] = zielIPAdresse;
        args[1] = Integer.valueOf(zielPort);

        ausfuehren("initialisiereSocket", args);
        ausfuehren("empfangeNachricht", null);
    }

    /**
     * Methode zum Aufbau einer Verbindung mit einem TCP-Socket. Diese Methode ist blockierend.
     */
    public synchronized void initialisiereSocket(String zielAdresse, Integer port) {
        LOG.debug("connect client {}:{}", zielAdresse, port);
        if (!istVerbunden()) {
            try {
                socket = new TCPSocket(getSystemSoftware(), zielAdresse, port);
                socket.verbinden();
                benachrichtigeBeobachter(messages.getString("sw_clientbaustein_msg2"));
            } catch (Exception e) {
                LOG.debug("", e);
                socket = null;
                benachrichtigeBeobachter(messages.getString("sw_clientbaustein_msg1") + e.getMessage());
            }
        }
    }

    /**
     * Methode zum trennen einer Verbindung. Der Socket wird durch den Aufruf der Methode schliessen() geschlossen und
     * und der Socket fuer diese Anwendung auf null gesetzt. <br />
     * Diese Methode ist <b> blockierend</b>.
     */
    public void trennen() {
        LOG.debug("disconnect client");
        if (socket != null) {
            socket.schliessen();
            benachrichtigeBeobachter(messages.getString("sw_clientbaustein_msg3"));
        }
    }

    /**
     * Diese Methode <b>blockiert</b> bis die Nachricht versand wurde. Der Empfang der Antwort erfolgt asynchron.
     */
    public void senden(String nachricht) {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (ClientBaustein), versendeNachricht(" + nachricht + ")");

        if (socket != null && socket.istVerbunden()) {
            try {
                socket.senden(nachricht);
                benachrichtigeBeobachter("<<" + nachricht);
            } catch (Exception e) {
                benachrichtigeBeobachter(e.getMessage());
                LOG.debug("", e);
            }
        } else {
            benachrichtigeBeobachter(messages.getString("sw_clientbaustein_msg4"));
        }
    }

    /**
     * Methode zum Empfang einer Nachricht vom Socket. Die empfangene Nachricht wird mit
     * <code>benachrichtigeBeobachter</code> an die GUI weiter gegeben. Diese Methode ist blockierend und sollte nicht
     * direkt von der GUI aufgerufen werden.
     */
    public void empfangeNachricht() {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (ClientBaustein), empfangeNachricht()");
        String nachricht;

        while (socket != null && socket.istVerbunden()) {
            try {
                nachricht = socket.empfangen(Long.MAX_VALUE);
                if (nachricht != null) {
                    benachrichtigeBeobachter(">>" + nachricht);
                } else if (null != socket) {
                    socket.schliessen();
                    benachrichtigeBeobachter(
                            messages.getString("sw_clientbaustein_msg5") + " " + socket.holeZielIPAdresse() + ":"
                                    + socket.holeZielPort() + " " + messages.getString("sw_clientbaustein_msg6"));
                }
            } catch (Exception e) {
                benachrichtigeBeobachter(e.getMessage());
                LOG.debug("", e);
                socket = null;
            }
        }
    }

    /** Methode fuer den Zugriff auf die Server-Adresse */
    public String getZielIPAdresse() {
        return zielIPAdresse;
    }

    /** Methode fuer den Zugriff auf die Server-Adresse */
    public void setZielIPAdresse(String zielIPAdresse) {
        this.zielIPAdresse = zielIPAdresse;
    }

    /**
     * Methode fuer den Zugriff auf die Port-Nummer, an dem der Server zu erreichen ist.
     */
    public int getZielPort() {
        return zielPort;
    }

    /**
     * Methode fuer den Zugriff auf die Port-Nummer, an dem der Server zu erreichen ist.
     */
    public void setZielPort(int zielPort) {
        this.zielPort = zielPort;
    }
}
