package s3.core.domain;

import ovm.core.domain.Code;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Method;
import ovm.core.domain.Type;
import ovm.core.domain.Type.Class;
import ovm.core.repository.Bytecode;
import ovm.core.repository.Mode;
import ovm.core.repository.RepositoryMember;
import ovm.core.repository.Selector;
import ovm.core.repository.TypeName;
import ovm.core.repository.UnboundSelector;
import ovm.services.bytecode.editor.CodeFragmentEditor;
import ovm.util.OVMError;
import s3.core.S3Base;

/**
 * @author KP, Christian Grothoff
 **/
public abstract class S3Method 
    extends S3Base 
    implements Method {

    public static S3Method[] EMPTY_ARRAY = new S3Method[0];
    
    // assume: new Interface and new Multiple will be called directly by
    // S3Type.Interface.collectMethods.  This factory method needs only
    // distinguish virtual/nonvirtual/static.
    public static S3Method makeMethod( RepositoryMember.Method rmm,
                                       Type.Compound type) {
        Mode.Method mode = rmm.getMode();
        if ( mode.isStatic() ) {
            if ( type.isSharedState() )
                return new NonVirtual( rmm, type);
            return new Static( rmm, type);
        }
        if ( rmm.isConstructor()  ||  mode.isPrivate() )
            return new NonVirtual( rmm, type);
        return new Virtual( rmm, type);
    }

    int number;
    int ctxNumber;

    private final RepositoryMember.Method info;
    
    private final Type.Compound type_; // "type_" must match string in C code!

    /**
     * The code representations for this method (of various kinds).
     * *Not* final since the list may change over time.
     */
    private S3Code code;

    S3Method(RepositoryMember.Method info,
	     Type.Compound type) {
        this.info = info;
	this.type_ = type;
	((S3TypeContext) type.getContext()).addMethod(this);
        if ( null != info ) {
	    Bytecode org_bc = getOriginalByteCode();
	    if (org_bc == null)
		// These dummy S3ByteCode objects are needed to build
		// dispatch tables
		this.code = new S3ByteCode(this, null, 
                                           (char) 0, (char) 0,
					   null, null, null,
					   getMode().isSynchronized());
	    else
		this.code = new S3ByteCode(this,
					   org_bc.getCode(),
					   org_bc.getMaxStack(),
					   org_bc.getMaxLocals(),
					   org_bc.getExceptionHandlers(),
					   org_bc.getAttributes(),
					   ((S3Type.Scalar) type).constants,
					   getMode().isSynchronized());
	}
        if (getMode().isStatic() && !getDeclaringType().isSharedState())
            fail("Congratulations! You win an exciting instance of bug 484!");
    }

    public final int getUID() { 
	return number; 
    }
    public final int getCID() {
	return ctxNumber;
    }

    public Type.Compound getDeclaringType() {
        return type_;
    }

    /**
     * Get the (unlinked) repository representation of this method.
     **/
    public RepositoryMember.Method getRepositoryMethod() {
        return info;
    }

    public Bytecode getOriginalByteCode() {
        return info.getCodeFragment();
    }

    public boolean isConstructor() {
        return getSelector().isConstructor();
    }
    public boolean isClassInit() {
        return getSelector().isClassInit();
    }
    public boolean isNonVirtual() {
        return false;
    }
    public boolean isVirtual() { return false; }
    public boolean isInterface() { return false; }

    public Selector.Method getSelector() {
        return info.getSelector();
    }
 
    // FIXME haven't thought about how correct this could possibly be ...
    protected Type resolve(TypeName tn) throws LinkageException {
        return getDeclaringType().getContext().typeFor(tn);
    }

    public Mode.Method getMode() {
        return info.getMode();
    }

    public Code getCode(Code.Kind kind) {
        for (S3Code c = code; c != null; c = c.next())
            if (c.getKind().equals(kind))
                return c;
        return null;
    }

    /**
     * Add a new piece of code for this method.  If code of the
     * same kind already exists, it is REPLACED.
     */
    public void addCode(Code cd) {
	S3Code _code = (S3Code) cd;
        S3Code prev = null;
        S3Code curr = this.code;
        for (; curr != null; prev = curr, curr = curr.next()) {
            if (curr.getKind().equals(_code.getKind())) {
		curr.bang(_code);
		return;
            }
        }
        // !c.hasNext();
        prev.setNext(_code);
    }

    public Type getReturnType() throws LinkageException {
        return resolve(getSelector().getDescriptor().getType());
    }

    //FIXME: this is really inefficient : retrieves the array every time
    // for only one element -- TH JT 02/17/2004
    // Nu? how hard is retrieving an array? A reference gets returned and
    // we index off of it and return an element.  JCF 2/23/2004
    public Type.Class getThrownType(int i) throws LinkageException {
        return (Type.Class) resolve(info.getThrownExceptions()[i]);
    }

    public int getThrownTypeCount() {
        return info.getThrownExceptions().length;
    }

    public Type getArgumentType(int i) throws LinkageException {
        return resolve(getSelector().getDescriptor().getArgumentType(i));
    }

    public int getArgumentCount() {
        return getSelector().getDescriptor().getArgumentCount();
    }

    public int getArgumentLength() {
        return getSelector().getDescriptor().getArgumentLength();
    }

    public String toString() {
        return /* getDeclaringType() + "::" + */ getSelector().toString();
    }

    public static class Virtual extends S3Method {
        Virtual( RepositoryMember.Method rmm, Type.Compound declarer) {
            super( rmm, declarer);
        }
	public boolean isVirtual() { return true; }
    }

    public static class NonVirtual extends S3Method {
        NonVirtual( RepositoryMember.Method rmm, Type.Compound declarer) {
            super( rmm, declarer);
        }
        public boolean isNonVirtual() {
            return true;
        }
    }

    public static class Static extends NonVirtual {
        Static( RepositoryMember.Method rmm, Type.Compound declarer) {
            super( rmm, declarer);
        }
    }

    public static class Interface extends S3Method {
        /** If I read the JLS properly, this is the only correct and possible mode
         * for an interface method--it has entropy zero.
         **/
        public static final Mode.Method mode =
            Mode.makeAbstractMethod();
        Interface( RepositoryMember.Method rmm, Type.Compound declarer) {
            super( rmm, declarer);
        }
        public Bytecode getOriginalByteCode() {
            OVMassert( null == super.getOriginalByteCode());
            return null;
        }
	public boolean isInterface() { return true; }

    }

    /**
     * A multiple method occurs when a class inherits/implements multiple methods
     * of the same signature.
     * 
     * @author Chap Flack
     */
    public static class Multiple extends Interface {
        
        protected final S3Method[] ancestors;  // the inherited methods
        private final Selector.Method selector_; // all methods have the same selector
       
        Multiple( Type.Compound declarer, Selector.Method selector,
                  S3Method[] bag, int from, int len) {
        
            super( null, declarer);
            OVMassert( 0 < len);
            selector_ = selector;
            ancestors = new S3Method [ len ];
            System.arraycopy( bag, from, ancestors, 0, len);
        }

        public void addCode(Code cd) {
            throw blooey();
        }

        // To the best of my understanding of JLS, this is the only correct and
        // possible mode for any interface method, and therefore this could be
        // pulled up into S3Method.Interface.  But the default implementation
        // works there, too.
        public Mode.Method getMode() {
            return mode;
        }

        // This method is well-defined even though the declaring class isn't
        // uniquely known.  Interface methods return null for getOriginalByteCode.
        // :)
        public Bytecode getOriginalByteCode() {
            return null;
        }

        public RepositoryMember.Method getRepositoryMethod() {
            throw blooey();
        }

        public ovm.core.repository.Selector.Method getSelector() {
            return selector_;
        }

        /** Asking for the thrown types of a multiply-inherited method is asking
         * for trouble.  The JLS doesn't impose requirements on throws-clause
         * compatibility among methods with the same signature inherited by an 
         * interface.  (It does require, if the interface *declares* the method,
         * that the declaration be throws-compatible with every inherited method
         * that it overrides.)  So what would be the "throws clause" of an
         * S3Method.Multiple, which represents a group of methods with the same
         * signature, inherited by an interface and not overridden by a local
         * declaration?  The least arbitrary choice, I suppose, would be as much
         * of the union as would be permissible if the interface did override the
         *  methods; i.e., the union of their throws clauses, minus every
         *  *checked* exception incompatible with any of them.  The union should
         *  be computed without having supertypes subsume subtypes.  But I'm lazy
         *  enough to avoid implementing that computation here for the moment, in
         *  the hope that client code will not ask for thrown types of
         * S3Method.Multiple, but will have specific questions it can directly
         * ask by visiting the ancestors.  Of course if a client site ever turns
         * out to have to implement the full union/culling computation just
         *  described, that really belongs here. --- Chap<p>
         * 
         * I ran into such a case and it took me a bit of time to track the error. --jv
         */
        public Class getThrownType(int i) throws LinkageException {
              throw unimp();
        }

        public int getThrownTypeCount() {
            for (int i = 0; i < ancestors.length; i++)    // a cheap hack--make it work as
                if (ancestors[i].getThrownTypeCount() != 0)  // long as we don't have exceptions                  
                    throw unimp();
            return 0;
        }
        
        public String toString() {
            Selector.Method sm = ancestors[0].getSelector();
            UnboundSelector.Method usm = sm.getUnboundSelector();
            StringBuffer sb;
            sb = new StringBuffer( getDeclaringType().toString()).append( "::{");
            sb = new StringBuffer( "{");
            sb.append( sm.getDefiningClass().toString());
            for ( int i = 1; i < ancestors.length; ++ i ) {
                sm = ancestors[i].getSelector();
                OVMassert( sm.getUnboundSelector() == usm); // sanity check
                sb.append( ',').append( sm.getDefiningClass().toString());
            }
            sb.append( "}.").append( usm.toString());
            return sb.toString();
        }

        private static OVMError.Unimplemented unimp() {
            return new OVMError.Unimplemented();
        }
        
        private static OVMError.UnsupportedOperation blooey() {
            return new OVMError.UnsupportedOperation();
        }
    }
} 
