package s3.services.bootimage;

import java.io.IOException;

import ovm.core.repository.Selector;
import ovm.core.repository.UnboundSelector;
import ovm.core.domain.Blueprint;
import ovm.core.domain.Domain;
import ovm.core.stitcher.InvisibleStitcher;
import ovm.core.services.memory.MemoryManager;
import ovm.util.HashMap;
import ovm.util.Iterator;
import ovm.util.S3Set;
import s3.core.domain.S3Domain;
import ovm.core.domain.Type;
import ovm.core.domain.Method;
import ovm.core.domain.DomainDirectory;
import ovm.util.BitSet;
import s3.core.domain.S3Blueprint;
import java.util.Stack;
import s3.util.Walkabout;

/**
 * Observe the heap-image building process.  This observer is
 * independent of the process by which classes are included in the
 * type-name closure and bytecode is rewritten, but is not independent
 * of the way in which INVOKE_NATIVE's immediate parameter is defined.
 * <p>
 * Methods are listed more-or-less in the order in which they are
 * called.
 * <p><B>NOTE:</B> Any ImageObserver hook may change the Driver's
 * current domain by iterating over getDomains().
 *
 * @author <a href=mailto://baker29@cs.purdue.edu> Jason Baker </a>
 */
public abstract class ImageObserver extends BootBase {
    /**
     * An interface to type-name and object reachability analysis.  It
     * plants roots and prunes branches.
     * @author <a href=mailto://baker29@cs.purdue.edu> Jason Baker </a>
     **/
    public interface Gardener {
	/** Add an object to the root set and image header */
	void declare(Object o, String s);

	/**
	 * Exclude a class from the image.  Don't generate a blueprint
	 * and don't store references
	 *
	 * @param pkg The '/' delimited package name
	 * @param cls The simple class name within pkg
	 */
	void excludeClass(String pkg, String cls);

	/**
	 * Exclude all classes within a package from the image.  Don't
	 * generate blueprints and don't store references.
	 *
	 * @param pkg The '/' delimited package name
	 */
	void excludePackage(String pkg);
    }

    public ImageObserver() { }
    public ImageObserver(String OVMMakefile, String gdbinit) {
	try {
	    int bootbase = MemoryManager.the().getImageBaseAddress();
	    InvisibleStitcher.addOption("BOOTBASE",
					"0x" + Integer.toHexString(bootbase));
	    int heapBase = MemoryManager.the().getFixedHeapAddress();
	    InvisibleStitcher.addOption("HEAPBASE",
					"0x" + Integer.toHexString(heapBase));
	    // Translate decimal with suffix to hex.
	    int heapSize = InvisibleStitcher.getInt("heap-size");
	    InvisibleStitcher.addOption("HEAPSIZE",
					"0x" + Integer.toHexString(heapSize));
	    InvisibleStitcher.expandOptions(OVMMakefile, "OVMMakefile");
	    InvisibleStitcher.expandOptions(gdbinit, ".gdbinit");
	} catch (IOException e) {
	    throw new InvisibleStitcher.MisconfiguredException
		("generating Makefile", e);
	}
    }
    
    /** Used to inform the image build process what the engine is. */
    public boolean isJ2c() { return false; }

    public boolean shouldQuickify() { return false; }
    /**
     * Called early in the bootimage process, as soon as headers,
     * domains and the executive domain are available through our
     * accessor methods.
     **/
    public void beforeRewriting(Gardener gardener) {  }

    public void registerGCAdvice(Walkabout w) {
    }

    /**
     * Called when the image build process determines that a method is
     * part of the program.
     **/
    public void addMethod(Method m) { }

    public void beforeGC(S3Set prev) {
    }

    /**
     * @param dom  a domain being compiled
     * @param heap the set of live objects when this domain starts
     **/
    public void defineInitialHeap(Domain dom, Analysis anal, S3Set heap) { }

    /**
     * Called before the objects to be included in the heap are
     * computed. 
     **/
    public void beforeObjectGraph() {  }

    /** Called before addresses are assigned to all objects in the heap. */
    public void beforeAllocation() {  }

    /** Called before the heap is written to disk.*/
    public void beforeImageWrite() { }

    /** Called after the image building process completes. */
    public void afterImageWrite() { }


    // Some variables that are made available through accessor methods
    // and defined through package-private hooks.  And, some state
    // Driver state transitions are mediated through inner classes.

    // The package-private hooks are called early in the bootimage
    // process (before TypeNameClosure), the first public hook
    // (beforeRewriting) is called from the last package-private hook
    // (defineDomains) when our accessor methods are fully functional.
    
    /**
     * Iterate over all domains in the image.  This class attempts to
     * abstract away some of the image build process's statefulness.
     * The build process has a notion of a `current' domain, which
     * determines the behavior of VM_Address.fromObject().
     * forAllDomains updates the current domain on entry to walkDomain
     * and restores the current domain after all domains have been
     * walked.
     * 
     * @author <a href=mailto://baker29@cs.purdue.edu> Jason Baker </a>
     **/
    public abstract class DomainWalker {
	public abstract void walkDomain(Domain dom);

