/**
 * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.portal.util;

import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.StringBundler;

import com.yahoo.platform.yui.compressor.CssCompressor;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
import com.yahoo.platform.yui.mozilla.javascript.ErrorReporter;
import com.yahoo.platform.yui.mozilla.javascript.EvaluatorException;

/**
 * @author Brian Wing Shun Chan
 * @author Raymond Augé
 * @author Roberto Díaz
 */
public class MinifierUtil {

	public static String minifyCss(String content) {
		if (!PropsValues.MINIFIER_ENABLED) {
			return content;
		}

		return _instance._minifyCss(content);
	}

	public static String minifyJavaScript(String content) {
		if (!PropsValues.MINIFIER_ENABLED) {
			return content;
		}

		return _instance._minifyJavaScript(content);
	}

	private MinifierUtil() {
	}

	private String _minifyCss(String content) {
		UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();

		try {
			CssCompressor cssCompressor = new CssCompressor(
				new UnsyncStringReader(content));

			cssCompressor.compress(
				unsyncStringWriter, PropsValues.YUI_COMPRESSOR_CSS_LINE_BREAK);

			return _processMinifiedCss(unsyncStringWriter.toString());
		}
		catch (Exception e) {
			_log.error("CSS Minifier failed for\n" + content);

			unsyncStringWriter.append(content);

			return unsyncStringWriter.toString();
		}
	}

	private String _minifyJavaScript(String content) {
		UnsyncStringWriter unsyncStringWriter = new UnsyncStringWriter();

		try {
			JavaScriptCompressor javaScriptCompressor =
				new JavaScriptCompressor(
					new UnsyncStringReader(content),
					new JavaScriptErrorReporter());

			javaScriptCompressor.compress(
				unsyncStringWriter, PropsValues.YUI_COMPRESSOR_JS_LINE_BREAK,
				PropsValues.YUI_COMPRESSOR_JS_MUNGE,
				PropsValues.YUI_COMPRESSOR_JS_VERBOSE,
				PropsValues.YUI_COMPRESSOR_JS_PRESERVE_ALL_SEMICOLONS,
				PropsValues.YUI_COMPRESSOR_JS_DISABLE_OPTIMIZATIONS);
		}
		catch (Exception e) {
			_log.error("JavaScript Minifier failed for\n" + content);

			unsyncStringWriter.append(content);
		}

		return unsyncStringWriter.toString();
	}

	private String _processMinifiedCss(String minifiedCss) {
		int index = 0;

		while ((index = minifiedCss.indexOf("calc(", index)) != -1) {
			index += 5;

			int parenthesesCount = 0;
			int startIndex = index;

			for (parenthesesCount = 1;
				parenthesesCount != 0 && index < minifiedCss.length();
				index++) {

				char c = minifiedCss.charAt(index);

				if (c == '(') {
					parenthesesCount++;
				}
				else if (c == ')') {
					parenthesesCount--;
				}
			}

			if (parenthesesCount == 0) {
				StringBundler sb = new StringBundler(3);

				sb.append(minifiedCss.substring(0, startIndex));

				String replacement = minifiedCss.substring(
					startIndex, index - 1);

				replacement = replacement.replaceAll("\\+", " + ");
				replacement = replacement.replaceAll("-", " - ");
				replacement = replacement.replaceAll("\\*", " * ");
				replacement = replacement.replaceAll("/", " / ");

				sb.append(replacement);

				sb.append(minifiedCss.substring(index - 1));

				index += replacement.length() - (index - startIndex);

				minifiedCss = sb.toString();
			}
		}

		return minifiedCss;
	}

	private static Log _log = LogFactoryUtil.getLog(MinifierUtil.class);

	private static MinifierUtil _instance = new MinifierUtil();

	private class JavaScriptErrorReporter implements ErrorReporter {

		@Override
		public void error(
			String message, String sourceName, int line, String lineSource,
			int lineOffset) {

			if (line < 0) {
				_log.error(message);
			}
			else {
				_log.error(line + ": " + lineOffset + ": " + message);
			}
		}

		@Override
		public EvaluatorException runtimeError(
			String message, String sourceName, int line, String lineSource,
			int lineOffset) {

			error(message, sourceName, line, lineSource, lineOffset);

			return new EvaluatorException(message);
		}

		@Override
		public void warning(
			String message, String sourceName, int line, String lineSource,
			int lineOffset) {

			if (!_log.isWarnEnabled()) {
				return;
			}

			if (line < 0) {
				_log.warn(message);
			}
			else {
				_log.warn(line + ": " + lineOffset + ": " + message);
			}
		}

	}

}