package mds;

import java.io.*;
import java.net.*;

/** MdsPlus
 * Exetremely cut down version of MDSPlus java client
 * Does not do events anymore!
 * 
 * Originally: The MdsPlus class provides a socket interface to the MdsPlus data system
 * 
 * @author Originally T.W.Fredian, but stripped down by Oliver Ford <codes@oliford.co.uk>
 */

public class MdsPlus extends Object
{
	private Socket s = null;
	private byte message_id;
	
	private String host;
	private int port;
	
	private int requestTimeout;

	//private static final byte MAX_BYTE=127;
	private static final String USER = "java-client";
	private static final byte JAVA_CLIENT=3;
	private static final byte BIG_ENDIAN=(byte)0x80;

	public boolean quiet = false;
	
	/** Create TCP connection to specified server */
	public MdsPlus(String host, int port, boolean quiet) throws MdsPlusException {
		this.quiet = quiet;
		this.host = host;
		this.port = port;
		this.s = null;
	}
	
	/** Create TCP connection to specified server, with no timeout */
	public MdsPlus(String host, int port, boolean quiet, boolean connectImmeadiately) throws MdsPlusException {
		this(host, port, quiet, connectImmeadiately, 0);
	}
	
	/** Create TCP connection to specified server with specfied timeout on all requests*/
	public MdsPlus(String host, int port, boolean quiet, boolean connectImmeadiately, int requestTimeout) throws MdsPlusException {
		this.quiet = quiet;
		this.host = host;
		this.port = port;
		this.requestTimeout = requestTimeout;
		if(connectImmeadiately)
			assureConnected();
		else
			this.s = null;
	}


	/** Create TCP connection to specified server */
	public MdsPlus(String host, int port) throws MdsPlusException {
		this.host = host;
		this.port = port;
		this.s = null;
	}

	/** Checks the TCP connection to the MDS+ server is alive and connects it if not */
	private void assureConnected() throws MdsPlusException{
		if(s == null || !s.isConnected()){
			try{
				System.out.print("MDS+: (Re)connecting to server " + host + ":" + port + "... ");
				s = new Socket(host,port);

				System.out.print("OK, Saying hello... ");
				SendDescriptor((byte)0,(byte)0,new MDSData(USER));
				s.setSoTimeout(requestTimeout);
				
				MDSData ans = GetMsg();
				if (!ans.isStatusOK()){
					System.out.println("FAILED");
					close();
					throw new MdsPlusException("You are not authorized to connect to this host");
				}
				System.out.println("OK");
				
			}catch(IOException e){
				throw new MdsPlusException(e);
			}
		}
		
	}
	
	/** Close the TCP socket */
	public void close() {
		try {
			if (s != null)
				s.close();
			s = null;
		} catch(IOException err){ //not really critical since we don't ever write
			System.err.println("WARNING: Error closing MDSPlus socket...\n" + err.toString());
		};
	}
	
	/** ??? */
	private short ArgLen(MDSData a) {
		short len;
		switch (a.dtype)
		{
		case MDSData.DTYPE_USHORT:
		case MDSData.DTYPE_SHORT: len = 2; break;
		case MDSData.DTYPE_UINT:
		case MDSData.DTYPE_INT:
		case MDSData.DTYPE_FLOAT: len = 4; break;
		case MDSData.DTYPE_DOUBLE:
		case MDSData.DTYPE_COMPLEX: len = 8; break;
		default: len = 1; break;
		case MDSData.DTYPE_CSTRING: len = (short)a.data.length; break;
		}
		return len;
	}

	/** Send the given MDSPlusDescriptor object to the server */
	private void SendDescriptor(byte idx, byte nargs, MDSData descr) throws IOException
	{
		int i;
		ByteArrayOutputStream buffer = new ByteArrayOutputStream(48+descr.data.length);
		DataOutputStream out = new DataOutputStream(buffer);
		out.writeInt(48+descr.data.length);
		out.writeInt(0);
		out.writeShort(ArgLen(descr));
		out.writeByte(nargs);
		out.writeByte(idx);
		out.writeByte(message_id);
		out.writeByte(descr.dtype);
		out.writeByte((byte)(JAVA_CLIENT | BIG_ENDIAN));
		out.writeByte(descr.dims.length);
		for (i=0;i<8;i++) out.writeInt(i < descr.dims.length ? descr.dims[i] : (int)0);
		out.write(descr.data,0,descr.data.length);
		out.flush();
		buffer.writeTo(s.getOutputStream());
		if (idx >= (nargs - 1))
			message_id++;
	}


	/** Big/Little Endian conversion for 32bit */
	private static final int FlipBytes(int in) {
		return 	(in << 24) |
				((in >> 24) & 0x000000ff) | 
				((in & 0x00ff0000) >> 8) |
				((in & 0x0000ff00) << 8);
	}

	/** Big/Little Endian conversion for 16bit */
	private static final short FlipBytes(short in) {
		return (short)((in >> 8) | (in << 8));
	}

	/** Get MdsPlusDescriptor object for incoming reply */
	private MDSData GetMsg() throws IOException {
		MDSData d = new MDSData();
		//byte bytes[]=null;
		DataInputStream in = new DataInputStream(s.getInputStream());
		int message_length = FlipBytes(in.readInt());

		d.status = 				FlipBytes(in.readInt());
		/*short length =*/ 		FlipBytes(in.readShort());
		/*byte nargs =*/ 		in.readByte();
		/*byte didx =*/ 		in.readByte();
		/*byte message_id =*/	in.readByte();
		d.dtype = 				in.readByte();
		/*byte client_type =*/ 	in.readByte();
		byte ndims = 			in.readByte();
		
		d.dims = new int[ndims];
		int i;
		for (i=0;i<8;i++) {
			int dim= FlipBytes(in.readInt());
			if (i < ndims) d.dims[i] = dim;
		}
		d.data = new byte[message_length - 48];
		

		int offset;
		int len;		
        for (offset = 0; offset < d.data.length; offset += len)
		{
        	len = d.data.length - offset;
			len = Math.min(8192, len);
			in.readFully(d.data, offset, len);
		}
		return d;
	}


	/** Send basic string request to DMS+ Server */
	public MDSData makeRequest(String expression) throws MdsPlusException	{
		return makeRequest(expression, new MDSData[0]);
	}

	/** Send request with arguments to MDS+ server */
	public MDSData makeRequest(String expression, MDSData args[]) throws MdsPlusException {
    	assureConnected();
		try{
			MDSData ans = new MDSData();
			synchronized(this)
			{
				byte nargs = (byte)(1 + args.length);
				SendDescriptor((byte)0, nargs, new MDSData(expression));
				for (int i=0;i<args.length;i++)
					SendDescriptor((byte)(i+1), nargs, args[i]);
				ans = GetMsg();
			}
			
			if (!ans.isStatusOK())
				throw new MdsPlusException(ans.GetErrorString());
			
			return ans;
			
    	}catch(IOException e){
    		throw new MdsPlusException(e);
    	}
	}
	
	/** Sets the MDS connection (socket) read timeout in ms. 0 for infinite. */
	public void setReadTimeout(int timeout){
		this.requestTimeout = timeout;
	}
	
}

