package s3.core.domain;

import ovm.core.domain.LinkageException;
import ovm.core.domain.MemberResolver;
import ovm.core.domain.Method;
import ovm.core.domain.Type;
import ovm.core.repository.Bytecode;
import ovm.core.repository.Mode;
import ovm.core.repository.RepositoryException;
import ovm.core.repository.RepositoryMember;
import ovm.core.repository.Selector;
import ovm.core.repository.TypeName;
import ovm.core.repository.UnboundSelector;
import ovm.util.ArrayList;
import ovm.util.List;
import ovm.util.OVMError;
import ovm.util.OVMException;
import s3.core.S3Base;

/**
 * KLB: insert class description here.
 *
 * @author K. Palacz
 * @author Christian Grothoff
 * @author Jason Baker 
 */
public class DispatchBuilder extends S3Base {

    private final static boolean saveSpaceOnFinals  = false; 
    private HTUnboundSel2int vtableOffsets_;
    private HTSelector_Method2int nvtableOffsets_;

    private S3Blueprint.Scalar bpt;
    private S3Type.Scalar type;
    private RepositoryMember.Method[] methods;
    private final S3MemberResolver resolver;

    // Hmm, should this be here or not ...
    private HTUnboundSel2int ifaceOffsets
        = new HTUnboundSel2int();

    private int lastGlobalInterfaceMethodIndex = -1;

    private UnboundSelector.Method[] offset2iface;

    
    public DispatchBuilder(S3MemberResolver resolver) {
	this.resolver = resolver;
	reset();
    }

    /**
     * Given a class or interface type and an IFTable index, return
     * the selector for the sole interface method that may appear at
     * that index, or null.
     *
     * FIXME Shouldn't be a selector!
     *
     * @param t     receiver type, in case multiple interface methods
     * share an IFtable slot
     * @param index index into iftable
     *
     * @return the method located at the IFTable index for the type
     */
    public UnboundSelector.Method getInterfaceMethod(Type _, 
						     int index) {
	return getIFTable()[index];
    }

    public void reset() {
	vtableOffsets_ = HTUnboundSel2int.EMPTY;
	nvtableOffsets_ = HTSelector_Method2int.EMPTY;

	bpt = null;
	methods = null;
    }

 
    public void setBlueprint(S3Blueprint.Scalar bpt) {
	boolean prealloc = (type == null);
	this.bpt = bpt;
	this.type = (S3Type.Scalar)bpt.getType().asScalar();
	methods = type.asScalar().getRepositoryClass().getInstanceMethods();
	if (prealloc) {
	    allocObjectMethods();
	}
    }
   
    /**
     * Create a vtable for the <code>Blueprint</code> associated with this
     * dispatch object. The entries in the vtbl can be null if we are
     * dealing with a static or abstract method.
     * FIXME change this to a singleton representing an arbitrary static method. --jv
     * @return an array of bytecode fragments representing this
     * dispatch object's vtable. The vtable is non-null by definition
     * since there are at least some method inherited from the root of
     * the hierarchy.  FIXME if class is abstract, allocate VTBL
     * entries for all abstractly implemented interface methods.
     **/
    public S3ByteCode[] createVTable() {
	
	S3Blueprint.Scalar parentBp = 
	    bpt.getParentBlueprint();
	
	S3ByteCode[] parentVTBL;
	if (parentBp == null) {
	    parentVTBL = S3ByteCode.EMPTY_ARRAY;
	} else {
	    parentVTBL = (S3ByteCode[])parentBp.getVTable();
	}

	if ( type.isInterface() || 0 == methods.length ) // FIXME ifc special 
	    return parentVTBL;  // CHANGE: if class does not define methods
	// it should be safe to return the parent's VTBL -- jv
	// This works on the assumption vtbl arrays are immutable.


	int guessChildVTBLSize = parentVTBL.length * 2;
	
	List slots = new ArrayList(guessChildVTBLSize);
	
	for (int i = 0; i < parentVTBL.length; i++) {
	    slots.add(parentVTBL[i]);
	}
	int localMethodCount = type.localMethodCount();
	for (int i = 0; i < localMethodCount; i++) {
	    Method method = type.getLocalMethod(i);
	    Mode.Method mode = method.getMode();

	    if (method.isConstructor() || mode.isPrivate()) {
		continue;
	    }

	    Bytecode cf = method.getOriginalByteCode();
	    if (cf == null) checkNullCode(method);

	    if (vtableOffsets_ 
		==  HTUnboundSel2int.EMPTY) {
		vtableOffsets_ = 
		    new HTUnboundSel2int(localMethodCount);
	    }
	    // NB: we tolerate collisions in vtableOffsets_ on the assumption
	    // that access speed is not primordial

	    UnboundSelector.Method sel 
		= method.getSelector().getUnboundSelector();
	    int old = HTUnboundSel2int.NOTFOUND;

	    // FIXME: see bug #513: this is broken since we must take
	    // visibility of the inherited methods
	    // (access modifiers [default]) into account!
	    if (parentBp != null) {
		try {
		    old = resolver.resolveVTableOffset(parentBp, sel, type);
		} catch (MemberResolver.IllegalAccessException e) {
		    throw e.fatal(); // this should not happen
		}
	    }
	    if (old != HTUnboundSel2int.NOTFOUND) { // overides parent method
		vtableOffsets_.put(sel, old);
		slots.set(old, method.getCode(S3ByteCode.KIND));
	    } else if (saveSpaceOnFinals && mode.isFinal()) {
		// a final method that does not override an existing method
		// doesn't need a vtable slot
		continue;
	    } else { // does not overide
		vtableOffsets_.put(sel, slots.size()); 
		slots.add(method.getCode(S3ByteCode.KIND));
	    }
	}
	S3ByteCode[] vt = new S3ByteCode[slots.size()];
	slots.toArray(vt);
	//debugPrintVTableAndHashtable(vt, vtableOffsets_);
	return vt;
    }

