package s3.services.j2c;

import s3.core.S3Base;
import ovm.core.domain.Type;
import ovm.core.repository.TypeCodes;
import ovm.util.HashSet;
import ovm.util.Iterator;
import s3.core.domain.S3Blueprint;
import s3.services.j2c.J2cValue.*;

/**
 * @author baker29
 **/
public class LocalAllocator extends S3Base {
    static final int INT_VAR = 0;
    static final int LONG_VAR = 1;
    static final int FLOAT_VAR = 2;
    static final int DOUBLE_VAR = 3;
    static final int PC_VAR = 4;
    static final int REF_VAR = 5;
    static final int N_VAR_FLAVORS = 6;
    
    static final int[] tagFlavor = new int[128];
    static final char[] flavorTag = new char[N_VAR_FLAVORS];
    static {
        flavorTag[INT_VAR] = TypeCodes.INT;
        tagFlavor[TypeCodes.BOOLEAN] = INT_VAR;
        tagFlavor[TypeCodes.BYTE] = INT_VAR;
        tagFlavor[TypeCodes.UBYTE] = INT_VAR;
        tagFlavor[TypeCodes.CHAR] = INT_VAR;
        tagFlavor[TypeCodes.USHORT] = INT_VAR;
        tagFlavor[TypeCodes.SHORT] = INT_VAR;
        tagFlavor[TypeCodes.INT] = INT_VAR;
        tagFlavor[TypeCodes.UINT] = INT_VAR;
        
        flavorTag[LONG_VAR] = TypeCodes.LONG;
        tagFlavor[TypeCodes.LONG] = LONG_VAR;
        tagFlavor[TypeCodes.ULONG] = LONG_VAR;
        
        flavorTag[FLOAT_VAR] = TypeCodes.FLOAT;
        tagFlavor[TypeCodes.FLOAT] = FLOAT_VAR;
        
        flavorTag[DOUBLE_VAR] = TypeCodes.DOUBLE;
        tagFlavor[TypeCodes.DOUBLE] = DOUBLE_VAR;
        
        flavorTag[REF_VAR] = TypeCodes.OBJECT;
        tagFlavor[TypeCodes.OBJECT] = REF_VAR;
        tagFlavor[TypeCodes.ARRAY] = REF_VAR;
        tagFlavor[TypeCodes.GEMEINSAM] = REF_VAR;
    }
    
    J2cValue[][] locals = new J2cValue[N_VAR_FLAVORS][];
    HashSet names = new HashSet();
    int cnt = 0;
    Context ctx;
    int nlocals;

    boolean hasXDCalls;
    boolean hasExceptions;
    
    public String genSym(String hint) {
	String name = hint;
	while (names.contains(name))
	    name = hint + "_" + cnt++;
	names.add(name);
	return name;
    }

    int bpFlavor(S3Blueprint bp) {
        if (bp == null)
            return PC_VAR;
	else if (bp == ctx.VM_AddressBP || bp == ctx.VM_WordBP)
	    return INT_VAR;
        else {
            Type t = bp.getType();
            char tag = t.getUnrefinedName().getTypeTag();
            return tagFlavor[tag];
        }
    }

    public J2cValue findLocal(int tag, int index) {
	return locals[tagFlavor[tag]][index];
    }

    public J2cValue findLocal(S3Blueprint bp, int index) {
	return locals[bpFlavor(bp)][index];
    }

    public J2cValue[] findLocals(int flavor) {
	return (J2cValue[]) locals[flavor].clone();
    }

    public J2cValue allocateLocal(J2cValue fromValue, int idx, String _name) {
	S3Blueprint bp = fromValue.getBlueprint(ctx.domain);
        int flavor = bpFlavor(bp);
        J2cValue ret = locals[flavor][idx];
        
        if (ret == null) {
	    String name = genSym(_name);
            switch (flavor) {
	    case REF_VAR:
		ret = new J2cReference(null, bp);
		break;
	    case INT_VAR:
		ret = new J2cInt(null, TypeCodes.INT);
		break;
	    case LONG_VAR:
		ret = new J2cLong(null, null, TypeCodes.LONG);
		break;
	    case FLOAT_VAR:
		ret = new J2cFloat(null, null);
		break;
	    case DOUBLE_VAR:
		ret = new J2cDouble(null, null);
		break;
            case PC_VAR:
                ret = new J2cJumpTarget(-1);
	    }
	    ret.allocate(name, J2cValue.LOCAL_VAR, flavor, idx);
	    locals[flavor][idx] = ret;
        } else if (flavor == REF_VAR) {
            ((J2cReference) ret).destructiveMerge(fromValue);
        }
        return ret;
    }

    /**
     * Used to spill reference values from c++ locals (registers) to
     * psuedo-locals (on the stack) at safe points.
     *
     * @param fromValue value being spilled
     * @param spillNo   uniquely identifies fromValue at this safe point.
     */
    public J2cValue allocateSpill(J2cValue fromValue, int spillNo) {
	return allocateLocal(fromValue,
			     nlocals + spillNo,
			     "spill_" + spillNo);
    }

    public J2cValue saveThis() {
	return allocateLocal(new J2cReference(null, ctx.OopBP),
			     locals[REF_VAR].length - 1,
			     J2cFormat.encode("this"));
    }
    
    public LocalAllocator(Context ctx, int maxLocals, int maxStack,
			  Exceptions exInfo) {
	this.ctx = ctx;
	this.nlocals = maxLocals + 1;
	hasExceptions = exInfo.hasAny();
	for (int i = 0; i < REF_VAR; i++) {
	    locals[i] = new J2cValue[maxLocals];
	}
	locals[REF_VAR] = new J2cValue[maxLocals + maxStack + 1];
	OVMassert(REF_VAR == N_VAR_FLAVORS - 1);
    }

    public Iterator varIterator(final int flavor) {
	return new Iterator() {
                J2cValue[] vars = locals[flavor];
		int nextIdx = -1;
		void findNext() {
		    while (++nextIdx < vars.length
			   && vars[nextIdx] == null)
			;
		}

		{ findNext(); }

		public boolean hasNext() {
		    return nextIdx < vars.length;
		}
		public Object next() {
		    Object ret = vars[nextIdx];
		    findNext();
		    return ret;
		}
		public void remove() {
		    throw new UnsupportedOperationException();
		}
	    };
    }
}
