package ro.isdc.wro.http.support;

import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;

import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.http.WroFilter;
import ro.isdc.wro.http.WroServletContextListener;
import ro.isdc.wro.manager.factory.WroManagerFactory;


/**
 * Encapsulates the details of storing/retrieving attributes from {@link ServletContext}. <br/>
 * This class know how to handle multiple {@link WroFilter} & {@link WroServletContextListener} configurations, by
 * setting the attributes with a unique key computed based on provided name (using
 * {@link ServletContextAttributeHelper#getName()}. Use this class to retrieve wro4j related attributes, instead of
 * hard-coding the name of the attribute to retrieve.
 * <p/>
 * Usage scenario:
 * <ul>
 * <li>This is the most common use-case, when there is a single {@link WroFilter} and a
 * {@link WroServletContextListener} configured in web.xml. In order to retrieve/store attributes in servlet context
 * use:
 * 
 * <pre>
 * {@link ServletContextAttributeHelper} helper = new {@link ServletContextAttributeHelper}(servletContext);
 * {@link WroConfiguration} config = helper.getWroConfiguration();
 * {@link WroManagerFactory} managerFactory = helper.getManagerFactory();
 * </pre>
 * 
 * </li>
 * <li>When there are multiple {@link WroFilter} configurations in web.xml (each should have a dedicated listener
 * extended form {@link WroServletContextListener}). In order to retrieve/store attributes in servlet context use:
 * 
 * <pre>
 *       String name = ..//the name of the filter (defined in init-param called name). 
 *       {@link ServletContextAttributeHelper} helper = new {@link ServletContextAttributeHelper}(servletContext, name);
 *       {@link WroConfiguration} config = helper.getWroConfiguration();
 *       {@link WroManagerFactory} managerFactory = helper.getManagerFactory();
 * </pre>
 * 
 * </li>
 * <li>When there are multiple {@link WroFilter} configurations in web.xml, you can retrieve/store attributes from
 * within a filter using:
 * 
 * <pre>
 * {@link ServletContextAttributeHelper} helper = {@link ServletContextAttributeHelper}.create(filterConfig);
 * {@link WroConfiguration} config = helper.getWroConfiguration();
 * {@link WroManagerFactory} managerFactory = helper.getManagerFactory();
 * </pre>
 * 
 * This example is similar to previous, except that the name of the filter will be extracted in the factory method.</li>
 * </ul>
 * 
 * @author Alex Objelean
 * @created 7 May 2012
 * @since 1.4.6
 */
public class ServletContextAttributeHelper {
  private static final Logger LOG = LoggerFactory.getLogger(WroServletContextListener.class);
  /**
   * The name of the init param used to retrieve the name of the filter. This is useful when you want to have multiple
   * filter declarations and a lister (of {@link WroServletContextListener} type) associated for each filter. This way
   * you can ensure that each filter will use its own configurations.
   * @VisibleForTesting
   */
  static final String INIT_PARAM_NAME = "name";
  /**
   * Default value of the name init param. This one is used when no value is defined.
   */
  public static final String DEFAULT_NAME = "default";
  /**
   * {@link ServletContext} where the attributes are stored.
   */
  private ServletContext servletContext;
  /**
   * Unique name associated with servletContext, required for multiple listener support.
   */
  private String name;
  
  /**
   * Supported attributes.
   * @VisibleForTesting
   */
  static enum Attribute {
    CONFIGURATION(WroConfiguration.class), MANAGER_FACTORY(WroManagerFactory.class);
    private Class<?> type;
    
    private Attribute(final Class<?> type) {
      this.type = type;
    }
    
    /**
     * @return true if the object is a valid subtype.
     */
    boolean isValid(final Object object) {
      return object == null || type.isAssignableFrom(object.getClass());
    }
  }
  
  /**
   * Uses default name for storing/retrieving attributes in {@link ServletContext}.
   */
  public ServletContextAttributeHelper(final ServletContext servletContext) {
    this(servletContext, DEFAULT_NAME);
  }
  
  /**
   * Factory method which uses default name for storing/retrieving attributes in {@link ServletContext}.
   */
  public static ServletContextAttributeHelper create(final FilterConfig filterConfig) {
    Validate.notNull(filterConfig);
    final String nameFromParam = filterConfig.getInitParameter(INIT_PARAM_NAME);
    final String name = nameFromParam != null ? nameFromParam : DEFAULT_NAME;
    return new ServletContextAttributeHelper(filterConfig.getServletContext(), name);
  }
  
  /**
   * Uses default name to work with {@link ServletContext} attribute.
   * 
   * @param servletContext
   *          a not null {@link ServletContext} object.
   * @param name
   *          of the listener for which the helper will be used.
   */
  public ServletContextAttributeHelper(final ServletContext servletContext, final String name) {
    Validate.notNull(servletContext);
    Validate.notBlank(name);
    this.servletContext = servletContext;
    this.name = name;
    LOG.debug("initializing attributeHelper named: {}", name);
  }
  
  /**
   * Creates a unique name used as a key to store the attribute in {@link ServletContext}.
   * 
   * @param attribute
   *          type of attribute.
   * @return the name of the attribute used to store in servlet context.
   * @VisibleForTesting
   */
  final String getAttributeName(final Attribute attribute) {
    Validate.notNull(attribute);
    return WroServletContextListener.class.getName() + "-" + attribute.name() + "-" + this.name;
  }
  
  /**
   * @return the value of the attribute stored in {@link ServletContext} for this listener.
   * @VisibleForTesting
   */
  final Object getAttribute(final Attribute attribute) {
    Validate.notNull(attribute);
    return servletContext.getAttribute(getAttributeName(attribute));
  }
  
  /**
   * @return the {@link WroManagerFactory} stored in servletContext.
   */
  public WroManagerFactory getManagerFactory() {
    return (WroManagerFactory) getAttribute(Attribute.MANAGER_FACTORY);
  }
  
  /**
   * @return the {@link WroConfiguration} stored in servletContext.
   */
  public WroConfiguration getWroConfiguration() {
    return (WroConfiguration) getAttribute(Attribute.CONFIGURATION);
  }
  
  /**
   * Sets the attribute into the servlet context. The name of the attribute will be computed for you.
   */
  final void setAttribute(final Attribute attribute, final Object object) {
    Validate.notNull(attribute);
    LOG.debug("setting attribute: {} with value: {}", attribute, object);
    Validate.isTrue(attribute.isValid(object), object + " is not of valid subType for attribute: " + attribute);
    servletContext.setAttribute(getAttributeName(attribute), object);
  }

  /**
   * Set the configuration object as a servletContext attribute. 
   */
  public void setWroConfiguration(final WroConfiguration config) {
    setAttribute(Attribute.CONFIGURATION, config);
  }
  

  /**
   * Set the configuration object as a servletContext attribute. 
   */
  public void setManagerFactory(final WroManagerFactory managerFactory) {
    setAttribute(Attribute.MANAGER_FACTORY, managerFactory);
  }
  
  
  /**
   * Remove all attributes from {@link ServletContext}.
   */
  public void clear() {
    LOG.debug("destroying servletContext: {}", this.name);
    for (Attribute attribute : Attribute.values()) {
      servletContext.removeAttribute(getAttributeName(attribute));
    }
  }
  
  /**
   * @VisibleForTesting
   * @return the name of the listener used by this helper.
   */
  final String getName() {
    return name;
  }
}
