package com.castortech.util.osgi;

import java.util.Collection;
import java.util.function.Supplier;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceObjects;
import org.osgi.framework.ServiceReference;

/**
 * Based on example from http://alblue.bandlem.com/2015/09/osgi-services-in-java-8.html
 * and extended to support filters
 * and then adapted for Prototype service instances as suggested by Tim Ward
 * 
 * @author alpic
 *
 * @param <T>
 */
public class OsgiProtoSupplier<T> implements Supplier<T> {
  private final BundleContext context;
  private final ServiceReference<T> serviceReference;

  private OsgiProtoSupplier(Class<T> target, Class<?> source, String filter) {
    if (target == null) {
      throw new IllegalArgumentException("Target cannot be null");
    }
    if (source == null) {
      throw new IllegalArgumentException("Source cannot be null");
    }
    Bundle bundle = FrameworkUtil.getBundle(source);
    BundleContext bcontext = bundle == null ? null : bundle.getBundleContext();
    if (bcontext == null) {
      throw new IllegalArgumentException("Unable to acquire bundle context for " + source.getCanonicalName());
    }
    
    context = bcontext;
    try {
			Collection<ServiceReference<T>> serviceReferences = bcontext.getServiceReferences(target, filter);
	    serviceReference = serviceReferences.isEmpty() ? null : serviceReferences.iterator().next();
		}
		catch (InvalidSyntaxException e) {
			throw new IllegalArgumentException("Invalid Filter Syntax Exception", e);
		}
  }
  
  public static <T> OsgiProtoSupplier<T> supply(Class<T> target, Class<?> source) {
    return new OsgiProtoSupplier<>(target, source, null);
  }

  public static <T> OsgiProtoSupplier<T> supply(Class<T> target, Class<?> source, String filter) {
    return new OsgiProtoSupplier<>(target, source, filter);
  }

  @Override
  public T get() {
    try {
    	T service = null;
      ServiceObjects<T> serviceObjects = context.getServiceObjects(serviceReference);
      if (serviceObjects != null) {
      	service = serviceObjects.getService();
      	serviceObjects.ungetService(service);
      }
      return service;
    } 
    catch (Exception e) {
      return null;
    }
  }
}