//-----------------------------------------------------------------------------
// Copyright (c) 1994,1995 Southeastern Universities Research Association,
//                         Continuous Electron Beam Accelerator Facility
//
// This software was developed under a United States Government license
// described in the NOTICE file included as part of this distribution.
//
// Jefferson Lab HPC Group, 12000 Jefferson Ave., Newport News, VA 23606
//-----------------------------------------------------------------------------
//
// Description:
//      Core part of rsvcClient Java Package
//
// Author:  
//      Jie Chen
//      Jefferson Lab HPC Group
//
// Revision History:
//   $Log: rsvcClient.java,v $
//   Revision 1.1.1.1  2000/05/23 15:12:50  pal
//   cdev_psi_1.7.2
//
//   Revision 1.1  1999/10/18 17:12:39  chen
//   *** empty log message ***
//
//
//
import rsvcData;
import rsvcEvent;
import rsvcConfig;
import rsvcEventHandler;
import rsvcClientReader;
import java.io.*;
import java.net.*;
import java.util.*;

public final class rsvcClient
{
    // connection flag
    private boolean connected_ = false;
    
    // Socket to the server
    private Socket tcpsocket_ = null;

    // server information
    private String server_host_ = null;
    private int    server_port_ = 0;

    // internal request id
    private int    eventid_ = 1234;

    // internal client data reader
    private rsvcClientReader reader_ = null;

    // internal thread id for reader
    private Thread           readerthread_ = null;


    // output stream
    private OutputStream output_ = null;

    
    /**
     * Construct an empty rsvcClient object
     */
    public rsvcClient () 
    {
	// empty
    }

    /**
     * Override default finalize method.This allows Java virtual
     * machine to clean up resource when this object is no longer
     * needed.
     */
    protected void finalize() throws Throwable
    {
	if (tcpsocket_ != null && connected_ == true) {
	    connected_ = false;
	    tcpsocket_.close ();
	}
    }

    /**
     * Connect to a server that is on a given host at a given port
     */
    public synchronized void connect (String host, int port) throws UnknownHostException, IOException
    {
	try {
	    tcpsocket_ = new Socket (host, port);
	}catch (UnknownHostException ue){
	    System.err.println (ue);
	    throw ue;
	}catch (IOException e) {
	    System.err.println(e);
	    throw e;
	}
	    
	// set connection flag
	connected_ = true;

	// cache server information
	server_host_ = new String(host);
	server_port_ = port;


	OutputStream toutput = null;
	try {
	    toutput = tcpsocket_.getOutputStream();
	}catch (IOException e) {
	    System.err.println (e);
	    throw e;
	}

	output_ = toutput;

	// create a single rsvcClient Data Reader and spawn a new thread
	reader_ = new rsvcClientReader (this);
	
	readerthread_ = new Thread (reader_);
	readerthread_.start ();
    }

    /**
     * Return underlying socket
     */
    public Socket getSocket () throws NullPointerException
    {
	if (tcpsocket_ == null)
	    throw new NullPointerException ("Socket is invalid");

	return tcpsocket_;
    }

    /**
     * Return underlying reader thread
     */
    public Thread getReaderThread () throws NullPointerException
    {
	if (readerthread_ == null)
	    throw new NullPointerException ("Reader Thread is not alive");
	return readerthread_;
    }

    /**
     * Disconnect from the server
     */
    public synchronized void disconnect () throws IOException
    {
	if (tcpsocket_ == null || connected_ == false) 
	    throw new IOException ("Socket is not connected to the server");

	try {
	    tcpsocket_.close();
	}catch (IOException e) {
	    throw e;
	}
	connected_ = false;

	// remove all event handlers
	reader_.cleanupEventHandlers ();
    }

    /**
     * Is connection established 
     */
    public boolean connected ()
    {
	return connected_;
    }

    /**
     * Register an event handler to handle disconnection event
     */
    public synchronized boolean addDisconnectHandler (rsvcEventHandler handler)
    {
	return reader_.addDisconnectHandler (handler);
    }

