/*******************************************************************************
 *  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.tlsscp.commons;

import java.io.IOException;
import java.net.BindException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

import cdac.medinfo.sdk.dcm30_04.services.dimsecommons.EnumSCPSessionStopListeningOptions;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.ClientSession;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.IClientStatusListener;
import cdac.medinfo.sdk.dcm30_04.services.scp.commons.SCPController;
import cdac.medinfo.sdk.dcm30_04.services.tls.commons.TLSServerConfig;
import cdac.medinfo.sdk.dcm30_04.utils.DicomConfig;
import cdac.medinfo.sdk.dcm30_04.utils.DicomLogger;
import cdac.medinfo.sdk.dcm30_04.utils.ResourceBundleWrapper;


/**
 * TLS SCP controller works as a agent at secure SCP side which manages different client when a request 
 * comes from secure or insecure clients. 
 */
class TLSSCPController extends SCPController 
{

	private static final String SSL_PROTOCOL = "TLS";
	private secureSocketThread trdsecure = null;
	private SSLServerSocket  tlsServerSock = null; 

	/**
	 * Instantiates a new TLS SCP controller.
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public TLSSCPController() throws IOException
	{
		super();
	}

	/**
	 * 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.
	 * @throws IOException  Signals that an I/O exception has occurred.
	 * @throws InterruptedException 
	 */
	public void accept(SocketAddress objunsecureSocketAddress,SocketAddress objsecureSocketAddress,TLSServerConfig objTLServerConfig,int itimeout) throws IOException
	{
		try {

			if(null != objunsecureSocketAddress && null != objsecureSocketAddress)
			{
				if(null != objsecureSocketAddress && null !=objTLServerConfig)
				{
					trdsecure = new secureSocketThread(objsecureSocketAddress,this,objTLServerConfig,itimeout);
					trdsecure.start();					
					super.accept(objunsecureSocketAddress,itimeout);					
					trdsecure.join();
				}
				else
				{
					if(DicomConfig.createInstance().isLoggingOn())
						DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("warning", "TLSConfigNULL", new Locale(DicomConfig.createInstance().getLocale())));	

					super.accept(objunsecureSocketAddress,itimeout);
				}
			}
			else if (null == objunsecureSocketAddress && null != objsecureSocketAddress)
			{
				if (null != objTLServerConfig)
				{
					trdsecure = new secureSocketThread(objsecureSocketAddress,this,objTLServerConfig,itimeout);
					trdsecure.start();			
					trdsecure.join();
				}
				else
				{
					if(DicomConfig.createInstance().isLoggingOn())
						DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("warning", "TLSConfigNULL", new Locale(DicomConfig.createInstance().getLocale())));	
				}
			}
			else
			{
				if(DicomConfig.createInstance().isLoggingOn())
					DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("warning", "SocketAddressNull", new Locale(DicomConfig.createInstance().getLocale())));	
			}
		} 
		catch (InterruptedException e) 
		{
			if(DicomConfig.createInstance().isLoggingOn())
				DicomLogger.getLogger().log(e.getMessage());		
		}
	}


	/*
	 * Thread responsible for handling secured communication.
	 */
	class secureSocketThread extends Thread 
	{    
		private SocketAddress objSocketAddress = null;
		private	IClientStatusListener objClientStatusListener = null;
		private SSLSocket tlssocket = null;
		private SSLContext tlsContext = null;
		private TLSServerConfig objTLServerConfig = null;
		private int itimeout;


		public secureSocketThread(SocketAddress objSocketAddress,IClientStatusListener objClientStatusListener,TLSServerConfig objTLServerConfig,int timeout)
		{
			this.objSocketAddress = objSocketAddress;
			this.objClientStatusListener = objClientStatusListener;
			this.objTLServerConfig = objTLServerConfig ;
			this.itimeout = timeout;

		}

		public void run()
		{
			try
			{


				ClientSession objClientSession = null;		

				tlsContext  = SSLContext.getInstance(SSL_PROTOCOL);
				tlsContext.init(objTLServerConfig.getKeyManagers(),objTLServerConfig.getTrustManagers(),objTLServerConfig.getSecureRandom());
				SSLServerSocketFactory  fact = tlsContext.getServerSocketFactory();
				tlsServerSock  = (SSLServerSocket)fact.createServerSocket();

				tlsServerSock.setWantClientAuth(objTLServerConfig.isWantClientAuth());
				tlsServerSock.setNeedClientAuth(objTLServerConfig.isNeedClientAuth());

				if(null != objTLServerConfig.getEnabledCipherSuites()) //cipher suits
					tlsServerSock.setEnabledCipherSuites(objTLServerConfig.getEnabledCipherSuites());

				tlsServerSock.bind(objSocketAddress);
				
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("status", "StartingTLSSCPSession",new Locale(DicomConfig.createInstance().getLocale())));
				}

				try
				{

					while(true)
					{

						try
						{
							tlssocket =(SSLSocket)tlsServerSock.accept();

//							tlssocket.addHandshakeCompletedListener(
//									new HandshakeCompletedListener()
//									{
//										public void handshakeCompleted(HandshakeCompletedEvent event)
//										{
//											SSLSession session = event.getSession();											
//											
//											System.out.println("Handshake Completed with peer " +
//													session.getPeerHost());
//											System.out.println("   cipher: " + session.getCipherSuite());
//											
//											javax.security.cert.X509Certificate[] certs = null;
//											
//											try
//											{
//												certs = session.getPeerCertificateChain();
//											}
//											catch (javax.net.ssl.SSLPeerUnverifiedException puv)
//											{
//												certs = null;
//											}
//											if  (certs != null)
//											{
//												System.out.println("   peer certificates:");
//												for (int z=0; z<certs.length; z++) System.out.
//												println("certs["+z+"]: " + certs[z]);
//											}
//											else
//											{
//												System.out.println("No peer certificates presented");
//											}
//										}
//									}
//							);

							tlssocket.setSoTimeout(itimeout);
							// for mutual authentication.
							tlssocket.startHandshake();

							objClientSession = new ClientSession(objProcessHandler,tlssocket);
							objClientSession.setSecured(true);
							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(objClientStatusListener);
								objClientSession.start();
							}
							else
							{
								tlssocket.close();
								objClientSession = null;//Client Connection Rejected
							}
						}
						catch(SSLException e)
						{
							if(DicomConfig.createInstance().isLoggingOn())
							{
								if(null != tlssocket)
									DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("warning", "SecureConnectionRejected", new Locale(DicomConfig.createInstance().getLocale()))+" : " + tlssocket.getRemoteSocketAddress().toString());
								else
								{
									DicomLogger.getLogger().log(ResourceBundleWrapper.getValue("warning", "TLSServerFailed", new Locale(DicomConfig.createInstance().getLocale())) + e.getMessage());
									break;
								}
							}

							if(null != tlssocket)
							{
								tlssocket.close();
								tlssocket = null;
							}
						}
					}
				}
				catch(SocketException se) // in case of Server socket is closed forcibly by user.
				{
					if(DicomConfig.createInstance().isLoggingOn())
					{
						DicomLogger objDicomLogger = DicomLogger.getLogger();
						objDicomLogger.log(ResourceBundleWrapper.getValue("warning", "TLSServerStopped", new Locale(DicomConfig.createInstance().getLocale())));
					}						
				}
			}
			catch(BindException bex)
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger objDicomLogger = DicomLogger.getLogger();
					objDicomLogger.log(ResourceBundleWrapper.getValue("exceptions", "TLSSCPSessionstartfailed", new Locale(DicomConfig.createInstance().getLocale()))
							+ bex.getMessage() +" ("+ objSocketAddress.toString()+")" );
				}
			//	throw bex;
			}
			catch (NoSuchAlgorithmException e) 
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger.getLogger().log(e.getMessage());
				}				
			} 
			catch (KeyManagementException e)
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger.getLogger().log(e.getMessage());
				}	
			}
			catch (IOException e)
			{
				if(DicomConfig.createInstance().isLoggingOn())
				{
					DicomLogger.getLogger().log(e.getMessage());
				}	
			}

		}
	}


	/**
	 * Closes the TLS SCP Controller. 
	 */
	@Override
	public void close()
	{	
		try
		{
			super.close();
			if(!tlsServerSock.isClosed())
				tlsServerSock.close();
			if(null != tlsServerSock)
				tlsServerSock = null;

			if(null != trdsecure)
				trdsecure.interrupt();
		}
		catch (IOException ioe) 
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(ioe);
			}
		}		
	}


	void stopListening(EnumSCPSessionStopListeningOptions enumSCPSessionStopListeningOptions)
	{
		try 
		{
			if (enumSCPSessionStopListeningOptions.equals(EnumSCPSessionStopListeningOptions.SECURE))
			{
				if(null != tlsServerSock)
					tlsServerSock.close();
			}
			else if (enumSCPSessionStopListeningOptions.equals(EnumSCPSessionStopListeningOptions.INSECURE))
			{
				super.stopListening();
			}
			else if (enumSCPSessionStopListeningOptions.equals(EnumSCPSessionStopListeningOptions.BOTH))
			{
				super.stopListening();

				if(null != tlsServerSock)
					tlsServerSock.close();
			}		
		} 
		catch (IOException e) 
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(e);
			}
		}

	}

	@Override
	public void stopListening() 
	{
		try 
		{
			super.stopListening();

			if(null != tlsServerSock)
				tlsServerSock.close();
		} 
		catch (IOException e) 
		{
			if(DicomConfig.createInstance().isLoggingOn())
			{
				DicomLogger objDicomLogger = DicomLogger.getLogger();
				objDicomLogger.log(e);
			}
		}
	}

}
