package s3.services.bootimage;

import s3.util.Walkabout;
import ovm.util.ArrayList;
import ovm.util.S3Set;
import ovm.core.domain.Domain;
import ovm.util.Iterator;
import ovm.core.domain.Blueprint;
import ovm.core.repository.TypeName;
import ovm.core.OVMBase;
import ovm.core.repository.RepositoryUtils;
import ovm.core.repository.UTF8Store;
import s3.core.domain.S3Domain;
import ovm.util.HashMap;
import ovm.core.domain.Field;
import ovm.core.domain.Oop;
import ovm.core.domain.Type;
import s3.core.domain.S3Blueprint;
import ovm.core.domain.LinkageException;
import ovm.core.domain.DomainDirectory;
import ovm.util.UnicodeBuffer;
import ovm.core.services.memory.VM_Address;

public class GC extends Walkabout implements Ephemeral.Void {
    Domain domain;
    Type.Context sysCtx;
    ArrayList rootObjects = new ArrayList();

    public GC(Domain domain) {
	super(true, false, Driver.mapSize_);
	this.domain = domain;
	sysCtx = domain.getSystemTypeContext();
    }

    public void init() {
	DomainSprout.registerAdvice(this);
	ImageObserver._.registerGCAdvice(this);
	register(new SproutObjectAdvice());
	registerAfter(new StringSquirter());
    }

    /** Discards all primitive fields during object graph computation.
     * Since primitive fields are boxed, the ObjectAdvice would mistakenly
     * try to serialize them.   **/
    private final class SproutObjectAdvice implements ObjectAdvice {

        public Class targetClass() {
            return Object.class;
        }

	HashMap definedTypes = new HashMap();
	
        public Object beforeObject(Object o) {
            if (o instanceof S3Blueprint) {
                S3Blueprint bp = (S3Blueprint) o;
                if (!bp.isSharedState()) // add shared state BPs to set
                    visit(bp.getSharedState().getBlueprint());
            }
	    Class c = o.getClass();
            if (Ephemeral.class.isAssignableFrom(o.getClass()))
                return o;
	    Object isDefined = definedTypes.get(o.getClass());
	    if (isDefined == null) {
		try {
		    TypeName tn = ReflectionSupport.typeNameForClass(c);
		    Type t = sysCtx.typeFor(tn);
		    isDefined = t != null ? Boolean.TRUE : Boolean.FALSE;
		} catch (LinkageException _) {
		    isDefined = Boolean.FALSE;
		}
		definedTypes.put(c, isDefined);
	    }
	    if (isDefined == Boolean.FALSE) {
		//System.err.println("skip " + c + " instance");
		return null;
	    } else {
		//System.err.println("push " + c + " instance");
		return o;
	    }
        }
    } // End of SproutObjectAdvice

    /**
     * Just convert-n-squirt Strings at once into the image as they are
     * encountered, even though it blurs the phases; no more Mr Nice Guy.
     * Exploits the happy fact that our replacement class happens for now to
     * be named exactly java.lang.String, so uncheckedBlueprintFor(String.class)
     * makes just the blueprint we want without any special effort.
     * <p>
     * FIXME: We are doing a lot of work for each GC pass here, and if
     * there ARE any 64k+ strings, we will allocate new byte[]s for
     * them on every GC.
     */
    private final class StringSquirter implements ObjectAdvice {
        //FIXME: document which of these are shared between
        //domains. --jv

        private final Blueprint myStringBP =
	    ReflectionSupport.blueprintFor(targetClass(), domain);
        private final Field.Reference dataField = (Field.Reference)fi( "data:[B");
	private final Field.Integer offsetField = (Field.Integer)fi( "offset:I");
        private final Field.Integer countField =  (Field.Integer)fi( "count:I");
        private final Field.Integer hashCodeField = (Field.Integer)fi( "hashCode:I");

	private final byte[] EMPTY_BYTE_ARRAY = new byte[0];
        private final Oop noBytes = VM_Address.fromObject(EMPTY_BYTE_ARRAY).asOop();

        public Class targetClass() {
            return String.class;
        }

        /** Write the address of the empty byte array singleton into
         *  the image at the offset of the static field emptySelector.*/
        StringSquirter() {
            Field.Reference emptyField = (Field.Reference)fs("empty_:[B");
            Oop shst = myStringBP.getSharedState();
            emptyField.set( shst, noBytes);
        }

