/**
	class:				BCC
	language:       	java 1.1.5
	compiler:     		sun jdk 1.1.5
	target:				windows 95/multiplatform
	tested:				windows NT
	author:         	Ing. Christian Groesswang
	extends:        	frame
	implements:			actionlistener, windowlistener, keylistener
	info:				Hauptbildschirm
	date:           	1998-05-04
	last edit:	        1998-05-15 gc
	release:			0.1.05
**/

import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import java.util.*;


/*
   WICHTIG: um deadlocks zu vermeiden, wurden alle funktionen atomar gestaltet und 
			duerfen keine anderen resourcen anfordern !!!
*/

public class BCC extends Frame  implements ActionListener, WindowListener, KeyListener
{
	// Componenten der Oberflaeche
    protected	TextField	oTxtIn;		// Eingabefeld
    protected	TextArea	oTxtOut;	// Ausgabefeld
    protected	Button 		bQuit;		// Knopf beenden
    protected	Button 		bNew;		// Knopf Neu
    protected	Button 		bList;		// Knopf List
    protected	Button 		bJoin;		// Knopf Join
    protected	Button 		bLeav;		// Knopf Leav

	final Object semOut = "semOut";	// Semaphore fuer Textausgabe
	final Object semLog = "semLog";	// Semaphore fuer Log

	// Verbindungsdaten
	BC_Connection 			cServer;	// Verbindung zum Server
	String					sName;		// ServerName
	int						iPort;		// ServerPort
	OutputStream			sLog;		// LogFile

	// Voreinstellungen
	int 		iTextWidth = 80;		// Spalten der Textfelder
	String 		copyright="BrabbleClient-Applet v0.01 by Ing. Christian Groesswang, chris@very.priv.at";
	PrintWriter logStream;               // Where we send our logging output to



/********************************************************************
							M A I N
*********************************************************************/

/**
	function:		main
	return:			null
	info:         	intialisiert das Hauptprogramm
	date:           1998-05-04
	last edit:      1998-05-06 gc
**/
    public static void main(String args[])
    {
		// Kommandozeile auswerten
	    try 
		{
    		if (args.length != 3)  // Check number of arguments
	        	throw new IllegalArgumentException("invalid number of parameters");
      
			String sName = args[0];
			int iPort = Integer.parseInt(args[1]);
			String sNick = args[2];

			// logDatei oeffnen
			FileOutputStream logFile = new FileOutputStream("BCC.log");

			// ChatClient starten
			new BCC(sName, iPort, sNick, logFile);
        } // try 
    	catch (Exception e) 
		{ // Display a message if anything goes wrong
			System.err.println("ERROR : "+e.getMessage());
		    System.err.println("Usage: java BCC <server> <port> ");
			System.exit(1);
    	} // catch


    } /** end main **/


/********************************************************************
		BCC
*********************************************************************/

/**
	constructor:    BCC
	info:         	erstellt das Hauptfenster
	date:           1998-05-04
	last edit:      1998-05-04 gc
**/
    public BCC(String sServerName, int iServerPort, String sNickname, OutputStream logStream)
    {
		// Form definieren
        super("BCC - Brabble Chat Client");

		// Variablen merken
		this.sLog = logStream;
		this.sName = sServerName;
		this.iPort = iServerPort;


		// LogStream definieren
		setLogStream(logStream);

        addNotify();
        setSize(600, 400);
		this.setLayout(new GridBagLayout());
		GridBagConstraints cGBC = new GridBagConstraints();
		cGBC.fill = GridBagConstraints.BOTH;	
		cGBC.insets = new Insets(5,5,5,5);


		// Elemente der Form definieren
		oTxtIn = new TextField("",iTextWidth);
    	oTxtIn.setFont(new Font("MonoSpaced", Font.PLAIN, 10));

		oTxtOut = new TextArea("",10,iTextWidth);
    	oTxtOut.setFont(new Font("MonoSpaced", Font.PLAIN, 10));
		oTxtOut.setEditable(false);

		bQuit=new Button("Exit");
		bNew=new Button("Neu");
		bList=new Button("List");
		bJoin=new Button("Join");
		bLeav=new Button("Leav");


		// Elemente hinzufuegen
		cGBC.gridx = 0;
		cGBC.gridy = 0;
		cGBC.gridwidth = 4;
		cGBC.gridheight = 9;
		cGBC.weightx = 1;
		cGBC.weighty = 1;
		this.add(oTxtOut, cGBC);

		cGBC.gridx = 0;
		cGBC.gridy = 9;
		cGBC.gridwidth = 1;
		cGBC.gridheight = 1;
		cGBC.weightx = 0;
		cGBC.weighty = 0;
		this.add(oTxtIn, cGBC);

		cGBC.gridx = 4;
		cGBC.gridy = 9;
		cGBC.gridwidth = 1;
		cGBC.gridheight = 1;
		this.add(bQuit, cGBC);

		cGBC.gridx = 4;
		cGBC.gridy = 10;
		cGBC.gridwidth = 1;
		cGBC.gridheight = 1;
		this.add(bNew, cGBC);

		cGBC.gridx = 4;
		cGBC.gridy = 0;
		cGBC.gridwidth = 1;
		cGBC.gridheight = 1;
		this.add(bJoin, cGBC);

		cGBC.gridx = 4;
		cGBC.gridy = 1;
		cGBC.gridwidth = 1;
		cGBC.gridheight = 1;
		this.add(bLeav, cGBC);

		cGBC.gridx = 4;
		cGBC.gridy = 2;
		cGBC.gridwidth = 1;
		cGBC.gridheight = 1;
		this.add(bList, cGBC);

		// Listener definieren
		oTxtIn.addKeyListener(this);
		bQuit.addActionListener(this);
		bNew.addActionListener(this);
		bJoin.addActionListener(this);
		bLeav.addActionListener(this);
		bList.addActionListener(this);
        this.addWindowListener(this);


		// Verbindung anlegen
		cServer=new BC_Connection(sServerName, iServerPort, sNickname);

		// Reader starten
		BC_Reader cReader=new BC_Reader(cServer);
		cReader.start();
		
		// Form anzeigen
        setVisible(true);
    } /** end constructor BCC  **/


/**
	function:		quit
	info:         	beendet BCC
	date:           1998-05-06
**/

