package io.ifar.dropwizard.shiro;

import io.dropwizard.Configuration;
import io.dropwizard.ConfiguredBundle;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

import java.util.EnumSet;

import javax.servlet.DispatcherType;

import org.apache.shiro.web.env.EnvironmentLoaderListener;
import org.apache.shiro.web.servlet.ShiroFilter;
import org.eclipse.jetty.server.session.SessionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Optional;

/**
 * A simple bundle class to initialze Shiro within Dropwizard.
 */
public abstract class ShiroBundle<T extends Configuration>
        implements ConfiguredBundle<T>, ConfigurationStrategy<T> {

    private static final Logger LOG = LoggerFactory.getLogger(ShiroBundle.class);

    /**
     * This method is a no-op.  All functionality is in the {@link #run(Object, com.yammer.dropwizard.config.Environment)} method.
     * @param bootstrap ignored
     */
    @Override
    public void initialize(Bootstrap<?> bootstrap) {
        // nothing to see here, all the action takes place from the run() method.
    }

    /**
     * Conditionally configures Dropwizard's environment to enable Shiro elements. The condition being: if there is a ShiroConfiguration present in the provided {@code configuration}, and its {@code enabled} field is set to {@code true}.
     *
     * @param configuration  used to retrieve the (optional) {@link ShiroConfiguration} instance.
     * @param environment  this is what gets configured
     * @throws Exception
     */
    @Override
    public void run(final T configuration, Environment environment) throws Exception {
        final Optional<ShiroConfiguration> shiroConfig = getShiroConfiguration(configuration);
        if (shiroConfig.isPresent()) {
            LOG.debug("Shiro is configured: {}", shiroConfig);
            initializeShiro(shiroConfig.get(), environment);
        } else {
            LOG.debug("Shiro is not configured");
        }
    }

    private void initializeShiro(final ShiroConfiguration config, Environment environment) {
        if (config.isEnabled()) {
            LOG.debug("Shiro is enabled");

            if (config.isDropwizardSessionHandler() && environment.getApplicationContext().getSessionHandler() == null) {
                LOG.debug("Adding DropWizard SessionHandler to environment.");
                environment.getApplicationContext().setSessionHandler(new SessionHandler());
            }

            // This line ensure Shiro is configured and its .ini file found in the designated location.
            // e.g., via the shiroConfigLocations ContextParameter with fall-backs to default locations if that parameter isn't specified.
            environment.servlets().addServletListeners( new EnvironmentLoaderListener() );

            final String filterUrlPattern = config.getSecuredUrlPattern();
            LOG.debug("ShiroFilter will check URLs matching '{}'.", filterUrlPattern);
            environment.servlets().addFilter("shiro-filter", new ShiroFilter()).addMappingForUrlPatterns( EnumSet.allOf(DispatcherType.class), true, filterUrlPattern );
        } else {
            LOG.debug("Shiro is not enabled");
        }
    }

}