/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *******************************************************************************/
package org.eclipse.rdf4j.query.algebra.evaluation.federation;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.rdf4j.query.QueryEvaluationException;

/**
 * Base class for {@link FederatedServiceResolver} which takes care for lifecycle management of produced
 * {@link FederatedService}s.
 * <p>
 * Specific implementation can implement {@link #createService(String)}.
 *
 * @author Andreas Schwarte
 */
public abstract class AbstractFederatedServiceResolver implements FederatedServiceResolver {

	/**
	 * Map service URL to the corresponding initialized {@link FederatedService}
	 */
	protected Map<String, FederatedService> endpointToService = new HashMap<>();

	/**
	 * Register the specified service to evaluate SERVICE expressions for the given url.
	 *
	 * @param serviceUrl
	 * @param service
	 */
	public void registerService(String serviceUrl, FederatedService service) {
		synchronized (endpointToService) {
			endpointToService.put(serviceUrl, service);
		}
	}

	/**
	 * Unregister a service registered to serviceURl
	 *
	 * @param serviceUrl
	 */
	public void unregisterService(String serviceUrl) {
		FederatedService service;
		synchronized (endpointToService) {
			service = endpointToService.remove(serviceUrl);
		}
		if (service != null && service.isInitialized()) {
			try {
				service.shutdown();
			} catch (QueryEvaluationException e) {
				// TODO issue a warning, otherwise ignore
			}
		}
	}

	/**
	 * Retrieve the {@link FederatedService} registered for serviceUrl. If there is no service registered for
	 * serviceUrl, a new {@link FederatedService} is created and registered.
	 *
	 * @param serviceUrl locator for the federation service
	 * @return the {@link FederatedService}, created fresh if necessary
	 * @throws RepositoryException
	 */
	@Override
	public FederatedService getService(String serviceUrl) throws QueryEvaluationException {
		FederatedService service;
		synchronized (endpointToService) {
			service = endpointToService.get(serviceUrl);
			if (service == null) {
				service = createService(serviceUrl);
				endpointToService.put(serviceUrl, service);
			}
		}
		if (!service.isInitialized()) {
			service.initialize();
		}
		return service;
	}

	/**
	 * Verify if a registered {@link FederatedService} exists for the given serviceUrul.
	 *
	 * @param serviceUrl locator for the federation service.
	 * @return {@code true} iff the FederatedService has been registered, {@code false} otherwise.
	 */
	public boolean hasService(String serviceUrl) {
		synchronized (endpointToService) {
			return endpointToService.containsKey(serviceUrl);
		}
	}

	/**
	 * Create a new {@link FederatedService} for the given serviceUrl. This method is invoked, if no
	 * {@link FederatedService} has been created yet for the serviceUrl.
	 *
	 * @param serviceUrl the service IRI
	 * @return a non-null {@link FederatedService}
	 * @throws QueryEvaluationException
	 */
	protected abstract FederatedService createService(String serviceUrl) throws QueryEvaluationException;

	public void unregisterAll() {
		synchronized (endpointToService) {
			for (FederatedService service : endpointToService.values()) {
				try {
					service.shutdown();
				} catch (QueryEvaluationException e) {
					// TODO issue a warning, otherwise ignore
				}
			}
			endpointToService.clear();
		}
	}

	public void shutDown() {
		unregisterAll();
	}
}
