package com.xebialabs.restito.builder.verify;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.xebialabs.restito.semantics.Call;
import com.xebialabs.restito.semantics.Condition;
import com.xebialabs.restito.server.StubServer;

import static java.lang.String.format;

/**
 * <p>Responsible for building verifications (e.g. something happened x times).</p>
 */
public class VerifyHttp {

    private final List<Call> calls;

    /**
     * Static factory
     */
    public static VerifyHttp verifyHttp(StubServer stubServer) {
        return new VerifyHttp(stubServer.getCalls());
    }

    /**
     * Static factory
     */
    public static VerifyHttp verifyHttp(List<Call> calls) {
        return new VerifyHttp(calls);
    }

    private VerifyHttp(List<Call> calls) {
        this.calls = calls;
    }

    /**
     * There should be only one call which satisfies given conditions
     */
    public VerifySequenced once(Condition... conditions) {
        return times(1, conditions);
    }

    /**
     * There should be no calls which satisfies given conditions
     */
    public VerifySequenced never(Condition... conditions) {
        return times(0, conditions);
    }

    /**
     * There should be <i>t</i> calls which satisfies given conditions
     */
    public VerifySequenced times(int t, Condition... conditions) {
        final List<Call> foundCalls = filterByConditions(conditions);
        if (t != foundCalls.size()) {
            throw new AssertionError(format("Expected to happen %s time(s), but happened %s times instead", t, foundCalls.size()));
        }

        return new VerifySequenced(getCallsAfterLastFound(foundCalls));
    }

    /**
     * There should be at least <i>t</i> calls which satisfies the given conditions
     */
    public VerifySequenced atLeast(int t, Condition... conditions) {
        final List<Call> foundCalls = filterByConditions(conditions);
        if (t > foundCalls.size()) {
            throw new AssertionError(format("Expected to happen at least %s time(s), but happened %s times instead", t, foundCalls.size()));
        }

        return new VerifySequenced(getCallsAfterLastFound(foundCalls));
    }

    private List<Call> getCallsAfterLastFound(final List<Call> foundCalls) {
        return foundCalls.size() == 0 ? calls :
                calls.subList(
                        calls.indexOf(foundCalls.get(foundCalls.size() - 1)) + 1,
                        calls.size()
                );
    }

    private List<Call> filterByConditions(Condition[] conditions) {
        List<Call> filteredCalls = new ArrayList<>(calls);

        for (Condition condition : conditions) {
            Iterator<Call> callsIterator = filteredCalls.iterator();
            while (callsIterator.hasNext()) {
                Call call = callsIterator.next();
                if (!condition.getPredicate().test(call)) {
                    callsIterator.remove();
                }
            }
        }
        return filteredCalls;
    }

}
