/**
  *
  *                      OOAS Compiler
  *
  *       Copyright 2015, AIT Austrian Institute of Technology.
  * This code is based on the C# Version of the OOAS Compiler, which is
  * copyright 2015 by the Institute of Software Technology, Graz University
  * of Technology with portions copyright by the AIT Austrian Institute of
  * Technology. All rights reserved.
  *
  * SEE THE "LICENSE" FILE FOR THE TERMS UNDER WHICH THIS FILE IS PROVIDED.
  *
  * If you modify the file please update the list of contributors below to in-
  * clude your name. Please also stick to the coding convention of using TABs
  * to do the basic (block-level) indentation and spaces for anything after
  * that. (Enable the display of special chars and it should be pretty obvious
  * what this means.) Also, remove all trailing whitespace.
  *
  * Contributors:
  *               Willibald Krenn (AIT)
  *               Stephan Zimmerer (AIT)
  *               Markus Demetz (AIT)
  *               Christoph Czurda (AIT)
  *
  */


package org.momut.ooas.codegen.java;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;

import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
import org.momut.ooas.parser.ParserError;
import org.momut.ooas.parser.ParserState;

/**
 * This code is based on a blog entry of Morten Nobel.
 *
 * http://blog.nobel-joergensen.com/2008/07/16/using-eclipse-compiler-to-create-dynamic-java-objects-2/
 *
 */
/// <summary>
/// This class can be used to execute dynamic uncompiled code at runtime
/// This class is not exception safe, all function calls should be exception handled.
/// </summary>
public class JavaCodeCompiler
{
	private static class MemorySource extends SimpleJavaFileObject {
		private final String src;
		public MemorySource(String name, String src) {
			super(URI.create("file:///" + name + ".java"), Kind.SOURCE);
			this.src = src;
		}
		@Override
		public CharSequence getCharContent(boolean ignoreEncodingErrors) {
			return src;
		}
		@Override
		public OutputStream openOutputStream() {
			throw new IllegalStateException();
		}
		@Override
		public InputStream openInputStream() {
			return new ByteArrayInputStream(src.getBytes());
		}
	}

	private static class SpecialJavaFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
		private final SpecialClassLoader xcl;
		public SpecialJavaFileManager(StandardJavaFileManager sjfm, SpecialClassLoader xcl) {
			super(sjfm);
			this.xcl = xcl;
		}
		@Override
		public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
			final MemoryByteCode mbc = new MemoryByteCode(name);
			xcl.addClass(name, mbc);
			return mbc;
		}
		@Override
		public ClassLoader getClassLoader(Location location) {
			return xcl;
		}
	}

	private static class MemoryByteCode extends SimpleJavaFileObject {
		private ByteArrayOutputStream baos;
		public MemoryByteCode(String name) {
			super(URI.create("byte:///" + name + ".class"), Kind.CLASS);
		}
		@Override
		public CharSequence getCharContent(boolean ignoreEncodingErrors) {
			throw new IllegalStateException();
		}
		@Override
		public OutputStream openOutputStream() {
			baos = new ByteArrayOutputStream();
			return baos;
		}
		@Override
		public InputStream openInputStream() {
			throw new IllegalStateException();
		}
		public byte[] getBytes() {
			return baos.toByteArray();
		}
	}

	private static class SpecialClassLoader extends ClassLoader {
		private final Map<String,MemoryByteCode> m = new HashMap<String, MemoryByteCode>();
		@Override
		protected Class<?> findClass(String name) throws ClassNotFoundException {
			MemoryByteCode mbc = m.get(name);
			if (mbc==null){
				mbc = m.get(name.replace(".","/"));
				if (mbc==null){
					return super.findClass(name);
				}
			}
			return defineClass(name, mbc.getBytes(), 0, mbc.getBytes().length);
		}
		public void addClass(String name, MemoryByteCode mbc) {
			m.put(name, mbc);
		}
	}

	public static Class<?> compileCode(String code, String className, String packageName, ParserState parserState)
	{
		try {
			final JavaCompiler javac = new EclipseCompiler();

			final StandardJavaFileManager sjfm = javac.getStandardFileManager(null, null, null);
			final SpecialClassLoader cl = new SpecialClassLoader();
			final SpecialJavaFileManager fileManager = new SpecialJavaFileManager(sjfm, cl);
			final List<String> options = Arrays.asList(new String[]{"-1.7"});

			final List<MemorySource> compilationUnits = Arrays.asList(new MemorySource(className, code));
//			DiagnosticListener<?> dianosticListener = null;
			final Iterable<String> classes = null;
			final Writer out = new PrintWriter(System.err);
			final JavaCompiler.CompilationTask compile = javac.getTask(out, fileManager, null, options, classes, compilationUnits);
			final boolean res = compile.call();
			if (res){
				return cl.findClass(packageName + "." + className);
			}
		} catch (final Exception e){
			parserState.AddErrorMessage(new ParserError("JavaCodeCompiler.java",0,0,e.getMessage()));
		}
		return null;
//		if (results.Errors.HasErrors)
//		{
//			StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
//			foreach (CompilerError error in results.Errors)
//			{
//				errors.AppendFormat("Line %s,%s\t: %s\n", error.Line, error.Column, error.ErrorText);
//			}
//			throw new UlyssesCompileException(errors.ToString());
//		}
//		else
//		{
//			return results.CompiledAssembly;
//		}
	}

	public static void ExecuteCode(Class<?> clazz, String methodname, JavaInitVisitor visitor, ParserState parserState)
	{
		try{
			final Object o = clazz.getConstructor().newInstance();
			final Method m = clazz.getMethod("start", JavaInitVisitor.class);
			m.invoke(o, visitor);
		}
		catch (final Exception e) {
			parserState.AddErrorMessage(new ParserError("JavaCodeCompiler.java",0,0,e.toString()));
		}
	}
}
