package gov.va.med.cds.registry;

import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;
import java.util.Set;

import javax.xml.registry.BulkResponse;
import javax.xml.registry.BusinessLifeCycleManager;
import javax.xml.registry.BusinessQueryManager;
import javax.xml.registry.Connection;
import javax.xml.registry.ConnectionFactory;
import javax.xml.registry.FindQualifier;
import javax.xml.registry.JAXRException;
import javax.xml.registry.JAXRResponse;
import javax.xml.registry.LifeCycleManager;
import javax.xml.registry.RegistryService;
import javax.xml.registry.infomodel.Classification;
import javax.xml.registry.infomodel.ClassificationScheme;
import javax.xml.registry.infomodel.Concept;
import javax.xml.registry.infomodel.InternationalString;
import javax.xml.registry.infomodel.Key;
import javax.xml.registry.infomodel.Organization;
import javax.xml.registry.infomodel.RegistryObject;
import javax.xml.registry.infomodel.Service;
import javax.xml.registry.infomodel.ServiceBinding;

import org.apache.ws.scout.registry.infomodel.KeyImpl;

public class JAXRUtility {
	
	private Properties connectionProperties = null;
	
	private Class<ConnectionFactory> factoryClass = null;
	
	private ConnectionFactory connectionFactory = null;
	
	private Set<PasswordAuthentication> credentials = null;
	
	public Connection createJaxrConnection() throws JAXRException
	{
		try {
			if(this.connectionFactory == null)
			{ 
				if (this.factoryClass != null)
				{
					connectionFactory = factoryClass.newInstance();
				}
				else {
					connectionFactory = ConnectionFactory.newInstance();
				}
			}

			connectionFactory.setProperties(this.connectionProperties);
			Connection connection = connectionFactory.createConnection();
			
			if(this.credentials != null)
            {
                connection.setCredentials( this.credentials );
            }

			return connection;
			
		} catch (Exception e) {
			throw new JAXRException("An error occurred while creating JAXR connection.", e);
		} 		
	}
	
	public void closeJaxrConnection(Connection connection) throws JAXRException
	{
		if( connection != null )
		{
			connection.close();
		}
	}
	
