/*
 *	MELANGE		- Java-Applet Chat Client (STANDARD EDITION)
 *			  Version 1.01 SE
 *			  Released Feb-23-1999
 *
 *	NOTE: THIS CLIENT IS BASED ON MICHEL KOHN`S JAVACHAT CHAT CLIENT
 *	(C) 1998 by Michael Kohn
 *
 *	Michael Kohn
 * 	http://www.inlink.com/~naken/
 *	Chesterfield, USA
 *
 *	Christian Walter
 *	chris@terminal.at
 * 	http://www.terminal.at/melange
 *	Vienna, Austria
 *
 *      Modified by Steve Narmontas for Manhattan
 *
 **************************************************************************
 *
 *	MClientStandard.java
 *
 *	REVISIONS:
 *		10-10-98 [cw]	original implementation
 *		02-23-99 [cw]	new code from the scratch up
 */

/*******************************************************************************
* IMPORTANT: To stay compatible with older browsers, for example Netscape 3.0  *
* or MS IExplorer 3, the applets are written in JAVA 1.0. If you compile this  *
* code with an "up to date" java-compiler, you'll get warnings, saying that    *
* the client uses a "deprecated API". Please, just ignore these warnings, it   *
* will work anyway - if you try to fix these errors, older browser will not be *
* able to run the client - you have been warned !                              *
*******************************************************************************/

import java.awt.*;
import java.applet.*;
import java.net.*;
import java.lang.*;
import java.io.*;
import java.util.*;

public class MClientStandard extends Applet implements Runnable {
    GridBagConstraints constraints;
    GridBagLayout gridbag;
    static int Online;
    Socket mySocket;
    InputStream myInputStream;
    BufferedInputStream myBuffInput;
    OutputStream myOutputStream;
    Thread myThread;
    Display display;
    TextField tf_UserMsg;
    Choice ch_Style, ch_FontSize;
    String userName;
    URL   main_menu_url;      // sn. param passed to this applet.  Where to go after logout?

    // page user sound added by manhattan developer
    AudioClip hi_hi;
    String hi_file = "harp.au";
    // end of adding


/*******************************************************************************
* The init() - methode is called by the browser at startup. You should build   *
* your GUI here. Below, you'll find a GUI which has buttons for <Logoff>,      *
* <Whois>, <Finger>, <Available Channels>, and <Help>. It also has textfields: *
* one for changing the nickname the other for typing in a message. And of      *
* course, there's a Canvas for displaying the messages.                        *
*******************************************************************************/

    public void init() {
    	Panel NorthPanel=new Panel();
	Panel CenterPanel=new Panel();
	Panel SouthPanel=new Panel();
	Panel WestPanel=new Panel();

	constraints=new GridBagConstraints();
	constraints.weightx=1.0;
	constraints.fill=GridBagConstraints.HORIZONTAL;
	setLayout(new BorderLayout());

      NorthPanel.setLayout(gridbag=new GridBagLayout());
	addComponent(NorthPanel,(new Label("Style",Label.CENTER)),1);
      addComponent(NorthPanel,(ch_Style=new Choice()),1);
	//ch_Style.addItem("Black");
      ch_Style.addItem("Black");
      ch_Style.addItem("Blue");
	addComponent(NorthPanel,(new Label("Size",Label.CENTER)),1);
	addComponent(NorthPanel,(ch_FontSize=new Choice()),1);
	ch_FontSize.addItem("13");
      ch_FontSize.addItem("8");
      ch_FontSize.addItem("10");
	ch_FontSize.addItem("12");
	ch_FontSize.addItem("15");
      addComponent(NorthPanel,(new Label("Melange Client 1.01 (modified for Manhattan by Steve Narmontas)",Label.RIGHT)),constraints.REMAINDER);

      CenterPanel.setLayout(new BorderLayout());
      // load the audio clip added by manhattan developer
      hi_hi = getAudioClip(getCodeBase(), hi_file);
      // modified by manhattan developer
	CenterPanel.add("Center",display=new Display(hi_hi));

	// end

	//CenterPanel.add("East",sb_ScrollBar=new Scrollbar(Scrollbar.VERTICAL));

        SouthPanel.setLayout(gridbag=new GridBagLayout());
        addComponent(SouthPanel,(new Button("Logoff")),1);
        addComponent(SouthPanel,(new Button("Who is with me?")),1);
        addComponent(SouthPanel,(new Button("Who is logged in?")),1);
        addComponent(SouthPanel,(new Button("Available channels")),1);
        addComponent(SouthPanel,(new Button("Help")),constraints.REMAINDER);
        addComponent(SouthPanel,(new Label("Message: ",Label.RIGHT)),1);
        addComponent(SouthPanel,(tf_UserMsg=new TextField("")),constraints.REMAINDER);
        addComponent(SouthPanel,(new Label("Melange Chat Server, (C) 1998, 1999 by Christian Walter  (http://www.terminal.at/melange)",Label.RIGHT)),constraints.REMAINDER);

        WestPanel.setLayout(gridbag=new GridBagLayout());
        addComponent(WestPanel,(new Label("",Label.CENTER)),constraints.REMAINDER);

        add("North",NorthPanel);
        add("Center",CenterPanel);
        add("South",SouthPanel);
	add("West",WestPanel);

	setBackground(Color.lightGray);	setForeground(Color.black);	Online=0;				// Set "Online" to 0 !!!

    }






/*******************************************************************************
* The start() - methode is called by the browser after the client has been     *
* initialised via the init() - method. This method is also called when the     *
* the user pressed the browser's "reload" button.                              *
* (Note: init() is NOT called when the reload button is pressed !)             *
*******************************************************************************/