    /**
     * Handle server unexpected close
     */
    public int handleClose () 
    {
	try {
	    tcpsocket_.close ();
	} catch (IOException e) {
	    System.err.println (e);
	    connected_ = false;
	    return -1;
	}
	connected_ = false;

	reader_.dispatchDiscEventHandlers ();
	
	// clean up all callbacks
	reader_.cleanupEventHandlers ();

	return 0;
    }


    /**
     * Create a memory based database with an event handler.
     * Data coming back to the event handler contain table definition
     */
    public rsvcEvent createMemDbase (String tablename, rsvcData data,
				     rsvcEventHandler handler) throws IOException
    {
	rsvcEvent id = null;
	
	data.insert (rsvcConfig.RSVC_TABLE_NAME, tablename);
	try {
	    id = sendCommand (rsvcConfig.RSVC_CREATE_MEMTABLE, data,
			      handler);
	}catch (IOException e) {
	    throw e;
	}

	return id;
    }

    /**
     * Open a database with a given name using an event handler.
     * Data retured to the event handler contain definition of database
     */
    public rsvcEvent openDatabase (String name, rsvcData data,
				   rsvcEventHandler handler) throws IOException
    {
	rsvcEvent  id = null;
	
	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	try {
	    id = sendCommand (rsvcConfig.RSVC_OPEN_DBASE, data,
			      handler);
	}catch (IOException e) {
	    throw e;
	}

	return id;
    }

    /**
     * Insert data into a database with a given name.
     * The last argument 'overwrite = 1' selects whether to overwite
     * existing items in the database.
     * Data must match table definition returned from the openDatabase call.
     */
    public rsvcEvent insertValue (String name, rsvcData data,
				  rsvcEventHandler handler,
				  boolean overwrite) throws IOException
    {
	rsvcEvent id = null;
	
	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	if (overwrite == true) {
	    try {
		id = sendCommand (rsvcConfig.RSVC_OVERWRITE, data,
				  handler);
	    }catch (IOException e) {
		throw e;
	    }
	}
	else {
	    try {
		id = sendCommand (rsvcConfig.RSVC_INSERT, data,
				  handler);
	    }catch (IOException e) {
		throw e;
	    }
	}

	return id;
    }

    /**
     * Get data out from the database with a given name.
     * Outbound data either contain a tagged value to denote a key value
     * such as : "id", "model+gold" or contain multiple tagged values that
     * can be constructed into a key value
     */
    public rsvcEvent getValue (String name, rsvcData data,
			       rsvcEventHandler handler) throws IOException
    {
	rsvcEvent id = null;
	
	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	try {
	    id = sendCommand (rsvcConfig.RSVC_GET, data,
			      handler);
	}catch (IOException e) {
	    throw e;
	}

	return id;
    }

    /**
     * Delete a data item from the database with a given name.
     * Outbound data either contain a tagged value to denote a key value
     * such as : "id", "model+gold" or contain multiple tagged values that
     * can be constructed into a key value
     */ 
    public rsvcEvent delValue (String name, rsvcData data,
			       rsvcEventHandler handler) throws IOException
    {
	rsvcEvent id = null;
	
	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	try {
	    id = sendCommand (rsvcConfig.RSVC_DEL, data,
			      handler);
	}catch (IOException e) {
	    throw e;
	}

	return id;
    }

    /**
     * Set value for a datum inside database with a given name.
     * The outbound data item either contain a tagged value to denote 
     * a index value e.g.:  "id", "model+gold"
     * or contains multiple tagged values which can be constructed
     * into a key value
     * plus a subset of tagged values defined in the table definition
     */
    public rsvcEvent setValue (String name, rsvcData data,
			       rsvcEventHandler handler) throws IOException
    {
	rsvcEvent id = null;
	
	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	try {
	    id = sendCommand (rsvcConfig.RSVC_SET, data,
			      handler);
	}catch (IOException e) {
	    throw e;
	}

	return id;
    }

