/*
 * Copyright wro4j@2011.
 */
package ro.isdc.wro.extensions.processor.support.csslint;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.List;

import org.mozilla.javascript.ScriptableObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.extensions.processor.support.linter.OptionsBuilder;
import ro.isdc.wro.extensions.script.RhinoScriptBuilder;
import ro.isdc.wro.util.StopWatch;
import ro.isdc.wro.util.WroUtil;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;


/**
 * CssLint script engine utility. The underlying implementation uses CSSLint script utility<br/>
 * {@link https ://github.com/stubbornella/csslint}. The underlying csslint version is 0.9.10.
 *
 * @author Alex Objelean
 * @since 1.3.8
 * @created 19 Jun 2011
 */
public class CssLint {
  private static final Logger LOG = LoggerFactory.getLogger(CssLint.class);
  /**
   * The name of the csslint script to be used by default.
   */
  private static final String DEFAULT_CSSLINT_JS = "csslint.min.js";
  private final OptionsBuilder optionsBuilder = new OptionsBuilder();
  /**
   * CSV Options to apply.
   */
  private String options;
  private ScriptableObject scope;

  /**
   * Initialize script builder for evaluation.
   */
  private RhinoScriptBuilder initScriptBuilder() {
    try {
      RhinoScriptBuilder builder = null;
      if (scope == null) {
        builder = RhinoScriptBuilder.newChain().evaluateChain(getScriptAsStream(), DEFAULT_CSSLINT_JS);
        scope = builder.getScope();
      } else {
        builder = RhinoScriptBuilder.newChain(scope);
      }
      return builder;
    } catch (final IOException ex) {
      throw new IllegalStateException("Failed reading init script", ex);
    }
  }

  /**
   * @return the stream of the csslint script. Override this method to provide a different script version.
   */
  protected InputStream getScriptAsStream() {
    return CssLint.class.getResourceAsStream(DEFAULT_CSSLINT_JS);
  }

  /**
   * Validates a js using jsHint and throws {@link CssLintException} if the js is invalid. If no exception is thrown,
   * the js is valid.
   *
   * @param data
   *          js content to process.
   * @throws CssLintException
   *           when parsed css has some kind of problems.
   */
  public void validate(final String data)
      throws CssLintException {
    final StopWatch watch = new StopWatch();
    watch.start("init");
    final RhinoScriptBuilder builder = initScriptBuilder();
    watch.stop();
    watch.start("cssLint");
    LOG.debug("options: {}", this.options);
    final String script = buildCssLintScript(WroUtil.toJSMultiLineString(data));
    LOG.debug("script: {}", script);
    builder.evaluate(script, "CSSLint.verify").toString();
    final boolean valid = Boolean.parseBoolean(builder.evaluate("result.length == 0", "checkNoErrors").toString());
    if (!valid) {
      final String json = builder.addJSON().evaluate("JSON.stringify(result)", "CssLint messages").toString();
      LOG.debug("json {}", json);
      final Type type = new TypeToken<List<CssLintError>>() {}.getType();
      final List<CssLintError> errors = new Gson().fromJson(json, type);
      LOG.debug("Errors: {}", errors);
      throw new CssLintException().setErrors(errors);
    }
    LOG.debug("isValid: {}", valid);
    watch.stop();
    LOG.debug(watch.prettyPrint());
  }

  private String buildCssLintScript(final String data) {
    return String.format("var result = CSSLint.verify(%s,%s).messages", data, optionsBuilder.buildFromCsv(options));
  }

  /**
   * @param options CSV representation of the options.
   */
  public CssLint setOptions(final String options) {
    this.options = options;
    return this;
  }

//
//  /**
//   * @param options
//   *          the options to set
//   */
//  public CssLint setOptions(final String... options) {
//    if (options != null && options.length > 0) {
//      this.options = options.length > 1 ? options : optionsBuilder.splitOptions(options[0]);
//    } else {
//      this.options = ArrayUtils.EMPTY_STRING_ARRAY;
//    }
//    LOG.debug("options: {}", Arrays.asList(this.options));
//    return this;
//  }
}
