package org.callimachusproject.behaviours;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import org.apache.http.HttpEntity;
import org.callimachusproject.client.HttpUriClient;
import org.callimachusproject.concepts.ScriptBundle;
import org.callimachusproject.server.exceptions.GatewayTimeout;
import org.callimachusproject.server.exceptions.InternalServerError;
import org.callimachusproject.traits.CalliObject;
import org.openrdf.OpenRDFException;
import org.openrdf.annotations.Sparql;

import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.CommandLineRunner;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import com.google.javascript.jscomp.DiagnosticGroups;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;

public abstract class ScriptBundleSupport implements ScriptBundle, CalliObject {
	/** very short lived cache to avoid race condition */
	private static final Map<String, Callable<String>> cache = new HashMap<String, Callable<String>>();

	@Override
	public String calliGetBundleSource() throws GatewayTimeout, IOException, OpenRDFException {
		HttpUriClient client = this.getHttpClient();
		List<SourceFile> scripts = new ArrayList<SourceFile>();
		for (Object ext : getCalliScriptsAsList()) {
			String url = ext.toString();
			String code = getJavaScriptCode(client, url);
			scripts.add(SourceFile.fromCode(url, code));
		}

		StringBuilder sb = new StringBuilder();
		for (SourceFile script : scripts) {
			sb.append(script.getCode()).append("\n");
		}
		return sb.toString();
	}

	@Override
	public String calliGetMinifiedBundle() throws Exception {
		final int minification = this.getMinification();
		if (minification < 1)
			return calliGetBundleSource();
		String uri = this.getResource().stringValue();
		final HttpUriClient client = this.getHttpClient();
		final List<String> scripts = new ArrayList<String>(getCalliScriptsAsList());
		Callable<String> future;
		synchronized (cache) {
			future = cache.get(uri);
			if (future == null) {
				cache.put(uri, future = new Callable<String>() {
					private String result;
					public synchronized String call() throws Exception {
						if (result == null)
							return result = compress(minification, scripts, client);
						return result;
					}
				});
			}
		}
		String source = future.call();
		synchronized (cache) {
			cache.remove(uri);
		}
		return "// @source: " + uri + "?source\n" + source;
	}

	@Sparql("PREFIX rdfs:<http://www.w3.org/2000/01/rdf-schema#>\n"
			+ "SELECT DISTINCT (str(?script) AS ?url)\n"
			+ "WHERE { {$this ?one ?script FILTER (regex(str(?one), \"#_\\\\d$\"))}\n"
			+ "UNION {$this ?two ?script FILTER (regex(str(?two), \"#_\\\\d\\\\d$\"))}\n"
			+ "UNION {$this ?three ?script FILTER (regex(str(?three), \"#_\\\\d\\\\d\\\\d+$\"))}\n"
			+ "UNION {?member rdfs:member ?script FILTER (?member = $this)}\n"
			+ "} ORDER BY ?member ?three ?two ?one")
	protected abstract List<String> getCalliScriptsAsList();

	static String compress(int minification, List<String> links, HttpUriClient client)
			throws IOException {
		final List<SourceFile> scripts = new ArrayList<SourceFile>();
		for (String url : links) {
			String code = getJavaScriptCode(client, url);
			scripts.add(SourceFile.fromCode(url, code));
		}

		Compiler compiler = new Compiler();
		CompilerOptions options = new CompilerOptions();
		options.setLanguageIn(LanguageMode.ECMASCRIPT5);
		options.setLanguageOut(LanguageMode.ECMASCRIPT5);
		options.setWarningLevel(DiagnosticGroups.NON_STANDARD_JSDOC, CheckLevel.OFF);
		getCompilationLevel(minification).setOptionsForCompilationLevel(options);

		List<SourceFile> externals = CommandLineRunner.getDefaultExterns();

		Result result = compiler.compile(externals, scripts, options);
		if (result.errors != null && result.errors.length > 0) {
			throw new InternalServerError(result.errors[0].toString());
		}
		return compiler.toSource();
	}

	private int getMinification() {
		int result = Integer.MAX_VALUE;
		for (Number number : getCalliMinified()) {
			if (number.intValue() < result) {
				result = number.intValue();
			}
		}
		if (result == Integer.MAX_VALUE)
			return 2;
		return result;
	}

	private static CompilationLevel getCompilationLevel(int minification) {
		if (minification == 1)
			return CompilationLevel.WHITESPACE_ONLY;
		if (minification == 2)
			return CompilationLevel.SIMPLE_OPTIMIZATIONS;
		return CompilationLevel.ADVANCED_OPTIMIZATIONS;
	}

	private static String getJavaScriptCode(HttpUriClient client, String url) throws IOException {
		Reader reader = openJavaScriptReader(url, 10, client);
		try {
			StringWriter writer = new StringWriter();
			int read;
			char[] cbuf = new char[1024];
			while ((read = reader.read(cbuf)) >= 0) {
				writer.write(cbuf, 0, read);
			}
			return writer.toString();
		} finally {
			reader.close();
		}
	}

	private static Reader openJavaScriptReader(String url, int max,
			HttpUriClient client) throws IOException {
		HttpEntity entity = client.getEntity(url, "text/javascript;charset=UTF-8");
		return new InputStreamReader(entity.getContent(), "UTF-8");
	}

}

@@ -69,94 +69,8 @@
 on;%0A
-import java.io.InputStreamReader;%0Aimport java.io.Reader;%0Aimport java.io.StringWriter;%0A
 impo
@@ -226,26 +226,32 @@
 he.http.
-Http
+util.
 Entity
+Utils
 ;%0Aimport
@@ -296,16 +296,68 @@
 Client;%0A
+import org.callimachusproject.client.HttpUriEntity;%0A
 import o
@@ -4911,554 +4911,109 @@
 %7B%0A%09%09
-Reader reader = openJavaScriptReader(url, 10, client);%0A%09%09try %7B%0A%09%09%09StringWriter writer = new StringWriter();%0A%09%09%09int read;%0A%09%09%09char%5B%5D cbuf = new char%5B1024%5D;%0A%09%09%09while ((read = reader.read(cbuf)) %3E= 0) %7B%0A%09%09%09%09writer.write(cbuf, 0, read);%0A%09%09%09%7D%0A%09%09%09return writer.toString();%0A%09%09%7D finally %7B%0A%09%09%09reader.close();%0A%09%09%7D%0A%09%7D%0A%0A%09private static Reader openJavaScriptReader(String url, int max,%0A%09%09%09HttpUriClient client) throws IOException %7B%0A%09%09HttpEntity entity = client.getEntity(url, %22text/javascript;charset=UTF-8%22);%0A%09%09return new InputStreamReader(entity.getContent()
+HttpUriEntity entity = client.getEntity(url, %22text/javascript%22);%0A%09%09return EntityUtils.toString(entity
 , %22U

Read and use charset from server if provided in script bundles