/**
 * Driver for the creation of the bootimage.
 **/
package s3.services.bootimage;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.jar.JarFile;

import ovm.core.domain.Blueprint;
import ovm.core.domain.Domain;
import ovm.core.domain.DomainDirectory;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Type;
import ovm.core.execution.CoreServicesAccess;
import ovm.core.execution.NativeConstants;
import ovm.core.repository.JavaNames;
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.stitcher.InvisibleStitcher;
import ovm.core.stitcher.InvisibleStitcher.MisconfiguredException;
import ovm.util.BitSet;
import ovm.util.ByteBuffer;
import ovm.util.CommandLine;
import ovm.util.OVMError;
import ovm.util.S3Set;
import ovm.util.SparseArrayList;
import s3.core.domain.S3Blueprint;
import s3.core.domain.S3Domain;
import s3.core.domain.S3ExecutiveDomain;
import s3.core.domain.S3JavaUserDomain;
import s3.core.domain.S3Type;
import s3.services.transactions.S3Transaction;
import s3.services.transactions.Transaction;
import s3.util.EphemeralBureau;

/**
 * Create the OVM bootimage containing at least one domain, a  
 * repository, a main method and a main object. All objects and types
 * reachable from the root set are will be included in the image. The
 * wiki page on bootimage generation and translation gives more
 * details.
 * @see s3.services.bootimage.BootImage
 *
 * @author Grothoff, Vitek, Palacz
 **/
public class Driver extends BootBase implements VM_Address.DomainOracle {


    public static void main(String[] args) throws Exception {
        CommandLine arguments = new CommandLine(args);
        pln("Driver invoked with " + arguments);
        try {
            InvisibleStitcher.init(arguments);

            if (arguments.getOption("classpath").length() == 0)
                throw new MisconfiguredException("-classpath empty");
            else if (arguments.getOption("bootclasspath").length() == 0)
                throw new MisconfiguredException("-bootclasspath empty");
            else if (arguments.getOption("xdpath").length() == 0)
                throw new MisconfiguredException("-xdpath empty");
            else if (arguments.getOption("main").length() == 0)
                throw new MisconfiguredException("no application specified");
            else if (arguments.argumentCount() != 0)
                throw new MisconfiguredException("extra arguments starting at " + arguments.getArgument(0));
            DomainDirectory.getExecutiveDomain();

            Driver driver = new Driver(arguments);
            dumpOvmIRAscii = arguments.getBoolean("dumpovmirascii");
            try {
                parseLogOptions(arguments.getLongString("log"));
            } catch (IOException e) { throw new MisconfiguredException("parse -log", e); }
            driver.run();
        } catch (MisconfiguredException e) {
            System.err.println("Configuration Error: " + e.getMessage());
            InvisibleStitcher.printUsage(System.err);
            System.exit(1);
        }
    }

    private static void merge(BitSet[] to, BitSet[] from) {
	for (int i = 0; i < to.length; i++)
	    to[i].or(from[i]);
    }

    private static SparseArrayList[] superTypes = null;
    
    private static BitSet[] getSuperTypes(Blueprint b) {
	int cid = b.getCID();
	int uid = b.getUID();
	BitSet[] ret = (BitSet[]) superTypes[cid].get(uid);
	if (ret == null) {
	    // Note the recursion on Object and its interfaces
	    ret = new BitSet[superTypes.length];
	    for (int i = 0; i < superTypes.length; i++)
		ret[i] = new BitSet();
	    ret[cid].set(uid);
	    Blueprint parent = ((S3Blueprint) b).getParentBlueprint();
	    if (parent != null) 
		merge(ret, getSuperTypes(parent));
	    Type t = b.getType();
	    Type.Interface[] ifc = t.getInterfaces();
	    for (int i = 0; i < ifc.length; i++)
		merge(ret, getSuperTypes(blueprintFor(ifc[i])));
	    superTypes[cid].set(uid, ret);
	}
	return ret;
    }

    public static boolean isSubtypeOf(Blueprint derived, Blueprint base) {
	BitSet[] superTypes = getSuperTypes(derived);
	return superTypes[base.getCID()].get(base.getUID());
    }

    public static Blueprint[] leastCommonSupertypes(Blueprint a,
						    Blueprint b)
    {
	if (a == b || isSubtypeOf(b, a)) return new Blueprint[] { a };
	else if (isSubtypeOf(a, b))      return new Blueprint[] { b };

	Type at = a.getType();
	Type bt = b.getType();
	Type[] lct = ((S3Type) at).getLeastCommonSupertypes(bt);
	Blueprint[] ret = new Blueprint[lct.length];
	Domain dom = a.getDomain();
	for (int i = 0; i < ret.length; i++) {
	    try { ret[i] = dom.blueprintFor(lct[i]); }
	    catch (LinkageException e) {
		throw new OVMError("blueprint not found for " + lct[i]);
	    }
	}
	return ret;
    }
	
    
    void switchTo(S3Domain dom) {
        //currentDomain_ = dom;
    }

