package s3.services.j2c;

import ovm.core.OVMBase;
import ovm.core.repository.Selector;
import ovm.core.repository.ExceptionHandler;
import ovm.services.bytecode.Instruction;
import ovm.services.bytecode.InstructionBuffer;
import ovm.services.bytecode.InstructionVisitor;
import ovm.util.ArrayList;
import ovm.util.Collections;
import ovm.util.Comparator;
import ovm.util.ListIterator;
import ovm.core.repository.TypeName;
import ovm.core.domain.Blueprint;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Type;

/**
 * This class transforms a method's exception handler table 
 * exception handlers can be expressed in C++.
 * <p>
 * J2c's approach is to translate each ExceptionHandler record into a
 * catch clause.  The C++ code in the catch clause uses
 * <code>goto</code> to jump to the handler PC:
<pre>
start_pc:
  try {
   ...
   } catch (Exception *e) { _phi = e; goto handler_pc; }
end_pc:
  ...
handler_pc:
  ...
</pre>
 *
 * This approach is only viable when the exception handler table is
 * compatible with C++ <code>try</code>/<code>catch</code>
 * statements.  Code generated by <code>javac</code> does <b>not</b>
 * have this property.  In particular, the exception handler for a
 * synchronized block is defined within the exception handler's
 * range.  We can handle this construct by generating one
 * <code>try</code> statement for the <code>syncronized</code> block's
 * body, and a second for the <code>synchronized</code> block's
 * handler:
<pre>
  monitorEnter(CSA, o);
start_pc:
  try {
    <i>synch block body</i>
    monitorExit(CSA, o);
    goto end_pc;
  } catch (Throwable *e) { phi = e; goto handler_pc; }
handler_pc:
  try {
     monitorExit(CSA, o);
     j2c_throw(phi);
  } catch (Throwable *e) { phi = e; goto handler_pc; }
end_pc:
</pre>
 * 
 * This class's first responsiblity is to split exception handlers so
 * that control flow only enters an exception handler at its start pc.
 * <p>
 * This class also presents exception handlers to the compiler in a
 * way can easily be mapped to try blocks.  Java exception handlers
 * are stored in a partial order such that more specific (nested)
 * handlers appear before less specific (outer) ones.  We are more
 * concerned with deterimining when to start and end try blocks, and
 * when multiple handlers can be defined on a single try block.
 * <p>
 * The first step is to modify exception handler ranges so that both
 * the start and end pc are reachable.  If this step is omitted, j2c
 * may not have a place to put its try blocks. This step also records
 * where either the handler pc, or the entire handler range is
 * unreachable.
 * <p>
 * Finally, we perform two stable sorts on the exception handler
 * list.  <code>byStartPC</code>  is used to determine where
 * <code>try</code> blocks should begin, and <code>byEndPC</code> is
 * used to deterimine both where <code>try</code> blocks end, and the
 * order in which <code>catch</code> clauses should appear for a given
 * try block.
 **/
public class Exceptions extends InstructionVisitor {
    static final ArrayList EMPTY_LIST = new ArrayList();
    static final ExceptionHandler[] EMPTY_ARRAY = new ExceptionHandler[0];
    static final ListIterator EMPTY_ITERATOR = EMPTY_LIST.listIterator();
    
    ArrayList handlers;
    Selector.Method m;

    /**
     * Detect a jump from outside a try block into the middle of it.
     * This try block must be split in half, so that the jump is to
     * the start of a try block.
     **/
    private void splitHandlers(int curPC, int nextPC) {
	for (ListIterator it = handlers.listIterator(); it.hasNext(); ) {
	    ExceptionHandler h = (ExceptionHandler) it.next();
	    if (h.getStartPC() < nextPC && h.getEndPC() > nextPC
		&& (h.getStartPC() > curPC || h.getEndPC() <= curPC)) {
		/* nextPC is in the middle of this handler, and curPC
		 * is outside of the handler.  Split the handler so
		 * that a handler starts at nextPC
		 */
// 		System.err.println("jump from " + curPC + " -> " + nextPC
// 				   + " splits " + h);
		it.remove();
		it.add(new ExceptionHandler((char) h.getStartPC(),
					    (char) nextPC,
					    (char) h.getHandlerPC(),
					    h.getCatchTypeName()));
		it.add(new ExceptionHandler((char) nextPC,
					    (char) h.getEndPC(),
					    (char) h.getHandlerPC(),
					    h.getCatchTypeName()));
	    }
	}
    }

    public void visit(Instruction.ConditionalJump i) {
	splitHandlers(getPC(), i.getBranchTarget(buf) + getPC());
    }
    public void visit(Instruction.UnconditionalJump i) {
	splitHandlers(getPC(), i.getTarget(buf) + getPC());
    }
    public void visit(Instruction.Switch i) {
	int[] target = i.getTargets(buf);
	for (int idx = 0; idx < target.length; idx++)
	    splitHandlers(getPC(), target[idx] + getPC());
	splitHandlers(getPC(), i.getDefaultTarget(buf) + getPC());
    }

