package s3.core.domain;

import ovm.core.domain.Blueprint;
import ovm.core.domain.Field;
import ovm.core.domain.LinkageException;
import ovm.core.domain.MemberResolver;
import ovm.core.domain.Method;
import ovm.core.domain.Oop;
import ovm.core.domain.ResolutionInfo;
import ovm.core.domain.Type;
import ovm.core.repository.Mode;
import ovm.core.repository.Selector;
import ovm.core.repository.TypeName;
import ovm.core.repository.UnboundSelector;
import ovm.util.OVMError;
import s3.core.S3Base;
import s3.util.PragmaTransformCallsiteIR.BCbootTime;

/**
 * FIXME revise this class, check the semantics, access rules etc
 **/
public class S3MemberResolver extends S3Base implements MemberResolver {

    private final S3Domain domain;
    
    public S3MemberResolver(S3Domain dom) { domain = dom; }

    public ResolutionInfo resolvePublicVirtualMethod(Blueprint bpt, 
                                                     UnboundSelector.Method sel) {
        int offset = resolvePublicVTableOffset((S3Blueprint) bpt, sel);
        return offset < 0 ? null : new RInfo(offset);
    }

    public ResolutionInfo resolveVirtualMethod(Blueprint bpt, UnboundSelector.Method sel, 
                                               Type.Compound callSite)
        throws IllegalAccessException {
        int offset = resolveVTableOffset((S3Blueprint) bpt, sel, callSite);
        return offset < 0 ? null : new RInfo(offset);
    }

    public ResolutionInfo resolveSpecialMethod(Blueprint bpt, Selector.Method sel, 
                                               Type.Compound callSite) {
        int offset = resolveNonVTableOffset( (S3Blueprint) bpt, sel, callSite);
        return offset < 0 ? null : new RInfo( offset);
    }

    /**
     * Get vtable offset for a public method.
     **/
    int resolvePublicVTableOffset(S3Blueprint target, UnboundSelector.Method sel) {
        int offset = target.getVTableOffsets().get(sel);
        if (offset != -1) {
            Type.Scalar thistype = target.getType().asScalar();
            Mode.Method mode = thistype.getRepositoryClass().getMethod(sel).getMode();
            return mode.isPublic() ? offset : -1;
        }
        Blueprint parent = target.getParentBlueprint();
        return parent != null ? resolvePublicVTableOffset((S3Blueprint) parent, sel)
                              : offset; // may be -1
    }

    /**
     * ignore the accesibility rules, intentionally long name
     **/
    int resolveVTableOffsetNoAccessibility(S3Blueprint target, UnboundSelector.Method sel) {
        int offset = target.getVTableOffsets().get(sel);
        if (offset != -1) return offset;
        Blueprint parent = target.getParentBlueprint();
        return parent != null ? resolveVTableOffsetNoAccessibility((S3Blueprint) parent, sel)
                              : -1;
    }

    /** This method takes an unbound selector, but at all currently existing
     *  callsites, the bound selector is available (that's how the callsite gets
     *  the <em>target</em> argument). Hmm. Ok, this interface provides strictly
     *  more information, as the Blueprint has a domain, and the bound selector
     *  has none.
     **/
    public ResolutionInfo resolveInterfaceMethod(
        Blueprint target,
        UnboundSelector.Method sel) {
        int offset = resolveIFTableOffset((S3Blueprint) target, sel);
        if (offset < 0) {
            return null;
        }
        return new RInfo( offset);
    }

    public Field resolveInstanceField(
        Type.Class target,
        UnboundSelector.Field sel,
        Type.Compound callSite) {
        return resolveField( target, sel, callSite);
    }

    // FIXME deal with accessSite
    public Field resolveStaticField( Type.Class target,
                                      UnboundSelector.Field sel,
                                      Type accessSite) {
        Type.Scalar instanceType, shstType;
        if ( target.isSharedState() ) {
            shstType = (Type.Scalar)target;
            Type.Context tc = target.getContext();
            TypeName.Compound tnc = target.asScalar().getName();
            tnc = tnc.asGemeinsam().getInstanceTypeName();
            try {
                instanceType = (Type.Scalar)tc.typeFor( tnc);
            } catch (LinkageException le) {
                throw le.fatal();
            }
        } else {
            instanceType = (Type.Scalar)target;
            shstType = target.getSharedStateType();
        }
        for (int count = 0; count < 100; count++) {
            Selector.Field osel = Selector.Field.make(sel, shstType.getName());
            Field fld = shstType.getField(osel);
            if ( null != fld )
                return fld;
            instanceType = instanceType.getSuperclass();
            if ( null == instanceType )
                return null;
	    shstType = instanceType.getSharedStateType();
        }
        throw new OVMError("loop count limit exceeded"); // FIXME arbitrary limit!
    }