        /**
         * Before each string object <em>s</em>:
         * Allocate space for <em>s</em> in the image (<code>sloc</code>).
         * What we want is for <code>sloc</code> to point to an allocated instance
         * of the Ovm class named <code>java.lang.String</code>, not the JDK class
         * of the same name, and the "happy fact" is that it Just Works: because the
         * class names are the same, <code>fromObject(s)</code> allocates the Ovm
         * class whose name is <em>s</em>'s class name, and there you have it.
          
         * Ovm's class <code>java.lang.String</code> has three fields,
         * <code>data</code>, <code>count</code>, and <code>hashCode</code>.
         * The last two are set from <em>s</em>'s <code>length()</code> and
         * <code>hashCode()</code> methods. The consequence should be noted that
         * <code>count</code> will be the number of <em>characters</em> in the
         * original String, and not necessarily (depending on the encoding into bytes)
         * the number of <em>bytes</em> in the byte array.  This is presumably the
         * desired behavior and will be reliable once we observe a consistent
         * convention for character encoding--which we don't at present (see Issue
         * #33).
         
         * If <em>s</em> is empty, the address of a statically allocated empty byte
         * array is copied into <code>data</code>. Otherwise, a byte array is
         * obtained from <em>s</em>, space allocated for it in the image, and the
         * allocation address is stored in <code>data</code>. The array is added to
         * the object set so it will later be visited (and its contents written to
         * the allocated space).
         
         * Finally, returning <code>null</code> ensures the fields of <em>s</em> will
         * not otherwise be processed.
         */
        public Object beforeObject(Object o) {
            String s = (String) o;
            int hash = s.hashCode();
            Oop dloc;
	    int off = 0;
            int len = 0;

            if (s.length() == 0) {
                dloc = noBytes;
            } else if (s.length() < 65536) {
		UnicodeBuffer sb = UnicodeBuffer.factory().wrap(s);
		// Interned strings are always stored in the Utf8
		// table, and aliasing the utf8 byte[] saves a second
		// copy.  Programatically generated strings, however,
		// will be added to the utf8 store.  We still save
		// space: the size of an array header - the utf8
		// entry's 2 byte length field.
		int index = UTF8Store._.installUtf8(sb);

		byte[] data = UTF8Store._.getUtf8Store(index);
		off = UTF8Store._.getUtf8Offset(index);
		len = UTF8Store._.getUtf8Length(index);
                dloc = VM_Address.fromObject( data).asOop();
		visit(data);
            } else {
		// We can't stuff extremely large strings in the utf8
		// store.  We also want to avoid storing multiple
		// copies of the Utf8 table in the image.
		UnicodeBuffer sb = UnicodeBuffer.factory().wrap(s);
		byte[] data = sb.toByteArray();
		off = 0;
		len = data.length;
		dloc = VM_Address.fromObject(data).asOop();
		visit(data);
	    }
            Oop sloc = VM_Address.fromObject( s).asOop(); // happy fact (see above)
                dataField.set( sloc, dloc);
	      offsetField.set( sloc, off);
               countField.set( sloc, len);
            hashCodeField.set( sloc, hash);
            return null;
        }

        private Field fi( String f) {
            return f( myStringBP.getType().asScalar(), f);
        }
        private Field fs(String f) {
            return f( myStringBP.getType().getSharedStateType(), f);
        }
        private Field f( Type.Scalar ts, String f) {
            return ts.getField(
                RepositoryUtils.fieldSelectorFor( ts.getName().toString(), f));
        }
    }

    public void addRoot(Object o) {
	rootObjects.add(o);
    }

    private final S3Set everSeen = new S3Set(Driver.mapSize_);

    /**
     * Compute the  final object graph in three steps.
     * <ol>
     * <li> Build up a superset of the object graph by calling
     *      {@link Analysis#analyzePartialHeap(S3Set)} and discovering newly
     *      reachable objects until a fixpoint is reached.
     * <li> Remove unneeded objects through a hook: pruneObjectGraph
     * <li> Compute the final object graph, and pass this to the
     *      analysis once more via @see Analysis.analyzeFullHeap
     * </ol>
     **/
    public S3Set fixObjectGraph(Analysis anal) {
	while (anal.analyzePartialHeap(everSeen)) {
	    S3Set latest = gc();
	    everSeen.union(latest);
	 //   System.err.println(everSeen.size() + " objects ever live");
	} 
	return everSeen;
    }

    S3Set freeze(Analysis anal) {
	// Domain.freeze() will allocate a variety of objects.  I have
	// no idea how to deal with it all.
	((S3Domain) domain).freeze();
        /* The Cache is a string to string mapping used to speed up
         * repository ops.  For the sake of a smaller image, we clear
         * it here.
	 */
        RepositoryUtils.Cache.clear();
	//everSeen.union(gc());
	anal.analysisComplete();
	return everSeen;
    }

    private S3Set seenSet = new S3Set(seen_);

    /**
     * Recompute the set of reachable objects from scratch.  
     * @return a new set of reachable objects
     **/
    public S3Set gc() {
	System.err.print("[garbage collecting... ");
	seen_.clear();
	ImageObserver._.beforeGC(everSeen);
	for (int i = 0; i < rootObjects.size(); i++)
	    visit(rootObjects.get(i));
	for (Iterator it = domain.getBlueprintIterator(); it.hasNext(); ) {
	    Blueprint bp = (Blueprint) it.next();
	    if (bp.isScalar() && !bp.isSharedState()) {
		TypeName tn = bp.getType().getUnrefinedName();
		Class c = ReflectionSupport.classForTypeName( tn);
		if (c.getClassLoader() != null) {
		    int before = worklist_.size();
		    visitFieldsOnly( c);
		    int after = worklist_.size();
// 		    System.err.println("walk statics of " + c +
// 				       " added " + (after-before) +
// 				       " references");
		}
		// else
		//   it is part of the standard library.  The
		//   documentation for Driver.isInOurRTL says that
		//   these classes cannot be loaded into the host vm.
		//   It seems to me that Class.forName can still be
		//   used to find the hosted library class with the
		//   same name.  Getting by with uninitialized static
		//   fields seems preferable to forcing agreement with
		//   Sun.
	    }
	}
	start();
	process(UTF8Store._.utf8s_);
	start();
	System.err.println(seen_.size() + " objects found]");
	return seenSet;
    }
}
