package s3.core.domain;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

import ovm.core.domain.Domain;
import ovm.core.domain.Code;
import ovm.core.repository.Attribute;
import ovm.core.repository.Attribute.LineNumberTable;
import ovm.core.repository.CodeBuilder;
import ovm.core.repository.Constants;
import ovm.core.repository.ExceptionHandler;
import ovm.core.repository.Selector;
import ovm.services.bytecode.InstructionBuffer;
import ovm.util.ByteBuffer;
import ovm.util.OVMRuntimeException;
import s3.services.bootimage.ImageObserver;

/**
 * Representation of bytecode in the LINKED, per type, domain-bound state.  
 * The S3ByteCode can be at any stage of rewriting and has a linked, S3Constants 
 * constant pool.  Thus S3ByteCode is different from the repository Bytecode, 
 * which is unlinked. The similarity between the two classes comes from the fact 
 * that they are both representing code that is expressed in terms of a stack machine.
 * @author Christian Grothoff
 */
public class S3ByteCode extends S3Code {

    public static final Kind KIND = new Kind() {
    };
    public static final S3ByteCode[] EMPTY_ARRAY = new S3ByteCode[0];
    private static final ExceptionHandler[] EMPTY_EXCEPTIONS =
        new ExceptionHandler[0];
    private static final Attribute[] EMPTY_ATTRIBUTES = new Attribute[0];
    public static final S3ByteCode DEAD_CODE = new S3ByteCode();

    protected byte[] dbg_string;

    /**The byte code array for this byte code fragment.*/
    private byte[] bytes;
    // ATTN: interpreter uses this field, MUST MATCH TEXT "bytes"

    /** The maximum stack height for this fragment */
    private char maxStack_;

    /** The number of local variables for this fragment*/
    private char maxLocals_;

    /** The exception table for this byte code fragment (can be null).
     * @see ovm.core.repository.ExceptionHandler*/
    private ExceptionHandler[] exceptions_;

    /** The attributes for this fragment*/
    private Attribute[] attributes_;

    /** The constant pool to be used by this fragment */
    private S3Constants constantPool_;

    /** The flag that indicates if this is synchronized
     * This field is here because the interpreter needs to know that a
     * method is synchronized in order to call monitor enter and so on.                     
     */
    private boolean isSynchronized_;

    // this is only used to create the DEAD_CODE object.
    private S3ByteCode() {
        super(null);
    }

    S3ByteCode(
        S3Method method,
        byte[] bytes,
        char maxStack,
        char maxLocals,
        ExceptionHandler[] exceptions,
        Attribute[] attributes,
        S3Constants constantPool,
        boolean isSynchronized) {
        super(method);
        this.bytes = bytes;
        maxStack_ = maxStack;
        maxLocals_ = maxLocals;
        exceptions_ = exceptions == null ? EMPTY_EXCEPTIONS : exceptions;
        attributes_ = attributes == null ? EMPTY_ATTRIBUTES : attributes;
        constantPool_ = constantPool;
        isSynchronized_ = isSynchronized;

        try {
            ByteArrayOutputStream b = new ByteArrayOutputStream();
            getMethod().getSelector().write(b);
            b.write(0);
	    dbg_string = b.toByteArray();
        } catch (IOException e) {
            throw new OVMRuntimeException(e);
        }
    }

    public void bang(Code c) {
        bang((S3ByteCode) c);
    }

    public void bang(S3ByteCode c) {
        this.bytes = c.bytes;
        this.maxStack_ = c.maxStack_;
        this.maxLocals_ = c.maxLocals_;
        this.exceptions_ = c.exceptions_;
        this.attributes_ = c.attributes_;
        this.constantPool_ = c.constantPool_;
        this.isSynchronized_ = c.isSynchronized_;
    }

    public void dumpAscii(String domainName, PrintWriter pw) {
        InstructionBuffer ib =
            InstructionBuffer.wrap(
                ByteBuffer.wrap(bytes),
                getSelector(),
                constantPool_);
	pw.println(domainName + ";" + itsMethod.toString());
	pw.println("Stack = " + (int)maxStack_ + " Locals = " + (int)maxLocals_);
	pw.println(ib.toString());
	String handlers = "";
	boolean atLeastOneHandler = false;
	for(int i = 0; i < exceptions_.length; i++) {
	    atLeastOneHandler = true;
	    handlers += "\t" + exceptions_[i] + "\n";
	}
	if (atLeastOneHandler) {
	    pw.println("Exception handlers:\n" + handlers);
	}
	for(int i = 0; i < attributes_.length; i++) {
	    pw.println(attributes_[i]);
	}
    }

    public String toString() {
	if (bytes == null)
	    return "S3ByteCode{" + itsMethod + "#null}";
        InstructionBuffer ib =
            InstructionBuffer.wrap(
                ByteBuffer.wrap(bytes),
                getSelector(),
                constantPool_);
        return "S3ByteCode{"
            + itsMethod
            + "#"
            + ib.toString()
            + " "
            + getSelector()
            + "}";
    }

    public Kind getKind() {
        return KIND;
    }
    public boolean isSynchronized() {
        return isSynchronized_;
    }
    public Constants getConstantPool() {
        return constantPool_;
    }
    public byte[] getBytes() {
        return bytes;
    }
    public char getMaxStack() {
        return maxStack_;
    }
    public char getMaxLocals() {
        return maxLocals_;
    }
    public Attribute[] getAttributes() {
        return attributes_;
    }
    public ExceptionHandler[] getExceptionHandlers() {
        return exceptions_;
    }

