package org.jcon.test.comm;

import org.jcon.ba.system.BeanActionStart;
import org.jcon.ba.system.BeanActionClose;
import org.jcon.util.GenLib;
import org.jcon.util.minor.Timer;
import org.jcon.util.minor.TimerUser;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * This class, a client, communicates with a server using 
 * a simple protocol. The current version encapsulates a
 * socket. This class may become an interface or superclass.
 * *** In evolution. Note we could skip the send int by
 * prefixing it to the text.
 *
 * @author Jack Harich
 */
public class CommClient implements TimerUser {

//---------- Private Fields ------------------------------
// Properties
private String host;
private int    port;
private int    readTimeout;
// Internal
private Socket socket;
private DataOutputStream outStream;
private DataInputStream  inStream;
private Timer            timer = new Timer();
 
//---------- Initialization ------------------------------
public CommClient() {
    timer.setTimeout(3000); // A reasonable timeout 
    timer.setTimerUser(this); 
}
//---------- TimerUser Implementation --------------------
public synchronized void timeoutExpired(Timer timer) {
    closeConnection(); // Sets socket to null
}
private void checkConnection() throws IOException { // Here for clarity
    if (socket == null) {
        openConnection();
        timer.start();           
    } else {
        timer.renewTimeout();
    }
}  
//---------- Properties ----------------------------------
//----- host
public void setHost(String host) {
    this.host = host;
}
public String getHost() {
    return host;   
}    
//----- port
public void setPort(int port) {
    this.port = port;   
}    
public int getPort() {
    return port;   
} 
//----- connectionTimeout
/**
* Sets the ConnectionTimeout in milliseconds. This
* determines how long the connection is left open after a
* communication is made. At the end of this time the
* connection is closed, to avoid too many connections to
* the server.
*/
public void setConnectionTimeout(int timeout) {
    timer.setTimeout(timeout);    
}
public int getConnectionTimeout() {
    return timer.getTimeout();   
}   
/**
* Set the ReadTimeout in millisecondds, which is used for
* replies. Zero will give no timeout. A positive number
* will cause send replies to timeout after that amount,
* which is useful for avoiding a hung client due to a
* hung connection or server. The default is zero.
*/         // NOT YET TESTED ***
public void setReadTimeout(int readTimeout) {
    this.readTimeout = readTimeout;    
}
public int getReadTimeout() {
    return readTimeout;   
}    
//----- Other
/**
 * Used to determine if currently connected.
 *
 * @return boolean - true if connected, false if not.
 */
public boolean isConnected() {
    return (socket == null ? false : true);
}
//---------- Public Methods ------------------------------
// Various send and receive, add more as needed
// Or move to another class if grows large or custom *****
/**
* Sends the command and then the text.
* 
* @param command   the command protocol constant.
* @param text      the text sent with the command. If
*                  null is provided it is changed to "".
*/
public synchronized void sendString(int command,
                        String text) throws IOException {
    if (text == null) text = "";                    

    checkConnection();
    
    outStream.writeInt(command);      
    outStream.writeUTF(text);
    outStream.flush();
}    
/**
* Sends the command, then the text, then waits for a
* reply in the form of a String, which it returns. This 
* will hang if no reply is received. *** timeout?
* 
* @param command   the command protocol constant.
* @param text      the text sent with the command. If
*                  null is provided it is changed to "".
*
* @return          the reply requested by the command.
*/
public synchronized String sendStringReply(int command,
                         String text) throws IOException {
    if (text == null) text = "";                    

    checkConnection(); 
         
    outStream.writeInt(command);               
    outStream.writeUTF(text); 
    outStream.flush();
    
    String reply = inStream.readUTF();        
    return reply;
}
//---------- Private Methods -----------------------------
private void openConnection() throws IOException {
    print(".openConnection() - Entered");
    try {
        socket = new Socket(host, port);
        socket.setSoTimeout(readTimeout);
        // Note - Hangs if inStream first
        outStream = new DataOutputStream(socket.getOutputStream());
        inStream  = new DataInputStream(socket.getInputStream());

    } catch(IOException ex) {
        GenLib.exception("CommClient.openConnection()",
            "Cannot open socket on " + getDescription(), ex);    
        socket = null;
        ex.fillInStackTrace();
        throw ex;
    }
}
private void closeConnection() {
    print(".closeConnection() - Entered");    
    try {
        if (outStream != null) {
            outStream.close();
            outStream = null;
        }    
        if (inStream != null) {
            inStream.close();
            inStream = null;
        }
        if (socket != null) {
            socket.close();
            socket = null;   
        }        
    } catch(IOException ex) {
        GenLib.exception("CommClient.closeConnection()",
            "Cannot close socket on " + getDescription(), ex);    
        socket = null;  
    }
}    
private String getDescription() {
     return "host '" + host + "', port '" + port + "'";   
}    
//--- Std
private static void print(String text) {
    System.out.println("CommClient" + text);
}

} // End class
