package fi.vincit.multiusertest.rule;

import java.util.HashSet;
import java.util.Set;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.security.access.AccessDeniedException;

import fi.vincit.multiusertest.rule.expection.Expectation;
import fi.vincit.multiusertest.runner.junit.MultiUserTestRunner;
import fi.vincit.multiusertest.util.UserIdentifier;

/**
 * Rule to be used with {@link MultiUserTestRunner} to define whether a test passes or
 * fails. Before a method to be tested is executed {@link AuthorizationRule#expect(Authentication)}
 * should be called with the users that are expected to fail. The user syntax is same as in
 * {@link MultiUserTestRunner}
 *
 */
public class AuthorizationRule implements TestRule {

    private Set<UserIdentifier> expectToFailOnRoles = new HashSet<UserIdentifier>();
    private UserIdentifier userIdentifier;
    private FailMode failMode = FailMode.NONE;
    private Class<? extends Throwable> expectedException = AccessDeniedException.class;
    private static final Statement NO_BASE = null;

    public AuthorizationRule expect(Authentication identifiers) {
        addIdentifiers(identifiers);
        return this;
    }

    public void expect(Expectation expectation) throws Throwable {
        expectation.execute(userIdentifier);
    }

    private void addIdentifiers(Authentication identifiers) {
        dontExpectToFail();
        for (UserIdentifier userIdentifier : identifiers.getIdentifiers()) {
            expectToFailOnRoles.add(userIdentifier);
        }
        this.failMode = identifiers.getFailMode();
    }

    public void setRole(UserIdentifier identifier) {
        setRole(identifier.getType(), identifier.getIdentifier());
    }

    public void setRole(UserIdentifier.Type type, String identifier) {
        this.userIdentifier = new UserIdentifier(type, identifier);
    }

    public void setExpectedException(Class<? extends Throwable> expectedException) {
        this.expectedException = expectedException;
    }

    @Override
    public Statement apply(Statement base, Description description) {
        return new AuthChecker(base);
    }

    public void dontExpectToFail() {
        try {
            new AuthChecker(NO_BASE).evaluate();
            expectToFailOnRoles.clear();
            failMode = FailMode.NONE;
        } catch(AssertionError ae) {
            throw new AssertionError("Expected to fail before call", ae);
        } catch (Throwable e) {
            throw new RuntimeException("Internal error", e);
        }
    }

    Set<UserIdentifier> getExpectToFailOnRoles() {
        return expectToFailOnRoles;
    }

    UserIdentifier getUserIdentifier() {
        return userIdentifier;
    }

    FailMode getFailMode() {
        return failMode;
    }

    Class<? extends Throwable> getExpectedException() {
        return expectedException;
    }

    private class AuthChecker extends Statement {

        private Statement next;

        public AuthChecker(Statement next) {
            this.next = next;
        }

        @Override
        public void evaluate() throws Throwable {
            try {
                if (next != null) {
                    next.evaluate();
                }
            } catch (Throwable e) {
                if (expectedException.isInstance(e)) {
                    if (!evaluateExpectToFailCondition()) {
                        throw new AssertionError("Not expected to fail with user role " + userIdentifier.toString(), e);
                    } else {
                        return;
                    }
                } else {
                    throw e;
                }
            }

            if (evaluateExpectToFailCondition()) {
                throw new AssertionError("Expected to fail with user role " + userIdentifier.toString());
            }
        }

        private boolean evaluateExpectToFailCondition() {
            if (failMode == FailMode.EXPECT_FAIL) {
                return expectToFailOnRoles.contains(userIdentifier);
            } else if (failMode == FailMode.EXPECT_NOT_FAIL) {
                return !expectToFailOnRoles.contains(userIdentifier);
            } else {
                return false;
            }
        }
    }
}

@@ -1135,134 +1135,1068 @@
     
-public AuthorizationRule expect(Authentication identifiers) %7B%0A        addIdentifiers(identifiers);%0A        return this;%0A    %7D%0A
+/**%0A     * Simple assertion for checking that method throws/doesn't throw an exception. If after the call no%0A     * exceptions are expected the %7B@link AuthorizationRule#dontExpectToFail()%7D has to be called. To expect%0A     * a custom exception, %7B@link AuthorizationRule#setExpectedException(Class)%7D can be used.%0A     * @param identifiers Identifiers for which the calls after this method are expected to fail.%0A     * @return%0A     * @since 0.1%0A     */%0A    public AuthorizationRule expect(Authentication identifiers) %7B%0A        addIdentifiers(identifiers);%0A        return this;%0A    %7D%0A%0A    /**%0A     * Advanced assertion which makes use of expecations to form %7B@link fi.vincit.multiusertest.rule.expection.Expectations%7D.%0A     * Immediately executes the call to the given method and makes defined assertions. Using this %7B@link AuthorizationRule#expect(Expectation)%7D%0A     * method, the %7B@link AuthorizationRule#dontExpectToFail()%7D method isn't required to be called.%0A     * @param expectation %7B@link Expectation%7D rule%0A     * @throws Throwable%0A     * @since 0.2%0A     */
 %0A   
@@ -2864,32 +2864,264 @@
 tifier);%0A    %7D%0A%0A
+    /**%0A     * Set a custom exception to expect. Works only with %7B@link AuthorizationRule#expect(Authentication)%7D%0A     * method (version 0.1 style simple assertion).%0A     * @param expectedException Exception class to except%0A     */%0A
     public void 
@@ -3369,32 +3369,318 @@
 r(base);%0A    %7D%0A%0A
+    /**%0A     * Checks that if any previous calls should've failed and throws%0A     * %7B@link AssertionError%7D if the calls haven't behaved as expected.%0A     * If unexpected exception is thrown, throws %7B@link RuntimeException%7D.%0A     * After this call don't expect any call to fail.%0A     */%0A
     public void 

Add JavaDoc for AuthorizationRule