	public void quit()
	{
		this.cServer.writeLine("quit");
		System.exit(0);
	} /** quit **/



/********************************************************************
		Listener
*********************************************************************/


/**
	function:		verschiedene
	return:			null
	info:         	methoden fuer den windowlistener
	date:           1998-05-04
	last edit:      1998-05-04 gc

**/
    public void windowClosed(WindowEvent event)
    {
        this.quit();
    } /** end Closed **/

    public void windowDeiconified(WindowEvent event)
    {
    } /** end Deiconified **/

    public void windowIconified(WindowEvent event)
    {
    } /** end windowIconified **/

    public void windowActivated(WindowEvent event)
    {
    } /** end windowActivated **/

    public void windowDeactivated(WindowEvent event)
    {
    } /** end windowDeactivated **/

    public void windowOpened(WindowEvent event)
    {
    } /** end windowOpened **/

    public void windowClosing(WindowEvent event)
    {
        dispose();
    } /** end windowsClosing **/

/**
	function:		actionperformed
	return:			null
	info:         	methode fuer den actionlistener
	date:           1998-05-04
	last edit:      1998-05-04 gc
**/
    public void actionPerformed(ActionEvent event)
    {
        Object source = event.getSource();
        if (source == bQuit)
        {
            setVisible(false);
            dispose();
            this.quit();
        }
        else if (source == bJoin)
        {
			this.cServer.writeLine("LIST");
        }
        else if (source == bLeav)
        {
			this.cServer.writeLine("LEAV "+this.cServer.sChannel);
			this.cServer.sChannel = null;
        }
        else if (source == bList)
        {
			this.cServer.writeLine("LIST");
        }
        else if (source == bNew)
        {
			// Neuen BCC starten
			new BCC(sName, iPort, cServer.sNickname, sLog);
        }
    } /** end actionPerformed **/

/**
	function:		verschiedene
	return:			null
	info:         	methoden fuer den keylistener
	date:           1998-05-04
	last edit:      1998-05-04 gc
**/