    public S3ByteCode[] createNVTable() {
        if ( type.isInterface() )
            return S3ByteCode.EMPTY_ARRAY;
	ArrayList list = new ArrayList();
	int localMethodCount = type.localMethodCount();
	if (bpt.isSharedState()) {
	    for (int i = 0; i < localMethodCount; i ++) {
		Method method = type.getLocalMethod(i);
		S3ByteCode cf = (S3ByteCode)method.getCode(S3ByteCode.KIND); 
		if (nvtableOffsets_ == HTSelector_Method2int.EMPTY) {
		    nvtableOffsets_ = new HTSelector_Method2int();
		}
		nvtableOffsets_.put(method.getSelector(), i);
		list.add(cf);
	    }
	    
	} else {
	    for (int i = 0; i < localMethodCount; i ++) {
		Method method = type.getLocalMethod(i);
		if (method.isConstructor() || method.getMode().isPrivate()) {
		    S3ByteCode code = 
			(S3ByteCode)method.getCode(S3ByteCode.KIND);
		    if (nvtableOffsets_ == HTSelector_Method2int.EMPTY) {
			nvtableOffsets_ = new HTSelector_Method2int();
		    }
		    OVMassert(nvtableOffsets_.get(method.getSelector()) == -1); 
		    nvtableOffsets_.put(method.getSelector(), list.size());
		    list.add(code);
		}
	    }
	}
	S3ByteCode[] nvtbl = new S3ByteCode[list.size()];
	return (S3ByteCode[])list.toArray(nvtbl);
    }
    

    public S3ByteCode[] createIFTable() throws LinkageException {
	try {
	    forceInterfaceLoad(type);
	    List slots = new ArrayList();
	    Type.Interface[] ifaces = type.getAllInterfaces();
	    int max = -1;
	    for (int i = 0; i < ifaces.length; i++) {
		Method.Iterator iter = ifaces[i].methodIterator();
		while (iter.hasNext()) {
		    Method ifmethod = iter.next();
		    if (!ifmethod.getMode().isAbstract()) {
			continue; 
			/* interfaces inherit all object methods
			 * don't put them in the iftable */
		    }
		    UnboundSelector.Method sel = 
			ifmethod.getSelector().getUnboundSelector();
		    int index = getIFTableOffset(sel);
		    int vtblIndex 
			= resolver.resolveVTableOffset(bpt, sel, type);

		    if (vtblIndex == HTUnboundSel2int.NOTFOUND) {
			Method meth = resolveInterfaceMethod(type, ifmethod);
			if (meth == null) {
			    slots.add(new IMTEntry(index, null)); // FIXME what purpose can this serve ...
			} else {
			    slots.add(new IMTEntry(index,         // FIXME ... or this?
						   (S3ByteCode)meth.getCode(S3ByteCode.KIND)));
			}
		    } else {
			S3ByteCode [] vTable = (S3ByteCode[])bpt.getVTable();
			slots.add(new IMTEntry(index, vTable[vtblIndex]));
		    }
		    max = Math.max(index, max);
		}
	    }
	    if (max < 0)
		 return S3ByteCode.EMPTY_ARRAY;

	    S3ByteCode[] itbl = new S3ByteCode[max + 1];

	    for (ovm.util.Iterator iter = slots.iterator(); iter.hasNext(); ) {
		IMTEntry entry =  (IMTEntry)iter.next();
		OVMassert(entry.offset >= 0);
		try {
		    itbl[entry.offset] = entry.code;
		} catch (ArrayIndexOutOfBoundsException e) {
		    throw new OVMError.Internal("itbl len=" + itbl.length
						+ " accessed at " 
						+ entry.offset + e);
		}
	    }
	    return itbl;
	} catch (OVMException e) {
	    throw e.fatal();
	} 
    }

