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

import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.util.Locale;

import cdac.medinfo.sdk.hl7282.hl7net.commons.IBatchProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IFragmentProcessor;
import cdac.medinfo.sdk.hl7282.hl7net.commons.IMLLP;
import cdac.medinfo.sdk.hl7282.hl7net.commons.ITransactionHandler;
import cdac.medinfo.sdk.hl7282.commoninterface.ISingleCollection;
import cdac.medinfo.sdk.hl7282.utils.HL7Config;
import cdac.medinfo.sdk.hl7282.utils.HL7Logger;
import cdac.medinfo.sdk.hl7282.utils.IPairedCollection;
import cdac.medinfo.sdk.hl7282.utils.PairedCollection;
import cdac.medinfo.sdk.hl7282.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.hl7282.utils.SingleCollection;

/**
 * The Class SCPController.
 */
public class Controller implements IClientStatusListener    
{
	

	/** The server channel. */
	private ServerSocket server;

	/** The channel. */
	private Socket socket;

	/** The obj client session. */
	private ClientSession objClientSession; 

	/** The obj client collection. */
	private IPairedCollection<InetSocketAddress, ClientSession> objClientCollection;

	/**The Message Processor*/
	private IProcessHandler objIProcessHandler;

	/**The Fragment Processor */
	private IFragmentProcessor objFragmentProcessor = null;

	/** The MLLP Processor **/
	private IMLLP objIMLLP = null;

	/** The Batch Processor
	 */
	private IBatchProcessor objBatchProcessor = null;

	private int iReadMaxLength,iWriteMaxLength;

	/* Server Side Transaction Handler*/
	private ITransactionHandler objServerSideTransactionHandler = null; 


	/**
	 * Instantiates a new sCP controller.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public Controller() throws IOException
	{
		server = new ServerSocket();
		objClientCollection = new PairedCollection<InetSocketAddress, ClientSession>();
	}

	/**
	 * Sets max length limit of reading and writing. 
	 * @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 the Fragment Processor
	 * @param objIFragmentProcessor the objIFragmentProcessor to set
	 */
	public void setFragmentProcessor(IFragmentProcessor objIFragmentProcessor) 
	{
		this.objFragmentProcessor = objIFragmentProcessor;
	}

	/**
	 * Sets the MLLP Processor.
	 * @param objIMLLP the MLLP to set.
	 */
	public void setMLLPProcessor(IMLLP objIMLLP)
	{
		this.objIMLLP = objIMLLP;
	}

	/**
	 * Sets the Batch Processor
	 * @param objIBatchProcessor the objIBatchProcessor to set
	 */
	public void setBatchProcessor(IBatchProcessor objIBatchProcessor) 
	{
		this.objBatchProcessor = objIBatchProcessor;
	} 

	/**
	 * Gets the IProcessHandler.
	 * @return IProcessHandler the IProcessHandler
	 */
	public IProcessHandler getIProcessHandler() 
	{
		return objIProcessHandler;
	}

	/**
	 * Sets the IProcessHandler.
	 * @param objIProcessHandler new IIProcessHandler
	 */
	public void setIProcessHandler(IProcessHandler objIProcessHandler)
	{
		this.objIProcessHandler = objIProcessHandler;
	}

	/**
	 * This will set the ITransactionHandler on Client session.
	 * @param objServerSideTransactionHandler - implementor of ITransactionHandler
	 */
	public void setServerSideTransactionHandler(ITransactionHandler objServerSideTransactionHandler)
	{
		this.objServerSideTransactionHandler = objServerSideTransactionHandler;
	}