    public void keyPressed(KeyEvent event)
    {
        Object source = event.getSource();
        int keyInt = event.getKeyCode();
        if ((source == oTxtIn) && (keyInt == KeyEvent.VK_ENTER))
        {
            // Eingabe absenden
			this.cServer.writeLine(oTxtIn.getText());
			oTxtIn.setText("");
        }
    } /** end keyPressed **/

    public void keyReleased(KeyEvent event)
    {
    } /** end keyReleased **/

    public void keyTyped(KeyEvent event)
    {
    } /** end keyTyped **/


/********************************************************************
		Sub-Klassen
*********************************************************************/

/**
	class:          	BCC_InfoScreen
	author:         	Ing. Christian Groesswang
	date:           	1998-05-15
	last edit:	        1998-05-15 gc
	info:				Frame zum ANzeigen von Infos
	release:			0.1.01
**/

class BCC_InfoScreen extends Frame implements ActionListener, WindowListener, KeyListener
{

	// Componenten der Oberflaeche
    protected	List		lChannels;	// Eingabefeld
    protected	Button 		bJoin;		// Knopf JOIN
    protected	Button 		bCancel;	// Knopf CANCEL
	BC_Connection sCon;

/**
	constructor:    BCC_InfoScreen
	info:         	erstellt das Hauptfenster
	date:           1998-05-15
	last edit:      1998-05-15 gc
**/
    public BCC_InfoScreen(BC_Connection sCon)
    {
		this.sCon = sCon;
		// Form definieren
        this.setTitle("Channels");

        addNotify();
        setSize(200, 200);
		this.setLayout(new GridBagLayout());
		GridBagConstraints cGBC = new GridBagConstraints();
		cGBC.fill = GridBagConstraints.BOTH;	
		cGBC.insets = new Insets(5,5,5,5);


		// Elemente der Form definieren
		lChannels = new List(15);
  		lChannels.setFont(new Font("MonoSpaced", Font.PLAIN, 10));

		bJoin=new Button("Join");
		bCancel=new Button("Cancel");

		// Elemente hinzufuegen
		cGBC.gridx = 0;
		cGBC.gridy = 0;
		cGBC.gridwidth = 4;
		cGBC.gridheight = 4;
		cGBC.weightx = 1;
		cGBC.weighty = 1;
		this.add(lChannels, cGBC);

		cGBC.gridx = 4;
		cGBC.gridy = 0;
		cGBC.gridwidth = 1;
		cGBC.gridheight = 1;
		this.add(bJoin, cGBC);

		cGBC.gridx = 4;
		cGBC.gridy = 1;
		cGBC.gridwidth = 1;
		cGBC.gridheight = 1;
		this.add(bCancel, cGBC);


		// Listener definieren
		bJoin.addActionListener(this);
		bCancel.addActionListener(this);
        this.addWindowListener(this);

    } /** end constructor BCC_InfoScreen  **/


/**
	function:		addChannel
	info:         	fuegt channel in LB hinzu
	date:           1998-05-15
**/

	public void addChannel(String ch)
	{
		this.lChannels.addItem(ch);
	} /** addChannel **/

/**
	function:		clear
	info:         	loescht LB 
	date:           1998-05-15
**/

	public void clear()
	{
		this.lChannels.clear();
	} /** addChannel **/



/********************************************************************
		Listener
*********************************************************************/


/**
	function:		verschiedene
	return:			null
	info:         	methoden fuer den windowlistener
	date:           1998-05-15
	last edit:      1998-05-15 gc

**/
    public void windowClosed(WindowEvent event)
    {
    } /** end Closed **/

