package net.redpipe.cdi;

import java.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.CDI;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.spi.InitialContextFactory;
import javax.naming.spi.InitialContextFactoryBuilder;
import javax.naming.spi.NamingManager;
import javax.validation.ValidatorFactory;
import javax.ws.rs.container.CompletionCallback;

import org.jboss.resteasy.cdi.CdiInjectorFactory;
import org.jboss.resteasy.cdi.ResteasyCdiExtension;
import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.weld.bean.builtin.BeanManagerProxy;
import org.jboss.weld.context.bound.BoundRequestContext;
import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.vertx.VertxExtension;

import io.reactivex.Completable;
import io.vertx.reactivex.core.Vertx;
import net.redpipe.engine.core.AppGlobals;
import net.redpipe.engine.spi.Plugin;
import net.redpipe.engine.spi.RunnableWithException;


public class CdiPlugin extends Plugin {

	private Weld weld;

	@Override
	public Completable init() {
		return Completable.defer(() -> {
			// Setup the Vertx-CDI integration
			VertxExtension vertxExtension = CDI.current().select(VertxExtension.class).get();
			BeanManager beanManager = CDI.current().getBeanManager();
			// has to be done in a blocking thread
			Vertx vertx = AppGlobals.get().getVertx();
			return vertx.rxExecuteBlocking(future -> {
				vertxExtension.registerConsumers(vertx.getDelegate(), BeanManagerProxy.unwrap(beanManager).event());
				future.complete();
			}).ignoreElement();
		});
	}

	@Override
	public Completable deployToResteasy(VertxResteasyDeployment deployment) {
		ResteasyCdiExtension cdiExtension = CDI.current().select(ResteasyCdiExtension.class).get();
		deployment.setActualResourceClasses(cdiExtension.getResources());
		deployment.setInjectorFactoryClass(CdiInjectorFactory.class.getName());
		deployment.getActualProviderClasses().addAll(cdiExtension.getProviders());
		return Completable.complete();
	}

	@Override
	public Completable shutdown() {
		return Completable.defer(() -> {
			weld.shutdown();
			return super.shutdown();
		});
	}
	
	@Override
	public Completable preInit() {
		return Completable.defer(() -> {
			// CDI
			weld = new Weld();
			weld.addExtension(new VertxExtension());
			weld.initialize();

			// Set up Resteasy to build BV with CDI
			try {
				NamingManager.setInitialContextFactoryBuilder(new InitialContextFactoryBuilder() {
					@Override
					public InitialContextFactory createInitialContextFactory(Hashtable<?, ?> environment) throws NamingException {
						return new InitialContextFactory() {

							@Override
							public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
								Context ctx = new InitialContext(){
									@Override
									public Object lookup(String name) throws NamingException {
										if(name.equals("java:comp/ValidatorFactory"))
											return CDI.current().select(ValidatorFactory.class).get();
										return null;
									}
								};
								return ctx;
							}
						};
					}
				});
			} catch (IllegalStateException e) {
				// ignore because it can only be set once
			} catch (NamingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return super.preInit();
		});
	}

	@Override
	public void aroundRequest(HttpRequest req, RunnableWithException<IOException> continuation) throws IOException {
        BoundRequestContext cdiContext = CDI.current().select(BoundRequestContext.class).get();
        Map<String,Object> contextMap = new HashMap<String,Object>();
        cdiContext.associate(contextMap);
        cdiContext.activate();
        try {
        	// FIXME: associate CDI thread context on thread change, like Resteasy context?
        	continuation.run();
        }finally {
    		if(req.getAsyncContext().isSuspended()) {
    			req.getAsyncContext().getAsyncResponse().register((CompletionCallback)(t) -> {
        			cdiContext.invalidate();
        			cdiContext.deactivate();
        			cdiContext.dissociate(contextMap);
    			});
    		}else {
    			cdiContext.invalidate();
    			cdiContext.deactivate();
    			cdiContext.dissociate(contextMap);
    		}		
        }
	}
}