package s3.services.bootimage;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import ovm.core.domain.Blueprint;
import ovm.core.domain.DomainDirectory;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Oop;
import ovm.core.domain.Type;
import ovm.core.repository.RepositoryClass;
import ovm.core.repository.RepositoryMember;
import ovm.core.repository.Selector;
import ovm.core.repository.TypeName;
import ovm.core.services.memory.VM_Address;
import ovm.core.services.memory.VM_Word;
import s3.core.domain.S3Blueprint;
import s3.util.Walkabout;

/**
 * Write object to memory.
 * @author Jan Vitek (based on everybody's ImageSerializer)
 * <blockquote><em>Y el ImageSerializer de ustedes, que es el mismo ImageSerializer
 * <br />Y el ImageSerializer de todos, que es mi propio ImageSerializer</em>
 *         <br />with apologies to Violeta Parra</blockquote>
 */
public class ISerializer extends Walkabout implements Ephemeral.Void {
    private final Driver driver_;
    protected Blueprint blueprint_;
    private final DomainSprout dsprout;
    protected Oop oop_;
    private final S3Blueprint.Array bpO_;
    int lastOffset;
    
    public ISerializer(Driver driver, DomainSprout dsprout) {
        super(false, true, Driver.mapSize_);
        // ensures that primitive fields will be traversed
        this.dsprout = dsprout;
        this.driver_ = driver;
	lastOffset = driver.getImage().getImageSize();
	bpO_ = blueprint(Object[].class);
   
	DomainSprout.registerAdvice(this);
        register(new ObjectHandler());
	registerAfter(new BlueprintHandler());
	registerAfter(new RefArrayHandler());
	registerAfter(new PrimArrayHandler(boolean[].class));
	registerAfter(new PrimArrayHandler(byte[].class));
	registerAfter(new PrimArrayHandler(char[].class));
	registerAfter(new PrimArrayHandler(short[].class));
	registerAfter(new PrimArrayHandler(int[].class));
	registerAfter(new PrimArrayHandler(long[].class));
	registerAfter(new PrimArrayHandler(float[].class));
	registerAfter(new PrimArrayHandler(double[].class));
	// Strings are "squirted" by GC, and we don't need to write
	// their fields normally.
	registerAfter(new IgnoreObjectAdvice(String.class));
    }
    // --------------------ObjectHandler----------------------------
    /**
     * This advice is responsible for writing instance field values
     * into the image.  Array elements and static fields are dealt
     * with seperately.
     **/
    class ObjectHandler extends BootBase implements FieldAdvice, StaticFieldAdvice {

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

        public Object beforeField(Object o, Field f, Object value) {
            Oop oop;
            try { oop = VM_Address.fromObject( o).asOop(); }
            catch (LinkageException.Runtime le) {
                d( "WARNING: no type for " + o.getClass().getName() +
                   ". Field set to null.");
                return null;
            }

            Object orig = value;
	    value = before(value);
            if (value == null) 
                return null;
            
             writeField( oop, f, value);
            return null;
        }
    }

    /**
     * This advice is responsible for writing the values of static
     * fields into the image.  It is called just before the instance
     * fields of the corresponding shared-state blueprint are written.
     * We could also define advice on the shared-state placeholder
     * type, if that type where public.
     **/
    class BlueprintHandler extends BootBase implements ObjectAdvice {
	public Class targetClass() { return Blueprint.class; }

        public Object beforeObject(Object object) {
            OVMassert(!(object instanceof Class));
	    Blueprint bp = (Blueprint) object;
	    if (bp.isSharedState()
		&& (bp.getType().getDomain()
		    == DomainDirectory.getExecutiveDomain()))
		// recover the oop
		writeStaticFields(bp.getInstanceBlueprint().getSharedState());
	    return object;
	}

    } // End of BlueprintHandler

    /**
     * This advice is responsible for writing primitive array
     * contents.  It should be uses as <em>after</em> advice to avoid
     * repeated calls.
     **/
    class PrimArrayHandler extends BootBase implements ObjectAdvice {
	private Class c;
	public PrimArrayHandler(Class c) { this.c = c; }
	
	public Class targetClass() { return c; }
	
	public Object beforeObject(Object object) {
	    Class objClass = object.getClass();
	    VM_Address address = VM_Address.fromObject(object);
	    Oop oop = address.asOop();

	    Blueprint.Array bp = (Blueprint.Array) oop.getBlueprint();
	    int len = bp.getLength(oop);
	    VM_Word size = VM_Word.fromInt(bp.byteOffset(len));
	    try {
		address.setBlock(address, size);
	    } catch (ArrayIndexOutOfBoundsException e) {
		System.err.println("writing " + object + " (" + bp + "):");
		System.err.println("real length = " +
				   java.lang.reflect.Array.getLength(object) +
				   ", ovm length = " + len);
		System.err.println("runtime address = " +
				   Integer.toHexString(address.asInt()));
		throw e;
	    }
	    return object;
	}
    }

    /**
     * This advice is responsible for writing reference array
     * contents.  It should be uses as <em>after</em> advice to avoid
     * repeated calls.
     **/
    class RefArrayHandler extends BootBase implements ObjectAdvice {
	public Class targetClass() { return Object[].class; }
	