    public void windowDeiconified(WindowEvent event)
    {
    } /** end Deiconified **/

    public void windowIconified(WindowEvent event)
    {
    } /** end windowIconified **/

    public void windowActivated(WindowEvent event)
    {
    } /** end windowActivated **/

    public void windowDeactivated(WindowEvent event)
    {
    } /** end windowDeactivated **/

    public void windowOpened(WindowEvent event)
    {
    } /** end windowOpened **/

    public void windowClosing(WindowEvent event)
    {
        dispose();
    } /** end windowsClosing **/

/**
	function:		actionperformed
	return:			null
	info:         	methode fuer den actionlistener
	date:           1998-05-04
	last edit:      1998-05-04 gc
**/
    public void actionPerformed(ActionEvent event)
    {
        Object source = event.getSource();
        if (source == bJoin)
        {
			// JOIN wurde gedrueckt!
			String selectedChannel = lChannels.getSelectedItem();

			if (selectedChannel == null) return;
			// Wenn noch angemeldet -> abmelden
			if (sCon.sChannel!=null) 
			{
				sCon.writeLine("LEAV "+sCon.sChannel);
			} // if
			// im ausgewaehlten Channel anmelden
			sCon.writeLine("JOIN "+selectedChannel);
			sCon.sChannel=selectedChannel;
			lChannels.clear();
	        this.hide();
            dispose();
        }
        else if (source == bCancel)
        {
			lChannels.clear();
	        this.hide();
            dispose();
        }
    } /** end actionPerformed **/

/**
	function:		verschiedene
	return:			null
	info:         	methoden fuer den keylistener
	date:           1998-05-04
	last edit:      1998-05-04 gc
**/

    public void keyPressed(KeyEvent event)
    {
        Object source = event.getSource();
        int keyInt = event.getKeyCode();
    } /** end keyPressed **/

    public void keyReleased(KeyEvent event)
    {
    } /** end keyReleased **/

    public void keyTyped(KeyEvent event)
    {
    } /** end keyTyped **/

} /** end BC_InfoScreen **/


/**
	class:          	BC_Reader
	author:         	Ing. Christian Groesswang
	date:           	1998-05-04
	last edit:	        1998-05-06 gc
	info:				Thread zum auslesen des Sockets
	release:			0.1.01
**/

class BC_Reader extends Thread
{

	BC_Connection	sCon;		// Verbindung zum Server

/**
	constructor:        BC_Reader
	info:               legt einen Thread an
	date:               1998-05-06
**/


	public BC_Reader(BC_Connection	sCon)
	{
		this.sCon = sCon;
	}