    public int getLineNumber(int pc) {
        for (int i = 0; i < attributes_.length; i++)
            if (attributes_[i] instanceof LineNumberTable)
		return ((LineNumberTable) attributes_[i]).getLineNumber(pc);
        return pc;
    }

    public Selector.Method getSelector() {
        return itsMethod.getSelector();
    }

    /** @author Christian Grothoff */
    public static class Builder implements CodeBuilder {

        /** The byte code array for this byte code fragment. */
        private byte[] bytes_;
        /**The maximum stack height for this fragment*/
        private char maxStack;
        /** The number of local variables for this fragment*/
        private char maxLocals;
        /**The exception table for this byte code fragment (can be null).*/
        private ExceptionHandler[] exceptions;
        /** The attributes for this fragment*/
        private Attribute[] attributes;
        /**The constant pool to be used by this fragment*/
        private S3Constants constantPool;
        /**The flag that indicates if this is synchronized*/
        private boolean isSynchronized;
        private final S3Method method_;

        public Builder(S3Method method) {
            this.method_ = method;
        }

        public Builder(S3ByteCode bc) {
            this.method_ = (S3Method) bc.getMethod();
            this.bytes_ = bc.getBytes();
            this.maxStack = bc.getMaxStack();
            this.maxLocals = bc.getMaxLocals();
            this.constantPool = (S3Constants) bc.getConstantPool();
            this.exceptions = bc.getExceptionHandlers();
            this.attributes = bc.getAttributes();
            this.isSynchronized = bc.isSynchronized();
        }

        public Builder(
            S3Method method,
            byte[] bytes,
            char maxStack,
            char maxLocals,
            ExceptionHandler[] exceptions,
            Attribute[] attributes,
            S3Constants constantPool,
            boolean isSynchronized) {
            this.method_ = method;
            this.bytes_ = bytes;
            this.maxStack = maxStack;
            this.maxLocals = maxLocals;
            this.exceptions = exceptions;
            this.attributes = attributes;
            this.constantPool = constantPool;
            this.isSynchronized = isSynchronized;
        }

        public Object unrefinedBuild() {
            return build();
        }

        public S3ByteCode build() {
            return new S3ByteCode(
                method_,
                bytes_,
                maxStack,
                maxLocals,
                exceptions,
                attributes,
                constantPool,
                isSynchronized);
        }

        public void declareTemporaries(char _maxStack, char _maxLocals) {
            setMaxStack(_maxStack);
            setMaxLocals(_maxLocals);
        }

        public void setCode(ByteBuffer buf) {
            setBytes(buf.array());
        }
        public void setBytes(byte[] code) {
            this.bytes_ = code;
        }
        public void setAttributes(Attribute[] attr) {
            this.attributes = attr;
        }
        public void setConstantPool(S3Constants cp) {
            this.constantPool = cp;
        }

        public void setUnrefinedConstantPool(Constants constantPool) {
            setConstantPool((S3Constants) constantPool);
        }

        public void setMaxStack(char ms) {
            this.maxStack = ms;
        }
        public void setMaxLocals(char ml) {
            this.maxLocals = ml;
        }
        public void setSynchronized(boolean value) {
            this.isSynchronized = value;
        }
        public char getMaxStack() {
            return maxStack;
        }
        public char getMaxLocals() {
            return maxLocals;
        }
        public void setExceptionHandlers(ExceptionHandler[] ex) {
            this.exceptions = ex;
        }

        public void declareExceptionHandler(ExceptionHandler re) {
            int len = exceptions == null ? 1 : exceptions.length + 1;
            ExceptionHandler[] dst = new ExceptionHandler[len];
            if (exceptions != null)
                System.arraycopy(exceptions, 0, dst, 0, exceptions.length);
            dst[len - 1] = re;
            this.exceptions = dst;
        }

        public void removeAttribute(Attribute attribute) {
            if (attributes == null)
                return;
            int match = -1;
            int len = attributes.length;
            for (int i = 0; i < len; i++)
                if (attributes[i] == attribute) {
                    match = i;
                    break;
                }
            if (match == -1)
                return; // warn? throw error?
            Attribute[] dst = new Attribute[len - 1];
            System.arraycopy(attributes, 0, dst, 0, match - 1);
            System.arraycopy(
                attributes,
                match + 1,
                dst,
                match,
                len - match - 1);
            this.attributes = dst;
        }

        public void replaceAttribute(Attribute old, Attribute newAttr) {
            if (attributes == null) {
                declareAttribute(newAttr);
                return;
            }
            int len = attributes.length;
            for (int i = 0; i < len; i++)
                if (attributes[i] == old) {
                    attributes[i] = newAttr;
                    return;
                }
            declareAttribute(newAttr);
        }

        public void declareAttribute(Attribute attribute) {
            int len = attributes == null ? 1 : attributes.length + 1;
            Attribute[] dst = new Attribute[len];
            if (attributes != null)
                System.arraycopy(attributes, 0, dst, 0, attributes.length);
            dst[len - 1] = attribute;
            this.attributes = dst;
        }
    }
}
