/*******************************************************************************
 * Copyright (c) 2008, 2014 Stuart McCulloch
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Stuart McCulloch - initial API and implementation
 *******************************************************************************/

package org.eclipse.sisu.peaberry.internal;

import static jsr166y.ConcurrentReferenceHashMap.ReferenceType.SOFT;
import static jsr166y.ConcurrentReferenceHashMap.ReferenceType.WEAK;
import static org.eclipse.sisu.peaberry.internal.ComputedMapFactory.computedMap;
import static org.eclipse.sisu.peaberry.internal.ImportProxyClassLoader.getProxyConstructor;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.concurrent.ConcurrentMap;

import org.eclipse.sisu.peaberry.Import;
import org.eclipse.sisu.peaberry.ServiceException;
import org.eclipse.sisu.peaberry.builders.ImportDecorator;
import org.eclipse.sisu.peaberry.internal.ComputedMapFactory.Function;

/**
 * Factory methods for dynamic service proxies.
 * 
 * @author mcculls@gmail.com (Stuart McCulloch)
 */
final class ServiceProxyFactory {

  // instances not allowed
  private ServiceProxyFactory() {}

  static <T> Iterable<T> serviceProxies(final Class<T> clazz, final Iterable<Import<T>> services,
      final ImportDecorator<? super T> decorator) {

    // attempt to cache service proxies between iterations
    final Constructor<T> ctor = getProxyConstructor(clazz);
    final ConcurrentMap<Import<T>, T> PROXY_CACHE =
        computedMap(WEAK, SOFT, 8, new Function<Import<T>, T>() {
          public T compute(final Import<T> service) {
            return buildProxy(ctor, decorator, service);
          }
        });

    return new Iterable<T>() {
      public Iterator<T> iterator() {
        return new Iterator<T>() {

          // original service iterator, provided by the registry
          private final Iterator<Import<T>> i = services.iterator();

          public boolean hasNext() {
            return i.hasNext();
          }

          public T next() {
            return PROXY_CACHE.get(i.next());
          }

          public void remove() {
            throw new UnsupportedOperationException();
          }
        };
      }

      @Override
      public String toString() {
        final StringBuilder buf = new StringBuilder().append('[');
        String delim = "";
        for (final T t : this) {
          buf.append(delim).append(t);
          delim = ", ";
        }
        return buf.append(']').toString();
      }
    };
  }

  static <T> T serviceProxy(final Class<T> clazz, final Iterable<Import<T>> services,
      final ImportDecorator<? super T> decorator) {

    // provide concurrent access to best import and wrap as a decorated proxy
    return buildProxy(getProxyConstructor(clazz), decorator, new ConcurrentImport<T>(services));
  }

  static <T> T buildProxy(final Constructor<T> constructor,
      final ImportDecorator<? super T> decorator, final Import<T> service) {

    try {
      // minimize wrapping of exceptions to help with problem determination
      return constructor.newInstance(null == decorator ? service : decorator.decorate(service));
    } catch (final InstantiationException e) {
      // /CLOVER:OFF
      throw new ServiceException(e);
    } catch (final IllegalAccessException e) {
      throw new ServiceException(e);
    } catch (final InvocationTargetException e) {
      throw new ServiceException(e);
      // /CLOVER:ON
    }
  }
}
