/***
 * Copyright (c) 2009 Caelum - www.caelum.com.br/opensource All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package br.com.caelum.vraptor.ioc.guice;

import java.util.Set;

import javax.servlet.ServletContext;

import br.com.caelum.vraptor.ComponentRegistry;
import br.com.caelum.vraptor.config.BasicConfiguration;
import br.com.caelum.vraptor.core.Execution;
import br.com.caelum.vraptor.core.RequestInfo;
import br.com.caelum.vraptor.ioc.Container;
import br.com.caelum.vraptor.ioc.ContainerProvider;
import br.com.caelum.vraptor.ioc.StereotypeHandler;
import br.com.caelum.vraptor.ioc.spring.VRaptorRequestHolder;
import br.com.caelum.vraptor.scan.WebAppBootstrap;
import br.com.caelum.vraptor.scan.WebAppBootstrapFactory;

import com.google.inject.Binder;
import com.google.inject.ConfigurationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.util.Modules;

/**
 *
 * A Container Provider that uses Google Guice as DI container.
 *
 * @author Lucas Cavalcanti
 * @author Sergio Lopes
 * @since 3.2
 *
 */
public class GuiceProvider implements ContainerProvider {

	private boolean stopSession = false;
	static final RequestCustomScope REQUEST = new RequestCustomScope();
	static final SessionCustomScope SESSION = new SessionCustomScope();
	static final ApplicationCustomScope APPLICATION = new ApplicationCustomScope();

	private final class GuiceContainer implements Container {
		public <T> T instanceFor(Class<T> type) {
			return injector.getInstance(type);
		}

		public <T> boolean canProvide(Class<T> type) {
		try {
		return injector.getProvider(type) != null;
		} catch (ConfigurationException e) {
		return false;
		}
		}
	}

	private Injector injector;
	private GuiceContainer container;
	protected ServletContext context;

	public <T> T provideForRequest(RequestInfo request, Execution<T> execution) {
		VRaptorRequestHolder.setRequestForCurrentThread(request);
		REQUEST.start();
		try {
			return execution.insideRequest(container);
		} finally {
			REQUEST.stop();
			VRaptorRequestHolder.resetRequestForCurrentThread();
		}
	}
	
	public Container getContainer() {
		return container;
	}

	public void start(ServletContext context) {
		this.context = context;
		APPLICATION.start();
		container = new GuiceContainer();
		injector = Guice.createInjector(Stage.PRODUCTION, Modules.override(new VRaptorAbstractModule(context, container)).with(customModule()));
		executeStereotypeHandlers();
		injector.injectMembers(REQUEST);
		injector.injectMembers(SESSION);
	}

	private void executeStereotypeHandlers() {
		Set<StereotypeHandler> handlers = injector.getInstance(Key.get(new TypeLiteral<Set<StereotypeHandler>>() {}));
		for (Key<?> key : injector.getAllBindings().keySet()) {
			for (StereotypeHandler handler : handlers) {
				Class<?> type = key.getTypeLiteral().getRawType();
				if (type.isAnnotationPresent(handler.stereotype())) {
					handler.handle(type);
				}
			}
		}
	}

	protected Module customModule() {
		return new Module() {
			public void configure(Binder binder) {
				ComponentRegistry registry = new GuiceComponentRegistry(binder, Multibinder.newSetBinder(binder, StereotypeHandler.class));
				BasicConfiguration config = new BasicConfiguration(context);

				// using the new vraptor.scan
				WebAppBootstrap webAppBootstrap = new WebAppBootstrapFactory().create(config);
				webAppBootstrap.configure(registry);

				// call old-style custom components registration
				registerCustomComponents(registry);
			}
		};
	}

	protected void registerCustomComponents(ComponentRegistry registry) {
		/* TODO: For now, this is an empty hook method to enable subclasses to use
		 * the scanner and register their specific components.
		 *
		 * In the future, if we scan the classpath for StereotypeHandlers, we can
		 * eliminate this hook.
		 */
	}
	
	protected void stopSession(Boolean value) {
		this.stopSession = value;
	}

	public void stop() {
		if(stopSession) {
			SESSION.stopAll();
		}
		APPLICATION.stop();
	}

}