package s3.services.simplejit.bootimage;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

import ovm.core.domain.Blueprint;
import ovm.core.domain.Code;
import ovm.core.domain.Domain;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Method;
import ovm.core.domain.Type;
import ovm.core.repository.JavaNames;
import ovm.core.repository.Selector;
import ovm.core.repository.UnboundSelector;
import ovm.core.services.memory.VM_Address;
import ovm.core.services.process.HostedProcess;
import ovm.util.HTObject;
import ovm.util.Iterator;
import ovm.util.OVMRuntimeException;
import s3.core.domain.S3Blueprint;
import s3.core.domain.S3ByteCode;
import s3.core.domain.S3Domain;
import s3.core.domain.S3NativeCode;
import s3.services.bootimage.ImageObserver;
import s3.services.simplejit.CompilerVMInterface;
import s3.services.simplejit.SimpleJITBootImageHeader;
import ovm.util.HashMap;
import s3.services.bootimage.Analysis;
import ovm.util.S3Set;
import ovm.core.domain.DomainDirectory;
import s3.util.Walkabout;
import ovm.core.domain.ReflectiveArray;
import ovm.core.repository.RepositoryUtils;

public class SimpleJITBootImageCompileObserver
    extends ImageObserver {
    boolean debugPrint_;
    boolean opt_;
    SimpleJITBootImageHeader jitHeader;
    S3Domain executiveDomain;
    Method mainMethod;

    public SimpleJITBootImageCompileObserver
	(String OVMMakefile, String gdbinit,
	 String debugPrint, String simplejitopt)
    {
	this(OVMMakefile, gdbinit,
	     debugPrint != null && !debugPrint.equals("false"),
	     simplejitopt != null && !simplejitopt.equals("false"));
    }

    public SimpleJITBootImageCompileObserver
	(String OVMMakefile, String gdbinit, boolean debugPrint, boolean opt)
    {
	super(OVMMakefile, gdbinit);
	this.debugPrint_ = debugPrint;
	this.opt_ = opt;
    }

    public void beforeRewriting(Gardener gardener) {
	executiveDomain = (S3Domain) getExecutiveDomain();
	mainMethod = ((Code) getHeader("mainMethod")).getMethod();
	jitHeader = new SimpleJITBootImageHeader();


	gardener.declare(jitHeader, "simpleJITBootImageHeader");
	// We don't create the actual dispatch tables until
	// defineInitialHeap, so we want to be sure that this array
	// type is live.
	new ReflectiveArray(executiveDomain,
			    RepositoryUtils.makeTypeName("s3/core/domain",
							 "S3NativeCode"));
	gardener.excludePackage("s3/services/simplejit/bootimage");
    }

    HashMap compilers = new HashMap();
    
    public void addMethod(Method method) {
	Type t = method.getDeclaringType();
	Domain dom = t.getDomain();
	SimpleJITBootImageCompiler compiler =
	    (SimpleJITBootImageCompiler) compilers.get(dom);
	if (compiler == null) {
	    // This code cannot safely execute in beforeRewriting, but it
	    // has to go somewhere
	    VM_Address jitHeaderAddr = VM_Address.fromObject(jitHeader);
	    UnboundSelector.Method[] csaMethods;
	    try {
		S3Blueprint bpcsa = (S3Blueprint) executiveDomain.blueprintFor
		    (JavaNames.ovm_core_execution_CoreServicesAccess,
		     executiveDomain.getSystemTypeContext());

		Selector.Method[] sels = bpcsa.getSelectors();
		csaMethods =
		    new UnboundSelector.Method[sels.length];
		for (int i = 0; i < sels.length; i++)
		    csaMethods[i] = sels[i].getUnboundSelector();
	    
	    } catch (LinkageException le) {
		throw le.fatal();
	    }
	    SimpleJITBootImageCompilerVMInterface compilerVMInterface = 
		new SimpleJITBootImageCompilerVMInterface(executiveDomain,
							  (S3Domain) dom,
							  csaMethods,
							  jitHeaderAddr);
	    compiler = new SimpleJITBootImageCompiler(debugPrint_,
						      opt_,
						      compilerVMInterface);
	    compilers.put(dom, compiler);
	}
	try {
	    compiler.compile(method, dom.blueprintFor(t));
	} catch (LinkageException e) {
	    throw e.fatal();
	}
	if (method == mainMethod) {
	    S3NativeCode nativeMainMethod
		= (S3NativeCode) mainMethod.getCode(S3NativeCode.KIND);
	    jitHeader.setMainMethod(nativeMainMethod);
	}
    }

    public void defineInitialHeap(Domain dom, Analysis anal, S3Set _) {
	new DomainWalker(this, anal).walkDomain(dom);
    }

    public static class DomainWalker extends ImageObserver.DomainWalker {
	SimpleJITBootImageCompileObserver outer;
	Analysis anal;

	public DomainWalker(SimpleJITBootImageCompileObserver outer,
			    Analysis anal) {
	    outer.super();
	    this.outer = outer;
	    this.anal = anal;
	}

	private void generateHeaderFile(SimpleJITBootImageCompiler compiler) {
	    try {
		PrintWriter pw =
		    new PrintWriter
		    (new FileOutputStream("runtime_functions.h"));

		String[] runtimeFunctions =
		    SimpleJITBootImageCompilerVMInterface
		    .runtimeFunctionSymbols;

		pw.println("/* Automatically generated file. Do not edit! */");
		pw.println("#ifndef RUNTIME_FUNCTION_H");
		pw.println("#define RUNTIME_FUNCTION_H");
		pw.println("void* runtimeFunctionTable[" 
			     + runtimeFunctions.length + "];");
		pw.println();
		for (int i = 0; i < runtimeFunctions.length; i++) {
		    pw.println("void* OVM_" + runtimeFunctions[i] + ";");
		}
		pw.println();
		pw.println("void installRuntimeFunctionTable() {");
		for (int i = 0; i < runtimeFunctions.length; i++) {
		    pw.println("\truntimeFunctionTable["
				 + i
				 + "] = OVM_"
				 + runtimeFunctions[i]
				 + ";");
		}
		pw.println("}");
		String stackFrameDefiningCFunctions = 
		    compiler.getStackLayoutAsCFunction();
		pw.println(stackFrameDefiningCFunctions);
        pw.println("int csa_generateThrowable_index = " + 
                compiler.getCodeGeneratorPrecomputed().csa_generateThrowable_index + ";");
		pw.println("#endif\n");
		pw.close();
	    } catch (IOException e) {
		throw new OVMRuntimeException(e);
	    }
	}

        /*
         * Invoke simpleJIT against the blueprint set and propagate
         * native code objects of inherited (not overriden) method
         * from parents' native vtables to children's vtables. HY
         */
	public void walkDomain(Domain _dom) {
	    S3Domain dom = (S3Domain) _dom;

	    // Want to run this once for a build
	    // There is no meaning with the executiveDomain
	    if (outer.executiveDomain == dom) {
		generateHeaderFile((SimpleJITBootImageCompiler)
				   outer.compilers.get(dom));
	    }

	    S3NativeCode[] arrayVTable = null;
	    S3NativeCode[] arrayIFTable = null;
	    S3NativeCode[] arrayNVTable = null;
            /*
             * The propagation phase
             *
             * Fill in the native vtable/iftable/nvtable slots for 
             * every method that has S3NativeCode.
             */
            for (Iterator it = dom.getBlueprintIterator(); it.hasNext();) {
                S3Blueprint bp = ((S3Blueprint) it.next());
		if (! (bp instanceof S3Blueprint.Scalar ||
		       bp instanceof S3Blueprint.Array)) // Arrays have methods !
		    continue;
                if (bp.getType().isInterface())
		    continue;
		if (bp.isScalar()
		    && !anal.shouldCompile(bp.isSharedState()
					   ? bp.getInstanceBlueprint()
					   : bp))
                    continue;

		if (bp.isArray() && arrayVTable != null) {
		    bp.setNativeVTable(arrayVTable);
		    bp.setNativeIFTable(arrayIFTable);
		    bp.setNativeNVTable(arrayNVTable);
		}

		// vtable -- used for invokespecial even in abstract
		// classes
		S3ByteCode[] vTable = (S3ByteCode[]) bp.getVTable();
// 		S3NativeCode[] nativeVTable = bp.getNativeVTable();
		if (vTable != null) {
		    S3NativeCode[] nativeVTable
			= new S3NativeCode[vTable.length];
		    boolean empty = true;
		    for (int i = 0; i < vTable.length; i++) {
			if (vTable[i] != null) {
			    S3ByteCode bc = vTable[i];
			    Method m = bc.getMethod();
			    S3NativeCode nc =
				(S3NativeCode) m.getCode(S3NativeCode.KIND);
			    if (nc != null) {
				nativeVTable[i] = nc;
				empty = false;
			    }
			}
		    }
		    if (!empty)
			bp.setNativeVTable(nativeVTable);
		}

		if (!bp.getType().getMode().isAbstract()
		    && !bp.isSharedState())
		 {
		    // iftable
		    S3ByteCode[] ifTable = (S3ByteCode[]) bp.getIFTable();
// 		    S3NativeCode[] nativeIfTable = bp.getNativeIFTable();
		    if (ifTable != null) {
			S3NativeCode[] nativeIfTable =
			    new S3NativeCode[ifTable.length];
			boolean empty = true;
			for (int i = 0; i < nativeIfTable.length; i++) {
			    if (ifTable[i] != null) {
				S3ByteCode bc = ifTable[i];
				Method m = bc.getMethod();
				S3NativeCode nc =
				    (S3NativeCode) m.getCode(S3NativeCode.KIND);
				if (nc != null) {
				    nativeIfTable[i] = nc;
				    empty = false;
				}
			    }
			}
			if (!empty)
			    bp.setNativeIFTable(nativeIfTable);
		    }
		}
                // nvtable
                S3ByteCode[] nvTable = (S3ByteCode[]) bp.getNVTable();
// 		S3NativeCode[] nativeNVTable = bp.getNativeNVTable();
                if (nvTable != null) {
		    S3NativeCode[] nativeNVTable =
			new S3NativeCode[nvTable.length];
		    boolean empty = true;
                    for (int i = 0; i < nativeNVTable.length; i++) {
                        if (nvTable[i] != null) {
                            S3ByteCode bc = nvTable[i];
                            Method m = bc.getMethod();
                            S3NativeCode nc =
                                (S3NativeCode) m.getCode(S3NativeCode.KIND);
			    if (nc != null) {
				nativeNVTable[i] = nc;
				empty = false;
			    }
                        }
                    }
		    if (!empty)
			bp.setNativeNVTable(nativeNVTable);
                }

		if (bp.isArray()) {
		    arrayVTable = bp.getNativeVTable();
		    arrayIFTable = bp.getNativeIFTable();
		    arrayNVTable = bp.getNativeNVTable();
		}
            }
        }
    }

}
