/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.labo.utils;

import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import lu.tudor.santec.ftp.FTPClient;
import lu.tudor.santec.ftp.FTPClientException;
import lu.tudor.santec.ftp.FTPFileDescriptor;
import lu.tudor.santec.gecamed.core.utils.Logger;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.labo.ejb.entity.beans.Connection;
import lu.tudor.santec.gecamed.labo.ejb.entity.beans.LaboratoryCertificate;
import lu.tudor.santec.gecamed.labo.ejb.session.beans.LaboratoryBean;
import lu.tudor.santec.gecamed.labo.ejb.session.interfaces.ImportInterface;
import lu.tudor.santec.gecamed.labo.ejb.session.interfaces.LaboratoryInterface;

import org.apache.log4j.Level;

public class CertificateSynchronizer
	{
	private FTPClient				m_FtpClient;
	private SmimeDecoder			m_SmimeDecoder;
	
	private LaboratoryInterface		m_LaboratoryInterface;
	
//	private static String		m_PathSeparator = System.getProperty("file.separator");

	private static Logger 		m_Logger = new Logger (CertificateSynchronizer.class);

//---------------------------------------------------------------------------
//***************************************************************************
//* Constants	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
	
	public static final String c_CertificateSuffix  = ".crt";
	
//	private static final String c_CertificateFolder  = "laboratories/";
	public static final String c_CertificateFolder  = "/home/ftp/labokeys/";
	
	public static final String c_CertificatePattern = "^(\\d{6}-\\d{2}[A-Z]?)\\.crt$";
	
//	private static final int	c_BufferSize		 = 4096;
	
	private static Pattern c_LabelPattern = Pattern.compile ("^(\\d{6}-\\d{2}[A-Z]?)\\.crt$",Pattern.CASE_INSENSITIVE);

//---------------------------------------------------------------------------
//***************************************************************************
//* Constructor	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

public CertificateSynchronizer ()
	{
	m_SmimeDecoder	= new SmimeDecoder ();
	}
	
//---------------------------------------------------------------------------
//***************************************************************************
//* Primitives	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
/**
 * The private getLaboratoryInterface returns an instance of the LaboratoryBean
 * session bean. On the first call, the LaboratoryBean will actualy be looked up
 * via JNDI. Once it has been found, the reference to the bean will be stored
 * in a private data member. Doing so avoids JNDI lookups on later calls.
 * @return an instance of the LaboratoryBean session bean.
 */
//---------------------------------------------------------------------------

private LaboratoryInterface getLaboratoryInterface ()
	{
	if (m_LaboratoryInterface != null) return m_LaboratoryInterface;

	try {
		m_LaboratoryInterface = (LaboratoryInterface) ManagerFactory.getRemote (LaboratoryBean.class);
		} 
	catch (Exception p_Exception) 
		{
		m_Logger.log (Level.FATAL, "Failed to lookup CryptoBean!",p_Exception);
		}

	return m_LaboratoryInterface;
	}

//---------------------------------------------------------------------------

private LaboratoryCertificate newCertificateFromPEMData (String p_Filename, byte[] p_PEMData, LaboratoryInterface p_LaboManager) throws LaboException
	{
	LaboratoryCertificate	l_Certificate 		= null;
	X509Certificate			l_X509Certificate	= null;
	Matcher					l_LabelMatcher;
	
	if (p_PEMData == null) return null;
	
	l_LabelMatcher = c_LabelPattern.matcher(p_Filename);
	if (!l_LabelMatcher.matches())
		{
		m_Logger.log (Level.ERROR, "Filename " + p_Filename + " does not have proper format. Data rejected!");
		return null;
		}
	
	l_X509Certificate = m_SmimeDecoder.getCertificate (p_PEMData, p_LaboManager);
	if (l_X509Certificate != null)
		{
		l_Certificate = new LaboratoryCertificate ();
		l_Certificate.setLabel      (l_LabelMatcher.group(1));
		l_Certificate.setPemData    (p_PEMData);
		l_Certificate.setExpiryDate (l_X509Certificate.getNotAfter());
		l_Certificate.setTrusted(true);
		}
	else 	
		{
		m_Logger.log (Level.WARN, "Certificate contained in file " + p_Filename + " was not issued by proper CA!");	
		}
	
	return l_Certificate;
	}
			
//---------------------------------------------------------------------------

private void downloadMissingCertificates (Collection <String> p_MissingCertificates, LaboratoryInterface p_LaboManager)
	{
	Iterator <String>		l_LabelIterator;
	String					l_Label;
	LaboratoryCertificate	l_Certificate;
	LaboratoryInterface		l_LaboratoryInterface;
	byte[]					l_PEMData;
	
	l_LaboratoryInterface = this.getLaboratoryInterface();
	if (l_LaboratoryInterface == null) return;
	
	l_LabelIterator = p_MissingCertificates.iterator();
	while (l_LabelIterator.hasNext())
		{
		l_Label = l_LabelIterator.next();
		m_Logger.log (Level.INFO,"Downloading certificate " + l_Label);
		
		try	{
			l_Label += c_CertificateSuffix;
			l_PEMData = m_FtpClient.fetchData (l_Label, FTPClient.c_Binary);			
			l_Certificate = this.newCertificateFromPEMData (l_Label,l_PEMData, p_LaboManager);		
			
			l_Certificate = l_LaboratoryInterface.saveCertificate (l_Certificate);
			}
		catch (FTPClientException p_Exception)
			{
			m_Logger.log (Level.ERROR,"Failed to download certificate " + l_Label, p_Exception);	
			}
		catch (Exception p_Exception)
			{
			m_Logger.log (Level.ERROR,"Failed to save certificate " + l_Label, p_Exception);	
			}
		}
	}

//---------------------------------------------------------------------------

private void deleteObsoleteCertificates (Collection <String> p_ObsoleteCertificates)
	{
	Iterator <String>		l_LabelIterator;
	String					l_Label;
	LaboratoryCertificate	l_Certificate;
	LaboratoryInterface		l_LaboratoryInterface;

	l_LaboratoryInterface = this.getLaboratoryInterface();
	if (l_LaboratoryInterface == null) return;

	l_LabelIterator = p_ObsoleteCertificates.iterator();
	while (l_LabelIterator.hasNext())
		{
		l_Label = l_LabelIterator.next();
		m_Logger.log (Level.INFO,"Deleting obsolete certificate " + l_Label);
		try	{
			l_Certificate = l_LaboratoryInterface.getCertificateByLabel (l_Label);
			l_LaboratoryInterface.deleteCertificate(l_Certificate);
			}
		catch (Exception p_Exception)
			{
			m_Logger.log (Level.ERROR,"Failed to delete obsolete certificate with label " + l_Label, p_Exception);	
			}
		}
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Class Body	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

public Boolean synchronizeCertificates (Connection p_Connection, LaboratoryInterface p_LaboManager) throws Exception
{
	FTPFileDescriptor l_Descriptor;
	Collection<String> l_ServerCertificates;
	Collection<String> l_LocalCertificates;
	Collection<String> l_NewCertificates;
	LaboratoryInterface l_LaboratoryInterface;
	Matcher l_LabelMatcher;
	
	if ((p_Connection == null) || (!p_Connection.getSynchronize()))
		return Boolean.FALSE;
	
	l_LaboratoryInterface = this.getLaboratoryInterface();
	if (l_LaboratoryInterface == null)
		return Boolean.FALSE;
	
	//========================================================================
	//= Connect to server using specified connection
	//========================================================================
	
	m_FtpClient = new FTPClient();
	
	m_FtpClient.setPassiveMode(p_Connection.getPassiveMode());
	if (p_Connection.getPort() != null)
		m_FtpClient.setPort(p_Connection.getPort());
	
	try
	{
		m_FtpClient.connectToServer(p_Connection.getServer(), p_Connection.getUsername(), p_Connection.getPassword());
		
		try
		{
			ResultDownloader.downloadCertifiers(p_Connection.getServer(), m_FtpClient, p_LaboManager);
		}
		catch (Exception e)
		{
			m_Logger.log(Level.ERROR, "Not able to check the certifiers on the server " +
					"(the certifiers are used to validate the origin of all certificates)", e);
		}
	}
	catch (Exception p_Exception)
	{
		throw new DecryptException(p_Exception.getMessage(), p_Exception, ImportInterface.COULD_NOT_ESTABLISH_CONNECTION);
	}
	if (!m_FtpClient.isConnected())
		return Boolean.FALSE;
	
	//========================================================================
	//= Retrieve list of available certificates on server.
	//========================================================================
	
	m_FtpClient.setFileMatchPattern(c_CertificatePattern);
	m_FtpClient.buildFileList(c_CertificateFolder);
	
	l_ServerCertificates = new ArrayList<String>();
	l_NewCertificates = new ArrayList<String>();
	
	do
	{
		l_Descriptor = m_FtpClient.getNextFileDescriptor(FTPFileDescriptor.c_File);
		if (l_Descriptor != null)
		{
			l_LabelMatcher = c_LabelPattern.matcher(l_Descriptor.getFileName());
			if (l_LabelMatcher.matches())
				l_ServerCertificates.add(l_LabelMatcher.group(1));
		}
	}
	while (l_Descriptor != null);
	
	//========================================================================
	//= Get List of certificates already availble in database. Using set operations
	//= we're going to determine which certificates need to be downloaded and
	//= which ones of the local ones are obsolete and can be deleted.
	//========================================================================
	
	l_LocalCertificates = l_LaboratoryInterface.getAllCertificateLabels();
	
	if (l_LocalCertificates != null)
	{
		l_NewCertificates.addAll(l_ServerCertificates);
		l_NewCertificates.removeAll(l_LocalCertificates);
		l_LocalCertificates.removeAll(l_ServerCertificates);
	}
	
	this.downloadMissingCertificates(l_NewCertificates, p_LaboManager);
	
	this.deleteObsoleteCertificates(l_LocalCertificates);
	
	m_FtpClient.disconnectFromServer();
	
	return Boolean.TRUE;
}

//---------------------------------------------------------------------------
//***************************************************************************
//* End of Class                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

	}
