package network;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import other.ConnectionNotifier;
import other.Timer;
import other.TimerListener;
 
/**
 * Network Client Klasse
 * Basisklasse, über die die Client/Server Kommunikation für Kommandos
 * und Nachrichten abgewickelt wird. 
 * Zuständig für Senden u. Empfangen der Daten ,Queuemanagement
 */
public class NClient implements TimerListener
{
	//Konstanten 
    private final int S_SIZE = 12800;
    private final int READ_BUFFER = 255;
    private final int PORT = 8079;
    
    //Attribute
    boolean bIsConnected = false;
	boolean bIsInPack; // gibt an, ob das Programm noch auf einen Endteil wartet
	byte[] bBuf = new byte[READ_BUFFER]; // gesamter Einlesepuffer
	int iBufLen; // Pointer auf das erste freie Zeichen im Puffer
    private String sAddress;
	
    private Socket sSocket;
    private InputStream inStream;
    private OutputStream outStream;
    private Queue comQueue;
    private Queue mesQueue;

    private ConnectionNotifier networkHandler;
    
    /**
     * Konstruktor zum initialisieren der Queues
     * @param cQueue Commandqueue
     * @param mQueue Messagequeue
	 * @param netH NetworkHandler Objekt zur Statusmeldung
     */
    public NClient(Queue cQueue, Queue mQueue, ConnectionNotifier netH){
    	comQueue=cQueue;
    	mesQueue=mQueue;
    	networkHandler=netH;
    }

	/* ##########################################################################
	 * 						CONNECTION METHODEN
	 * ##########################################################################*/	    
    
    /**
     * Verbindet den Client mit dem Server
     * @param pAddress Adresse des Servers
     * @return true wenn die Verbindung erfolgreich hergestellt wurde
     */
    public boolean connect(String pAddress){
    	// Sicherheitsabfrage
    	if(bIsConnected){
    		System.out.println("Fehler connect(): bereits verbunden");
    		return false;
    	}
    	
    	sAddress = pAddress;
    	try{
    		sSocket = new Socket(InetAddress.getByName(pAddress), PORT);
    		inStream = new BufferedInputStream(sSocket.getInputStream(), S_SIZE);
    		outStream = new BufferedOutputStream(sSocket.getOutputStream(), S_SIZE);
        }catch(Exception e){
        	System.out.println("Cmd Connection konnte nicht hergestellt werden!");
        	return false;
        }
        
        // Refresh Database initieren
        comQueue.enqueue(new Command(NetPack.REFRESH_DATABASE));
        
        //DEBUG
        System.out.println("Client connected to :" + pAddress); 
        bIsConnected = true;
        networkHandler.connectionNotification();
        return true;
    }

    /**
     * schließt die Verbindung zum Server
     * @return true wenn die Verbindung erfolgreich beendet wurde
     */
    public boolean disconnect(){
    	if(!bIsConnected) return true;
    	bIsConnected = false;
        try{
        	sSocket.close();
        	inStream.close();
        	outStream.close();
        }catch(Exception e){
        	//e.printStackTrace();
        	return false;
        }
        networkHandler.connectionNotification();
        //DEBUG
        System.out.println("Client disconnected!");
        return true;
    }

    /**
     * Liefert zurück, ob man gerade verbunden ist
     * @return true wenn verbunden, sonst false
     */
    public boolean isConnected(){
    	return bIsConnected;
    }
    
    /**
     * Versucht durch senden eines sinnlosen Paketes herauszufinden,
     * ob die Verbindung noch aufgebaut ist
     */
    public void checkConnection(){
        try{
        	byte x[] = new byte[2];
        	x[0]=0x02;
        	x[1]=0x03;
        	outStream.write(x,0,2);
        	outStream.flush();
        }catch(Exception e){
        	disconnect();
        } 
    }
    
	/* ##########################################################################
	 * 						ÖFFENTLICHE METHODEN
	 * ##########################################################################*/	
    
