
/*
 * Copyright (C) 2002 - 2007 Lehrstuhl Grafische Systeme, BTU Cottbus
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package de.grogra.c4d.model;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;

import javax.vecmath.Matrix4d;
import javax.vecmath.Tuple3d;
import javax.vecmath.Vector3d;

import de.grogra.annotation.Range;
import de.grogra.c4d.BaseDocument;
import de.grogra.c4d.BaseObject;
import de.grogra.c4d.Constants;
import de.grogra.c4d.Library;
import de.grogra.c4d.XLObject;
import de.grogra.grammar.RecognitionException;
import de.grogra.grammar.RecognitionExceptionList;
import de.grogra.grammar.Tokenizer;
import de.grogra.reflect.Annotation;
import de.grogra.reflect.AnnotationImpl;
import de.grogra.reflect.Reflection;
import de.grogra.reflect.Type;
import de.grogra.util.DetailedException;
import de.grogra.vecmath.Math2;
import de.grogra.vecmath.VecmathOperators;
import de.grogra.vfs.LocalFileSystem;
import de.grogra.vfs.MemoryFileSystem;
import de.grogra.xl.compiler.ASMTypeLoader;
import de.grogra.xl.compiler.BytecodeWriter;
import de.grogra.xl.compiler.CClass;
import de.grogra.xl.compiler.CompilationUnit;
import de.grogra.xl.compiler.Compiler;
import de.grogra.xl.compiler.CompilerOptions;
import de.grogra.xl.compiler.ProblemReporter;
import de.grogra.xl.compiler.scope.ClassPath;
import de.grogra.xl.compiler.scope.CompilationUnitScope;
import de.grogra.xl.compiler.scope.Scope;
import de.grogra.xl.lang.ImplicitDoubleToFloat;
import de.grogra.xl.modules.DefaultModuleSuperclass;
import de.grogra.xl.modules.InstantiationProducerType;
import de.grogra.xl.modules.Instantiator;
import de.grogra.xl.parser.XLParser;
import de.grogra.xl.parser.XLTokenizer;
import de.grogra.xl.query.UseModel;
import de.grogra.xl.util.ObjectList;
import de.grogra.xl.util.Operators;

public class C4D
{
	public static native void print (String msg);

	public static native void println (String msg);

	public static native long getNext(long ctx, long object);

	public static native long getPred(long ctx, long object);

	public static native long getUp(long ctx, long object);

	public static native long getDown(long ctx, long object);

	public static native long getDownLast(long ctx, long object);

	public static native void insertUnder(long ctx, long object, long parent);

	public static native void insertUnderLast(long ctx, long object, long parent);

	public static native void removeChildren(long ctx, long object);

	public static native void insertObject(long ctx, long object, long parent, long pred, boolean checkNames);

	public static native long getFirstObject(long ctx);

	public static native long findSibling(long ctx, long start, String name);

	public static native long searchObject(long ctx, String name);

	public static native int getType(long ctx, long object);

	public static native long[] enumerateNodes(long ctx, long object, boolean siblings, int basetype, int type);
	
	public static native boolean addEdgeBits(long ctx, long source, long target, int bits);

	public static boolean addEdgeBits(BaseObject source, BaseObject target, int bits)
	{
		return addEdgeBits(source.getDocumentPointer(), source.getHandle(), target.getHandle(), bits);
	}

	public static native void removeEdgeBits(long ctx, long source, long target, int bits);

	public static void removeEdgeBits(BaseObject source, BaseObject target, int bits)
	{
		removeEdgeBits(source.getDocumentPointer(), source.getHandle(), target.getHandle(), bits);
	}

	public static native int getEdgeBits(long ctx, long source, long target);

	public static int getEdgeBits(BaseObject source, BaseObject target)
	{
		return getEdgeBits(source.getDocumentPointer(), source.getHandle(), target.getHandle());
	}

	public static native long newBaseObject(long ctx, int type, boolean addPhongTag);

	public static native boolean getBoolean(long ctx, long node, int id);

	public static native int getInt(long ctx, long node, int id);

	public static native double getReal(long ctx, long node, int id);

	public static native void getVector(long ctx, long node, int id, Tuple3d out);

	public static native void getMatrix(long ctx, long node, int id, Matrix4d out);

	public static native String getString(long ctx, long node, int id);

	public static native long getLink(long ctx, long node, int id);

	public static native void setBoolean(long ctx, long node, int id, boolean v);

	public static native void setInt(long ctx, long node, int id, int v);

	public static native void setReal(long ctx, long node, int id, double v);

	public static native void setVector(long ctx, long node, int id, double x, double y, double z);

	public static native void setMatrix(long ctx, long node, int id, double m00, double m01, double m02, double m03, double m10, double m11, double m12, double m13, double m20, double m21, double m22, double m23);

	public static native void setString(long ctx, long node, int id, String v);

	public static native void setLink(long ctx, long node, int id, long link);

	public static native int getUserInt(long ctx, long node, int id);

	public static native double getUserReal(long ctx, long node, int id);

	public static native void getUserVector(long ctx, long node, int id, Tuple3d out);

	public static native void getUserMatrix(long ctx, long node, int id, Matrix4d out);

	public static native String getUserString(long ctx, long node, int id);

	public static native void setUserInt(long ctx, long node, int id, int v);

	public static native void setUserReal(long ctx, long node, int id, double v);

	public static native void setUserVector(long ctx, long node, int id, double x, double y, double z);

	public static native void setUserMatrix(long ctx, long node, int id, double m00, double m01, double m02, double m03, double m10, double m11, double m12, double m13, double m20, double m21, double m22, double m23);

	public static native void setUserString(long ctx, long node, int id, String v);

	public static native void setPosition(long ctx, long node, double x, double y, double z);
	
	public static native void getPosition(long ctx, long node, Tuple3d out);

	public static native void setFrozenPosition(long ctx, long node, double x, double y, double z);

	public static native void getFrozenPosition(long ctx, long node, Tuple3d out);

	public static native void setRotation(long ctx, long node, double x, double y, double z);
	
	public static native void getRotation(long ctx, long node, Tuple3d out);

	public static native void setFrozenRotation(long ctx, long node, double x, double y, double z);
	
	public static native void getFrozenRotation(long ctx, long node, Tuple3d out);
	
	public static native void setScale(long ctx, long node, double x, double y, double z);
	
	public static native void getScale(long ctx, long node, Tuple3d out);

	public static native void setFrozenScale(long ctx, long node, double x, double y, double z);
	
	public static native void getFrozenScale(long ctx, long node, Tuple3d out);

	public static native void setMl(long ctx, long node, double m00, double m01, double m02, double m03, double m10, double m11, double m12, double m13, double m20, double m21, double m22, double m23);

	public static native void setMg(long ctx, long node, double m00, double m01, double m02, double m03, double m10, double m11, double m12, double m13, double m20, double m21, double m22, double m23);
	
	public static native void getMl(long ctx, long node, Matrix4d out);

	public static native void getMg(long ctx, long node, Matrix4d out);

	public static native void getUpMg(long ctx, long node, Matrix4d out);

	public static native void setEditorMode(long ctx, long node, int value);
	
	public static native int getEditorMode(long ctx, long node);

	public static native void setRenderMode(long ctx, long node, int value);
	
	public static native int getRenderMode(long ctx, long node);
	
	public static native void setColorProperties(long ctx, long node, int mode, double r, double g, double b, boolean xray);

	public static native void setMaterial(long ctx, long node, String material);

	public static native void setName(long ctx, long node, String value);
	
	public static native String getName(long ctx, long node);

	public static BaseDocument createDocument(long pointer)
	{
		return new BaseDocument(pointer);
	}

	public static native BaseDocument getDocument(long ctx);
	
	public static native void setJavaObject(long ctx, long node, XLObject object);

	public static native void addToPriorityList(long list, long doc, long node, int priority);

	public static native double getTime(long doc);

	public static native int getFps(long doc);

	public static native void setSelection(long ctx, long node, int mode);

	public static native void setPoints(long ctx, long node, Tuple3d[] points);

	public static void setVector(Tuple3d v, double x, double y, double z)
	{
		v.x = x;
		v.y = y;
		v.z = z;
	}

	public static Vector3d newVector(double x, double y, double z)
	{
		return new Vector3d(x, y, z);
	}

	public static void setMatrix(Matrix4d m, double m00, double m01, double m02, double m03, double m10, double m11, double m12, double m13, double m20, double m21, double m22, double m23)
	{
		m.m00 = m00; m.m01 = m01; m.m02 = m02; m.m03 = m03;
		m.m10 = m10; m.m11 = m11; m.m12 = m12; m.m13 = m13;
		m.m20 = m20; m.m21 = m21; m.m22 = m22; m.m23 = m23;
		m.m30 = 0; m.m31 = 0; m.m32 = 0; m.m33 = 1;
	}


	public static final RuntimeModel RUNTIME = new RuntimeModel ();
	private static final ThreadLocal<BaseDocument> documents = new ThreadLocal<BaseDocument>();

	private static void setCurrentDocument(BaseDocument scene)
	{
		Graph g = new Graph(RUNTIME);
		RUNTIME.setCurrentGraph(g);
		documents.set(scene);
	}

	private static void clearCurrentDocument()
	{
		documents.set(null);
	}

	static int documentForXLObjectCounter = 0;

	public static synchronized BaseDocument getDocumentForXLObject()
	{
		return (documentForXLObjectCounter != 0) ? getCurrentDocument() : null;
	}

	private static ASMTypeLoader loaderForAll;

	public static synchronized ASMTypeLoader getLoaderForAll ()
	{
		if (loaderForAll == null)
		{
			String classPath = System.getProperty ("user.dir")
				+ File.pathSeparatorChar + de.grogra.util.Utils.urlToFile(C4D.class.getProtectionDomain().getCodeSource().getLocation());
			File[] cpath = ASMTypeLoader.getClassPath (classPath);
			loaderForAll = ASMTypeLoader.getDefaultLoader (C4D.class.getClassLoader());
			loaderForAll.addFiles (LocalFileSystem.FILE_ADAPTER, cpath, true);
		}
		return loaderForAll;
	}

	public static CompilationUnitScope[] compile(XLParser p, ClassPath path, Scope imports, CClass shell, MemoryFileSystem[] fsOut) throws IOException, RecognitionExceptionList
	{
		ProblemReporter problems = new ProblemReporter (-1L, 0);
		try
		{
			p.parse ();
		}
		catch (RecognitionException e)
		{
		}
		problems.addAll (p.getExceptionList ());
		problems.check ();

		MemoryFileSystem fs = new MemoryFileSystem ("classes");
		fsOut[0] = fs;

		CompilerOptions opts = new CompilerOptions ();

		ObjectList<Annotation> annotations = new ObjectList<Annotation>();
		annotations.add(new AnnotationImpl (UseModel.class).setValue ("value", CompiletimeModel.class));
		/*list.add (new AnnotationImpl (de.grogra.xl.property.UseModel.class)
				  .setValue ("type", Node.class)
				  .setValue ("model", PropertyCompiletime.class));*/
		annotations.add(new AnnotationImpl (DefaultModuleSuperclass.class).setValue ("value", XLObject.class));
		annotations.add(new AnnotationImpl (InstantiationProducerType.class).setValue ("value", C4DGenerator.class));
		//annotations.add(new AnnotationImpl (UseExtension.class).setValue ("value", CompilerExtension.class));
		annotations.add(new AnnotationImpl (ImplicitDoubleToFloat.class).setValue ("value", true));
		CompilationUnit unit = new CompilationUnit (path, p.getAST (), p.getFilename (), p.getExceptionList (), imports, opts, annotations);

		Compiler comp = new Compiler ();
		comp.problems = problems;

		BytecodeWriter writer = new BytecodeWriter (opts);

		CompilationUnitScope[] results = comp.compile (new CompilationUnit[] {unit}, shell, writer, fs, fs.getRoot(), shell != null);

		problems.check ();
		if (!problems.isEmpty ())
		{
			println (problems.getDetailedMessage (false));
		}

		fs.writeJar(new FileOutputStream("/Users/ok/classes.zip"));

		return results;
	}

	public static Scope getImports(ClassPath path) throws ClassNotFoundException
	{
		return path.createImports(new String[] {"java.lang", "javax.vecmath", "de.grogra.annotation", "de.grogra.c4d"}, new Class[0],
								  new Class[] {Math2.class},
								  new Class[] {Operators.class, Library.class, VecmathOperators.class, Constants.class});
	}

	public static XLObject compileSource(BaseDocument scene, long node, String file)
	{
		setCurrentDocument(scene);
		try
		{
			Tokenizer t = new XLTokenizer ();
			t.setSource (new StringReader (file), "Code");
			
			MemoryFileSystem[] fs = {null};

			ASMTypeLoader loader = getLoaderForAll();
			ClassPath root = new ClassPath (loader);
			CompilationUnitScope[] results = compile(new XLParser (t), root, getImports(root), null, fs);
			println("Compilation successful");

			for (int i = 0; i < results.length; i++)
			{
				Type[] a = results[i].getDeclaredTypes ();
				for (int j = 0; j < a.length; j++)
				{
					if (Reflection.isPublic(a[i])
						&& (a[i].getDeclaringType() == null)
						&& Reflection.isSuperclassOrSame(XLObject.class, a[i]))
					{
						URL u = fs[0].toURL (fs[0].getRoot ());
						ClassLoader cloader = new URLClassLoader (new URL[] {u}, getLoaderForAll().getClassLoader ());
						Class cls = Class.forName (a[i].getBinaryName(), false, cloader);
						try
						{
							synchronized (C4D.class)
							{
								documentForXLObjectCounter++;
								scene.setHandleForXLObject(node);
							}
							return (XLObject) cls.newInstance();
						}
						finally
						{
							synchronized (C4D.class)
							{
								scene.setHandleForXLObject(0);
								documentForXLObjectCounter--;
							}
						}
					}
				}
			}

			return null;
		}
		catch (Exception e)
		{
			println ("");
			println ("");
			println ((e instanceof DetailedException) ? ((DetailedException) e).getDetailedMessage(false) : e.getMessage());
			return null;
		}
		finally
		{
			clearCurrentDocument();
		}
	}

	private static Method[] getMethodsImpl(Object o)
	{
		Method[] a = o.getClass().getDeclaredMethods();
		int n = a.length;
		for (int i = n - 1; i >= 0; i--)
		{
			Method m = a[i];
			if ((m.getReturnType () != Void.TYPE) || (m.getParameterTypes().length > 0)
					|| ((m.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) != Modifier.PUBLIC)
					|| m.getName().equals("reset"))
			{
				a[i] = a[--n];
			}
		}
		Method[] methods = new Method[n];
		System.arraycopy (a, 0, methods, 0, n);
		return methods;
	}

	public static String[] getMethods(Object o)
	{
		Method[] methods = getMethodsImpl(o);
		int n = methods.length;
		String[] s = new String[n];
		for (int i = 0; i < n; i++)
		{
			s[i] = methods[i].getName();
		}
		return s;
	}

	public static class FieldInfo
	{
		public String name;
		public boolean isStatic;
		public int dtype;
		public boolean isDouble;
		public double min;
		public double max;
	}

	public static FieldInfo[] getFields(Object o)
	{
		Field[] a = o.getClass().getDeclaredFields();
		ObjectList<FieldInfo> info = new ObjectList<FieldInfo> ();
		for (Field f : a)
		{
			if ((f.getModifiers() & (Modifier.PUBLIC | Modifier.FINAL)) == Modifier.PUBLIC)
			{
				FieldInfo i = new FieldInfo ();
				Class<?> t = f.getType();
				if (t == boolean.class)
				{
					i.dtype = Constants.DTYPE_BOOL;
				}
				else if (t == int.class)
				{
					i.dtype = Constants.DTYPE_LONG;
				}
				else if (t == float.class)
				{
					i.dtype = Constants.DTYPE_REAL;
				}
				else if (t == double.class)
				{
					i.dtype = Constants.DTYPE_REAL;
					i.isDouble = true;
				}
				else if (t == Vector3d.class)
				{
					i.dtype = Constants.DTYPE_VECTOR;
				}
				else if (t == String.class)
				{
					i.dtype = Constants.DTYPE_STRING;
				}
				else if (t == BaseObject.class)
				{
					i.dtype = Constants.DTYPE_BASELISTLINK;
				}
				else
				{
					continue;
				}
				i.name = f.getName();
				i.isStatic = (f.getModifiers() & Modifier.STATIC) != 0;
				Range r = f.getAnnotation(Range.class);
				if (r != null)
				{
					i.min = r.min();
					i.max = r.max();
				}
				else
				{
					i.min = 1;
					i.max = 0;
				}
				info.add(i);
			}
		}
		return info.toArray(new FieldInfo[info.size()]);
	}

	public static BaseDocument getCurrentDocument()
	{
		return documents.get();
	}