    private void run() throws IOException, LinkageException {
        S3ExecutiveDomain executiveDomain = 
	    (S3ExecutiveDomain) DomainDirectory.getExecutiveDomain();
	currentDomain_ = executiveDomain;
        // It is not safe to use DomainDirectory from Driver.<clinit>, so
        // we do it here
        superTypes = new SparseArrayList[DomainDirectory.maxContextID() + 1];
	for (int i = 0; i < superTypes.length; i++)
	    superTypes[i] = new SparseArrayList();
        switchTo(executiveDomain);
	IRewriter eRewriter = new IRewriter(this);
	NativeCallGenerator eNcg = new NativeCallGenerator.Executive();
	eRewriter.setNativeCallGenerator(eNcg);
	GC gc = new GC(executiveDomain);
	Analysis eAnal = Analysis.factory().make(executiveDomain, eRewriter, eNcg);
	DomainSprout eds = new DomainSprout(this, executiveDomain, eAnal, gc, arguments);

        String umainClassName = arguments.getLongString("udmain");
        TypeName.Scalar umain = JavaFormat._.parseTypeName(umainClassName).asScalar();
        S3JavaUserDomain udom = (S3JavaUserDomain) DomainDirectory.createUserDomain(umain);
        String appClassList = arguments.getLongString("main");
        if (appClassList != null) {
            String[] appClasses = appClassList.split(",");
            for (int i = 0; i < appClasses.length; i++) {
		String str = appClasses[i].trim();
		TypeName name = JavaFormat._.parseTypeName(str);
		name = name.asScalar().getGemeinsamTypeName();
		Selector.Method msel =
		    Selector.Method.make(JavaNames.MAIN, name.asCompound());
		udom.registerCall(msel);
	    }
        }
	String udtargets = arguments.getLongString("udtargets");
	boolean appRewrite = arguments.getBoolean("system-rewrite-application-code");
	
	IRewriter uRewriter = new IRewriter(this);
	NativeCallGenerator uNcg = new NativeCallGenerator.User(appRewrite);
	uRewriter.setNativeCallGenerator(uNcg);
	Analysis uAnal = Analysis.factory().make(udom, uRewriter, uNcg);
        DomainSprout uds = new DomainSprout(this, udom, uAnal, arguments);

        switchTo(executiveDomain);
        ImageObserver._.defineDomains(this, new S3Domain[] { executiveDomain, udom });
        pln("[0] Freezing header...");
	// XXX: VM_Address.fromObject(Object) must be called after
	// freezeHeader, but it should be safe to call freezeHeader
	// from the executive DomainSprout constructor (modulo headers
	// introduced by simplejit).
        BootImage.freezeHeader();
	VM_Address eRTE = VM_Address.fromObject(executiveDomain.getRuntimeExports());
	eRewriter.setRuntimeExports(eRTE);
	VM_Address uRTE = VM_Address.fromObject(udom.getRuntimeExports());
	uRewriter.setRuntimeExports(uRTE);
	executiveDomain.registerBoundaryMethods(executiveDomain);
	executiveDomain.registerBoundaryMethods(udom);
	gc.init();

        pln("[1] Loading classes...");
	// All we really have to do with the user domain is load and
	// rewrite all its code.  We must finish allocating executive
	// objects for the user domain before calling gc.fixObjectGraph().
        pln("user domain ...");
        Class _ = EphemeralBureau.class;
        //switchTo(udom);
	uAnal.analyzeCode();
	uAnal.analysisComplete();
        uds.getDomain().freeze();
        cxxGen_.genStructs(udom, uAnal);

	pln("executive domain ...");
	//switchTo(executiveDomain);
	gc.fixObjectGraph(eAnal);

	

if (Transaction.the().transactionalMode()) {
System.out.println("# Method" + IRewriter.CNT);
System.out.println("# NOB " + IRewriter.NOB);
System.out.println("# NOA  " + IRewriter.NOA);
System.out.println("# NOC " + IRewriter.NOC);
System.out.println("# NONE" + IRewriter.NONE);
    System.out.println("ATOMIC_METHOD_COUNT " + S3Transaction.ATOMIC_METHOD_COUNT);
    System.out.println("NESTED_ATOMIC_METHOD_COUNT " + S3Transaction.NESTED_ATOMIC_METHOD_COUNT);
    System.out.println("NESTED_ATOMIC_BLOCK_COUNT " + S3Transaction.NESTED_ATOMIC_BLOCK_COUNT);
    System.out.println("ATOMIC_NONLOGGED_METHOD_COUNT " + S3Transaction.ATOMIC_NONLOGGED_METHOD_COUNT);
    if (S3Transaction.IDX>0) {
    int[] r = new int[S3Transaction.IDX];
    int[] w = new int[S3Transaction.IDX];
    System.arraycopy(S3Transaction.READ_COUNTS,0,r,0,S3Transaction.IDX);
    System.arraycopy(S3Transaction.WRITE_COUNTS,0,w,0,S3Transaction.IDX);
    Arrays.sort(r);
    Arrays.sort(w);
    System.out.println("MAX WRITE " + w[w.length-1]);
    int m = (int)((w.length-1)/2);
    System.out.println("MEDIAN WRITE " + w[m]);
    System.out.println("MAX READ " + r[w.length-1]);
    System.out.println("MEDIAN WRITE " + r[m]);
    }
}

        // not sure about this.  Can dumpStructs/genStructs allocate blueprints?
	S3Set image = gc.freeze(eAnal);
        cxxGen_.genStructs(executiveDomain, eAnal);
        eds.dumpStructs(eAnal);

        ImageObserver._.defineInitialHeap(executiveDomain, eAnal, image);
        ImageObserver._.defineInitialHeap(udom, uAnal, new S3Set());
        NativeCallGenerator.output(nativeCallsStream_);
        dumpImageHeaders(eds);
        stream_.close();
	cxxStream_.close();
        nativeCallsStream_.close();

        pln("[4] Allocating (more of) image...");
        ImageObserver._.beforeAllocation();
	image = gc.gc();
        eds.allocate(image);

        VM_Address.freezeAllocation();
        pln("[5] Writing into memory image...");
        ImageObserver._.beforeImageWrite();
        eds.writeFields(image);
        pln("[6] Writing image to file...");
        write(eds);

        //switchTo(executiveDomain);
        ImageObserver._.afterImageWrite();
        if (uAnal instanceof RapidTypeAnalysis) {
            ((RapidTypeAnalysis)uAnal).printStats();
            ((RapidTypeAnalysis)eAnal).printStats();
        }
        gen_ovm_c.close();
	img_ovmir_ascii.close();
    }