    /**
     * Find all jump targets in the method.  If any path jumps into
     * the middle of an exception handler, split the handler at jump
     * target.
     *
     * The jumps that bother us are generated for synchronized
     * blocks.  In this case, we have an exception handler where the
     * handlerPC is in the exception handler range.  This cannot be
     * written in C++ or Java.
     **/
    void splitAllHandlers() {
	int startlen = -1;
	for(int endlen = handlers.size();
	    startlen != endlen;
	    endlen = handlers.size())
	{
	    startlen = endlen;

	    ExceptionHandler[] h = new ExceptionHandler[endlen];
	    handlers.toArray(h);
	    for (int i = 0; i < h.length; i++)
		splitHandlers(h[i].getEndPC(), h[i].getHandlerPC());

	    buf.rewind();
	    while (buf.hasRemaining())
		visitAppropriate(buf.get());
	}
    }

    public Exceptions(ovm.core.domain.Code code) {
	super(InstructionBuffer.wrap(code));
	m = code.getMethod().getSelector();
	Type.Context ctx = code.getMethod().getDeclaringType().getContext();
	ExceptionHandler[] h = code.getExceptionHandlers();
	if (h.length > 0 && !J2cImageCompiler.EXCEPTIONS_BROKEN) {
	    handlers = new ArrayList(h.length);
	    for (int i = 0; i < h.length; i++) {
		TypeName n = h[i].getCatchTypeName();
		try {
		    Type t = ctx.typeFor(n);
		    handlers.add(h[i]);
		} catch (LinkageException _) {
		    System.err.println("catch nonexistent type " + n);
		}
	    }
	    splitAllHandlers();
	} else 
	    handlers = EMPTY_LIST;
    }

    boolean hasAny() { return handlers != EMPTY_LIST; }

    /**
     * Return the exception handlers in JVM order.
     */
    public ExceptionHandler[] getHandlers() {
	if (handlers == EMPTY_LIST)
	    return EMPTY_ARRAY;

	ExceptionHandler[] ret = new ExceptionHandler[handlers.size()];
	handlers.toArray(ret);
	return ret;
    }

    /**
     * Remove handlers that the abstract interpreter has determined to
     * be unreachable, and ensure that the start and end pc of every
     * handler is reachable.
     * <p>
     * Handler splitting may have rendered some C++ catch clauses
     * unreachable, but we do not detect that here.
     **/
    public void recompute(boolean[] reachable) {
	for (ListIterator it = handlers.listIterator(); it.hasNext(); ) {
	    ExceptionHandler h = (ExceptionHandler) it.next();
	    if (!reachable[h.getHandlerPC()]) {
//		System.err.println(m + ": " + h + " has unreachable handler");
		it.remove();
		continue;
	    }

	    int oldStart = h.getStartPC();
	    int newStart = oldStart;
	    int oldEnd = h.getEndPC();
	    int newEnd = oldEnd;

	    while (newStart < reachable.length && !reachable[newStart])
		newStart++;
		
	    if (newStart >= oldEnd) {
		System.err.println(m + ": " + h + " has empty body");
		it.remove();
		continue;
	    }

	    while (newEnd < reachable.length && !reachable[newEnd])
		newEnd++;
		    
	    if (oldStart != newStart || oldEnd != newEnd) {
		ExceptionHandler newH
		    = new ExceptionHandler((char) newStart,
					   (char) newEnd,
					   (char) h.getHandlerPC(),
					   h.getCatchTypeName());
// 		System.err.println("updated range of " + h +
// 				   " to " + newH);
		it.set(newH);
	    }
	}
    }

    boolean byStart = false;

    /**
     * Iterate of exception handlers from first to last in a try,
     * innermost to outermost try at a start PC, and in start
     * PC order.
     */
    public ListIterator byStartPC() {
	byStart = true;
	if (handlers == EMPTY_LIST)
	    return EMPTY_ITERATOR;

	Collections.sort(handlers, new Comparator() {
		public int compare(Object _a, Object _b) {
		    ExceptionHandler a = (ExceptionHandler) _a;
		    ExceptionHandler b = (ExceptionHandler) _b;
		    return a.getStartPC() - b.getStartPC();
		}
	    });
	return handlers.listIterator();
    }

    /**
     * Iterate of exception handlers from first to last in a try,
     * innermost to outermost try at an end PC, and in end PC order.
     */
    public ListIterator byEndPC() {
	OVMBase.OVMassert(byStart);
	if (handlers == EMPTY_LIST)
	    return EMPTY_ITERATOR;

	ArrayList l = new ArrayList(handlers);
	Collections.sort(l, new Comparator() {
		public int compare(Object _a, Object _b) {
		    ExceptionHandler a = (ExceptionHandler) _a;
		    ExceptionHandler b = (ExceptionHandler) _b;
		    int ecmp = a.getEndPC() - b.getEndPC();
		    return (ecmp != 0 ? ecmp
			    : b.getStartPC() - a.getStartPC());
		}
	    });
	return l.listIterator();
    }

    /**
     * Return the next handler in a list or null
     **/
    public static ExceptionHandler next(ListIterator l) {
	return ((l == EMPTY_ITERATOR || !l.hasNext())
		? null
		: (ExceptionHandler) l.next());
    }
}
