package de.rhauswald.util.guice.proxy;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.text.MessageFormat;

import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.RawTypeExtractor;

public class ProxyBindingProvider<T> implements Provider<T> {
	private static final String EXCEPTION_MESSAGE = "Exception creating proxy instance {0} with target {1}. Make shure the proxy has a constructor taking one argument of type object";
	private final Key<T> targetKey;
	private final Class<? extends InvocationHandler>[] proxyClasses;

	@Inject
	Injector injector;
	@Inject
	RawTypeExtractor rawTypeExtractor;

	public ProxyBindingProvider(Key<T> targetKey,
			Class<? extends InvocationHandler>... proxyClasses) {
		this.targetKey = targetKey;
		this.proxyClasses = proxyClasses;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T get() {
		T ret = injector.getInstance(targetKey);
		for (Class<? extends InvocationHandler> proxyClass : proxyClasses) {
			Constructor<? extends InvocationHandler> constructor = (Constructor<? extends InvocationHandler>) proxyClass
					.getConstructors()[0];
			try {
				ret = (T) Proxy.newProxyInstance(targetKey.getClass()
						.getClassLoader(), new Class[] { rawTypeExtractor
						.getRawType(targetKey.getTypeLiteral()) }, constructor
						.newInstance(ret));
			} catch (IllegalArgumentException e) {
				throw wrapException(proxyClass, e);
			} catch (InstantiationException e) {
				throw wrapException(proxyClass, e);
			} catch (IllegalAccessException e) {
				throw wrapException(proxyClass, e);
			} catch (InvocationTargetException e) {
				throw wrapException(proxyClass, e);
			}
		}
		return ret;
	}

	private RuntimeException wrapException(
			Class<? extends InvocationHandler> proxyClass, Exception e) {
		return new RuntimeException(MessageFormat.format(EXCEPTION_MESSAGE,
				proxyClass, targetKey.getClass()), e);
	}
}
