package com.xtremelabs.robolectric.bytecode;

/**
 * Policy that defined how direct calls are handled. Policy is thread local.
 */
public interface DirectCallPolicy {

    /** NoDirectCallPolicy instance. */
    DirectCallPolicy NOP = new NoDirectCallPolicy();

    /**
     * Decide whether call must be performed directly. 
     * @param target target object
     * @return true if call must be direct
     */
    boolean shouldCallDirectly(Object target);

    /**
     * Called after each object method invocation. 
     * It's executed in <code>finally</code> block.
     * @param target object which method has been invoked on
     * @return policy to replace current instance
     */
    DirectCallPolicy onMethodInvocationFinished(Object target);

    /**
     * Called when direct call request appears. This method can either throw {@link DirectCallException} or return false 
     * if policy change is not appropriate. 
     * @param previousPolicy previous policy instance
     * @return true if policy change should be applied
     */
    boolean checkForChange(DirectCallPolicy previousPolicy);

    /** Throw by policy instances in case of illegal state exception. */
    public static class DirectCallException extends IllegalStateException {

        private static final long serialVersionUID = 4326926182637261742L;

        public DirectCallException(String msg) {
            super(msg);
        }

    }

    /** Direct call is not performed. */
    public static class NoDirectCallPolicy implements DirectCallPolicy  {

        private NoDirectCallPolicy() { /* hidden */ }

        @Override
        public boolean shouldCallDirectly(Object target) {
            return false;
        }
        @Override
        public DirectCallPolicy onMethodInvocationFinished(Object target) {
            return this;
        }
        @Override
        public boolean checkForChange(DirectCallPolicy previousPolicy) {
            return true;
        }
    }

    abstract static class SafeDirectCallPolicy implements DirectCallPolicy {
        /** Direct instance. */
        Object expectedInstance;

        public SafeDirectCallPolicy(Object directInstance) {
            if (directInstance == null) { throw new DirectCallException("Direct call target cannot be null, use class instance for static calls"); }
            this.expectedInstance = directInstance;
        }
        
        @Override
        public boolean shouldCallDirectly(Object target) {
            if (expectedInstance == null) { return false; }
            
            if (expectedInstance != target) {
                Object expected = expectedInstance;
                expectedInstance = null;
                throw new DirectCallException("expected to perform direct call on <" + expected + "> but got <" + target + ">");
            }
            
            return true;
        }
    }
    
    /** Direct call is performed once only. */
    public static class OneShotDirectCallPolicy extends SafeDirectCallPolicy {

        public OneShotDirectCallPolicy(Object directInstance) {
            super(directInstance);
        }

        @Override
        public boolean shouldCallDirectly(Object target) {
            boolean result = super.shouldCallDirectly(target);
            expectedInstance = null;
            return result;
        }

        @Override
        public DirectCallPolicy onMethodInvocationFinished(Object target) {
            return NOP;
        }
        
        @Override
        public boolean checkForChange(DirectCallPolicy previousPolicy) {
            // first setup
            if (previousPolicy == NOP) { return true; }
            
            // twice setup
            if (previousPolicy instanceof OneShotDirectCallPolicy) {
                throw new DirectCallException("already expecting a direct call on <" + ((OneShotDirectCallPolicy) previousPolicy).expectedInstance + "> but here's a new request for <" + expectedInstance + ">");
            }
            
            // we are inside full stack direct call => do not change anything
            if (previousPolicy instanceof FullStackDirectCallPolicy && ((FullStackDirectCallPolicy) previousPolicy).checkWeAreDeepInsideStack()) {
                return false;
            }
            
            // unexpected
            throw new DirectCallException("Direct call policy is already set to " + previousPolicy);
        }
    }

    /** Direct call is performed within the invocation full stack. */
    public static class FullStackDirectCallPolicy extends SafeDirectCallPolicy {

        /** Stack depth. */
        private int depth = -1;

        public FullStackDirectCallPolicy(final Object directInstance) {
            super(directInstance);
        }
        
        @Override
        public boolean shouldCallDirectly(Object target) {
            boolean result = depth == -1 ? super.shouldCallDirectly(target) : true;
            if (result) { ++depth; }
            return result;
        }

        @Override
        public DirectCallPolicy onMethodInvocationFinished(Object target) {
            if (expectedInstance == null) {
                // error happened
                return NOP;
            }
            
            if (depth < 0) {
                throw new DirectCallException("Stack depth is negative: " + depth + ", target: " + expectedInstance);
            }
            if (depth-- == 0) {
                if (target != expectedInstance) {
                    throw new DirectCallException("Unexpected state: stack should have been collapsed on <" + expectedInstance + ">, but got <" + target + ">");
                }
                return NOP;
            }
            return this;
        }

        boolean checkWeAreDeepInsideStack() {
            return depth >= 0;
        }
        
        @Override
        public boolean checkForChange(DirectCallPolicy previousPolicy) {
            // first setup
            if (previousPolicy == NOP) { return true; }
            
            // we are inside full stack direct call => do not change anything
            if (previousPolicy instanceof FullStackDirectCallPolicy && ((FullStackDirectCallPolicy) previousPolicy).checkWeAreDeepInsideStack()) {
                return false;
            }
            
            // unexpected, bad setup
            throw new DirectCallException("Direct call policy is already set to " + previousPolicy);
        }
        
    }

}

@@ -2377,33 +2377,25 @@
 ;%0A        %7D%0A
-        
 %0A
+
         @Ove
@@ -2515,37 +2515,25 @@
 rn false; %7D%0A
-            
 %0A
+
             
@@ -2798,37 +2798,25 @@
           %7D%0A
-            
 %0A
+
             
@@ -2844,21 +2844,17 @@
 %7D%0A    %7D%0A
-    
 %0A
+
     /** 
@@ -3421,33 +3421,25 @@
 ;%0A        %7D%0A
-        
 %0A
+
         @Ove
@@ -3596,37 +3596,25 @@
 urn true; %7D%0A
-            
 %0A
+
             
@@ -3918,37 +3918,25 @@
           %7D%0A
-            
 %0A
+
             
@@ -4188,37 +4188,25 @@
           %7D%0A
-            
 %0A
+
             
@@ -4663,33 +4663,25 @@
 ;%0A        %7D%0A
-        
 %0A
+
         @Ove
@@ -5114,37 +5114,25 @@
           %7D%0A
-            
 %0A
+
             
@@ -5702,33 +5702,25 @@
 ;%0A        %7D%0A
-        
 %0A
+
         @Ove
@@ -5881,29 +5881,17 @@
 true; %7D%0A
-            
 %0A
+
         
@@ -6155,21 +6155,9 @@
   %7D%0A
-            
 %0A
+
     
@@ -6300,24 +6300,16 @@
       %7D%0A
-        
 %0A    %7D%0A%0A

Fix formatting