package s3.services.bootimage;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.StringTokenizer;

import org.ovmj.util.Runabout;

import ovm.core.domain.Blueprint;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Method;
import ovm.core.domain.Type;
import ovm.core.execution.Context;
import ovm.core.execution.Processor;
import ovm.core.repository.JavaNames;
import ovm.core.repository.RepositoryUtils;
import ovm.core.repository.Selector;
import ovm.core.repository.TypeName;
import ovm.core.services.format.JavaFormat;
import ovm.core.services.memory.VM_Address;
import ovm.core.services.memory.VM_Word;
import ovm.core.stitcher.InvisibleStitcher;
import ovm.core.stitcher.InvisibleStitcher.MisconfiguredException;
import ovm.util.CommandLine;
import ovm.util.HashSet;
import ovm.util.Iterator;
import ovm.util.S3Set;
import s3.core.domain.S3Blueprint;
import s3.core.domain.S3ByteCode;
import s3.core.domain.S3Domain;
import s3.core.domain.S3ExecutiveDomain;
import s3.core.domain.S3JavaUserDomain;
import s3.core.execution.S3BottomFrame;
import s3.util.Walkabout;
import s3.util.Walkabout.ObjectAdvice;

public class DomainSprout extends BootBase
    implements ImageObserver.Gardener
{
    final private S3Domain dom;
    final private Type.Context defaultTypeCtx;
    final private Method mainMethod;
    final private Driver driver;
    final static String ENTRY_POINT = "startup:()V";
    final static Class EXECUTIVE = ovm.core.Executive.Interface.class;
   
    static StructGenerator structGen_;

    final private NativeCallGenerator nativeCallGenerator; // XXX: unused
    
    private final BuildTimeLoader sysLoader;

    public DomainSprout(final Driver driver, final S3JavaUserDomain domain, 
	    final Analysis analysis, final CommandLine arguments) {
	this.driver = driver;
	this.dom = domain;
	this.nativeCallGenerator = analysis.ncg;

	try {
	    BuildTimeLoader bootLoader = new BuildTimeLoader(domain.getSystemTypeContext(), 
		    		null, arguments.getLongString("bootclasspath"));
	    sysLoader = new BuildTimeLoader(domain.getApplicationTypeContext(), 
		    		bootLoader, arguments.getLongString("classpath"));
	    ((S3Domain) domain).bootstrap();
	}
	catch (LinkageException e) { throw e.unchecked(); }
	catch (IOException e) { throw new RuntimeException(e); }

	this.defaultTypeCtx = domain.getApplicationTypeContext();
        this.mainMethod = null;

	// list of classes to be added explicity to run SpecJVM
	// which uses lots of reflection
	try {
	    String rt = arguments.getLongString("ud-reflective-classes");
	    if (rt == null) rt = "";
	    for (StringTokenizer t = new StringTokenizer(rt, ", \n\t"); t.hasMoreTokens();) {
		String rawName = t.nextToken();
		TypeName tn = JavaFormat._.parseTypeName(rawName);
		if (tn.isArray()) {
		    dom.registerNew(tn.asArray());
		} else if (tn.isScalar()) {
		    TypeName.Scalar gtn = tn.asScalar().getGemeinsamTypeName();
		    dom.registerCall(Selector.Method.make(JavaNames.CLINIT, gtn));
		} else {
		    throw new MisconfiguredException("bad reflective class " + rawName);
		}
	    }

	    
	    String udtargets = arguments.getLongString("udtargets");
	    udtargets = udtargets.replaceAll(" T:", "!T:");  // TRANSACTION/PAR 
	    // We have methods with white spaces.... -jv 
	    
	    // these strings are easy to mess up.  Provide good error reporting
	    StringTokenizer tokenizer = new StringTokenizer(udtargets == null ? "" : udtargets);
	    if ((tokenizer.countTokens() & 1) == 1) 
		throw new MisconfiguredException(	"odd number of tokens in -udtargets option"+
			"-udtargets consists of TypeName, UnboundSelector pairs"+
			"(value provided: " + udtargets + ")");	    
	    if (udtargets != null) {
		tokenizer = new StringTokenizer(udtargets);
		if ((tokenizer.countTokens() & 1) == 1) 
			throw new MisconfiguredException(	"odd number of tokens in -udtargets option"+
				"-udtargets consists of TypeName, UnboundSelector pairs"+
				"(value provided: " + udtargets + ")");	    		 
		while (tokenizer.hasMoreTokens()) {
		    String tn = tokenizer.nextToken();
		    String msel = tokenizer.nextToken();
		   msel = msel.replaceAll("!T:"," T:"); // TRANSACTION/PAR undo previous replace
		    try {
			dom.registerCall(RepositoryUtils.methodSelectorFor(tn, msel));
		    } catch (Error e) {
			System.err.println("bad target " + tn + " " + msel);
			throw e;
		    } catch (RuntimeException e) {
			System.err.println("bad target " + tn + " " + msel);
			throw e;
		    }
		}
	    }
	} catch (IOException e) {
	    throw new MisconfiguredException("parsing reflection hints", e);
	}
    }

    public DomainSprout(final Driver driver, final S3ExecutiveDomain domain, 
	    	final Analysis analysis, final GC walker,  CommandLine options) {
	this.driver = driver;
	this.dom = domain;
	this.nativeCallGenerator = analysis.ncg;
	this.walker_ = walker;
	this.defaultTypeCtx = domain.getSystemTypeContext();
	ImageObserver._.defineExecutiveDomain(domain, this);

	try {
	    sysLoader = new BuildTimeLoader(defaultTypeCtx, null, options.getLongString("xdpath")) {
		public Type loadType(TypeName.Scalar tn) throws LinkageException {
		    Type ret = super.loadType(tn);
		    ReflectionSupport.classForTypeName(tn);
		    Type gret = ret.getSharedStateType();
		    Method boot = gret.getMethod(JavaNames.BOOT_);
		    if (boot != null) domain.addBootMethod(boot);
		    return ret;
		}
	    };
	    defineExcludes();
	    ((S3Domain) domain).bootstrap();
	}
	catch (LinkageException e) { throw e.unchecked(); }
	catch (IOException e) { throw new RuntimeException(e); }
	
        /**
         * Phase 0: create the objects that are in the image header.
         *
         * The field names of header object will be used as identifiers of a
         * generated C-struct.<p> Basic types are included in the header to
         * ensure that corresponding Java classes are included in the image.
         * It is necessary to force inclusion of basic array types because the
         * interpreter needs C-structs for compiling its array ops.
         **/
        try {
            Object mainObject = InvisibleStitcher.singletonFor(EXECUTIVE.getName());
	    Selector.Method sel = 
		ReflectionSupport.methodSelectorFor(mainObject.getClass(), ENTRY_POINT, false);
	    domain.registerCall(sel);
	    mainMethod = defaultTypeCtx.typeFor(sel.getDefiningClass()).asScalar().getMethod(sel);
	    OVMassert(mainMethod != null);
            declare(mainObject, "mainObject");
            declare(mainMethod.getCode(S3ByteCode.KIND), "mainMethod");
        } catch (Exception e) {
            throw failure(e);
        }

        declare(driver.getCoreServicesAccess(), "coreServicesAccess");
        declare(Context.factory().make(), "bootContext");
        declare(new Processor(), "bootProcessor");
        declare(getBottomCode(), "bottomFrameCode");
        declare(new S3BottomFrame(), "bottomFrameObject");
    }

    public void declare(Object target, String name) {
        OVMassert(!(target instanceof Class));
        // FIXME document the rationale for adding classes to the header.
        if (!name.equals(""))
            driver.getImage().addHeader(name, target);
        if (target != null) {
            walker_.addRoot(target);
        }
    }

    public void excludeClass(String pkg, String cls) {
	sysLoader.excludeClass(RepositoryUtils.makeTypeName(pkg, cls));
    }
    public void includeClass(TypeName tn) {
	sysLoader.includeClass(tn);
    }
    public void excludePackage(String pkg) {
	sysLoader.excludePackage(pkg);
    }
    public void excludeClasses(TypeName[] tn) {
	sysLoader.excludeClasses(tn);
    }

    public String toString() {
        return "DomainSprout[" + dom + "]";
    }

    public S3Domain getDomain() {
        return dom;
    }

    private S3ByteCode getByteCode(Class definer, String method) {
	Selector.Method sel = ReflectionSupport.methodSelectorFor(definer, method, false);
	TypeName dtn = sel.getDefiningClass();
	Method mm;
	try {
	    mm = defaultTypeCtx.typeFor(dtn).asScalar().getMethod(sel);
	    if (null == mm) { // try static methods now
		sel = ReflectionSupport.methodSelectorFor(definer, method, true);
		dtn = sel.getDefiningClass();
		mm = defaultTypeCtx.typeFor(dtn).asScalar().getMethod(sel);
	    }
	} catch (LinkageException e) { throw e.unchecked(); }
	OVMassert(null != mm);
	return (S3ByteCode) mm.getCode(S3ByteCode.KIND);
    }

    // FIXME: Is this interpreter-only?  I think that simplejit has a
    // similar notion, but it doesn't use S3ByteCode
    S3ByteCode getBottomCode() {
	return getByteCode(s3.core.execution.S3BottomFrame.class, "bottomFrame:()V");
    }

    /* *
     * FIXME: buildBlueprintClosure is gone except for the one little
     * part that honored -app-classes-live.  I'm not exactly sure
     * where this functionality belongs in the new system.
     * */
//     S3Set buildBlueprintClosure(TypeClosure.Pruner _pruner, boolean userdom)
//         throws LinkageException
//     {
//         TypeName.Scalar[] specificRoots;
// 	if (userdom
// 	    && driver.arguments.getBoolean("app-classes-live"))
// 	{
// 	    try {
// 		// XXX:  reimplement with Type.Loader and Resource.Action
// 		Bundle.Path p = null; //dom.getApplicationTypeContext().getBundlePath();
// 		p.loadAll(false);
// 		p.forAll
// 		    (new RepositoryClass.Action() {
// 			    public void process(RepositoryClass c) {
// 				declare(c.getName());
// 			    }
// 			},
// 		     false);
// 	    }
// 	    catch (OVMException e) { throw e.unchecked(); }
// 	} 
// 	return null;
//     }


    public void dumpStructs(Analysis anal ) {
	HashSet dumpedBpts = new HashSet();
	for (Iterator it = dom.getBlueprintIterator(); it.hasNext(); ) {
	    Blueprint bp = (Blueprint) it.next();
	    if (bp.isReference() && !bp.isSharedState()
		&& (bp.isArray() || anal.shouldCompile(bp)))
		dumpStruct(bp, dumpedBpts);
	}
    }

    /**
     * Dump a C struct for the given typename to the struct-file.
     * This method loads the TypeName into our domain.  Hence, after
     * all structs have been generated, Domain.getBlueprintIterator
     * will iterate over all blueprints.
     *
     * @param tn type name to dump
     * @param dom which domain
     **/
    private void dumpStruct(Blueprint _bp, HashSet dumpedBpts) {
        if (dumpedBpts.contains(_bp)) {
            return;
        } else {
            dumpedBpts.add(_bp);
        }
        S3Blueprint bp = (S3Blueprint) _bp;
        S3Blueprint parent = bp.getParentBlueprint();
        if (parent != null) {
            //make sure parent types written
            dumpStruct(parent, dumpedBpts);
        }
	structGen_.dumpStruct(bp);
    }


    private GC walker_;

    /**
     * Phase 3b: Find all objects in the object graph and return a
     * hashtable containing all the reachable objects.
     **/
    public static void registerAdvice(Walkabout walker_) {
	// The StringSquirter updates the UTF8Store.  To ensure that
	// we save the UTF8Store in a consistent state, we avoid
	// walking it (and the object graph reachable from its private
	// fields), until everything else is walked.

	// This is no longer needed because we perform an entirely new
	// GC each time a GC adds objects to the heap
	walker_.registerAfter(new Walkabout.IgnoreObjectAdvice(Ephemeral.class));
	walker_.register(new Walkabout.IgnoreStaticFieldAdvice(Ephemeral.class));
        ignoreClass(walker_, Ephemeral.Void.class);
        ignoreClass(walker_, java.net.URL.class);
        ignoreClass(walker_, Runabout.class);
	ignoreClass(walker_, ThreadLocal.class);
	ignoreClass(walker_, java.io.PrintWriter.class);

	// Safe whether we are j2c or not.
	ignoreClass(walker_, ovm.core.repository.Bytecode.class);
	if (ImageObserver._.isJ2c()) { // FIXEME -- DELETEME ?? Cheap hack ??
	    // We may miss some invisibly stitched entries
	    // ignoreClass(walker_, ovm.core.repository.Constants.class);
            // walker_.register(new S3ByteCodeCleaner());
        }
    }

    static class S3ByteCodeCleaner implements Walkabout.FieldAdvice {// FIXME hacking the image
        public Class targetClass() { return S3ByteCode.class;  }
        public Object beforeField(Object o, java.lang.reflect.Field f, Object value) {
           if ( value == null ) return null;
	   if (f.getName().equals("bytes"))  return null;
          // if (f.getName().equals("exceptions_")) return null;
          // if (f.getName().equals("attributes_")) return null;              
          // if (f.getName().equals("constantPool_")) return null;              
          return value;		// nextAdvice(o, f, value);
        }
    }   
    static final class Stream extends ObjectOutputStream {
        public Object replaceObject(Object o) {
            cnt++;
            pln("Class: " + o.getClass());
            if (o.getClass() == VM_Address.class
                || o.getClass() == VM_Word.class
                || o.getClass() == InvisibleStitcher.class) {
                return null;
            }
            return o;
        }
        int cnt;
        public Stream(OutputStream arg0) throws IOException {
            super(arg0);
            this.enableReplaceObject(true);
        }
    }

    /**
     * Phase 4: allocate space for all the objects that were found
     **/
    void allocate(S3Set objects_) {
        // This loop is necessary behavior (unioning the 3 arrays
        // of each constant pool) though it is not strictly
        // necessary to do it HERE.

        // This loop is less necessary - if it were gone
        // VM_Address would manage allocation anyway. But it may
        // be nice to think that most allocation is done in one
        // isolated phase.  
        objects_.forAll(new S3Set.Visitor() {
            public void visit(Object o) {
                try { // fromObject uses unchecked blueprintFor;could change if need be
                    VM_Address.fromObject(o);
                } catch (ovm.util.ReadonlyViewException e) {
                    d(e.toString());
                } catch (LinkageException.Runtime e) {
		    d(e.toString());
		}
            }
        });
    }

    /**
     * Phase 5: Write fields of all the objects now that they are
     * allocated.
     **/
    void writeFields(S3Set objects_) throws LinkageException {
	final ISerializer writer = new ISerializer(driver, this);
	objects_.forAll(/*not filtered*/
	new S3Set.Visitor() {
	    public void visit(Object o) {
		writer.visit(o);
		writer.start();
	    }
	});



        ReflectionSupport.dumpOvmFieldStats();
    }

    static public final byte[] EMPTY_BYTE_ARRAY = new byte[0];
    static public final char[] EMPTY_CHAR_ARRAY = new char[0];
    
    private final class MutableObjectAdvice implements ObjectAdvice {
	Class target;
	ObjectAdvice value;

	public Object beforeObject(Object o) { return value.beforeObject(o); }
	public Class targetClass() { return target; }
	public MutableObjectAdvice(Class target, ObjectAdvice value) {
	    this.target = target;
	    this.value = value;
	}
	public void set(ObjectAdvice value) { this.value = value; }
    }

    void defineExcludes() {
	excludeClasses(new TypeName[] { JavaNames.java_lang_ClassLoader,
		RepositoryUtils.makeTypeName("s3/services/bytecode", "S3ClassProcessor"),
		RepositoryUtils.makeTypeName("ovm/services/io", "Resource$HostedFile"),
		RepositoryUtils.makeTypeName("ovm/services/io", "Resource$HostedZippedFile"),
		RepositoryUtils.makeTypeName("ovm/services/io", "ResourceContainer$HostedDir"),
		RepositoryUtils.makeTypeName("ovm/services/io", "ResourceContainer$HostedZip"), });

	excludePackage("java/lang/reflect");
	excludePackage("java/util/jar");
	excludePackage("java/util/zip");
	excludePackage("java/net");
	excludePackage("s3/services/bytecode/verifier");
	excludePackage("s3/services/bytecode/writer");
	excludePackage("s3/services/bootimage");
	excludePackage("s3/services/process");
	excludePackage("s3/services/bytecode/interpreter");
	excludePackage("s3/services/simplejit/bytecode");
	excludePackage("s3/services/simplejit/ir");
	excludePackage("ovm/services/bytecode/analysis");
	excludePackage("ovm/services/bytecode/editor");
	excludePackage("ovm/services/bytecode");
	excludePackage("s3/services/j2c");
	includeClass(RepositoryUtils.makeTypeName("s3/services/j2c", "J2cCodeFragment"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/j2c", "J2cCodeFragment$1"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/j2c", "J2cActivation"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/j2c", "J2cActivation$Factory"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/j2c", "J2cActivation$Native"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/j2c", "J2cLocalReferenceIterator"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/j2c", "J2cLocalReferenceIterator$Factory"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/j2c", "J2cLocalReferenceIterator$Native"));
	includeClass(RepositoryUtils.makeTypeName("ovm/services/bytecode", "JVMConstants"));
	includeClass(RepositoryUtils.makeTypeName("ovm/services/bytecode", "JVMConstants$InvokeSystemArguments"));
	includeClass(RepositoryUtils.makeTypeName("ovm/services/bytecode", "JVMConstants$Opcodes"));
	includeClass(RepositoryUtils.makeTypeName("ovm/services/bytecode", "JVMConstants$PrimitiveArrayTypes"));
	includeClass(RepositoryUtils.makeTypeName("ovm/services/bytecode", "JVMConstants$Throwables"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/bytecode/writer",
						"S3Dumper$ConstantWriter_synthetic_Acceptor"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/bytecode/writer",
						"S3OVMIRDumper$OVMIRConstantWriter_synthetic_Acceptor"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/bootimage", "Ephemeral"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/bootimage", "Ephemeral$Void"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/simplejit/bytecode", "Translator$RegisterTable"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/simplejit/bytecode", "Translator$Liveness"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/simplejit/bytecode", "Translator$RegisterTable$Entry"));
	includeClass(RepositoryUtils.makeTypeName("s3/services/simplejit/bytecode", "Translator$Liveness$Entry"));
        }

    public final static void ignoreClass(Walkabout walker_, Class cls) {
        walker_.register(new Walkabout.IgnoreObjectAdvice(cls));
        walker_.register(new Walkabout.IgnoreClassAdvice(cls));
        walker_.register(new Walkabout.IgnoreFieldAdvice(cls));
        walker_.register(new Walkabout.IgnoreStaticFieldAdvice(cls));
    }
}
