package com.castortech.util.osgi;

import java.text.MessageFormat;
import java.util.function.Supplier;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceObjects;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;

/**
 * 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 OsgiProtoTracker<T> implements AutoCloseable, Supplier<T> {
	private final BundleContext context;
  private final ServiceTracker<T,T> serviceTracker;
  private boolean closed = true;
  
  private OsgiProtoTracker(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);
    context = bundle == null ? null : bundle.getBundleContext();
    if (context == null) {
      throw new IllegalArgumentException("Unable to acquire bundle context for " + source.getCanonicalName());
    }
    
    if (filter == null) {
    	serviceTracker = new ServiceTracker<>(context, target, null);
    }
    else {
    	String fullFilter = MessageFormat.format("(&({0}={1}){2})", Constants.OBJECTCLASS, target.getName(), filter); //$NON-NLS-1$

      try {
      	serviceTracker = new ServiceTracker<>(context, context.createFilter(fullFilter), null);
  		}
  		catch (InvalidSyntaxException e) {
  			throw new IllegalArgumentException("Invalid Filter Syntax Exception", e);
  		}
    }
  }
  
  public static <T> OsgiProtoTracker<T> supply(Class<T> target, Class<?> source) {
    return new OsgiProtoTracker<>(target, source, null);
  }
  
  public static <T> OsgiProtoTracker<T> supply(Class<T> target, Class<?> source, String filter) {
    return new OsgiProtoTracker<>(target, source, filter);
  }
  
  @Override
  public T get() {
    if (closed) {
      serviceTracker.open();
      closed = false;
    }
    
    ServiceReference<T> serviceReference = serviceTracker.getServiceReference();
    T service = null;
    ServiceObjects<T> serviceObjects = context.getServiceObjects(serviceReference);
    if (serviceObjects != null) {
    	service = serviceObjects.getService();
    	serviceObjects.ungetService(service);
    }
    return service;
  }
  
  @Override
	protected void finalize() throws Throwable {
    close();
    super.finalize();
  }
  
  @Override
	public void close() throws Exception {
    if (serviceTracker != null && !closed) {
      serviceTracker.close();
    }
  }
}