    // ********************* Instance Fields ****************************

    /** Stream where the C structs are written (defaults to stdout).  **/
    private final PrintWriter stream_;
    private final PrintWriter nativeCallsStream_;
    private final CoreServicesAccess coreServicesAccess_;
    private S3Domain currentDomain_;
    private final ByteBuffer memory_;
    private final BootImage image_;

    private final PrintWriter cxxStream_;
    private final CxxStructGenerator cxxGen_;

    // This is static and non-private so that DomainSprout can see
    // it.
    static int mapSize_;

    /** Command line arguments.    **/
    final CommandLine arguments;
    private final JarFile ovmrt_;
    private String outputFileName_;

    IRewriter rewriter_;

    public static boolean dumpOvmIRAscii = false;
    public static final PrintWriter img_ovmir_ascii;
 
    static {
	try {
	    img_ovmir_ascii = new PrintWriter(new BufferedOutputStream(new FileOutputStream("img.ovmir.ascii")));
	} catch (IOException e) { throw new RuntimeException(e); }
    }

 
    static public final PrintWriter gen_ovm_c;
 
    static {
	try {
	    gen_ovm_c = new PrintWriter(new FileOutputStream("gen-ovm.c"));
	} catch (IOException e) { throw new RuntimeException(e); }
    }

    public Domain getCurrentDomain() {
        return currentDomain_;
    }

    public java.util.Map makeObjectMap() {
        return new java.util.IdentityHashMap(mapSize_);
    }

    /** jdb hook **/
    private Object proband_;

