/*******************************************************************************
 *  Copyright 2017-2018 Centre for Development of Advanced Computing(C-DAC), Pune
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package cdac.medinfo.sdk.dcm30_04.services.scp.commons;

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.dcm30_04.services.association.DefaultAssociation;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.DicomLogger;
import cdac.medinfo.sdk.dcm30_04.utils.IPairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ISingleCollection;
import cdac.medinfo.sdk.dcm30_04.utils.PairedCollection;
import cdac.medinfo.sdk.dcm30_04.utils.ResourceBundleWrapper;
import cdac.medinfo.sdk.dcm30_04.utils.SingleCollection;


/**
 * SCP controller works as a agent at SCP side which manages different client when a client request comes
 */
public class SCPController implements IClientStatusListener  
{

	private ServerSocket server;
	//private Socket socket;
	protected DefaultAssociation objDefaultAssociation;
	protected IProcessHandler objProcessHandler;
	protected IPairedCollection<InetSocketAddress, ClientSession> objClientCollection;


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

	/**
	 * Gets the default association.
	 * @return DefaultAssociation - instance of default association
	 */
	public DefaultAssociation getDefaultAssociation()
	{
		return objDefaultAssociation;
	}

	/**
	 * Sets the default association.
	 * @param objDefaultAssociation instance of default association
	 */
	public void setDefaultAssociation(DefaultAssociation objDefaultAssociation)
	{
		this.objDefaultAssociation = objDefaultAssociation;
	}

	/**
	 * Gets the process handler.
	 * @return IProcessHandler - instance of process handler
	 */
	public IProcessHandler getProcessHandler() 
	{
		return objProcessHandler;
	}

	/**
	 * Sets process handler.
	 * @param objIProcessHandler instance of Process Handler.
	 */
	public void setProcessHandler(IProcessHandler objIProcessHandler)
	{
		this.objProcessHandler = objIProcessHandler;
	}
	
	
	void initClientSession(Socket socket, int itimeout)throws IOException
	{
		ClientSession objClientSession = null;
		socket.setSoTimeout(itimeout);				
		objClientSession = new ClientSession(objProcessHandler,socket);
		objClientSession.setDefaultAssociation(objDefaultAssociation);
//		Notify User that connection is received
		objProcessHandler.onConnect(objClientSession);
//		If User accepts connection initiate and start Client Session 
		if(objClientSession.isConnectionAccepted())
		{
			objClientCollection.put(objClientSession.getClientAddress(), objClientSession);
			objClientSession.addClientStatusListener(this);
			objClientSession.start();
		}
		else
		{
			socket.close();
			objClientSession = null;//Client Connection Rejected
		}
	}

	
	/**
	 * Accepts the connection.
	 * For each connected client it creates a clientsession 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.
	 * @throws IOException  Signals that an I/O exception has occurred.
	 */
	public void accept(SocketAddress objSocketAddress,int itimeout) throws IOException
	{
		try
		{
			server.bind(objSocketAddress);
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("status", "StartingSCPSession",new Locale(DicomConfig.createInstance().getLocale())));
			}
			
			while(true)
			{
				Socket socket = server.accept();
				initClientSession(socket,itimeout);
			}

		}
		catch(BindException bex)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "SCPSessionstartfailed", new Locale(DicomConfig.createInstance().getLocale()))
						+ bex.getMessage() +" ("+ objSocketAddress.toString()+")" );
			}	
			throw bex;
		}
		catch(SocketException se)
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "ServerStopped", new Locale(DicomConfig.createInstance().getLocale())));
			}

			try 
			{
				if(objClientCollection !=null && objClientCollection.size() > 0)
					synchronized(this)
					{
						this.wait();
					}
			}
			catch(InterruptedException ie)
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ie);
				}
			}
		}
	}

	/**
	 * Update status of Client. This method removes the client from SCP when it goes down.
	 * @param objClientSession object of client session.
	 */
	public void updateStatus(ClientSession objClientSession)
	{
		InetSocketAddress objClientAddress = objClientSession.getClientAddress();
		if(objClientCollection.contains(objClientAddress))
		{
			objClientCollection.remove(objClientAddress);
		}

		if(objClientCollection.size() == 0)
			synchronized (this)
			{
				this.notify();
			}
	}

	/**
	 * Retrieves number of clients currently connected to SCP.    
	 * @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()
	{
		try
		{
			ISingleCollection<InetSocketAddress> clientSessionKeyCollection = new SingleCollection<InetSocketAddress>();
			if(objClientCollection != null)
			{
				for(ClientSession objClientSession : objClientCollection)
				{
					InetSocketAddress objAddress = objClientSession.getClientAddress();
					objClientSession.close(0);
					clientSessionKeyCollection.add(objAddress);
				}
				for(InetSocketAddress objAddress :clientSessionKeyCollection )
				{
					objClientCollection.remove(objAddress);
				}
				clientSessionKeyCollection = null;
			}
			objDefaultAssociation = null;
			objProcessHandler = null;
			objClientCollection = null;
			server.close();
		}
		catch (IOException ioe) 
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ioe);
			}
		}		
	}


	/**
	 * Stops listening on this SCPSession so that new client connection
	 * not accepted and continue to process already connected clients.
	 */
	public void stopListening()
	{
		try 
		{
			if(null != server)
				server.close();
		} 
		catch (IOException e) 
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(e);
			}
		}

	}
}