    /**
     * Monitor on a data item inside a database with a given name.
     * Any changes to this item will trigger an event coming back.
     * The outbound data either contain a tagged value to denote a index value
     * Example:  "id", "model+gold"
     * or contains multiple tagged values which can be constructed
     * into a key value.
     */
    public rsvcEvent monitorValue (String name, rsvcData data,
				   rsvcEventHandler handler) throws IOException
    {
	rsvcEvent id = null;

	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	try {
	    id = sendMonitor (rsvcConfig.RSVC_MONITOR_ON, data,
			      handler);
	} catch (IOException e) {
	    throw e;
	}
	return id;
    }

    /**
     * Monitor off  a data inside a database with a given name
     * Outbound data either contain a tagged value to denote a index value
     * Example:  "id", "model+gold"
     * or contains multiple tagged values which can be constructed
     * into a key value.
     */
    public rsvcEvent  monitorOffValue (String name, rsvcData data,
				       rsvcEvent mid) throws IOException
    {
	rsvcEvent id = null;

	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	try {
	    id = sendMonitorOff (rsvcConfig.RSVC_MONITOR_OFF, data, mid);
	} catch (IOException e) {
	    throw e;
	}

	return id;
    }

    /**
     * monitor a single attribute of a data inside a database with a given name.
     * any changes to this attribute will trigger an event.
     * Data coming back to the event handler containing a whole data.
     * Outbound data either contain a tagged value to denote a index value
     * Example:  "id", "model+gold"
     * or contains multiple tagged values which can be constructed
     * into a key value
     */
    public rsvcEvent monitorAttr (String name, String attr, rsvcData data,
				  rsvcEventHandler handler) throws IOException
    {
	rsvcEvent id = null;

	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	data.insert (rsvcConfig.RSVC_MONITOR_TAG, attr);
	
	try {
	    id = sendMonitor (rsvcConfig.RSVC_MONITOR_ONATTR, data,
			      handler);
	} catch (IOException e) {
	    throw e;
	}
	return id;
    }

    /**
     * Stop monitoring  a single attribute of a data inside a database.
     * Outbound data either contain a tagged value to denote a index value
     * Example:  "id", "model+gold"
     * or contains multiple tagged values which can be constructed
     * into a key value .
     */
    public rsvcEvent monitorOffAttr (String name, String attr, rsvcData data,
				     rsvcEvent mid) throws IOException
    {
	rsvcEvent id = null;

	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	data.insert (rsvcConfig.RSVC_MONITOR_TAG, attr);
	
	try {
	    id = sendMonitorOff (rsvcConfig.RSVC_MONITOR_OFFATTR, data,
				 mid);
	} catch (IOException e) {
	    throw e;
	}
	return id;
    }
	
    /**
     * query a particular database 'name'
     * query msg can be like regular C logic expression for all
     * attributes.
     */
    public rsvcEvent query (String name, String qmsg, 
			    rsvcEventHandler handler) throws IOException
    {
	rsvcEvent id = null;

	rsvcData data = new rsvcData ();

	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
  
	data.insert (rsvcConfig.RSVC_QUERY_TAG, qmsg);
	
	try {
	    id = sendCommand (rsvcConfig.RSVC_QUERY, data,
			      handler);
	}catch (IOException e) {
	    throw e;
	}
	return id;
    }

    /**
     * Monitor incoming entries inside database
     * any insertion to a database will trigger an event
     */
    public rsvcEvent monitorIncomingEntries (String name, rsvcData data,
					     rsvcEventHandler handler) throws IOException
    {
	rsvcEvent id = null;
  
	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	try {
	    id = sendMonitor (rsvcConfig.RSVC_MONITOR_ENTRIES, data,
			      handler);
	} catch (IOException e) {
	    throw e;
	}

	return id;
    }

    /**
     * Stop monitoring the incoming entries inside database.
     */
    public rsvcEvent monitorOffIncomingEntries (String name, rsvcData data,
						rsvcEvent mid) throws IOException
    {
	rsvcEvent id = null;
	
	data.insert (rsvcConfig.RSVC_TABLE_NAME, name);
	try {
	    id = sendMonitorOff(rsvcConfig.RSVC_MONITOR_OFFENTRIES, data,
				mid);
	}catch (IOException e) {
	    throw e;
	}

	return id;
    }	
	