/*
	private static void dumpTree(long node, String indent)
	{
		println(indent + node + " " + getType(node));
		indent += "  ";
		for (long child = getDown(node); child != 0; child = getNext(child))
		{
			dumpTree(child, indent);
		}
	}
*/
	public static String getStackTrace(Throwable e)
	{
		StringWriter w = new StringWriter ();
		PrintWriter pw = new PrintWriter (w);
		e.printStackTrace(pw);
		return w.toString();
	}

	
	private static final ThreadLocal<BaseObject> roots = new ThreadLocal<BaseObject>();

	public static BaseObject getCurrentRoot()
	{
		return roots.get();
	}

	public static void invokeMethod (XLObject o, int n)
	{
		Method[] methods = getMethodsImpl(o);
		if ((n < 0) || (n >= methods.length))
		{
			return;
		}
		setCurrentDocument(o.getDocument());
		try
		{
			roots.set(o);
			methods[n].invoke(o);
			RUNTIME.currentGraph().derive();
		}
		catch (Throwable e)
		{
			println(getStackTrace(e));
		}
		finally
		{
			roots.set(null);
			clearCurrentDocument();
		}
	}

	public static void invokeReset (XLObject o)
	{
		Method m = null;
		try
		{
			m = o.getClass().getMethod("reset", new Class[0]);
		}
		catch (Exception e)
		{
			return;
		}
		if ((m.getModifiers() & Modifier.PUBLIC) == 0)
		{
			return;
		}
		setCurrentDocument(o.getDocument());
		try
		{
			roots.set(o);
			m.invoke(o);
			RUNTIME.currentGraph().derive();
		}
		catch (Throwable e)
		{
			println(getStackTrace(e));
		}
		finally
		{
			roots.set(null);
			clearCurrentDocument();
		}
	}

	public static void removeDeletedObjects(BaseDocument scene, long[] handles)
	{
		scene.removeObjectReferences(handles);
	}

	public static void generate(XLObject generator, long cache)
	{
		if (generator instanceof Instantiator)
		{
			C4DGenerator gen = new C4DGenerator();
			gen.initialize(generator.getDocument().getBaseObject(cache));
			setCurrentDocument(generator.getDocument());
			try
			{
				((Instantiator<C4DGenerator>) generator).instantiate(gen);
			}
			catch (Throwable e)
			{
				println(getStackTrace(e));
			}
			finally
			{
				clearCurrentDocument();
			}
		}
	}

	public static void addToExecution(XLObject model, long list)
	{
		model.addToExecution(list);
	}

	public static void execute(XLObject model, int priority, int flags)
	{
		setCurrentDocument(model.getDocument());
		try
		{
			roots.set(model);
			model.execute(priority, flags);
			RUNTIME.currentGraph().derive();
		}
		catch (Throwable e)
		{
			println(getStackTrace(e));
		}
		finally
		{
			roots.set(null);
			clearCurrentDocument();
		}
	}

	public static void setupReferenceObjects(BaseDocument doc)
	{
		doc.setupReferenceObjects();
	}

	public static C4DShell createShell()
	{
		return new C4DShell();
	}

	public static boolean executeInShell(BaseDocument doc, C4DShell s, String cmd)
	{
		setCurrentDocument(doc);
		try
		{
			s.execute(cmd);
			RUNTIME.currentGraph().derive();
			return true;
		}
		catch (Throwable e)
		{
			println(getStackTrace(e));
			return false;
		}
		finally
		{
			roots.set(null);
			clearCurrentDocument();
		}
	}

	public static void resetShell(C4DShell s)
	{
		s.reset();
	}

	public static BaseObject getBaseObject(BaseDocument doc, long handle)
	{
		return doc.getBaseObject(handle);
	}

	public static long getHandle(BaseObject o)
	{
		return o.getHandle();
	}
}