    // FIXME how about nonpublic interfaces....
    // FIXME is `target' here because we anticipate implementations that use it?
    // should we be getting the dispatch builder from the target's domain, instead
    // of a fixed domain?
    int resolveIFTableOffset(
        S3Blueprint target,
        UnboundSelector.Method sel) {
        return domain.getDispatchBuilder().getIFTableOffset(sel);
    }

    /**
     * Get vtable offset for a any method. Requires the callSite
     * argument to deal correctly with protection modifiers.
     **/
    int resolveVTableOffset(
        S3Blueprint target,
        UnboundSelector.Method sel,
        Blueprint callSite)
        throws IllegalAccessException {
        return resolveVTableOffset(
            target,
            sel,
            callSite.getType().asCompound());
    }
    
    private boolean isAccessible(
        Mode.Member modifier,
        Type.Compound what,
        Type.Compound fromWhere) {
        if (modifier.isPrivate()) {
            return what == fromWhere;
            // Private methods are not in the vtable!
        } else if (modifier.isProtected()) {
            // FIXME: Bug 506 - subtypeof is incorrect test for package access
            // it will allow some illegal access and prevent legal access.
            // "protected" is much more subtle than simply "is-subtype-of"
            // As a partial fix we check to see if the call-site is a G-type
            // and if so convert to an L-type and then do subtype-of - DH
            TypeName.Compound fromWhereName = fromWhere.getName();
            if (fromWhereName.isGemeinsam() &&
                !what.getName().isGemeinsam()) {
//                  d("Using Gemeinsam conversion - from: " + fromWhere
//                      + "   what: " + what);
                TypeName.Scalar.Gemeinsam gname = 
                    (TypeName.Scalar.Gemeinsam) fromWhereName;
                TypeName.Scalar instTypeName = gname.getInstanceTypeName();
                try {
                    Type instType = fromWhere.getContext().typeFor(instTypeName);
                    boolean result = instType.isSubtypeOf(what);
//                    d("subtype test result is: " + result);
                    return result
                        || (what.getName().getPackageNameIndex()
                            == fromWhere.getName().getPackageNameIndex());
                }
                catch (LinkageException ex) {
                    throw new Error("Stuffed up gemeinsam conversion");
                }
            }
            else {
                return fromWhere.isSubtypeOf(what)
                    || (what.getName().getPackageNameIndex()
                        == fromWhere.getName().getPackageNameIndex());
            }
        } else if (modifier.isPublic()) {
            return true;
        } else { // Default
            return what.getName().getPackageNameIndex()
                == fromWhere.getName().getPackageNameIndex();
        }
    }

    /**************** package API ********************/

    // debugging aid
    void dumpVTableSelectors(S3Blueprint bp) throws BCbootTime {
        HTUnboundSel2int.Iterator iter = bp.getVTableOffsets().getIterator();
        System.out.println("Dumping VTable selectors for: " + bp);
        for (int i = 0; iter.hasNext(); i++) {
            System.out.println(" ["+i+"] : " + iter.next());
        }
    }

    /**
     * Get vtable offset for a any method. Requires the callSite to
     * deal correctly with protection modifiers.
     **/
    int resolveVTableOffset(
        S3Blueprint target,
        UnboundSelector.Method sel,
        Type.Compound callSite)
        throws IllegalAccessException {

        int offset = target.getVTableOffsets().get(sel);
        if (offset != -1) {
            Type.Scalar thistype = target.getType().asScalar();
	    // FIXME: getMethod(UnboundSelector) is coming
	    Selector.Method bsel = Selector.Method.make(sel,thistype.getName());
            Mode.Method mode = thistype.getMethod(bsel).getMode();
            if (isAccessible(mode, thistype, callSite)) {
                return offset;
            } else {
                throw new IllegalAccessException(
                    thistype.getName(),
                    sel,
                    callSite.getName());
            }
        }
        S3Blueprint parent = target.getParentBlueprint();
        if (parent != null) {
            return resolveVTableOffset(parent, sel, callSite);
        } else {
            return offset; // may be -1
        }
    }

    int resolveNonVTableOffset(S3Blueprint target, Selector.Method sel, Type.Compound callSite) {
        // FIXME assert access rights, etc
        return target.getNonVTableOffsets().get( sel);
    }