    /**
     * Generic monitor on command
     */
    private synchronized rsvcEvent sendMonitor (int opcode, rsvcData data,
						rsvcEventHandler handler) throws IOException
    {
	int status = rsvcConfig.RSVC_SUCCESS;

	// create an event that is being shipped to server
	rsvcEvent cbk = new rsvcEvent (data);
	cbk.setEventid (eventid_);
	cbk.setOpcode (opcode);


	// write this event to server
	int len = cbk.streamSize ();
	// create a buffered data output
	BufferedOutputStream boutput = new BufferedOutputStream (output_, len);
	try {
	    cbk.streamOut (boutput);
	}catch (IOException e) {
	    System.err.println (e);
	    status = rsvcConfig.RSVC_IOFAILED;
	    throw e;
	}

	if (status == rsvcConfig.RSVC_SUCCESS) {
	    try {
		boutput.flush ();
	    }catch (IOException e) {
		System.err.println (e);
		status = rsvcConfig.RSVC_IOFAILED;
		throw e;
	    }
	}
	
	if (status == rsvcConfig.RSVC_SUCCESS) {
	    cbk.setHandler (handler);
	    reader_.addMonitorEventHandler (eventid_, cbk);
	}

	eventid_ ++;
	return cbk;
    }


    /**
     * Generic monitor on command
     */
    private synchronized rsvcEvent sendMonitorOff (int opcode, rsvcData data,
						   rsvcEvent monitorEvent) throws IOException
    {
	int status = rsvcConfig.RSVC_SUCCESS;

	if (reader_.containsMonitorEvent (monitorEvent) != true) {
	    throw new IOException ("Monitor Off using an invalid event");
	}

	// create new event with differen opcode
	rsvcEvent cbk = new rsvcEvent (monitorEvent);
	cbk.setOpcode (opcode);

	// write this event to server
	int len = cbk.streamSize ();
	// create a buffered data output
	BufferedOutputStream boutput = new BufferedOutputStream (output_, len);
	try {
	    cbk.streamOut (boutput);
	}catch (IOException e) {
	    System.err.println (e);
	    status = rsvcConfig.RSVC_IOFAILED;
	    throw e;
	}

	if (status == rsvcConfig.RSVC_SUCCESS) {
	    try {
		boutput.flush ();
	    }catch (IOException e) {
		System.err.println (e);
		status = rsvcConfig.RSVC_IOFAILED;
		throw e;
	    }
	}
	
	return cbk;
    }


    /**
     * Generic command call with ith an event handler.
     * Data coming back to the event handler contain table definition
     */
    private synchronized rsvcEvent sendCommand (int opcode, rsvcData data,
						rsvcEventHandler handler) throws IOException
    {
	int status = rsvcConfig.RSVC_SUCCESS;

	// create an event that is being shipped to server
	rsvcEvent cbk = new rsvcEvent (data);
	cbk.setEventid (eventid_);
	cbk.setOpcode (opcode);


	// write this event to server
	int len = cbk.streamSize ();
	// create a buffered data output
	BufferedOutputStream boutput = new BufferedOutputStream (output_, len);
	try {
	    cbk.streamOut (boutput);
	}catch (IOException e) {
	    System.err.println (e);
	    status = rsvcConfig.RSVC_IOFAILED;
	    throw e;
	}

	if (status == rsvcConfig.RSVC_SUCCESS) {
	    try {
		boutput.flush ();
	    }catch (IOException e) {
		System.err.println (e);
		status = rsvcConfig.RSVC_IOFAILED;
		throw e;
	    }
	}
	
	if (status == rsvcConfig.RSVC_SUCCESS) {
	    cbk.setHandler (handler);
	    reader_.addCommandEventHandler (eventid_, cbk);
	}

	eventid_ ++;
	return cbk;
    }


}
