package cdac.medinfo.sdk.hl7282.hl7net.communication;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.util.Locale;

import cdac.medinfo.sdk.hl7282.exceptions.ConnectionClosedException;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IBufferReceiver;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IConnectionListener;
import cdac.medinfo.sdk.hl7282.hl7net.commons.INetworkAgent;
import cdac.medinfo.sdk.hl7282.utils.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.HL7Logger;
import cdac.medinfo.sdk.hl7282.utils.ResourceBundleWrapper;

/**
 * ClientSideNetworkAgent contains all methods required for Client Session to interact with Network Layer. 
 */
public class ClientSideNetworkAgent extends Thread implements INetworkAgent   
{
	

	/* The ReadbufferListener. */
	//private IReadBufferListener listener = null;
	private IBufferReceiver listener = null;
	/* The socket . */
	private Socket socket =null;
	/*InputStream of Socket*/
	private InputStream socketInputstream;
	/*Output Stream of Socket */
	private OutputStream socketOutputstream;

	private char messageDelimiter = '\r'; 

	private int iReadMaxLength;//,iWriteMaxLength;

	
	private IConnectionListener objConnectionListener = null;
	
	/**
	 * Instantiates a new ClientSideNetworkAgent.
	 * @throws IOException  Signals that an IO Exception has occurred.
	 */
	public ClientSideNetworkAgent() throws IOException
	{
		socket = new Socket();
	}

	/**
	 * Retrieves Socket InputStream
	 * @return InputStream - instance of InputStream
	 */
	public InputStream getSocketInputstream() 
	{
		return socketInputstream;
	}

	/**
	 * Sets SocketInputStream
	 * @param socketInputstream instance of InputStream
	 */
	public void setSocketInputstream(InputStream socketInputstream) 
	{
		this.socketInputstream = socketInputstream;
	}

	/**
	 * Retrieves Socket OutputStream
	 * @return OutputStream - instance of OutputStream
	 */
	public OutputStream getSocketOutputstream() 
	{
		return socketOutputstream;
	}

	/**
	 * Sets Socket InputStream
	 * @param socketOutputstream instance of OutputStream
	 */
	public void setSocketOutputstream(OutputStream socketOutputstream)
	{
		this.socketOutputstream = socketOutputstream;
	}

	/**
	 * Sets max length limit of reading. 
	 * @param iReadLength value of Reading max length
	 * @param iWriteLength value of Writing max length
	 */
	public void setReadWriteLength(int iReadLength,int iWriteLength)
	{
		iReadMaxLength = iReadLength;
		//iWriteMaxLength = iWriteLength;
	}

	/**
	 * Sets ConnectionListener.
	 * @param IConnectionListener
	 */
	public void setConnectionListener(IConnectionListener objIConnectionListener)
	{
		this.objConnectionListener = objIConnectionListener;
	}
	
	/**
	 * Run method
	 */
	public void run()
	{
		try
		{
			read();
		}
		catch (InterruptedException e)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(e);
			}
		}
		catch(SocketTimeoutException ste)
		{
			if (HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();		
				if(null != socket)
				{
					objHL7Logger.log(ResourceBundleWrapper.getValue("warnings", "SocketReadTimeout", new Locale(HL7Config.createInstance().getLocale()))
							+"["+
							ResourceBundleWrapper.getValue("warnings", "IPAddress",new Locale(HL7Config.createInstance().getLocale()))+"= "+ ((InetSocketAddress)socket.getLocalSocketAddress()).getAddress().getHostAddress()+" , "+
							ResourceBundleWrapper.getValue("warnings", "Port",new Locale(HL7Config.createInstance().getLocale())) + "= "+((InetSocketAddress)socket.getLocalSocketAddress()).getPort()+"]");
				}
				objHL7Logger.log(ste.getMessage());
			}
		}
		catch (IOException e) 
		{			
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(e.getMessage());
			}
		}
		catch (Exception e) 
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHLogger = HL7Logger.getLogger();
				objHLogger.log(e.getMessage());
			}
		}
		objConnectionListener.closeConnection();
	}

	/**
	 * Connect to specified socket address
	 * @param objSocketAddress the obj socket address
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void connect(SocketAddress objSocketAddress,int itimeout) throws IOException
	{
		socket.setSoTimeout(itimeout);
		socket.connect(objSocketAddress);
		socketOutputstream = socket.getOutputStream();
		socketInputstream = socket.getInputStream();
	}

	/**
	 * Reads Data From Input Stream of Underlying Socket with Packet Size of 1MB.
	 * @throws InterruptedException thrown when read operation is interrupted 
	 */
	public void read() throws InterruptedException, IOException
	{
		ByteArrayOutputStream msgBuffer = new ByteArrayOutputStream();

		int iBytesRead = 0,iBytesWritten = 0;
		byte buffer[] = null;			
		boolean status = false;
		boolean isClosed = false;

		
		while(!isInterrupted() && !isClosed && socket != null && socketInputstream != null && !socket.isInputShutdown())
		{
			if(iReadMaxLength != 0)
				buffer = new byte[iReadMaxLength];
			else
				buffer = new byte[1024];

			if(!isInterrupted()&& status && msgBuffer.size() > 0 && socketInputstream.available()== 0)
			{
				iBytesRead = 0;

				//read last two bytes of msgBuffer to confirm message delimiter character
				byte[] messageBufferBytes = msgBuffer.toByteArray();
				char ch= (char)messageBufferBytes[messageBufferBytes.length-1];
				if(ch == this.messageDelimiter && listener.onMessageBufferReceived(msgBuffer.toByteArray()))
				{
					status = false;
					buffer = null;
					msgBuffer = new ByteArrayOutputStream();
					continue;
				}
			}

			if(!isInterrupted()&& socketInputstream.available() == 0)
			{synchronized (this) {
				wait(25);
			}}	

			if(socketInputstream != null)
				iBytesRead = socketInputstream.read(buffer,0, buffer.length);
			else
				throw new ConnectionClosedException();

			if(iBytesRead < 0)
			{
				throw new ConnectionClosedException();
			}
			else
			{
				if(buffer.length > 0)
				{
					msgBuffer.write(buffer,0,iBytesRead);
					iBytesWritten += iBytesRead;
					status = true;
				}
			}
		}

	}



	/**
	 * Sets IBufferReceiver on AuxAgent.
	 * @param objIMessageBufferReceiver object of Buffer Receiver. 
	 */
	public void addBufferReceiver(IBufferReceiver objIMessageBufferReceiver)
	{
		listener = objIMessageBufferReceiver;
	}

	/**
	 * Writes data on Output Stream of Underlying Socket.
	 * @param buffer the buffer to be written.
	 * @throws IOException  Signals that an IO Exception has occurred.
	 */
	public void write(byte[] buffer) throws IOException
	{
		socketOutputstream.write(buffer);
		socketOutputstream.flush();
	}

	/**
	 * Closes the socket.
	 */
	public void close() throws IOException
	{
		if(socket != null)
			socket.close();
		socketInputstream = null;
		socketOutputstream = null;
		socket = null;
		listener = null;
	}
}
