package s3.core.domain;

import ovm.core.domain.Domain;
import ovm.core.domain.Type;
import ovm.util.ArrayList;
import ovm.util.HTObject2int;
import ovm.util.HashSet;
import ovm.util.Iterator;
import ovm.util.LinkedList;
import ovm.util.List;
import s3.core.S3Base;

/**
 * Provides internal functions necessary for creating and
 * maintaining data structures used in subtype tests.
 * <blockquote>Inferred by Chap: each type has an array of
 * buckets, a bucket index, and a bucketID. Desired invariant:
 * given types A and B, B's bucketID equals the entry in A's
 * bucket array [at B's bucket index] if and only if A is
 * assignable to B.</blockquote>
 **/
class Subtyping extends S3Base {

    private ArrayList bucketDescriptors = new ArrayList();
    private HTObject2int bucketAssignment = new HTObject2int();

    /**
     * Get the bucket descriptor for the bucket at the given index.
     * @param bucket the bucket index
     * @return the bucket descriptor
     **/
    private BucketDescriptor getDescriptor(int bucket) {
        return (BucketDescriptor) bucketDescriptors.get(bucket);
    }
    /**
     * Given a <code>Type</code>, return its bucketID, i.e. the
     * type's order of addition to its assigned bucket (0 if it is not
     * found as a member of that bucket?). The value can equal but never exceed
     * {@link #MAX_BUCKET_SIZE} (S1). -jcf (inferred)
     * @param t the type whose bucket ID is wanted
     * @return the bucket ID
     **/
    private int getBucketID(Type t) {
        // S1. MAX_BUCKET_SIZE is the maximum number of members. It is one greater
        // than the maximum index of any member. It is therefore the maximum possible
        // value of index+1.
        return getDescriptor(getBucket(t)).members.indexOf(t) + 1;
    }
    /**
     * Return the bucket index assigned to a type.
     * @param t a Type
     * @param its assigned index
     **/
    private int getBucket(Type t) {
        return bucketAssignment.get(t);
    }
    /** 
     * @return the number of buckets a display must accommodate **/
    private int displaySize() {
        return bucketDescriptors.size();
    }

    private static class BucketDescriptor {
        HashSet marked = new HashSet();
        LinkedList members = new LinkedList();

        void mark(Type t) { // needs a split
            OVMassert(members.contains(t));
            marked.add(t);
        }
        boolean isSplitNeeded() {
            return marked.size() > 1;
        }
        void clear() {
            marked.clear();
        }
        int markCount() {
            return marked.size();
        }
        void add(Type t) {
            if ( members.size() < MAX_BUCKET_SIZE )
                members.add(t); // S1. Size may now == but cannot > MAX_BUCKET_SIZE.
            else
                failUnimplemented( "Grow a subtyping bucket.");
        }
    }
    
    /** Dump the contents of the descriptor buckets (for debugging). */
    private void dump() {
        for (int i = 0; i < bucketDescriptors.size(); i++)
            d(i + ": " + getDescriptor(i).members);
    }

    private byte[] makeDisplay(Type t) {
        byte[] arr = new byte[displaySize()];
        if (t.isArray()) {
            Domain dom = t.getContext().getDomain();
            arr[0] = (byte) getBucketID(dom.getHierarchyRoot());
            Type.Interface[] ifaces = dom.getArrayInterfaces();
            for (int i = 0; i < ifaces.length; i++) {
                int bucket = getBucket(ifaces[i]);
                arr[bucket] = (byte) (getBucketID(ifaces[i]) & 0xFF);
            }
            return arr;
        }
        for (int bucket = 0; bucket < arr.length; bucket++) {
            List members = getDescriptor(bucket).members;
            for (int j = members.size() - 1; j >= 0; j--) {
                Type anc = (Type) members.get(j);
                if (!t.isSubtypeOf(anc))
                    continue;
                if ((t == anc) && (arr[bucket] != 0)) {
                    d("duh " + t); // you don't say...
                    continue;
                }
                byte id = (byte) ((j + 1) & 0xFF);
                OVMassert(
                    (arr[bucket] == 0)
                        || (arr[bucket] == id)
                            ? "PASS"
                            : t + ": disp[" + bucket + "]=" + arr[bucket] + " != "
                                + id + ", anc " + anc);
                arr[bucket] = id;
            }
        }
        return arr;
    }

    final private static int EXTENDS_CUTOFF = 8;
    /*The bucket index of any <em>sui generis</em> type, that is, a type for which
     * no subtype test returns true except against the exact same type.
     * @deprecated   unused!
     *
    final private static int SUI_GENERIS = Integer.MAX_VALUE;
*/
    /** The number of bucket members, and therefore a bucketID, can equal but
     *  never exceed this value. (S1)
     **/
    final private static int MAX_BUCKET_SIZE = 255;

    private int getHierarchyDepth(Type.Class cls) {
        int count = -1;
        do {
            count++;
            cls = cls.getSuperclass();
        } while (cls != null);
        return count;
    }

    // that does not contain a related class
    private int smallestBucketAtTheEnd(int span, Type t) {
        int lastBucket = bucketDescriptors.size() - 1;
        int smallestSize = Integer.MAX_VALUE;
        int choice = lastBucket;
        int i = Math.max(lastBucket - span, EXTENDS_CUTOFF + 1);
        for (; i <= lastBucket; i++) {
            BucketDescriptor bd = getDescriptor(i);
            for (Iterator iter = bd.members.iterator(); iter.hasNext();) {
                Type member = (Type) iter.next();
                //				don't put related guys together!
                if (t.isSubtypeOf(member) || member.isSubtypeOf(t))
                    continue;
            }
            if (bd.members.size() < smallestSize) {
                choice = i;
                smallestSize = bd.members.size();
            }
        }
        return choice;
    }