    public void start() {
	display.init();				// Initialise the Display !!!
	tf_UserMsg.setText("");
	if (Online<2) 				// If we are not already
	    logon();				// "online", we logon now !!
	if (Online==2) {
	    myThread=new Thread(this);		// Start a new thread, which
	    myThread.start();			// will listen on the socket
	}					// for incoming messages. If
    }						// you don't use a own thread
						// for this, the client will
						// block until data is available

/*******************************************************************************
* The stop() - methode is called by the browser if a user leaves the page the  *
* chat-client is implemented in as a applet. We should loggoff if this happens *
*******************************************************************************/

    public void stop() {
	    logoff();
    }

/*******************************************************************************
* The destroy () - methode is called by the browser usually after the stop()   *
* call. You should destroy all resource you allocated ! We do logoff again.    *
*******************************************************************************/

    public void destroy() {
	    logoff();
    }





/*******************************************************************************
* The run()-methode is called when we start the new thread at startup (in the  *
* start() - methode). This part of code is running until we kill the thread.   *
*******************************************************************************/

    public void run() {
	byte[] in=new byte[2048];
	int i,c=0;
	String myString="";

        display.addText("Connecting ...");
        display.disp_msg();

						// If we've got a nickname via
						// the environment, we should
						// tell it the server.
	if (getParameter("UserName")!=null) {

	    try { myOutputStream.write(toByteArray(".n "+getParameter("UserName"))); }
    	    catch (Exception e) {}
	    userName=getParameter("UserName");
	}



      // sn - next 'if' added to record where to return on logout

      if (getParameter("return_url")!=null) {

	    try {main_menu_url = new URL(getDocumentBase(),getParameter("return_url"));}
            catch(MalformedURLException e) {}
	}



						// Listen at the socket until
						// we are killed, and if we
						// find data, call addText()
						// to display it.
	while(true) {
	    if (Online==2) {
    		try { c=myBuffInput.read(in,0,2048); } catch (Exception e) { Online=1; }
    		for(i=0; i<c; i++) {
            	    if (in[i]!='\n' && in[i]!='\r')
			myString+=""+(char)in[i];
		    else {
			if ((myString.length()>5)&&
			    (myString.charAt(0)=='+')&&
			    (myString.charAt(1)=='+')&&
			    (myString.charAt(2)=='+')) {
			    runSysCommand(myString);
			}
			else
        		    display.addText(myString);
			display.disp_msg();
    			if (i+1<c && (in[i+1]=='\r' || in[i+1]=='\n')) i++;
    			    myString="";
    		    }
		}
	    }
    	    else {
		logoff();
	    }
	}
    }

/*******************************************************************************
* The following methods are private and do not display anything. You should    *
* not change them until you really need to.                                    *
*******************************************************************************/

    private byte[] toByteArray(String s) {		// Converts a string to
	byte[] tmp=new byte[2048];			// a byte array.
        int i;

	for(i=0; i<s.length(); i++)
	    tmp[i]=(byte)s.charAt(i);
	return tmp;
    }

    private void addComponent(Panel p, Component c, int w) {	// Helper for
	constraints.gridwidth = w;				// building the
	gridbag.setConstraints(c, constraints);			// GUI.
	p.add(c);
    }

