/**
 * Copyright Alex Objelean
 */
package ro.isdc.wro.model.resource.processor.support;

import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.PatternSyntaxException;

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

/**
 * Css minify barryvan implementation.
 */
public class CSSMin {
  private static final Logger LOG = LoggerFactory.getLogger(CSSMin.class);


  public void formatFile(final String input, final Writer writer)
    throws Exception {
    try {
      final StringBuilder sb = new StringBuilder(input.replaceAll("[\t\n\r]", "").replaceAll("  ", " "));
      int k, n;

      // Find the start of the comment
      while ((n = sb.indexOf("/*")) != -1) {
        k = sb.indexOf("*/", n + 2);
        if (k == -1) {
          throw new Exception("Error: Unterminated comment.");
        }
        sb.delete(n, k + 2);
      }

      final List<Selector> selectors = new LinkedList<Selector>();
      n = 0;
      while ((k = sb.indexOf("}", n)) != -1) {
        try {
          selectors.add(new Selector(sb.substring(n, k + 1)));
        } catch (final Exception e) {
          // skip
        }
        n = k + 1;
      }

        for (Selector selector : selectors) {
            writer.write(selector.toString());
        }
      writer.write("\r\n");
      writer.flush();
    } catch (final Exception e) {
      LOG.error(e.getMessage(), e);
    }

  }
}


class Selector {
  private static final Logger LOG = LoggerFactory.getLogger(CSSMin.class);
  private final Property[] properties;
  private final String selector;


  /**
   * Creates a new Selector using the supplied strings.
   *
   * @param selector The selector; for example, "div { border: solid 1px red; color: blue; }"
   */
  public Selector(final String selector) throws Exception {
    final String[] parts = selector.split("\\{"); // We have to escape the { with a \ for the regex, which itself
    // requires escaping for the string. Sigh.
    if (parts.length < 2) {
      throw new Exception("Warning: Incomplete selector: " + selector);
    }
    this.selector = parts[0].trim();
    String contents = parts[1].trim();
    if (contents.length() <= 1) {
      throw new Exception("Warning: Empty selector body: " + selector);
    }
    if (contents.charAt(contents.length() - 1) != '}') { // Ensure that we have a leading and trailing brace.
      throw new Exception("Warning: Unterminated selector: " + selector);
    }
    contents = StringUtils.substringBefore(contents, "}");
    properties = parseProperties(contents);
    sortProperties(properties);
  }


  /**
   * Prints out this selector and its contents nicely, with the contents sorted alphabetically.
   */
  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    sb.append(selector).append("{");
    for (final Property property : properties) {
      if (property != null) {
        sb.append(property.toString());
      }
    }
    sb.append("}");
    return sb.toString();
  }


  /**
   * Parses out the properties of a selector's body.
   *
   * @param contents The body; for example, "border: solid 1px red; color: blue;"
   */
  private Property[] parseProperties(final String contents) {
    final String[] parts = contents.split(";");
    final List<Property> resultsAsList = new ArrayList<Property>();

      for (String part : parts) {
          try {
              // ignore empty parts
              if (!StringUtils.isEmpty(part.trim())) {
                  resultsAsList.add(new Property(part));
              }
          } catch (final Exception e) {
              LOG.warn(e.getMessage(), e);
          }
      }
    return resultsAsList.toArray(new Property[resultsAsList.size()]);
  }


  private void sortProperties(final Property[] propertiesToSort) {
    Arrays.sort(propertiesToSort);
  }
}


class Property
  implements Comparable {
  private static final Logger LOG = LoggerFactory.getLogger(CSSMin.class);
  protected String property;
  protected Value[] values;


  /**
   * Creates a new Property using the supplied strings. Parses out the values of the property selector.
   *
   * @param property The property; for example, "border: solid 1px red;" or
   *        "-moz-box-shadow: 3px 3px 3px rgba(255, 255, 0, 0.5);".
   */
  public Property(final String property) throws Exception {
    try {
      // Parse the property.
      //final String[] parts = property.split(":"); // Split "color: red" to ["color", " red"]
      final List<String> parts = Arrays.asList(property.split(":", 2)); // Split "color: red" to ["color", " red"]
      final List<String> nonEmptyParts = new ArrayList<String>();
      for (String part : parts) {
        if (!StringUtils.isEmpty(part.trim())) {
          nonEmptyParts.add(part.trim());
        }
      }
      if (nonEmptyParts.size() < 2) {
        throw new Exception("Warning: Incomplete property: " + property);
      }
      this.property = nonEmptyParts.get(0).toLowerCase();

      values = parseValues(nonEmptyParts.get(1).replaceAll(", ", ","));

    } catch (final PatternSyntaxException e) {
      // Invalid regular expression used.
    }
  }


  /**
   * Prints out this property nicely, with the contents sorted in a standardised order.
   */
  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    sb.append(property).append(":");
    for (final Value v : values) {
      sb.append(v.toString()).append(",");
    }
    sb.deleteCharAt(sb.length() - 1); // Delete the trailing comma.
    sb.append(";");
    return sb.toString();
  }


  public int compareTo(final Object other) {
    return property.compareTo(((Property)other).property);
  }


  private Value[] parseValues(final String contents) {
    final String[] parts = contents.split(",");
    final Value[] results = new Value[parts.length];

    for (int i = 0; i < parts.length; i++) {
      try {
        results[i] = new Value(parts[i]);
      } catch (final Exception e) {
        LOG.error(e.getMessage(), e);
        results[i] = null;
      }
    }

    return results;
  }
}


class Value {

  String[] parts;


  public Value(final String value) throws Exception {
    // Parse the value.
    parts = value.split(" "); // Split "solid 1px red" to ["solid","1px","red"] and sort them.
  }


  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder();
    for (final String part : parts) {
      sb.append(part).append(" ");
    }
    return sb.toString().trim();
  }
}
