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

import java.util.LinkedList;

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

/**
 * Diese Klasse ist die Oberklasse von Protokoll-Threads. Die Aufgabe ist, den Puffer zu ueberwachen, und die
 * Verarbeitung der eingehenden Dateneinheiten zu starten.
 * 
 * @author stefan
 * 
 */
public abstract class ProtokollThread<T extends ProtocolDataUnit> extends Thread {
    private static Logger LOG = LoggerFactory.getLogger(ProtokollThread.class);

    /** ob der Thread gerade am laufen ist */
    protected boolean running = false;

    /** der von dem Thread zu ueberwachende Puffer */
    private LinkedList<T> puffer;

    /**
     * Leerer Konstruktur, der nur dann genutzt werden darf, wenn von diesem Thread kein Puffer ueberwacht werden soll.
     * In diesem Fall <b>muss</b> auch die run()-Methode ueberschrieben werden!
     */
    public ProtokollThread() {}

    /**
     * Konstruktur, der ausschliesslich die Initialisierung des zu ueberwachenden Puffers uebernimmt, der als Parameter
     * uebergeben wird.
     * 
     * @param puffer
     *            der zu ueberwachende Puffer
     */
    public ProtokollThread(LinkedList<T> puffer) {
        this.puffer = puffer;
    }

    /**
     * Diese Methode implementiert die Ueberwachung des Puffers in einem eigenen Thread. Solange sich keine Dateneinheit
     * in dem Puffer befindet, geht der Thread in den wartenden Zustand. Wenn eine Dateneinheit eintrifft, wird diese
     * mit dem Aufruf der Methode verarbeiteDatenEinheit() zur weiteren Verarbeitung weitergegeben.
     */
    public void run() {
        LOG.trace(getClass() + " (ProtkollThread), run()");
        while (running) {
            synchronized (puffer) {
                if (puffer.size() < 1) {
                    try {
                        puffer.wait();
                    } catch (InterruptedException e) {}
                }
                if (puffer.size() > 0) {
                    verarbeiteDatenEinheit(puffer.removeFirst());
                }
            }
        }
    }

    /**
     * Diese Methode wird aufgerufen, wenn eine Dateneinheit in dem ueberwachten Puffer eintrifft.
     * 
     * @param datenEinheit
     *            die im ueberwachten Puffer eingetroffene Dateneinheit
     */
    protected abstract void verarbeiteDatenEinheit(T datenEinheit);

    /** Methode fuer den Zugriff auf den zu ueberwachenden Puffer */
    protected LinkedList<T> holeEingangsPuffer() {
        return puffer;
    }

    /**
     * Methode zum Starten des Threads beim Wechsel vom Entwurfs- in den Aktionsmodus. Wenn sich der Thread noch in
     * einem wartenden oder blockierten Zustand wird kein neuer Thread gestartet, sondern lediglich gewaehrleistet, dass
     * der Thread nicht beendet wird.
     */
    public void starten() {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (ProtokollThread), starten()");
        if (!running) {
            running = true;
            if (getState().equals(State.WAITING) || getState().equals(State.BLOCKED)) {
                LOG.debug("ProtokollThread: Thread laeuft bereits.");
            } else {
                start();
            }
        }
    }

    /**
     * Methode zum Beenden des Threads. Wenn der Thread noch in einem wartenden oder blockierten Zustand ist, wird
     * interrupt() aufgerufen, um die Verarbeitung fortzusetzen, damit der Thread dann beendet werden kann.
     */
    public void beenden() {
        LOG.trace("INVOKED (" + this.hashCode() + ", T" + this.getId() + ") " + getClass()
                + " (ProtokollThread), beenden()");
        running = false;
        if (getState().equals(State.WAITING) || getState().equals(State.BLOCKED)) {
            interrupt();
        }
        if (this.puffer != null)
            this.puffer.clear();
    }
}
