/*
 * Copyright (c) 2010-2011. Axon Framework
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.axonframework.test.matchers;

import org.axonframework.domain.DomainEvent;
import org.axonframework.domain.Event;
import org.axonframework.domain.EventBase;
import org.axonframework.test.FixtureExecutionException;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;

import java.lang.reflect.Field;
import java.util.Arrays;

/**
 * Matcher that will match an Event if all the fields on that Event contain values equal to the same field in the
 * expected event. All fields in the hierarchy are compared, with the exclusion of the aggregate identifier, sequence
 * number and meta data, which are generally not set in the expected events.
 *
 * @author Allard Buijze
 * @since 1.1
 */
public class EqualEventMatcher<T extends Event> extends BaseMatcher<T> {

    private final T expected;
    private Field failedField;
    private Object failedFieldExpectedValue;
    private Object failedFieldActualValue;

    /**
     * Initializes an EqualEventMatcher that will match an event with equal properties as the given
     * <code>expected</code> event.
     *
     * @param expected The expected event
     */
    public EqualEventMatcher(T expected) {
        this.expected = expected;
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public boolean matches(Object item) {
        return expected.getClass().isInstance(item) && matchesSafely((T) item);
    }

    private boolean matchesSafely(Event actualEvent) {
        return expected.getClass().equals(actualEvent.getClass())
                && fieldsMatch(expected.getClass(), expected, actualEvent);
    }

    private boolean fieldsMatch(Class<?> aClass, Event expectedEvent, Event actualEvent) {
        boolean match = true;
        for (Field field : aClass.getDeclaredFields()) {
            field.setAccessible(true);
            try {

                Object expected = field.get(expectedEvent);
                Object actual = field.get(actualEvent);
                if (expected != null && actual != null && expected.getClass().isArray()) {
                    if (!Arrays.deepEquals(new Object[]{expected}, new Object[]{actual})) {
                        failedField = field;
                        failedFieldExpectedValue = expected;
                        failedFieldActualValue = actual;
                        return false;
                    }
                } else if ((expected != null && !expected.equals(actual)) || expected == null && actual != null) {
                    failedField = field;
                    failedFieldExpectedValue = expected;
                    failedFieldActualValue = actual;
                    return false;
                }
            } catch (IllegalAccessException e) {
                throw new FixtureExecutionException("Could not confirm event equality due to an exception", e);
            }
        }
        if (aClass.getSuperclass() != DomainEvent.class
                && aClass.getSuperclass() != EventBase.class
                && aClass.getSuperclass() != Object.class) {
            match = fieldsMatch(aClass.getSuperclass(), expectedEvent, actualEvent);
        }
        return match;
    }

    /**
     * Returns the field that failed comparison, if any. This value is only populated after {@link #matches(Object)} is
     * called and a mismatch has been detected.
     *
     * @return the field that failed comparison, if any
     */
    public Field getFailedField() {
        return failedField;
    }

    /**
     * Returns the expected value of a failed field comparison, if any. This value is only populated after {@link
     * #matches(Object)} is called and a mismatch has been detected.
     *
     * @return the expected value of the field that failed comparison, if any
     */
    public Object getFailedFieldExpectedValue() {
        return failedFieldExpectedValue;
    }

    /**
     * Returns the actual value of a failed field comparison, if any. This value is only populated after {@link
     * #matches(Object)} is called and a mismatch has been detected.
     *
     * @return the actual value of the field that failed comparison, if any
     */
    public Object getFailedFieldActualValue() {
        return failedFieldActualValue;
    }

    @Override
    public void describeTo(Description description) {
        description.appendText(expected.getClass().getName());
        if (failedField != null) {
            description.appendText(" (failed on field '")
                       .appendText(failedField.getName())
                       .appendText("')");
        }
    }
}

@@ -1302,16 +1302,51 @@
 s.%0D%0A *%0D%0A
+ * @param %3CT%3E The type of event m%0D%0A
  * @auth
@@ -2540,26 +2540,24 @@
      try %7B%0D%0A
-%0D%0A
             
@@ -2575,16 +2575,26 @@
 expected
+FieldValue
  = field
@@ -2644,16 +2644,26 @@
 t actual
+FieldValue
  = field
@@ -2702,32 +2702,42 @@
     if (expected
+FieldValue
  != null && actu
@@ -2730,32 +2730,42 @@
 = null && actual
+FieldValue
  != null && expe
@@ -2760,32 +2760,42 @@
 null && expected
+FieldValue
 .getClass().isAr
@@ -2868,16 +2868,26 @@
 expected
+FieldValue
 %7D, new O
@@ -2900,16 +2900,26 @@
 %5D%7Bactual
+FieldValue
 %7D)) %7B%0D%0A 
@@ -3014,32 +3014,42 @@
 Value = expected
+FieldValue
 ;%0D%0A             
@@ -3082,32 +3082,42 @@
 alValue = actual
+FieldValue
 ;%0D%0A             
@@ -3201,16 +3201,26 @@
 expected
+FieldValue
  != null
@@ -3232,16 +3232,26 @@
 expected
+FieldValue
 .equals(
@@ -3260,22 +3260,58 @@
 tual
-))
+FieldValue))%0D%0A                       
  %7C%7C 
+(
 expected
  == 
@@ -3306,16 +3306,26 @@
 expected
+FieldValue
  == null
@@ -3334,16 +3334,26 @@
 & actual
+FieldValue
  != null
@@ -3345,32 +3345,33 @@
 ldValue != null)
+)
  %7B%0D%0A            
@@ -3451,24 +3451,34 @@
 e = expected
+FieldValue
 ;%0D%0A         
@@ -3519,16 +3519,26 @@
 = actual
+FieldValue
 ;%0D%0A     

Update issue #144 Javadoc and code style