package org.xtest.results;

import java.util.Collection;
import java.util.List;

import org.eclipse.emf.ecore.EObject;
import org.xtest.XTestEvaluationException;
import org.xtest.validation.XTestJavaValidator;
import org.xtest.xTest.Body;
import org.xtest.xTest.XTestExpression;

import com.google.common.collect.Lists;

/**
 * Xtest result
 * 
 * @author Michael Barry
 */
public class XTestResult {
    /**
     * Key to use as index to store {@link XTestResult}s in {@link XTestJavaValidator} context map
     */
    public static final String KEY = "XtestResult";
    private final EObject eObject;
    private final Collection<XTestEvaluationException> expressions = Lists.newArrayList();
    private final String name;
    private final XTestResult parent;
    private boolean pending = false;
    private Object resultObject;

    private XTestState state = XTestState.NOT_RUN;

    private final List<XTestResult> subTests = Lists.newArrayList();
    private final List<String> syntaxErrors = Lists.newArrayList();
    private boolean uniqueFailure = false;

    /**
     * Construct a top-level xtest result
     * 
     * @param main
     *            The xtest expression model
     */
    public XTestResult(Body main) {
        this(null, null, main);
    }

    // Private constructor so that users are forced to call subTest to
    // instantiate a non-top-level test
    private XTestResult(XTestResult parent, String name, EObject eObject) {
        this.parent = parent;
        this.name = name != null ? name.replaceAll("[\n\r]", "") : null;
        this.eObject = eObject;
    }

    /**
     * Fails the test and all of its parents and stores the evaluation exception
     * 
     * @param exception
     *            The evaluation exception
     */
    public void addEvaluationException(XTestEvaluationException exception) {
        fail();
        expressions.add(exception);
    }

    /**
     * Fails this test and all of its parents and adds a syntax error for this test.
     * 
     * @param string
     *            The syntax error.
     */
    public void addSyntaxError(String string) {
        fail();
        syntaxErrors.add(string);
    }

    /**
     * Counts the number of failures
     * 
     * @return The number of failures including this and sub tests
     */
    public int countFailures() {
        int result = uniqueFailure ? 1 : 0;
        for (XTestResult sub : subTests) {
            result += sub.countFailures();
        }
        return result;
    }

    /**
     * Counts the number of pending tests
     * 
     * @return The number of pending tests including this and sub tests
     */
    public int countPendings() {
        int result = pending ? 1 : 0;
        for (XTestResult sub : subTests) {
            result += sub.countPendings();
        }
        return result;
    }

    /**
     * Counts the total number of tests
     * 
     * @return The total number of tests including this and sub tests
     */
    public int countTests() {
        int result = 1;
        for (XTestResult sub : subTests) {
            result += sub.countTests();
        }
        return result;
    }

    /**
     * Fails the test and all of its parents
     */
    public void fail() {
        uniqueFailure = true;
        internalFail();
    }

    /**
     * Returns the {@link EObject} associated with this result
     * 
     * @return The {@link EObject} assocated with this result
     */
    public EObject getEObject() {
        return eObject;
    }

    /**
     * Returns the syntax errors in this test
     * 
     * @return The syntax errors in this test
     */
    public List<String> getErrorMessages() {
        return syntaxErrors;
    }

    /**
     * Returns the evaluation exception for this result, or null if there was none
     * 
     * @return the evaluation exception for this result, or null if there was none
     */
    public Collection<XTestEvaluationException> getEvaluationException() {
        return expressions;
    }

    /**
     * Returns the name of this test
     * 
     * @return The name of this test (can be null)
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the name of this test, prepended with all of the tests it is contained within.
     * 
     * @return The name of this test, prepended with all of the tests it is contained within (can't
     *         be null, can be empty)
     */
    public String getQualifiedName() {
        StringBuilder builder = new StringBuilder(name == null ? "" : name);
        for (XTestResult cursor = parent; cursor != null; cursor = cursor.parent) {
            if (cursor.getName() != null) {
                builder.insert(0, '.');
                builder.insert(0, cursor.getName());
            }
        }
        return builder.toString();
    }

    /**
     * Gets the return value of the test
     * 
     * @return The return value of the test
     */
    public Object getResultObject() {
        return resultObject;
    }

    /**
     * Returns the state of this test result
     * 
     * @return The state of this test result
     */
    public XTestState getState() {
        return state;
    }

    /**
     * Returns the tests contained within this test
     * 
     * @return The tests contained within this test
     */
    public List<XTestResult> getSubTests() {
        return subTests;
    }

    /**
     * Returns true if this test is pending, false if not
     * 
     * @return True if this test is pending, false if not
     */
    public boolean isPending() {
        return pending;
    }

    /**
     * Passes this result. Only passes its parent if the parent has not been run yet.
     */
    public void pass() {
        if (state == XTestState.NOT_RUN) {
            state = XTestState.PASS;
            if (parent != null && parent.getState() == XTestState.NOT_RUN) {
                parent.pass();
            }
        }
    }

    /**
     * Sets this test as pending
     */
    public void setPending() {
        this.pending = true;
    }

    /**
     * Sets the return value of the test
     * 
     * @param resultObject
     *            The return vale of the test
     */
    public void setResultObject(Object resultObject) {
        this.resultObject = resultObject;
    }

    /**
     * Constructs a new test result that is contained in this test.
     * 
     * @param name
     *            The name of the test
     * @param eObject
     *            The test test expression that the result corresponds to
     * @return The new test result
     */
    public XTestResult subTest(String name, XTestExpression eObject) {
        XTestResult result = new XTestResult(this, name, eObject);
        subTests.add(result);
        return result;
    }

    private void internalFail() {
        state = XTestState.FAIL;
        if (parent != null) {
            parent.internalFail();
        }
    }

}

@@ -280,24 +280,64 @@
 ression;%0D%0A%0D%0A
+import com.google.common.base.Objects;%0D%0A
 import com.g
@@ -7082,24 +7082,206 @@
 ;%0D%0A    %7D%0D%0A%0D%0A
+    @Override%0D%0A    public String toString() %7B%0D%0A        return Objects.toStringHelper(XTestResult.class).add(%22name%22, name).add(%22state%22, state)%0D%0A                .toString();%0D%0A    %7D%0D%0A%0D%0A
     private 

Improve toString()