	/**
	 * Accepts the connection.
	 * For each connected client it creates a client session and perform call back to user process handler.
	 * If user sets the acceptance status as true for that client session only then it proceeds further 
	 * else that request will be rejected and connection will be closed. 
	 * @param objSocketAddress object of Socket Address.
	 * @param itimeout timeout period for which connection could be idle
	 * @throws IOException  Signals that an I/O exception has occurred.
	 */
	public void accept(SocketAddress objSocketAddress, int itimeout) throws IOException
	{
		try
		{
			server.bind(objSocketAddress);

			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(ResourceBundleWrapper.getValue("status", "StartingSCPSession",new Locale(HL7Config.createInstance().getLocale())));
			}
			while(true)
			{
				socket = server.accept();
				initClientSession(socket,itimeout);
			}
		}
		catch(BindException bex)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(ResourceBundleWrapper.getValue("exceptions", "SCPSessionstartfailed", new Locale(HL7Config.createInstance().getLocale()))
						+ bex.getMessage() +" ("+ objSocketAddress.toString()+")" );
			}	
			throw bex;
		}

	}

	/**
	 * Accepts socket and starts client session at given socket.
	 * @param socket Specifies socket to accept connection.
	 * @param itimeout timeout period for which connection could be idle.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public void accept(Socket socket, int itimeout)throws IOException
	{
		initClientSession(socket,itimeout);
	}


	/**
	 * Initiates Client Session.
	 * @param socket Specifies socket to accept connection.
	 * @param itimeout timeout period for which connection could be idle.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	void initClientSession(Socket socket, int itimeout)throws IOException
	{
		try
		{
			socket.setSoTimeout(itimeout);
			objClientSession = new ClientSession(objIProcessHandler,socket);
			
			if(objFragmentProcessor != null)
				objFragmentProcessor.setMaxLength(iWriteMaxLength);
			objClientSession.setFragmentProcessor(objFragmentProcessor);
			objClientSession.setMLLPProcessor(objIMLLP);
			objClientSession.setBatchProcessor(objBatchProcessor);

			objClientSession.setReadWriteLength(iReadMaxLength,iWriteMaxLength);
			objClientSession.setServerSideTransactionHandler(objServerSideTransactionHandler);
			//		Notify User that connection is received
			objIProcessHandler.OnConnect(objClientSession);

			//		If User accepts connection initiate and start Client Session 
			if(objClientSession.isConnectionAccepted())
			{
				objClientCollection.put(objClientSession.getClientAddress(), objClientSession);
				objClientSession.addClientStatusListener(this);
				if(HL7Config.createInstance().isLoggingOn())
				{
					HL7Logger objHL7Logger = HL7Logger.getLogger();
					objHL7Logger.log(ResourceBundleWrapper.getValue("status","ConnectionEstablished",new Locale(HL7Config.createInstance().getLocale())) + objClientSession.getClientAddress());
				}
				objClientSession.start();
			}
			else
			{
				socket.close();
				objClientSession = null;//Client Connection Rejected
			}
		}
		catch(SocketException se)
		{
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(ResourceBundleWrapper.getValue("warning", "ServerStopped", new Locale(HL7Config.createInstance().getLocale())));
			}
		}
	}


	/**
	 * Update status.
	 * 
	 * @param objClientSession
	 *            the obj client session
	 */
	public void updateStatus(ClientSession objClientSession)
	{
		InetSocketAddress objClientAddress = objClientSession.getClientAddress();
		if(objClientCollection.contains(objClientAddress))
		{
			objClientCollection.remove(objClientAddress);
			if(HL7Config.createInstance().isLoggingOn())
			{
				HL7Logger objHL7Logger = HL7Logger.getLogger();
				objHL7Logger.log(ResourceBundleWrapper.getValue("status","ConnectionClosed",new Locale(HL7Config.createInstance().getLocale())) + objClientAddress);
			}
		}
	}

	/**
	 * Retrieves number of clients currently connected to Service Provider.    
	 * @return int - no. of Clients connected
	 */
	public int noOfClientsConnected()
	{
		return objClientCollection.size();
	}

	/**
	 * Shows current status of client,whether it is active or not, for given Inet Socket Address
	 * @param objAddress InetSocketAddress of client 
	 * @return boolean - shows status as true or false.
	 */
	public boolean isClientConnected(InetSocketAddress objAddress)
	{
		if(objClientCollection.contains(objAddress))
			return true;

		return false;
	}

	/**
	 * Retrieves list of connection info for all currently active clients
	 * @return ISingleCollection<InetSocketAddress> - list of InetSocketAddress for all active clients
	 */
	public ISingleCollection<InetSocketAddress> getClientsConnectionInfo()
	{
		ISingleCollection<InetSocketAddress> objInetSocketAddressList = new SingleCollection<InetSocketAddress>();
		if(objClientCollection != null && objClientCollection.size() != 0)
		{
			Object []arrObjInfo = objClientCollection.getKeys();
			for(int i=0 ; i<arrObjInfo.length ; i++)
			{
				InetSocketAddress objInetAddress = (InetSocketAddress)arrObjInfo[i];
				objInetSocketAddressList.add(objInetAddress);
			}
		}
		return objInetSocketAddressList;
	}
	
	/**
	 * Closes the SCP Controller. 
	 */
	public void close() throws IOException
	{
		if(objClientSession!=null)
			objClientSession.close();
		if(socket != null)
			socket.close();
		if(server != null)
			server.close();

		server = null;
		socket = null;
		objClientSession = null; 
		objClientCollection = null;
		objIProcessHandler = null;
		objFragmentProcessor = null;
		objIMLLP = null;
		objBatchProcessor = null;
	}
}
