package s3.core.domain;
import ovm.core.Executive;
import ovm.core.OVMBase;
import ovm.core.domain.Domain;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Type;
import ovm.core.repository.JavaNames;
import ovm.core.repository.RepositoryClass;
import ovm.core.repository.TypeName;
import ovm.core.services.memory.MemoryPolicy;
import ovm.services.bytecode.reader.Parser;
import ovm.util.ByteBuffer;
import ovm.util.Collections;
import ovm.util.IdentityHashMap;
import ovm.util.Map;
import ovm.util.ReadSafeHashMap;
import s3.services.transactions.Transaction;

public class S3TypeContext extends OVMBase implements Type.Context {

    private final S3Domain domain;
    private Type.Loader loader;
    private final int id;

    // Maps a TypeName n to a Type t if typeFor(n) has successfully
    // returned t.
    private ReadSafeHashMap initiatedTypes = new ReadSafeHashMap(2047);

    // Maps a TypeName to either a Type (if it has been defined), or
    // Boolean.FALSE (if we are currently resolving its ancestors)
    // FIXME: defineType() synchronizes on this variable.  Is there a
    // potential for deadlock when two loaders delegate cyclically?
    private Map definedTypes = new IdentityHashMap(2047);

    private boolean frozen = false;
    
    private static final Parser parser = (ovm
					  .services
					  .bytecode
					  .reader
					  .Services
					  .getServices()
					  .getOVMIRParserFactory()
					  .makeParser());

    public S3TypeContext(S3Domain dom) {
        domain = dom;
        id = S3DomainDirectory.registerContext( this);
    }

    private int blueprintCount;
    private int methodCount;

    synchronized void addBlueprint(S3Blueprint bp) {
	bp.number = (char) blueprintCount++;
	bp.ctxNumber = (char) id;
    }

    synchronized void addMethod(S3Method meth) {
	meth.number = methodCount++;
	meth.ctxNumber = id;
    }
    
    public int getBlueprintCount()     { return blueprintCount; }
    public int getMethodCount()        { return methodCount;  }    
    public Domain getDomain()          { return domain;    }
    public int getUID()                { return id;        }
    // FIXME: this isn't part of the interface
    public Map getTypes()              { return definedTypes; }

    public Type.Loader getLoader()     { return loader; }
    public void setLoader(Type.Loader loader) {
	this.loader = loader;
    }

    /** Return the type matching the argument, throw a fatal exception if not found. 
     */
    public Type typeForKnown(TypeName name) {
        try {  return typeFor(name); }
        catch (LinkageException e) { throw e.fatal(); }
    }

    public Type typeFor(TypeName name) throws LinkageException {
	Type ret = (Type) initiatedTypes.get(name);
	if (ret != null)
	    return ret;
	Object r = MemoryPolicy.the().enterMetaDataArea(this);
	try { 
	    Object _ret = definedTypes.get(name);
	    // FIXME: Should probably store current thread, rather
	    // than FALSE, and use wait/notify when another thread
	    // tries to load
	    if (_ret == Boolean.FALSE)
		throw new LinkageException.CyclicInheritance(name.asScalar());

	    if (name.isGemeinsam()) {
		Type t = typeFor(name.asScalar().getInstanceTypeName());
		ret = t.getSharedStateType();
	    } else if (name.isScalar()) {
		if (loader == null) { // no dynamic loading possible here
		    throw new LinkageException.NoClassDef(name);
		}
		ret = loader.loadType(name.asScalar());
		if (ret == null) {
		    // initiatedTypes.put(name, ERROR_TYPE)?
		    throw new LinkageException.NoClassDef(name);
		}
	    } else if (name.isArray()) {
		TypeName.Array aname = name.asArray();
		Type comp = typeFor(aname.getInnermostComponentTypeName());
		Type.Context ctx = comp.getContext();
		ret = ((S3TypeContext) ctx).makeArrayType(comp, aname);
	    } else {
		OVMBase.OVMassert(name.isPrimitive());
		S3TypeContext sys = (S3TypeContext)  domain.getSystemTypeContext();
		ret = (Type) sys.definedTypes.get(name);
		OVMBase.OVMassert(ret != null);
	    }
	    initiate(name, ret);
	} finally { MemoryPolicy.the().leave(r); }

	return ret;
    }

    private void initiate(TypeName n, Type t) throws LinkageException {
	synchronized (initiatedTypes) {
	    Type existing = (Type) initiatedTypes.get(n);
	    if (existing != null && existing != t)
		throw new LinkageException("duplicate class definition: "
					   + t);
	    else if (existing == null)
		initiatedTypes.put(n, t);
	}
    }

