/**
 * Copyright@2011 wro4j
 */
package ro.isdc.wro.model.resource.processor.factory;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.imageio.spi.ServiceRegistry;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.model.resource.processor.decorator.ExtensionsAwareProcessorDecorator;
import ro.isdc.wro.model.resource.processor.decorator.ProcessorDecorator;
import ro.isdc.wro.model.resource.processor.support.ProcessorProvider;
import ro.isdc.wro.model.resource.support.AbstractConfigurableMultipleStrategy;


/**
 * A {@link ProcessorsFactory} implementation which is easy to configure using a {@link Properties} object.
 * 
 * @author Alex Objelean
 * @created 30 Jul 2011
 * @since 1.4.0
 */
public class ConfigurableProcessorsFactory
    implements ProcessorsFactory {
  private static final Logger LOG = LoggerFactory.getLogger(ConfigurableProcessorsFactory.class);
  
  /**
   * Property name to specify pre processors.
   */
  public static final String PARAM_PRE_PROCESSORS = "preProcessors";
  /**
   * Property name to specify post processors.
   */
  public static final String PARAM_POST_PROCESSORS = "postProcessors";
  private Map<String, ResourcePreProcessor> preProcessorsMap;
  private Map<String, ResourcePostProcessor> postProcessorsMap;

  /**
   * Holds the configurations describing available processors.
   */
  private Properties properties;
  
  private final AbstractConfigurableMultipleStrategy<ResourcePreProcessor, ProcessorProvider> configurablePreProcessors = createConfigurablePreProcessorsStrategy();

  private final AbstractConfigurableMultipleStrategy<ResourcePostProcessor, ProcessorProvider> configurablePostProcessors = createConfigurablePostProcessorsStrategy();


  /**
   * @return the {@link AbstractConfigurableMultipleStrategy} responsible for providing postProcessors strategies.
   */
  private AbstractConfigurableMultipleStrategy<ResourcePostProcessor, ProcessorProvider> createConfigurablePostProcessorsStrategy() {
    return new AbstractConfigurableMultipleStrategy<ResourcePostProcessor, ProcessorProvider>() {
      @Override
      protected String getStrategyKey() {
        return PARAM_POST_PROCESSORS;
      }
      
      @Override
      protected void overrideDefaultStrategyMap(final Map<String, ResourcePostProcessor> map) {
        copyAll(ConfigurableProcessorsFactory.this.getPostProcessorsMap(), map);
      }
      
      @Override
      protected Map<String, ResourcePostProcessor> getStrategies(final ProcessorProvider provider) {
        return getPostProcessorStrategies(provider);
      }
      
      @Override
      protected ResourcePostProcessor getStrategyForAlias(final String alias) {
        ResourcePostProcessor processor = super.getStrategyForAlias(alias);
        if (processor == null) {
          final String extension = FilenameUtils.getExtension(alias);
          boolean hasExtension = !StringUtils.isEmpty(extension);
          if (hasExtension) {
            final String processorName = FilenameUtils.getBaseName(alias);
            LOG.debug("processorName: {}", processorName);
            processor = super.getStrategyForAlias(processorName);
            if (processor != null) {
              LOG.debug("adding Extension: {}", extension);
              processor = ExtensionsAwareProcessorDecorator.decorate(new ProcessorDecorator(processor)).addExtension(
                  extension);
            }
          }
        }
        return processor;
      }
      
      @Override
      protected Properties newProperties() {
        return ConfigurableProcessorsFactory.this.getProperties();
      }
    };
  }

  /**
   * @return the {@link AbstractConfigurableMultipleStrategy} responsible for providing preProcessors strategies.
   */
  private  AbstractConfigurableMultipleStrategy<ResourcePreProcessor, ProcessorProvider> createConfigurablePreProcessorsStrategy() {
    return new AbstractConfigurableMultipleStrategy<ResourcePreProcessor, ProcessorProvider>() {
      @Override
      protected String getStrategyKey() {
        return PARAM_PRE_PROCESSORS;
      }
      
      @Override
      protected void overrideDefaultStrategyMap(final Map<String, ResourcePreProcessor> map) {
        copyAll(ConfigurableProcessorsFactory.this.getPreProcessorsMap(), map);
      }

      @Override
      protected Map<String, ResourcePreProcessor> getStrategies(final ProcessorProvider provider) {
        return getPreProcessorStrategies(provider);
      }
      
      @Override
      protected ResourcePreProcessor getStrategyForAlias(final String alias) {
        ResourcePreProcessor processor = super.getStrategyForAlias(alias);
        if (processor == null) {
          final String extension = FilenameUtils.getExtension(alias);
          boolean hasExtension = !StringUtils.isEmpty(extension);
          if (hasExtension) {
            final String processorName = FilenameUtils.getBaseName(alias);
            LOG.debug("processorName: {}", processorName);
            processor = super.getStrategyForAlias(processorName);
            if (processor != null) {
              LOG.debug("adding Extension: {}", extension);
              processor = ExtensionsAwareProcessorDecorator.decorate(processor).addExtension(extension);
            }
          }
        }
        return processor;
      }

      @Override
      protected Properties newProperties() {
        return ConfigurableProcessorsFactory.this.getProperties();
      };
    };
  }

  
  /**
   * @return default implementation of {@link Properties} containing the list of pre & post processors.
   */
  protected Properties newProperties() {
    return new Properties();
  }
  
  /**
   * {@inheritDoc}
   */
  public final Collection<ResourcePreProcessor> getPreProcessors() {
    return configurablePreProcessors.getConfiguredStrategies();
  }
  
  /**
   * {@inheritDoc}
   */
  public final Collection<ResourcePostProcessor> getPostProcessors() {
    return configurablePostProcessors.getConfiguredStrategies();
  }
  
  /**
   * @param map
   *          containing preProcessors with corresponding alias (as key). The map must not be null and once set, the
   *          default map will be overridden.
   */
  public ConfigurableProcessorsFactory setPreProcessorsMap(final Map<String, ResourcePreProcessor> map) {
    Validate.notNull(map);
    this.preProcessorsMap = map;
    return this;
  }
  
  /**
   * @param map
   *          containing postProcessors with corresponding alias (as key). The map must not be null and once set, the
   *          default map will be overridden.
   */
  public ConfigurableProcessorsFactory setPostProcessorsMap(final Map<String, ResourcePostProcessor> map) {
    Validate.notNull(map);
    this.postProcessorsMap = map;
    return this;
  }
  
  public ConfigurableProcessorsFactory setProperties(final Properties properties) {
    Validate.notNull(properties);
    this.properties = properties;
    return this;
  }

  /**
   * By default the processor will be discovered using {@link ServiceRegistry} pattern (by inspecting META-INF/services
   * folder of each dependency).
   * 
   * @return a default map of preProcessors.
   */
  protected Map<String, ResourcePreProcessor> newPreProcessorsMap() {
    return new HashMap<String, ResourcePreProcessor>();
  }
  
  /**
   * By default the processor will be discovered using {@link ServiceRegistry} pattern (by inspecting META-INF/services
   * folder of each dependency).
   * 
   * @return a default map of postProcessors.
   */
  protected Map<String, ResourcePostProcessor> newPostProcessorsMap() {
    return new HashMap<String, ResourcePostProcessor>();
  }
  
  /**
   * To be used for internal usage. Ensure that returned object is not null.
   */
  private Properties getProperties() {
    if (this.properties == null) {
      this.properties = newProperties();
    }
    return this.properties;
  }
  
  /**
   * To be used for internal usage. Ensure that returned object is not null.
   */
  private Map<String, ResourcePreProcessor> getPreProcessorsMap() {
    if (this.preProcessorsMap == null) {
      synchronized (this) {
        if (this.preProcessorsMap == null) {
          this.preProcessorsMap = newPreProcessorsMap();
        }
      }
    }
    return this.preProcessorsMap;
  }
  
  /**
   * To be used for internal usage. Ensure that returned object is not null.
   */
  private Map<String, ResourcePostProcessor> getPostProcessorsMap() {
    if (this.postProcessorsMap == null) {
      synchronized (this) {
        if (this.postProcessorsMap == null) {
          this.postProcessorsMap = newPostProcessorsMap();
        }
      }
    }
    return this.postProcessorsMap;
  }

  /**
   * @VisibleForTesting
   * @return the list of all available pre processors;
   */
  Collection<ResourcePreProcessor> getAvailablePreProcessors() {
    return configurablePreProcessors.getAvailableStrategies();
  }
  
  /**
   * @VisibleForTesting
   * @return the list of all available pre processors;
   */
  Collection<ResourcePostProcessor> getAvailablePostProcessors() {
    return configurablePostProcessors.getAvailableStrategies();
  }

  protected Map<String, ResourcePostProcessor> getPostProcessorStrategies(final ProcessorProvider provider) {
    return provider.providePostProcessors();
  }

  protected Map<String, ResourcePreProcessor> getPreProcessorStrategies(final ProcessorProvider provider) {
    return provider.providePreProcessors();
  }
}