   	public void run()
    {
       	/** Das ist die eigentliche Routine eines Threads, diese wird beim Start
		eines Threads ausgeführt.                                            **/

		String sLine=new String();		// eingelesene Zeile
		String sCmd=new String();		// Kommando in der Zeile
		String sNickname=new String();	// Nickname aus der Zeile
		String sChannel=new String();	// Channel aus der Zeile
		String sData=new String();		// Text aus der Zeile
		String sRC=new String();		// Return Code
		String sTmp=new String();
		int i,j;
		int aktMode = 0;
		int lastMode = 0;

		Color cData = new Color(0,0,255);
		Color cCmd 	= new Color(0,255,0);

		BCC_InfoScreen scrChannels = new BCC_InfoScreen(sCon);

        log("Reader Thread started");
        try
	    {
			// Now read the server's response and display it in the textarea
      		while((sLine = sCon.readLine()) != null) 
			{
				// Zeile wurde gelesen, jetzt parsen auf Commandos
				sCmd = sLine.substring(0,4);
				if (sCmd.equalsIgnoreCase("DATA"))
				{
					aktMode = 1;
					// Befehl verwerfen
					i = sLine.indexOf(' ');
					sTmp = sLine.substring(i+1);
					// Channel ermitteln
					i = sTmp.indexOf(' ');
					sChannel = sTmp.substring(0,i);
					sTmp = sTmp.substring(i+1);
					// Nickname ermitteln
					i = sTmp.indexOf(' ');
					sNickname = sTmp.substring(0,i);
					sData = sTmp.substring(i+1);
					myPrintln(sNickname+":"+ sData);
					log(sRC+":"+sCmd+" ("+sChannel+"/"+sNickname+"/"+sData+")");
				}
				else if (sCmd.equalsIgnoreCase("HELO"))
				{
					aktMode = 2;
					i = sLine.lastIndexOf(' ');
					sRC = sLine.substring(i+1);
					sData = sLine.substring(5,i);
					myPrintln("HELO:"+ sData);
					log(sRC+":"+sCmd+" ("+sData+")");
				}
				else if (sCmd.equalsIgnoreCase("NICK"))
				{
					aktMode = 3;
					i = sLine.lastIndexOf(' ');
					sRC = sLine.substring(i+1);
					sData = sLine.substring(5,i);
					myPrintln("NICK:"+ sData);
					log(sRC+":"+sCmd+" ("+sData+")");
				}
				else if (sCmd.equalsIgnoreCase("LIST"))
				{
					// Es wurde ein LIST empfangen
					if (lastMode!=4) scrChannels.clear();
					aktMode = 4;
					scrChannels.show();
					sData = sLine.substring(5);
					scrChannels.addChannel(sData);
				}
				else if (sCmd.equalsIgnoreCase("JOIN"))
				{
					// Es wurde ein JOIN empfangen
					aktMode = 5;
					// Befehl verwerfen
					i = sLine.indexOf(' ');
					sTmp = sLine.substring(i+1);
					// Channel ermitteln
					i = sTmp.indexOf(' ');
					sChannel = sTmp.substring(0,i);
					sTmp = sTmp.substring(i+1);
					// Nickname ermitteln
					i = sTmp.indexOf(' ');
					sNickname = sTmp.substring(0,i);
					myPrintln(sNickname+" has joined the channel");
					log(sRC+":"+sCmd+" ("+sChannel+"/"+sNickname+")");
				}
				else if (sCmd.equalsIgnoreCase("LEAV"))
				{
					// Es wurde ein LEAV empfangen
					aktMode = 5;
					// Befehl verwerfen
					i = sLine.indexOf(' ');
					sTmp = sLine.substring(i+1);
					// Channel ermitteln
					i = sTmp.indexOf(' ');
					sChannel = sTmp.substring(0,i);
					sTmp = sTmp.substring(i+1);
					// Nickname ermitteln
					i = sTmp.indexOf(' ');
					sNickname = sTmp.substring(0,i);
					myPrintln(sNickname+" has left the channel");
					log(sRC+":"+sCmd+" ("+sChannel+"/"+sNickname+")");
				}
				else
				{
					myPrintln("unknown Cmd: ("+ sLine+")");
					log("ERROR:"+sCmd+" ("+sLine+")");
				} // if

				lastMode=aktMode;
			} // while
			log("Connection closed by Server");
		} // try

    	catch (IOException e)
	    {
           /** Auf eine Unterbrechung reagieren **/
			handleError("Reader ERROR",e,-1);
	    } // catch
        log("Reader Thread stopped");
		this.stop();
    } /** End public void run() **/
	
} /** end BC_Reader **/

/**
	class:          	BC_Connection
	author:         	Ing. Christian Groesswang
	date:           	1998-05-04
	last edit:	        1998-05-06 gc
	info:				Objekt fuer eine Verbindung
	release:			0.1.01
**/

class BC_Connection
{
	String	sServerName;			// Hostname des BC-Servers
	int		iServerPort;			// Port des BC-Servers
	protected Socket	cSocket;	// Socket-Verbindung zum BCS
	protected BufferedReader in;	// Eingabestrom aus dem Socket
	protected PrintWriter out;		// Ausgabestrom in den Socket
	public 	String	sNickname;		// Nickname der aktuellen Verbindung
	public 	String	sChannel;		// Channel der aktuellen Verbindung