	public Object beforeObject(Object object) {
	    Class objClass = object.getClass();
	    VM_Address address = VM_Address.fromObject(object);
	    Oop oop = address.asOop();

	    Object[] array = (Object[]) object;
	    for (int i = 0; i < array.length; i++) {
		Object value = array[i];
		if (value == null)
		    continue; // Assume zero-inited heap, ignore null vals
		value = before(value);
		if (value == null)
		    continue;
		VM_Address objectAddress = null;
		if (!(value instanceof VM_Word))
		    try {
			objectAddress = VM_Address.fromObject(value);
		    } catch (LinkageException.Runtime e) {
			d(e.toString());
			objectAddress = VM_Address.fromObject(null);
		    }
		VM_Address addr = address.add(bpO_.byteOffset(i));

		if (value instanceof VM_Word) {
		    addr.setWord((VM_Word) value);
		} else {
		    addr.setAddress(objectAddress);
		}
	    }
	    return object;
	}
    }
    // ------------ utility ---------------
    protected S3Blueprint.Array blueprint(Class cl) {
        ovm.core.domain.Domain dom = dsprout.getDomain();
        return (S3Blueprint.Array) ReflectionSupport.blueprintFor(cl, dom);

    }

    protected void writeFieldValue(
        Oop address,
        Blueprint blueprint,
        ovm.core.domain.Field ovmFld,
        Class fieldType,
        Object value)
        throws LinkageException {

        try {
            //          FIXME how important is this? nothing seems to break without it
            //          driver_.blueprintFor(fieldType, dsprout.getDomain()); // ensure
            // that we have a BP for this type!
	    if (ovmFld instanceof ovm.core.domain.Field.Reference
		&& VM_Address.fromObject(value).asRelativeInt() > lastOffset)
	    {
		System.err.println("did not walk " + value.getClass() +
				   "@" +
				   Integer.toHexString(System.identityHashCode(value))
				   + " reachable from " + ovmFld.getSelector());
	    }
            ovmFld.setUnrefined(address, value);
        } catch (LinkageException.Runtime ovme) {
            d(ovme.toString());
        }
    }

    protected void writeField(Oop target, Field f, Object value) {
        // FIXME: used to be: value = dsprout.stringByteArrays.get(value);
        if (oop_ != target) {
            oop_ = target;
            blueprint_ = oop_.getBlueprint();
        }
        
        // We may be hitting an ephemeral object from a non-ephemeral
        // field, in which case we should avoid writing it, because
        // the object advice that will execute next will substitute
        // null anyway. Or, we may be hitting a shared-state
        // placeholder that should be given magic handling according
        // to VM_Address.fromObject.
        //         if (value instanceof Ephemeral)
        //             return;
        Class fieldType = f.getType();
        ovm.core.domain.Field ovmFld =
            ReflectionSupport.ovmFieldFor(f, blueprint_);
        if (ovmFld == null) {
            System.err.println(
                "ReflectionSupport could not find field "
                    + f.toString()
                    + " in blueprint "
                    + blueprint_);
	    return;
	}
        try {
            writeFieldValue(oop_, blueprint_, ovmFld, fieldType, value);
        } catch (LinkageException e) {
            throw e.fatal();
        }
    }

    protected void writeStaticFields(Oop oop) {
        Blueprint bp = oop.getBlueprint();
        TypeName.Compound bpName = bp.getName().asCompound();
        if (driver_.isInOurRTLibrary(bpName)) {
            writeRTLStaticFields(oop, bp);
            return;
        }
        Class cl = ReflectionSupport.classForTypeName(bpName);
        if (cl == null)
            fail("not found " + bpName);

        if (cl == VM_Address.class) {
            d(" dropping VM_Address Class and its statics ");
            return;
        }

        Field[] fields = cl.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            if (!Modifier.isStatic(field.getModifiers()))
                continue;
            field.setAccessible(true);
            try {
                writeField(oop, field, field.get(cl));
            } catch (IllegalAccessException e) {
                throw failure(e);
            }
        }
    }

    protected void writeRTLStaticFields(Oop oop, Blueprint bp) {
        Type.Scalar type = bp.getType().asScalar();
        RepositoryClass rc = type.getRepositoryClass();
        RepositoryMember.Field[] fields = rc.getStaticFields();
        for (int i = 0; i < fields.length; i++) {
            Object value = fields[i].getConstantValue();
            if (value == null)
                continue;
            Selector.Field sel =
                Selector.Field.make(
                    fields[i].getUnboundSelector(),
                    type.getName());
            ovm.core.domain.Field ovmFld = type.getField(sel);
            Class jdkType = value.getClass();
            assertValidConstantValueType(jdkType);
            try {
                writeFieldValue(oop, bp, ovmFld, jdkType, value);
            } catch (LinkageException e) {
                throw e.fatal();
            }
        }
    }
    private static void assertValidConstantValueType(Class cl) {
        if ((cl == String.class)
            || (cl == Integer.class)
            || (cl == Long.class)
            || (cl == Float.class)
            || (cl == Double.class)) {
            return;
        }
        fail("bad value type: " + cl);
    }


}