    /** Create a new Driver.  */
    Driver(CommandLine arguments) throws IOException {

        this.arguments = arguments;
        String sstructName = arguments.getLongString("s");
        stream_ = sstructName == null
                ? new PrintWriter(System.out)
                : new PrintWriter(new FileOutputStream(sstructName));
        cxxStream_ = sstructName == null
                ? new PrintWriter(new FileOutputStream("/dev/null"))
                : new PrintWriter(new FileOutputStream(sstructName + "h"));
        String nativeCallsName = arguments.getLongString("n");
        this.nativeCallsStream_ = nativeCallsName == null
                ? new PrintWriter( System.out)
                : new PrintWriter( new FileOutputStream( nativeCallsName));
        ovmrt_ = new JarFile(arguments.getLongString("xdbootpath"));
        outputFileName_ = arguments.getLongString("o");
        OVMassert(outputFileName_ != null, "Use with option -o IMAGEFILE");
        mapSize_ = arguments.getInt("build-size");
        memory_ = ByteBuffer.allocate(Integer.MAX_VALUE).order(NativeConstants.BYTE_ORDER);
        memory_.position(4); /* first 4 bytes reserved for 'null' */

        // Force initialization of Repository._. This probably isn't needed, but can't hurt.
        ovm.core.repository.Services.getServices().getRepository();
        
        coreServicesAccess_ = DomainDirectory.getExecutiveDomain().getCoreServicesAccess();

        image_ = new BootImage(memory_);
        VM_Address.initialize(memory_, this);
        DomainSprout.structGen_ = new StructGenerator(stream_);
        cxxGen_ = new CxxStructGenerator(cxxStream_);
    }

    private void dumpImageHeaders(DomainSprout eds) {
        Class csaClass = ovm.core.execution.CoreServicesAccess.class;
        S3Blueprint bpt = ReflectionSupport.blueprintFor(csaClass, eds.getDomain());
        DomainSprout.structGen_.dumpVTBL(bpt);
        cxxGen_.dumpVTBL(bpt);
        image_.writeHeadersToC_h_File(stream_);
        image_.writeHeadersToC_h_File(cxxStream_);
    }

    /**
     * Phase 6: Write the image to a file.
     **/
    private void write(DomainSprout eds) {
        try {
            image_.save(outputFileName_);
        } catch (IOException io) { fail(io); }
    }

    /** jdb hook */
    void VMAlookup() {
        proband_ = VM_Address.fromObject(proband_);
    }

    /* ********************* Phase 0 helper methods *********************** */

    public CoreServicesAccess getCoreServicesAccess() {
        return coreServicesAccess_;
    }

    public BootImage getImage() {
        return image_;
    }

    /** Returns true if the typename given is the name of a class that
     * occurs within the ovmrt_.jar. (These are classes which can not be
     * loaded on the host vm.)
     **/
    public boolean isInOurRTLibrary(TypeName.Compound tn) {
        if (!tn.isScalar())
            return false;
        return null != ovmrt_.getJarEntry(ReflectionSupport.asClassFileName(tn.asScalar()));
    }

    /* ********************* Phase 2 helper methods *********************** */

//     private UnboundSelector.Method selBoot = RepositoryUtils.makeUnboundSelector("boot_:()V").asMethod();
    /**
     * Check the static initializers of the class and generate code
     * to run them as appropriate.
     **/
//     void checkStaticInitializers(Blueprint bp) {
//         if (!(bp instanceof Blueprint.Scalar))
//             return;
//         if (!bp.isSharedState())
//             return;
//         /* only run static initializers for the shared
//             		       state blueprints -- otherwise we run them twice
//             		       see Mantis #502 */
//         Type.Scalar type = bp.getType().asScalar();
//         RepositoryClass rc = type.asScalar().getRepositoryClass();
//         RepositoryMember.Method[] sm = rc.getStaticMethods();

//         for (int i = 0; i < sm.length; i++) {
//             if (sm[i].getUnboundSelector() == selBoot) {
//                 rewriter_.addBootCall(sm[i].getSelector());
//             }
//             if (sm[i].getUnboundSelector() == JavaNames.CLINIT) {
//                 // check that this class is not in OVM_RT.  if it is not,
//                 // trigger static initializer using reflection!
//                 if (isInOurRTLibrary(type.getName()))
//                     fail("Unsupported static initializer in class " + type);
//                 ReflectionSupport.classForTypeName(type.getName());
//                 // this forces the initializer
//             }
//         }
//     }

    public S3Blueprint blueprintFor(Class definingClass, Domain dom) throws LinkageException {
        TypeName.Compound name = ReflectionSupport.typeNameForClass(definingClass).asCompound();
        return blueprintFor(name, dom);
    }

    public S3Blueprint blueprintFor(TypeName.Compound tname, Domain dom) throws LinkageException {
        return (S3Blueprint) dom.blueprintFor(tname, dom.getApplicationTypeContext());
    }

    public static S3Blueprint blueprintFor(Type t) {
	try { return (S3Blueprint) t.getDomain().blueprintFor(t); }
	catch (LinkageException e) { throw e.unchecked(); }
    }
}