	final Object inStream = "inStream";
	final Object outStream = "outStream";

/**
	constructor:        BC_Connection
	info:               legt eine Verbindung an
	date:               1998-05-06
**/
	
	public BC_Connection(String sName, int iPort, String sNick)
	{
		// Variablen Speichern
		this.sServerName=sName;
		this.iServerPort=iPort;
		this.sNickname = sNick;
		this.sChannel=null;
	    try 
		{
			// Socket anlegen
			this.cSocket = new Socket(sName, iPort);

			// Ein-/AusgabeStreams oeffnen
			in = new BufferedReader(new InputStreamReader(this.cSocket.getInputStream()));
			out = new PrintWriter(new OutputStreamWriter(this.cSocket.getOutputStream()));
		}
	    catch (IOException e) 
		{ 
			handleError("ERROR creating Socket",e,-1);
    	} // catch
	} /** end constructor BC_Connection **/

/**
	function:		close
	info:         	schliesst die Verbindung
	date:           1998-05-06
**/

	public void close()
	{
	    try 
		{
			// Socket schliessen
			this.cSocket.close();
		} // try
	    catch (IOException e) 
		{ 
			handleError("ERROR closing Socket",e,-1);
    	} // catch
		
	} /** close **/

/**
	function:		readLine
	info:         	liest aus dem Socket
	date:           1998-05-06
**/

	public String readLine() throws IOException
	{
	
		if (this.in==null) return null;

		String line;
	    try 
		{
			// Socket auslesen
			synchronized(inStream)
			{
				line = this.in.readLine();
			} // synchronized
		} // try
	    catch (IOException e) 
		{ 
			handleError("ERROR reading Socket",e,-1);
			line = null;
    	} // catch
		
		System.out.println("READ :"+line);
		return line;

	} /** readLine **/


/**
	function:		writeLine
	info:         	schreibt in den Socket
	date:           1998-05-06
**/

	public void writeLine(String line)
	{
		System.out.println("WRITE:"+line);
		synchronized(outStream)
		{
			this.out.println(line );
			this.out.flush();
		} // synchronized
	} /** writeLine **/

}



/********************************************************************
		Hilfsfunktionen
*********************************************************************/

/**
	function:		setVisible
	return:			null
	info:         	positioniert und zeigt das Fenster
	date:           1998-05-04
**/
    public void setVisible(boolean b)
    {
        setLocation(50, 50);
        super.setVisible(b);
    } /** end setVisible **/


/**
	function:		setLogStream
	return:			null
	info:         	oeffnet einen Ausgabe-Stream zur Protokollierung
	date:           1998-05-05
**/

	public void setLogStream(OutputStream out)
	{
		if (out != null) 
		{
			logStream = new PrintWriter(new OutputStreamWriter(out));
		}
		else
		{
			logStream = null;
		} // if
	} // setLogStream

/**
	function:		log
	return:			null
	info:         	schreibt eine Ausgabe ins Log
	date:           1998-05-05
**/
    public void log(String s)
    {
		synchronized(semLog)
		{
			if (logStream != null)
			{
				logStream.println("["+ new Date() + "] "+s);
				logStream.flush();
			} // if
		} // synchronized
    } /** end log **/

    public void log(Object o)
    {
		log(o.toString());
    } /** end log **/


/**
	function:		handleError
	return:			null
	info:         	Zeigt eine Fehlermeldung an und reagiert darauf
	date:           1998-05-05
**/


    public void handleError(String s1, Exception e, int errLevel)
    {
		System.err.println(s1+" : "+e.getMessage());
		log(s1+" : "+e.getMessage());
		if (errLevel>=0)
		{
			System.exit(errLevel);
		} // if
    } /** end handleError **/


    public void myPrintln(String s)
    {
		if (oTxtOut != null)
		{
			synchronized(semOut)
			{
				oTxtOut.append(s+"\n");
			} // synchronized
		} // if
    } /** end myPrintln **/


} /** end BCC **/