    /** returns max int for shared state types */
    private int pickInitialBucket(Type type) {
        if (type.isClass()) {
            int depth = getHierarchyDepth((Type.Class) type);
            if (depth <= EXTENDS_CUTOFF)
                return depth;
        }
        if (bucketDescriptors.size() == 0)
            bucketDescriptors.add(new BucketDescriptor());
        int choice =
            Math.max(smallestBucketAtTheEnd(25, type), EXTENDS_CUTOFF + 1);
        // 25?
        return choice;
    }

    private void addTypeToBucket(Type type, int bucket) {
        OVMassert(!type.isSharedState());
        while (bucket >= bucketDescriptors.size())
            bucketDescriptors.add(new BucketDescriptor());
        getDescriptor(bucket).add(type);
        bucketAssignment.remove(type); // in case it was already assigned
        bucketAssignment.put(type, bucket);
    }

    /** The types which we have all really allocated. (per domain) */
    private HashSet seen = new HashSet();

    /**
     * When adding a new type to the domain we assign it some type information.
     * In the process we may have to split some buckets and reassign some 
     * other types
     * @param t the newly added type
     */
    // NB: there used to be a recursive check. After tracing through the external
    // methods used here I could not find a case where adding a type would trigger
    // class loading. If I am wrong retrieve the previous version of the class.
    // --jv 5/7/3
    // The method is now synchronized to prevent against concurrent updates.
    synchronized public void addType(Type t) {
        if (seen.contains(t) || t.isSharedState())
            return;
        seen.add(t);
        int bucket = pickInitialBucket(t);
        addTypeToBucket(t, bucket);
        getDescriptor(bucket).mark(t);
        if (markAncestorsDescriptors(t))
            splitBuckets(t);
    }

    private void splitBuckets(Type t) {
        for (Iterator i = bucketDescriptors.iterator(); i.hasNext();) {
            BucketDescriptor bd = (BucketDescriptor) i.next();
            if (!bd.isSplitNeeded())
                continue;
            int newBucketCount = bd.markCount() - 1;
            int lastBucket = bucketDescriptors.size() - 1;

            int conflictingCount = 0;
            int nonconflictingCount = 0;
            for (Iterator j = bd.members.iterator(); j.hasNext();) {
                Type anc = (Type) j.next();
                int newBucket;
                if (bd.marked.contains(anc)) {
                    if (conflictingCount == 0) { // keep it where it was
                        conflictingCount++;
                        continue;
                    }
                    newBucket = lastBucket + conflictingCount;
                    conflictingCount++;
                } else {
                    int where = nonconflictingCount % (newBucketCount + 1);
                    if (where == 0) {
                        nonconflictingCount++;
                        continue;
                    }
                    newBucket = lastBucket + where;
                    nonconflictingCount++;
                }
                j.remove();
                addTypeToBucket(anc, newBucket);
            }
        }
    }
    /*
    private void unmarkDescriptors() {
        for (int i = bucketDescriptors.size() - 1; i >= 0; i--)
            getDescriptor(i).clear();
    }
    */
    private boolean markAncestorsDescriptors(Type t) {
        boolean isSplitNeeded = false;
        for (Type.Class superclass = t.getSuperclass();
            superclass != null;
            superclass = superclass.getSuperclass()) {
            int bucket = getBucket(superclass);
            BucketDescriptor desc = getDescriptor(bucket);
            desc.mark(superclass);
            isSplitNeeded = isSplitNeeded || desc.isSplitNeeded();
        }
        Type.Interface[] allIfaces = ((S3Type) t).getAllInterfaces();
        for (int i = 0; i < allIfaces.length; i++) {
            int bucket = getBucket(allIfaces[i]);
            BucketDescriptor desc = getDescriptor(bucket);
            desc.mark(allIfaces[i]);
            isSplitNeeded = isSplitNeeded || desc.isSplitNeeded();
        }
        return isSplitNeeded;
    }

    private final byte[] empty_display = new byte[0];

    /**
     * Recompute the type information for a whole domain. Root types are allocated
     * in bucket 0. Primitive types have a tid equal to their type tag (which we 
     * assume to be unique), shared states are allocated a tid of 0 (a value that is 
     * reserved) and a bucket of MAXINT (guaranteed to fail).<p> 
     * See "Java Subtype Tests in Real-Time", ECOOP03, for more details.
     * @param dom domain to recompute
     */
    synchronized public void recomputeSubtypeInfo(S3Domain dom) {
        if (false) dump();
        //dom.bucket2bp_ = new S3Blueprint[displaySize()][256];
        for (Iterator iter = dom.getBlueprintIterator(); iter.hasNext();) {
            S3Blueprint bpt = (S3Blueprint) iter.next();
            Type type = bpt.getType();
            try { OVMassert(bpt == dom.blueprintFor(type)); }
            catch (ovm.core.domain.LinkageException le) {  throw le.fatal();  }
            if (type.isPrimitive()) {
                Type.Primitive prim = (Type.Primitive) type;
                bpt.setSubtypingInfo( 0, prim.getUnrefinedName().getTypeTag(), empty_display);
            } else if (type.isSharedState())
                bpt.setSubtypingInfo(Integer.MAX_VALUE, 0, empty_display);
            else {
                int bucket = getBucket(type);
                OVMassert(bucket >= 0);
                int id = (byte) getBucketID(type); // obligatory cast (sign ext)
                bpt.setSubtypingInfo(bucket, id, makeDisplay(type));
               // dom.bucket2bp_[bucket][id & 0xff] = bpt;
            }
        }
    }
}