    /**
     * Sendet die Daten an den Server
     * @param nPack beinhaltet das Byte-Array das gesendet wird
     */
    public void sendData(NetPack nPack){
    	// Sicherheitsabfrage
    	if(!bIsConnected){
    		System.out.println("Fehler sendData(): nicht verbunden");
    		return;
    	}

        try{
        	//System.out.println(nPack.getNData());
        	outStream.write(nPack.getNData(),0,nPack.getPackLen()); //schreiben der Daten
        	outStream.flush();
        }catch(Exception e){
        	System.out.println("Transmission Error!");
        	e.printStackTrace();
        	disconnect();
        }
    }
    /**
     * Liest (wenn möglich) die Daten aus dem Netzwerk ein.
     * Die Aufforderung kommt von der Timer-Funktion
     */
    public void readData(){
    	// Sicherheitsabfrage
    	if(!bIsConnected){
    		System.out.println("Fehler readData(): nicht verbunden");
    		return;
    	}
    	
    	byte[] bBigBuf = new byte[READ_BUFFER];
    	int iSize=0;	
     
    	try{
    		iSize=inStream.read(bBigBuf);    	
    	}catch(Exception e){
    		System.out.println("Fehler beim Lesen des InputStreams!");
        	disconnect();
    		return;
    	}

    	byte[] bPackBuf = new byte[32];
    	int iStart=0;

    	// Daten zum Buffer hinzufgen
    	for(int i=0;i<iSize;i++) bBuf[iBufLen+i]=bBigBuf[i];
    
    	// Buf-Size aktualisieren
    	iBufLen+=iSize;
    	bBuf[iBufLen]=0;
    	bBuf[iBufLen+1]=0;

    	// Gesamten Buffer durchlaufen
    	for(int i=0;i<iBufLen;i++){
    		// Anfang vom Package gefunden
    		if(bBuf[i]==0x02){
    			bIsInPack=true;
    			iStart=i;
    		}
    		// Ende vom Package gefunden
    		if(bBuf[i]==0x03){
    			bIsInPack=false;
    			// Array kopieren
    			for(int j=0;j<=(i-iStart);j++)
    				bPackBuf[j]=bBuf[iStart+j];
    			bPackBuf[i-iStart+1]=0;

    			// neues MessagePackage erzeugen und speichern
    			mesQueue.enqueue(new NetMessage(bPackBuf,i-iStart+1));
    			iStart=i;
    		}
    	}
    	
    	// Gesamter Puffer durchlaufen
    	// evtl. Daten nach vorne kopieren
    	if(bIsInPack){
    		if(iStart>3){
    			int i;
    			for(i=0;(i<iStart)&&(iStart+i<255);i++){
    				bBuf[i]=bBuf[iStart+i];
    				bBuf[iStart+i]=0;
    				if(bBuf[i]==0){
    					iBufLen=i;
    					break;
    				}
    			}
    		}
    	}else{
    		iBufLen=0;
    	}
    }
    
    /**
     * Timer Funktion, die in bestimmten Zeitintervall aufgerufen
     * wird u. die Client/Server Kommunikation abwickelt
     * @param t Timerobjekt von dem die Methode aufgerufen wird
     */
    public void timerAction(Timer t){
    	// Sicherheitsabfrage
    	if(!bIsConnected || !sSocket.isConnected()){
    		t.stop();
    		disconnect();
    		return;
    	}

    	// Kontrolle, ob Daten da sind 
    	try{
    		// Kontrolle, ob Daten gekommen sind
    		// gegebenenfalls Aufruf der read-Funktion
    		if(inStream.available()>0) readData();
    	} catch(Exception e){
    		System.out.println("Lesen vom Netzwerk nicht möglich!");
        	disconnect();
    	}
    	
    	// Message-Queue abarbeiten (nach oben weiterreichen)
    	while(mesQueue.size()>0)
    		networkHandler.messageEvent(new Message((NetMessage)mesQueue.dequeue()));
     
    	// Kommando-Queue abarbeiten
    	while(comQueue.size()>0)
    		sendData(new NetPack((Command)comQueue.dequeue()));
    }
        
}