    private UnboundSelector.Method[] getIFTable() {
	if (offset2iface == null) {
	    offset2iface
		= new UnboundSelector.Method[ifaceOffsets.size()];
	    HTUnboundSel2int.Iterator it
		= ifaceOffsets.getIterator();
	    while(it.hasNext()) {
		UnboundSelector.Method m = it.next();
		offset2iface[ifaceOffsets.get(m)] = m;
	    }
	}
	return offset2iface;
    }



    int getIFTableOffset(UnboundSelector.Method sel) {
        synchronized (ifaceOffsets) {
            int offset = ifaceOffsets.get(sel);
            if (offset == HTUnboundSel2int.NOTFOUND) {
                lastGlobalInterfaceMethodIndex++;
                ifaceOffsets.put(sel, lastGlobalInterfaceMethodIndex);
                return lastGlobalInterfaceMethodIndex;
            }
            return offset;
        }
    }

    private void allocObjectMethods() {
	Method.Iterator iter = getDomain().getHierarchyRoot().methodIterator();
	while (iter.hasNext()) {
	    Method method = iter.next();
	    if (method.isConstructor()) {
		continue; 
		/* interfaces inherit all object methods
		 * except the Object constructor */
	    }
	    UnboundSelector.Method sel = 
		method.getSelector().getUnboundSelector();
	    getIFTableOffset(sel);
	}
    }
	

    HTUnboundSel2int getVTableOffsets() {
	return vtableOffsets_;
    }

    HTSelector_Method2int getNonVTableOffsets() {
	return nvtableOffsets_;
    }

    private S3Domain getDomain() {
	return (S3Domain)type.getDomain();
    }


    private static void checkNullCode(Method method) {
	Mode.Method mode = method.getMode();
	if (mode.isNative()) {
	    //Logger.global.fine("Native stub " + cf + " for:" + methods[i]);
	} else if (mode.isAbstract()) {
	    // ok
	} else 
	    fail("Null selector for " + method);
    }

    /**
     * Entries into the interface method table (IMT) are instances
     * of this class.
     **/
    private static class IMTEntry {
	private final int offset;
	private final S3ByteCode code;
	IMTEntry(int offset, S3ByteCode code) {
	    this.offset = offset;
	    //	    OVMassert(code != null);  
	    // I would really like this to be true... 
	    // FIXME but it is not true for native/abstract methods --jv
	    this.code = code;
	}
    }

    // FIXME
    private static Method resolveInterfaceMethod(Type.Scalar receiver, 
						 Method ifmethod) {
	Selector.Method ifsel = ifmethod.getSelector();
	for (Type.Scalar t = receiver; t != null; t = t.getSuperclass()) {
	    Selector.Method sel =
		Selector.Method.make(ifsel.getUnboundSelector(),
				     t.getName());
	    Method result = t.getMethod(sel);
	    if (result != null) {
		return result;
	    }
	}
	return null;
    }

    /**
     * Force all of the interfaces implemented by the class associated
     * with the input type (in the given context) to be loaded.
     * @param type the type whose implemented interfaces should be forced
     *             to load
     * @param context the context in which the input type occurs
     * @throws LinkageException if the repository class
     *         for the given type can't be found within the domain
     **/
    private static void forceInterfaceLoad(Type.Scalar t) 
	throws LinkageException, RepositoryException {
	Type.Context ctx = t.getContext();
	TypeName.Scalar[] ifaces = t.getRepositoryClass().getInterfaces();
	for (int i = 0; i < ifaces.length; i++) {
	    forceInterfaceLoad((Type.Interface)ctx.typeFor(ifaces[i])); 
	}
    }

} // end of DispatchBuilder