    private void logon() {				// Connect to the chat
	String host=getParameter("host");		// server.
	try {
	    if ((host==null)||(host==""))
		host="localhost";
	    InetAddress server=InetAddress.getByName(host);
	    try {
		mySocket=new Socket(server,Integer.parseInt(getParameter("port")));
		myInputStream=mySocket.getInputStream();
		myBuffInput=new BufferedInputStream(myInputStream,2048);
		myOutputStream=mySocket.getOutputStream();
		Online=2;
	    }
	    catch (SecurityException e) {


		display.addText("-- Connection to MELANGE CHAT SERVER at " + server.getHostAddress() + " failed ! --");
		display.disp_msg();
            display.addText("-- MELANGE info: JAVA SECURITY ERROR (Please see the FAQ for details)");
            display.disp_msg();
		display.addText("-- MELANGE debug: " + e);
            display.disp_msg();
		Online=1;
	    }
	    catch (Exception e) {
		display.addText("-- Connection to MELANGE CHAT SERVER at " + server.getHostAddress() + " failed ! --");
		display.disp_msg();
            display.addText("-- MELANGE debug: " + e);
            display.disp_msg();
		Online=1;
	    }
	}
	catch (Exception e) {
	    display.addText("-- Connection to MELANGE CHAT SERVER failed ! --");
          display.disp_msg();
	    display.addText("-- MELANGE debug: Invalid host name or host not found: " + getParameter("host"));
	    display.disp_msg();
          Online=1;
	}
   }

   private void logoff() {				// Close the connection
	Online=1;   					// to the server.
	myThread.stop();

	try { mySocket.close(); } catch (Exception e) {}


   }

   private void runSysCommand(String seq) {
       String command;
       char commNum;

       commNum=seq.charAt(3);
       command=seq.substring(4);
       switch (commNum) {
           case 'Q': Online=1;
                     tf_UserMsg.setText("Press reload to log in again !");
                     logoff();
		     break;
       }
   }

   public boolean action(Event e, Object o) {		// This is where all the
	String obj;					// event-handling is done
	String message;					// eg. a button is pressed
							// or we've got a new
	obj=o.toString();				// message to send to
	try {						// the server.
    	    if (e.target==tf_UserMsg) { 		// -> New Message in the
    		message=tf_UserMsg.getText();		// MessageField available
    		if (message.startsWith(".n")) {
		    userName=message.substring(3);

		}
		  else
    		if ((message.startsWith("/NICK"))||(message.startsWith("/nick"))) {
		    userName=message.substring(6);

		}
    		myOutputStream.write(toByteArray(message+"\n"));
    		tf_UserMsg.setText("");
            }
    	      else

            if (obj.equals("Who is with me?")) 			// -> Button WHOIS pressed
        	myOutputStream.write(toByteArray(".w\n"));
    	      else
            if (obj.equals("Who is logged in?"))			// -> Button FINGER pressed
        	myOutputStream.write(toByteArray(".f\n"));
    	      else
            if (obj.equals("Available channels"))	// -> Button CHANNELS pressed
		myOutputStream.write(toByteArray(".a\n"));
    	      else
            if (obj.equals("Help"))			// -> HELP Button pressed
		myOutputStream.write(toByteArray(".he\n"));
    	      else
            if (obj.equals("Logoff")) { 		// -> Button LOGOFF pressed
    		myOutputStream.write(toByteArray(".qu\n"));
    		Online=1;
    		tf_UserMsg.setText("Press reload to log in again !");
		getAppletContext().showDocument(main_menu_url);
	    }
	     /* else
            if (obj.equals("Black")) {
		display.color_Msg=Color.white;
		display.color_Dsp=Color.black;
            display.addText(" ");
		display.disp_msg();
            }*/
    	      else
            if (obj.equals("Black")) {
		display.color_Msg=Color.black;
		display.color_Dsp=Color.white;
		display.addText(" ");
		display.disp_msg();

            }
    	      else
            if (obj.equals("Blue")) {
		display.color_Msg=Color.blue;
		display.color_Dsp=Color.white;
		display.addText(" ");
		display.disp_msg();

            }
	    if (obj.equals("8")) {
    		display.setMyFont("Dialog",8,Font.BOLD);
		display.addText(" ");
		display.disp_msg();

            }
	    if (obj.equals("10")) {
    		display.setMyFont("Dialog",10,Font.BOLD);
		display.addText(" ");
		display.disp_msg();

            }
	    if (obj.equals("12")) {
    		display.setMyFont("Dialog",12,Font.BOLD);
		display.addText(" ");
		display.disp_msg();

            }
	    if (obj.equals("13")) {
    		display.setMyFont("Dialog",13,Font.BOLD);
		display.addText(" ");
		display.disp_msg();

            }
	    if (obj.equals("15")) {
    		display.setMyFont("Dialog",15,Font.BOLD);
		display.addText(" ");
		display.disp_msg();

            }
	}
	catch (Exception ex) {}
        return false;
    }

   /* public boolean handleEvent(Event e) {
        if (e.target==sb_ScrollBar) {
	   if (display.scrolloffset!=sb_ScrollBar.getValue()) {
    		display.scrolloffset=sb_ScrollBar.getValue();
		display.repaint();
    	    }
    	    return true;
	}
        else
	    return super.handleEvent(e);
    }*/

}

