content: /*
   * Copyright wro4j@2011.
   */
  package ro.isdc.wro.extensions.processor.support.uglify;
  
  import static ro.isdc.wro.extensions.processor.support.uglify.UglifyJs.Type.UGLIFY;
  
  import java.io.IOException;
  import java.io.InputStream;
  
  import org.apache.commons.io.IOUtils;
  import org.apache.commons.lang3.Validate;
  import org.mozilla.javascript.ScriptableObject;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import ro.isdc.wro.extensions.script.RhinoScriptBuilder;
  import ro.isdc.wro.util.StopWatch;
  import ro.isdc.wro.util.WroUtil;
  
  
  /**
   * The underlying implementation use untagged version (commited on 2012-09-08 14:15:12).
   * <p/>
   * {@link https://github.com/mishoo/UglifyJS}.
   * <p/>
   * The uglify script is resulted from merging of the following two scripts: parse-js.js, process.js.
   * 
   * @author Alex Objelean
   * @since 1.3.1
   */
  public class UglifyJs {
    private static final Logger LOG = LoggerFactory.getLogger(UglifyJs.class);
    /**
     * The name of the uglify script to be used by default.
     */
    public static final String DEFAULT_UGLIFY_JS = "uglifyJs.min.js";
    private String invokeScript;
    private String defaultOptionsAsJson;
    /**
     * If true, the script is uglified, otherwise it is beautified.
     */
    private final boolean uglify;
    /**
     * Comma delimited variable names to have uglify not mangle
     */
    private String reservedNames;
    private ScriptableObject scope;
    
    /**
     * The type of processing supported by UglifyJs library. This enum replaces ugly boolean constructor parameter.
     */
    public static enum Type {
      BEAUTIFY, UGLIFY
    }
    
    /**
     * @return the script responsible for invoking the uglifyJs script.
     */
    private String getInvokeScript()
        throws IOException {
      if (invokeScript == null) {
        invokeScript = IOUtils.toString(UglifyJs.class.getResourceAsStream("invoke.js"));
      }
      return invokeScript;
    }
    
    /**
     * @param uglify
     *          if true the code will be uglified (compressed and minimized), otherwise it will be beautified (nice
     *          formatted).
     */
    public UglifyJs(final Type uglifyType) {
      Validate.notNull(uglifyType);
      this.uglify = uglifyType == UGLIFY ? true : false;
    }
    
    /**
     * Factory method for creating the uglifyJs engine.
     */
    public static UglifyJs uglifyJs() {
      return new UglifyJs(UGLIFY);
    }
    
    /**
     * Factory method for creating the beautifyJs engine.
     */
    public static UglifyJs beautifyJs() {
      return new UglifyJs(Type.BEAUTIFY);
    }
    
    /**
     * some libraries rely on certain names to be used, so this option allow you to exclude such names from the mangler.
     * For example, to keep names require and $super intact you'd specify –reserved-names "require,$super".
     * 
     * @param reservedNames
     *          the reservedNames to set
     */
    public UglifyJs setReservedNames(final String reservedNames) {
      this.reservedNames = reservedNames;
      return this;
    }
    
    /**
     * @return not null value representing reservedNames.
     */
    private String getReservedNames() {
      return this.reservedNames == null ? "" : reservedNames;
    }
    
    /**
     * Initialize script builder for evaluation.
     */
    private RhinoScriptBuilder initScriptBuilder() {
      // TODO: Find a way to encapsulate this code
      RhinoScriptBuilder builder = null;
      try {
        if (scope == null) {
          builder = RhinoScriptBuilder.newChain().addJSON().evaluateChain(UglifyJs.class.getResourceAsStream("init.js"),
              "initScript").evaluateChain(getScriptAsStream(), DEFAULT_UGLIFY_JS);
          scope = builder.getScope();
        } else {
          builder = RhinoScriptBuilder.newChain(scope);
        }
        return builder;
      } catch (final Exception ex) {
        throw new IllegalStateException("Failed initializing js", ex);
      }
    }
    
    /**
     * @return the stream of the uglify script. Override this method to provide a different script version.
     */
    protected InputStream getScriptAsStream() {
      return UglifyJs.class.getResourceAsStream(DEFAULT_UGLIFY_JS);
    }
    
    /**
     * @param data
     *          js content to process.
     * @return packed js content.
     */
    public String process(final String filename, final String code)
        throws IOException {
      final StopWatch watch = new StopWatch();
      watch.start("init " + filename);
      final RhinoScriptBuilder builder = initScriptBuilder();
      watch.stop();
      final String originalCode = WroUtil.toJSMultiLineString(code);
      // TODO handle reservedNames
      final String optionsAsJson = createOptionsAsJson();
      Validate.notNull(optionsAsJson);
      final String scriptAsString = String.format(getInvokeScript(), originalCode, optionsAsJson);
      watch.start(uglify ? "uglify" : "beautify");
      final Object result = builder.evaluate(scriptAsString, "uglifyIt");
      
      watch.stop();
      LOG.debug(watch.prettyPrint());
      return String.valueOf(result);
    }
    
    /**
     * Reads by default options from options.js file located in the same package. This is an example of how the options
     * could look like:
     * 
     * <pre>
     * {
     *    codegen_options: {
     *      beautify: false,
     *      space_colon: false
     *    },
     *    squeeze: true,
     *    dead_code: true,
     *    mangle: true
     * }
     * </pre>
     * 
     * @return json representation of options.
     */
    protected String createOptionsAsJson()
        throws IOException {
      return String.format(getDefaultOptions(), !uglify, getReservedNames());
    }
    
    /**
     * @return default options string representation loaded from options.js resource file.
     */
    private String getDefaultOptions()
        throws IOException {
      if (defaultOptionsAsJson == null) {
        defaultOptionsAsJson = IOUtils.toString(UglifyJs.class.getResourceAsStream("options.js"));
      }
      return defaultOptionsAsJson;
    }
  }
  