	public URL findServiceEndpoint(String organizationName, String serviceName) throws JAXRException
	{
		Connection connection = null;
		Organization organization = null;
		ServiceBinding serviceBinding = null;
		try {
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessQueryManager bqm = regSvc.getBusinessQueryManager();
			
			organization = getOrganizationByName(bqm, organizationName);
			serviceBinding = getServiceBinding(organization, serviceName);
			
			return new URL(serviceBinding.getAccessURI());
		}
		catch ( MalformedURLException ex )
		{
			throw new JAXRException(String.format("The service binding access URI (%s) is malformed. " +
					"Please correction the issue and retry.", serviceBinding.getAccessURI()));
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public Service registerService(String organizationName, String serviceName, String endpointUrl) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessQueryManager bqm = regSvc.getBusinessQueryManager();
			BusinessLifeCycleManager blm = regSvc.getBusinessLifeCycleManager();
			
			// register this service with a particular organization
			Organization organization = getOrganizationByName(bqm, organizationName);
			
			// register new service binding with the service
			ServiceBinding serviceBinding = blm.createServiceBinding();
			serviceBinding.setAccessURI(endpointUrl);
			
			// create the base service
			Service service = blm.createService(serviceName);
			service.addServiceBinding(serviceBinding);
			
			organization.addService(service);
			Collection<Organization> organzations = new ArrayList<Organization>(); 
			organzations.add(organization);
			blm.saveOrganizations(organzations);
			
			return service;
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public ClassificationScheme findClassificationScheme(String classificationSchemeName) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessQueryManager bqm = regSvc.getBusinessQueryManager();
			Collection<String> findQualifiers = new ArrayList<String>();
			findQualifiers.add(FindQualifier.EXACT_NAME_MATCH);
			
			ClassificationScheme cs = bqm.findClassificationSchemeByName(findQualifiers, classificationSchemeName);
			
			return cs;
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public Collection<ClassificationScheme> findClassificationSchemes(String classificationSchemeName) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessQueryManager bqm = regSvc.getBusinessQueryManager();
			Collection<String> findQualifiers = new ArrayList<String>();
			findQualifiers.add(FindQualifier.SORT_BY_NAME_ASC);
			
			Collection<String> namePatterns = new ArrayList<String>();
			namePatterns.add(classificationSchemeName);
			
			BulkResponse br = bqm.findClassificationSchemes(findQualifiers, namePatterns, null, null);
			return (Collection<ClassificationScheme>)br.getCollection();
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public ClassificationScheme createClassificationScheme(String classificationSchemName, String classificationSchemDescription) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessLifeCycleManager blm = regSvc.getBusinessLifeCycleManager();
			
			InternationalString name = blm.createInternationalString(classificationSchemName);
			InternationalString description = blm.createInternationalString(classificationSchemDescription);
			
			ClassificationScheme classificationScheme = blm.createClassificationScheme(name, description);
			Collection<ClassificationScheme> classificationSchemes = new ArrayList<ClassificationScheme>();
			classificationSchemes.add(classificationScheme);
			BulkResponse response = blm.saveClassificationSchemes(classificationSchemes);
			
			if(response.getStatus() != JAXRResponse.STATUS_SUCCESS)
			{
				Throwable cause = null;
				if(response.getExceptions() != null && response.getExceptions().size() > 0)
				{
					cause = (Throwable)response.getExceptions().toArray()[0];
				}
				throw new JAXRException(String.format("An error occured while creating the classification scheme with name %s.", classificationSchemName), cause);
			}
			else
			{
				classificationScheme.setKey(((Key)(response.getCollection().toArray()[0])));
			}
			
			return classificationScheme;
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public void deleteClassificationScheme(ClassificationScheme cs) throws JAXRException {
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessLifeCycleManager blm = regSvc.getBusinessLifeCycleManager();
			
			Collection<Key> cSchemesKeys = new ArrayList<Key>();
			cSchemesKeys.add(cs.getKey());
			
			blm.deleteClassificationSchemes(cSchemesKeys);
		}
		finally
		{
			closeJaxrConnection(connection);
		}
		
	}
	
	public Concept addConcept(ClassificationScheme cScheme, String conceptName, String conceptValue) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessLifeCycleManager blm = regSvc.getBusinessLifeCycleManager();
			
			Concept c = blm.createConcept(cScheme, blm.createInternationalString(conceptName), conceptValue);
			cScheme.addChildConcept(c);
			
			Collection<ClassificationScheme> cSchemes = new ArrayList<ClassificationScheme>();
			cSchemes.add(cScheme);
			
			BulkResponse br = blm.saveClassificationSchemes(cSchemes);
			
			return c;
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public Organization addConcept(Organization organization, Concept concept) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessLifeCycleManager blm = regSvc.getBusinessLifeCycleManager();
			
			Classification c = blm.createClassification(concept);
			organization.addClassification(c);
			
			Collection<Organization> organizations = new ArrayList<Organization>();
			organizations.add(organization);
			
			BulkResponse br = blm.saveOrganizations(organizations);
			
			if(br != null && br.getStatus() != JAXRResponse.STATUS_SUCCESS)
			{
				throw new JAXRException("Error adding concept classification to Organization.");
			}
			
			return organization;
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public Organization findOrganizationByName(String organizationName) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessQueryManager bqm = regSvc.getBusinessQueryManager();
			return getOrganizationByName(bqm, organizationName);
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	
	public RegistryObject findByIdAndType(String id, String type) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessQueryManager bqm = regSvc.getBusinessQueryManager();
			
			RegistryObject registryObject = bqm.getRegistryObject(id, type);
			return registryObject;
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public Collection findByType(String type) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessQueryManager bqm = regSvc.getBusinessQueryManager();
			
			BulkResponse br = bqm.getRegistryObjects(type);
			
			return br.getCollection();
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public Collection<Service> findServices(String orgName, String serviceName) throws JAXRException
	{
		Connection connection = null;
		try { 
			connection = createJaxrConnection();
			
			RegistryService regSvc = connection.getRegistryService();
			BusinessQueryManager bqm = regSvc.getBusinessQueryManager();
			
			Collection<String> findQualifiers = new ArrayList<String>();
			findQualifiers.add(FindQualifier.EXACT_NAME_MATCH);
			Collection<String> namePatterns = new ArrayList<String>();
			namePatterns.add(serviceName);
			
			BulkResponse bulkResponse = bqm.findServices(new KeyImpl(orgName), findQualifiers, namePatterns, null, null);
			Collection<Service> services = bulkResponse.getCollection();
			
			return services;
		}
		finally
		{
			closeJaxrConnection(connection);
		}
	}
	
	public URL serviceToServiceEndpointURL(Service service) throws JAXRException
	{
		ServiceBinding serviceBinding = null;
		try {
			if( service != null && service.getServiceBindings().size() == 1)
			{
				serviceBinding = (ServiceBinding)service.getServiceBindings().toArray(new ServiceBinding[]{})[0];
				return new URL(serviceBinding.getAccessURI());
			}
			
			throw new JAXRException("Unexpected number of service binding for service.");
		} catch (MalformedURLException e) {
			throw new JAXRException(String.format("The access URI for the service is not valid. URI: '%s'", serviceBinding.getAccessURI()));
		}
	}
	
	
	private ServiceBinding getServiceBinding(Organization organization,
			String serviceName) throws JAXRException {
		
		@SuppressWarnings("unchecked")
		Collection<Service> services = organization.getServices();
		for(Service svc : services)
		{
			if(serviceName.equalsIgnoreCase(svc.getName().getValue()))
			{
				@SuppressWarnings("unchecked")
				Collection<ServiceBinding> bindings = svc.getServiceBindings();
				
				if(bindings.size() == 1)
				{
					return (ServiceBinding)bindings.toArray()[0];
				}
			}
		}
		
		throw new JAXRException(String.format("Error locating service binding. [Organization Name: %s, Service Name: %s]", organization.getName().getValue(), serviceName));
	}

	private Organization getOrganizationByName(BusinessQueryManager businessQueryManager, String organizationName) throws JAXRException{
		
		Collection<String> findQualifiers = new ArrayList<String>();
		findQualifiers.add(FindQualifier.EXACT_NAME_MATCH);
		Collection<String> namePatterns = new ArrayList<String>();
		namePatterns.add(organizationName);
		
		
		BulkResponse response = businessQueryManager.getRegistryObjects(LifeCycleManager.ORGANIZATION);
		Collection<Organization> orgs = response.getCollection();
		for(Organization o : orgs)
		{
			if( organizationName.equalsIgnoreCase(o.getName().getValue()))
			{
				return o;
			}
		}
		
		throw new JAXRException("Error locating unique organzation.");
	}

	public void setConnectionProperties(Properties connectionProperties) {
		this.connectionProperties = connectionProperties;
	}
	
	public void setFactoryClass(Class factoryClass) {
		this.factoryClass = factoryClass;
	}
	
	public void setCredentials( Set<PasswordAuthentication> credentials )
    {
        this.credentials = credentials;
    }

}