	public void forAllDomains() {
	    S3Domain before = (S3Domain) driver.getCurrentDomain();
	    try {
		for (int i = 0; i < domains.length; i++)
		    walkDomain(domains[i]);
	    } finally { driver.switchTo(before); }
	}
    }

    public abstract class BlueprintWalker extends DomainWalker {
	public abstract void walkBlueprint(Blueprint bp);

	public void beforeDomain(Domain d) {
	}

	// return false to skip this domain
	public boolean acceptDomain(Domain d) {
	    beforeDomain(d);
	    // return d == getExecutiveDomain();
	    return true;
	}

	public void walkDomain(Domain d) {
	    if (acceptDomain(d))
		for (Iterator it = d.getBlueprintIterator(); it.hasNext(); ) {
		    Blueprint bp = (Blueprint) it.next();
		    walkBlueprint(bp);
		}
	}
    }
	    
	
    /**
     * Each time we enter a domain, VM_Address enters a state in which
     * it can resolve blueprints for that domain.  Sometimes we need
     * to do global things involving executive-domain objects.  These
     * activities can be bracketed inside
     * <code>new RunInExecutive() { void run() {</code>
     * and <code> } }.go(); </code>
     * @author <a href=mailto://baker29@cs.purdue.edu> Jason Baker </a>
     **/
    public abstract class RunInExecutive {
	public abstract void run();
	
	public void go() {
	    S3Domain cur = (S3Domain) driver.getCurrentDomain();
	    try {
		driver.switchTo(executiveDomain);
		run();
	    } finally { driver.switchTo(cur); }
	}
    }

    /**
     * Called to extract field values from the image header.
     * Useful fields include:
     * <dl>
     * <dt>mainMethod<dd>        the image's entry point as <tt>S3ByteCode</tt>
     * <dt>mainObject<dd>        mainMethod's receiver
     * <dt>coreServicesAccess<dd>the (initial?) CSA instance
     * <dt>repository<dd>        the Repository instance
     * </dl>
     *
     * @param key the name of the field
     */
    public Object getHeader(String key) {
	return headers.get(key);
    }

    public Domain getExecutiveDomain() {
	return executiveDomain;
    }

    private HashMap headers = new HashMap();
    private S3Domain executiveDomain;
    private DomainSprout executiveDS;
    private S3Domain[] domains;
    private Driver driver;
    
    /**
     * addHeader is mostly called in the executive domain-sprout
     * constructor.  It can also be called when the Observer defines
     * its own header.  I guess there is no escape from statefullness.
     **/
    final void addHeader(String name, Object value) {
	headers.put(name, value);
    }

    /**
     * Passed the image's executive domain before defineDomains.
     **/
    final void defineExecutiveDomain(S3Domain d, DomainSprout ds) {
	executiveDomain = d;
	executiveDS = ds;
    }

    /**
     * Called with a complete list of domains in the image.  At this
     * point, DomainWalker, RunInExecutive, getHeader(), and
     * getExecutiveDomain() should all work, so we can reasonably call
     * beforeRewriting()
     **/
    final void defineDomains(Driver drv, S3Domain[] doms) {
	driver = drv;
	domains = doms;
	beforeRewriting(executiveDS);
    }

    public static abstract class WithTypeHook extends ImageObserver {
	/**
	 * Called from addMethod when a new declaring Class is
	 * encountered.  There are a couple invarients on how this
	 * method is called.
	 * <ul>
	 * <li> It is called on a superclass before any subclass
	 * <li> It is called on every declaring type of an addMethod
	 *      call before that call returns.
	 * </ul>
	 **/
	public abstract void addClass(Blueprint bp);

	private final BitSet[] knownBPs;
	private final Stack stack = new Stack();

	protected WithTypeHook(String OVMMakefile, String gdbinit) {
	    super(OVMMakefile, gdbinit);
	    knownBPs = new BitSet[DomainDirectory.maxContextID() + 1];
	    for (int i = 0; i < knownBPs.length; i++)
		knownBPs[i] = new BitSet();
	}

	public void addMethod(Method m) {
	    Blueprint bp = Driver.blueprintFor(m.getDeclaringType());
	    if (bp.isSharedState())
		bp = bp.getInstanceBlueprint();
	    while (bp != null && !knownBPs[bp.getCID()].get(bp.getUID())) {
		knownBPs[bp.getCID()].set(bp.getUID());
		stack.push(bp);
		// FIXME: Why is this in S3?
		// Do arrays not have ROOT_BLUEPRINT as a parent.
		// There is no single-inheritence hierarchy for
		// arrays, but there is none for interfaces either.
		bp = ((S3Blueprint.Scalar) bp).getParentBlueprint();
	    }
	    while (!stack.isEmpty())
		addClass((Blueprint) stack.pop());
	}
    }

    // public static class Default extends ImageObserver { }

    // Since this a boot-time only class, don't bother with
    // PragmaStitchSingleton. In fact, why do we ever bother with PragmaStitchSingleton?
    public static final ImageObserver _ = (ImageObserver) InvisibleStitcher.singletonFor(ImageObserver.class);
}