    private Field resolveField(
        Type.Class target,
        UnboundSelector.Field sel,
        Type.Compound callSite) {
        do { // want NPE if target is null as passed in.
            Field fld = target.getField(makeSelector(target.getName(), sel));
            // FIXME callSite
            if (null != fld)
                return fld;
            target = target.getSuperclass();
        } while ( null != target );
        return null;
    }

    static public  class RInfo implements ResolutionInfo {
        private final int offset_;
        public RInfo(int offset) { offset_ = offset; }
        public Oop getTarget()   { return null;      }
        public int getOffset()   { return offset_;    }
    }
    static public final class RInfoWithOop extends RInfo {
        private final Oop target_;
        public RInfoWithOop(Oop target, int offset) { super(offset); target_ = target; }
        public Oop getTarget() { return target_; }
    }
    
    // FIXME deal with accessSite
    public ResolutionInfo resolveStaticMethod(Blueprint target,
					      UnboundSelector.Method sel,
					      Type accessSite) {
        S3Blueprint instanceBpt, shstBpt;
        // it is now possible (by Phase1) that 'target' already is the shared state
        // blueprint. Handle both cases.
        // FIXME once Phase1 is de rigueur, old case (target not shst) should go
        if ( target.isSharedState() ) {
            shstBpt = (S3Blueprint)target;
            instanceBpt = (S3Blueprint)target.getInstanceBlueprint();
        } else {
            shstBpt = (S3Blueprint)target.getSharedState().getBlueprint();
            instanceBpt = (S3Blueprint)target;
        }
        for ( int count = 0 ; count < 100 ; ++ count ) {
            // FIXME here we assume that the selector can not occur in the root
            // of the hierarchy.
            OVMassert(
                shstBpt.isSharedState()
                    ? "PASS"
                    : "not shared state " + shstBpt + " started at " + target);

            int idx =
                resolveNonVTableOffset(
                    shstBpt,
                    makeSelector(shstBpt.getName(), sel),
                    accessSite.asCompound());

            Oop shst = instanceBpt.getSharedState();
            if (idx >= 0 && shst != null) {
                return new RInfoWithOop(shst, idx);
            }
            if (instanceBpt.getType().isRoot()) {
                return null;
            }
            instanceBpt = instanceBpt.getParentBlueprint();
            shstBpt = (S3Blueprint)instanceBpt.getSharedState().getBlueprint();
        }
        throw new OVMError("loop count limit exceeded");
    }

    // FIXME access site
    public Method resolveStaticMethod(Type.Compound target,
				      UnboundSelector.Method usel,
				      Type accessSite) {
        // it is now possible (by Phase1) that 'target' already is the shared state
        // blueprint. Handle both cases.
        // FIXME once Phase1 is de rigueur, old case (target not shst) should go
        Type.Scalar instanceType, shstType;
        if ( target.isSharedState() ) {
            shstType = (Type.Scalar)target;
            Type.Context tc = target.getContext();
            TypeName.Compound tnc = target.asScalar().getName();
            tnc = tnc.asGemeinsam().getInstanceTypeName();
            try {
                instanceType = (Type.Scalar)tc.typeFor( tnc);
            } catch (LinkageException le) {
                throw le.fatal();
            }
        } else {
            instanceType = (Type.Scalar)target;
            shstType = target.getSharedStateType();
        }
        for ( int count = 0 ; count < 100 ; ++ count ) {
	    Selector.Method sel = 
		Selector.Method.make(usel, shstType.getName());
	    Method answer = shstType.getMethod(sel);
	    if (answer != null) {
		return answer;
	    }
            instanceType = instanceType.getSuperclass();
            if ( null == instanceType )
                return null;
            shstType = instanceType.getSharedStateType();
        }
        throw new OVMError("loop count limit exceeded");
    }

    // FIXME access site
    public Method resolveInstanceMethod(
        Type.Compound target,
        UnboundSelector.Method usel,
        Type accessSite) {
        OVMassert(!target.isSharedState());
        for (Type.Compound t = target; t != null; t = t.getSuperclass()) {
            Selector.Method sel =
                Selector.Method.make(usel, t.getName().asScalar());
            Method answer = t.asScalar().getMethod(sel); //asScalar(): yuck!
            if (answer != null) {
                return answer;
            }
        }
        return null;
    }

    private static Selector.Method makeSelector(TypeName name, UnboundSelector.Method sel) {
        return Selector.Method.make(sel, name.asCompound());
    }

    private static Selector.Field makeSelector(TypeName name, UnboundSelector.Field sel) {
        return Selector.Field.make(sel, name.asCompound());
    }

}
