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

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.model.resource.Resource;
import ro.isdc.wro.model.resource.processor.ResourcePostProcessor;
import ro.isdc.wro.model.resource.processor.ResourcePreProcessor;
import ro.isdc.wro.util.ObjectFactory;
import ro.isdc.wro.util.WroUtil;


/**
 * A processor which parse a resource and search for placeholders of this type: <code>${}</code> and replace them with
 * the values found in a map provided the by client.
 *
 * @author Alex Objelean
 * @since 1.3.8
 * @created 15 Jun 2011
 */
public class PlaceholderProcessor
  implements ResourcePreProcessor, ResourcePostProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(PlaceholderProcessor.class);
  private static final Pattern PATTERN_PLACEHOLDER = Pattern.compile(WroUtil.loadRegexpWithKey("placeholder"));
  private static final Properties EMPTY_PROPERTIES = new Properties();

  /**
   * Factory used to build Properties object containing values of the variables to substitute.
   */
  private ObjectFactory<Properties> propertiesFactory;

  /**
   * If false, when a variable is not defined, a runtime exception will be thrown. Default value is true - meaning that
   * missing variables will be replaced with empty value.
   */
  private boolean ignoreMissingVariables = true;

  /**
   * {@inheritDoc}
   */
  public void process(final Reader reader, final Writer writer)
    throws IOException {
    process(null, reader, writer);
  }


  /**
   * {@inheritDoc}
   */
  public void process(final Resource resource, final Reader reader, final Writer writer)
    throws IOException {
    final String content = IOUtils.toString(reader);
    final Matcher matcher = PATTERN_PLACEHOLDER.matcher(content);
    final StringBuffer sb = new StringBuffer();

    Properties properties = null;
    if (propertiesFactory != null) {
      properties = propertiesFactory.create();
    }
    //be sure that properties will never be null;
    if (properties == null) {
      properties = EMPTY_PROPERTIES;
    }
    while (matcher.find()) {
      final String variableName = matcher.group(1);
      LOG.debug("found placeholder: {}", variableName);
      matcher.appendReplacement(sb, replaceVariable(properties, variableName));
    }
    matcher.appendTail(sb);
    writer.write(sb.toString());
  }


  /**
   * @param variableName
   * @return
   */
  private String replaceVariable(final Properties properties, final String variableName) {
    final String variableValue = properties.getProperty(variableName);
    if (!ignoreMissingVariables && variableValue == null) {
      throw new WroRuntimeException("No value defind for variable called: [" + variableName + "]");
    }

    final String result = variableValue == null ? StringUtils.EMPTY : variableValue;
    LOG.debug("replacing: [{}] with [{}]", variableName, result);
    return result;
  }


  /**
   * @param ignoreMissingVariables the ignoreMissingVariables to set
   */
  public PlaceholderProcessor setIgnoreMissingVariables(final boolean ignoreMissingVariables) {
    this.ignoreMissingVariables = ignoreMissingVariables;
    return this;
  }


  /**
   * @param propertiesFactory the propertiesFactory to set
   */
  public PlaceholderProcessor setPropertiesFactory(final ObjectFactory<Properties> propertiesFactory) {
    this.propertiesFactory = propertiesFactory;
    return this;
  }
}