/*******************************************************************************
* The Display() - class is responsible for displaying all messages onto        *
* our display. Beside the routines for displaying the stuff, there are also    *
* methods for setup and initializing, eg. setting the font, etc.               *
* This class must be changed if you want to use another GUI, other colors, etc *
*******************************************************************************/

class Display extends Panel {
    Font myFont;
    FontMetrics fm;
    Color color_SysMsg, color_PrivMsg, color_ErrMsg, color_Msg, color_Dsp;
    int strSize;
    String received_str;
    // display messge
    TextArea main_text;
    // added by manhattan developer
    AudioClip hi_hi;
    GridBagConstraints constraints;

/*******************************************************************************
* Constructor Display() - This method is called from the init()-method of the  *
* main-class, that means only once at startup !                                *
*******************************************************************************/
    // modified the constructor for passing the AudioClip
    Display(AudioClip sound) {
	constraints=new GridBagConstraints();
	constraints.weightx=1.0;
      constraints.weighty=1.0;

	//constraints.fill=GridBagConstraints.HORIZONTAL;
      constraints.fill=GridBagConstraints.BOTH;


	color_SysMsg=new Color(0,190,0);		// System Messages (green)
	color_PrivMsg=new Color(30,30,255);		// Private Messages (blue)
	//color_ErrMsg=Color.red;				// Error Messages (red)
	color_Msg=Color.black;				// Default Lines (white)
	color_Dsp=Color.white;				// Display Color (black)
	main_text = new TextArea("",16,50,TextArea.SCROLLBARS_VERTICAL_ONLY);
	main_text.setEditable(false);
      main_text.setForeground(color_Msg);
      main_text.setBackground(color_Dsp);

      setLayout(new GridBagLayout());
      add(main_text, constraints);
	strSize = 0;
      received_str = "";
	hi_hi = sound;
	// end of modification

    }

/*******************************************************************************
* The init() - methode is called by the start()-method of the main-class.      *
* That means it is called AFTER init() at startup and every time               *
* the user pressed the browser's "reload" button.                              *
*******************************************************************************/

    public void init() {
	setMyFont("Dialog",13,Font.BOLD);
    }

/*******************************************************************************
* The addText(String text) - methode is called from the main-class every time  *
* the message passed via <text> should be displayed on the Display. It doesn't *
* include any format or something, it's just the plain message coming from the *
* server.                                                                      *
*******************************************************************************/

    public void addText(String text) {

	/* for bell added by manhattan developer */
	int beep_flag = 0, add_flag = 0, i, j = 0;

	int len = text.length();

	for(i = 0; i < len; i++)
	{
	   if(text.charAt(i)==':' &&
	      ((i+ 1) <len && text.charAt(i+1) == ':' ) &&
	      ((i+ 2) <len && text.charAt(i+2) == ':' ) &&
	      ((i+ 3) <len && text.charAt(i+3) == ':' ))
	    {
	       beep_flag = 1;
	       add_flag = 1;
	       i = i + 3;
	    }
	   if(add_flag == 0)
         {
	     received_str += text.charAt(i);
           j++;
          }
	   if(add_flag == 1)
		add_flag = 0;
	}
      if(j>1)
        received_str += "\n";
      strSize = strSize + received_str.length();

   // added by manhattan developer
   if(beep_flag == 1)
       hi_hi.play();

}

/*******************************************************************************
* The setMyFont(...) - methode is used to set the font to be used to display   *
* the messages on the dispaly.                                                 *
*******************************************************************************/

    public void setMyFont(String mFontName, int mFontHeight, int mFontStyle) {


	if ((mFontName==null)||(mFontName==""))
	    mFontName="Default";

	myFont=new Font(mFontName,mFontStyle,mFontHeight);
        main_text.setFont(myFont);

    }

/*******************************************************************************
* The paint(...) - methode is responsible to display the right lines on the    *
* display. If there is no scrollbar, these are always the last x lines, other  *
* we have to show the right part from the textbuffer. THIS is the ONLY method  *
* which actually really DRAWS on the display !                                 *
*******************************************************************************/

    public void disp_msg() {
	/*if (received_str.startsWith(">")) main_text.setForeground(color_SysMsg);
	    else if (received_str.startsWith("<")) main_text.setForeground(color_PrivMsg);
	    else if (received_str.startsWith("-")) main_text.setForeground(color_ErrMsg);*/
	    main_text.setForeground(color_Msg);
	    main_text.append(received_str);
	    main_text.setCaretPosition(strSize);
          received_str = "";
//hi_hi.play();


    }

}