    /**
     * This is the executive domain equivalent of
     * ClassLoader.findLoadedClass.  JavaDoc claims that
     * findLoadedClass will return null unless we are an initiating
     * loader for this name.  Experiments with jdk-1.4.2 indicate that
     * it also find classes that are defined but not used locally.
     * (Classes that we are the defining loader, but not an initiating
     * loader for.)  We do the same, but initiate loading as well.
     * <p>
     * Remember, definedTypes contains Boolean.FALSE while supertypes
     * are loaded
     **/
    public Type loadedTypeFor(TypeName name) {
	Type ret = (Type) initiatedTypes.get(name);
	if (ret == null && definedTypes.get(name) instanceof Type)
	    // Oops, we're an initiating loader now
	    try { return typeFor(name); }
	    catch (LinkageException e) { Executive.panicOnException(e); }
	return ret;
    }

    private Type.Array makeArrayType(Type innerMost, TypeName.Array name)
	throws LinkageException
    {
	Type inner = innerMost;
	TypeName innerName = inner.getUnrefinedName();
	int depth = 1;
	while (innerName != name) {
	    TypeName.Array outerName = TypeName.Array.make(innerName, 1);
	    synchronized (definedTypes) {
		Type.Array outer = (Type.Array) definedTypes.get(outerName);
		if (outer == null) {
		    if (frozen) {
			throw new LinkageException.DomainFrozen(outerName);
		    }
		    outer = new S3Type.Array(innerMost, depth, inner, this);
		    definedTypes.put(outerName, outer);
                    domain.newTypeHook(outer);
		}
		inner = outer;
		innerName = outerName;
		depth++;
	    }
	}
	return inner.asArray();
    }

    /**
     * defineType implements JVMspec section 5.3.5, and performs the
     * work of ClassLoader.defineClass().  The LinkageExceptions this
     * method throws are can be converted to Java LinkageErrors
     * suitable for return from defineClass.
     **/
    public Type.Scalar defineType(TypeName.Scalar name, ByteBuffer bytes)
	throws LinkageException
    {
	if (frozen)
	    throw new LinkageException.DomainFrozen(name);
	synchronized (definedTypes) {
	    RepositoryClass rclass;
	    rclass = parser.parse(bytes, bytes.remaining());
	    if (name != null && rclass.getName() != name) {
		throw new LinkageException.NoClassDef("bad classfile: expected "
						      + name + " but found "
						      + rclass.getName());
	    }
	    // FIXME: check version #
	    name = rclass.getName();
	    if (definedTypes.get(name) != null || initiatedTypes.get(name) != null)
		throw new LinkageException("duplicate class definition: "  + name);
	    // We use Boolean.FALSE to detect cyclic inheritence, see typeFor
	    // FIXME: should store current thread and notifyAll when complete
	    definedTypes.put(name, Boolean.FALSE);
	    S3Type.Scalar ret = null;
	    try {
		Type.Class parent = null;
		// FIXME: should check getSuper for null whether this is java.lang.Object or not.
//PARBEGIN
	 if (name == Transaction.ABORTED_EXCEPTION) {
		   parent = (Type.Class) typeFor( JavaNames.java_lang_Object);
		}else
//PAREND		    
		if (name != JavaNames.java_lang_Object) 
		    try {
			parent =  (Type.Class) typeFor( rclass.getSuper());
		    } catch (ClassCastException e) {
			throw new LinkageException.ClassChange(name + " extends " + rclass.getSuper()
			     + ", which is not a class");
		    }
		TypeName.Scalar[] ifn = rclass.getInterfaces();
		Type.Interface[] ifc = new Type.Interface[ifn.length];
		for (int i = 0; i < ifc.length; i++) 
		    try { ifc[i] = (Type.Interface) typeFor(ifn[i]); }
		    catch (ClassCastException e) {
			throw new LinkageException.ClassChange(name + " implements " + ifn[i] + ", which is not an interface");
		    }

		ret = 
		    (rclass.getAccessMode().isInterface()
		     ? (S3Type.Scalar) new S3Type.Interface( rclass,ifc, this)
		     : (S3Type.Scalar) new S3Type.Class( rclass, parent, ifc, this));
		if (parent == null) 
		    domain.ROOT_TYPE = ret.asClass();
		try {
		    domain.newTypeHook(ret);
		    domain.newTypeHook(ret.getSharedStateType());
		}
		catch (LinkageException e) {
		    ret = null;
		    throw e;
		}
	    } finally {
		// If we took an exception, put(name, null) correctly
		// allows later attempt to load the type.
		definedTypes.put(name, ret);
		if (ret != null)
		    initiate(name, ret);
	    }
	    return ret;
	}
    }

   /** Unlock the context to allow the addition of new types. */
   public void thaw() {
       definedTypes = new IdentityHashMap(definedTypes);
       frozen = false;
   }

   /** Lock the context to prevent addition of new types. */
   public void freeze() {
       frozen = true;
       definedTypes = Collections.unmodifiableMap(definedTypes);
       //initiatedTypes = Collections.unmodifiableMap(initiatedTypes);
   }
   
   public String toString() {
       return domain.toString() + id;
   }

   /* testing utility     */
   protected final boolean contains(TypeName name) {
       return definedTypes.containsKey(name);
   }
}
