package com.palmnest.webapp.listener;

import com.palmnest.application.Constants;
import com.palmnest.service.GenericManager;
import com.palmnest.service.LookupManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.RememberMeAuthenticationProvider;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>StartupListener class used to initialize and database settings
 * and populate any application-wide drop-downs.
 * <p/>
 * <p>Keep in mind that this listener is executed outside of OpenSessionInViewFilter,
 * so if you're using Hibernate you'll have to explicitly initialize all loaded data at the
 * GenericDao or service level to avoid LazyInitializationException. Hibernate.initialize() works
 * well for doing this.
 *
 * @author <a href="mailto:chasel.java@gmail.com">Chasel Li</a>
 */
@Slf4j
public class StartupListener implements ServletContextListener {


	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	public void contextInitialized(ServletContextEvent event) {

		log.debug("Initializing context...");

		ServletContext context = event.getServletContext();

		// Orion starts Servlets before Listeners, so check if the config
		// object already exists
		Map<String, Object> config = (HashMap<String, Object>) context.getAttribute(Constants.CONFIG);

		if (config == null) {
			config = new HashMap<String, Object>();
		}

		ApplicationContext ctx =
				WebApplicationContextUtils.getRequiredWebApplicationContext(context);

		PasswordEncoder passwordEncoder = null;

		try {

			ProviderManager provider = (ProviderManager) ctx.getBean("org.springframework.security.authentication.ProviderManager#0");

			for (Object o : provider.getProviders()) {
				AuthenticationProvider p = (AuthenticationProvider) o;
				if (p instanceof RememberMeAuthenticationProvider) {
					config.put("rememberMeEnabled", Boolean.TRUE);
				} else if (ctx.getBean("passwordEncoder") != null) {
					passwordEncoder = (PasswordEncoder) ctx.getBean("passwordEncoder");
				}
			}

		} catch (NoSuchBeanDefinitionException n) {
			log.debug("authenticationManager bean not found, assuming test and ignoring...");
			// ignore, should only happen when testing
		}

		context.setAttribute(Constants.CONFIG, config);

		// output the retrieved values for the Init and Context Parameters
		if (log.isDebugEnabled()) {
			log.debug("Remember Me Enabled? " + config.get("rememberMeEnabled"));
			if (passwordEncoder != null) {
				log.debug("Password Encoder: " + passwordEncoder.getClass().getSimpleName());
			}
			log.debug("Populating drop-downs...");
		}

		setupContext(context);
	}

	/**
	 * This method uses the LookupManager to lookup available roles from the data layer.
	 *
	 * @param context The servlet context
	 */
	@SuppressWarnings("rawtypes")
	public static void setupContext(ServletContext context) {

		ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
		LookupManager      mgr = (LookupManager) ctx.getBean("lookupManager");

		// get list of possible roles
		context.setAttribute(Constants.AVAILABLE_ROLES, mgr.getAllRoles());
		log.debug("Drop-down initialization complete [OK]");

		// Any manager extending GenericManager will do:
		GenericManager manager = (GenericManager) ctx.getBean("userManager");
		doReindexing(manager);
		log.debug("Full text search reindexing complete [OK]");
	}

	@SuppressWarnings("rawtypes")
	private static void doReindexing(GenericManager manager) {
		manager.reindexAll(false);
	}

	/**
	 * Shutdown servlet context (currently a no-op method).
	 *
	 * @param servletContextEvent The servlet context event
	 */
	public void contextDestroyed(ServletContextEvent servletContextEvent) {

		//LogFactory.release(Thread.currentThread().getContextClassLoader());
		//Commented out the above call to avoid warning when SLF4J in classpath.
		//WARN: The method class org.apache.commons.logging.impl.SLF4JLogFactory#release() was invoked.
		//WARN: Please see http://www.slf4j.org/codes.html for an explanation.
	}
}
