package org.jtwig.support;

import com.google.common.base.Objects;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

import com.google.common.base.Optional;

public class IsOptional<T> extends TypeSafeMatcher<Optional<? extends T>> {

    public static IsOptional<Object> isAbsent() {
        return new IsOptional<>(false);
    }

    public static IsOptional<Object> isPresent() {
        return new IsOptional<>(true);
    }

    public static <T> IsOptional<T> isValue(T value) {
        return new IsOptional<T>(value);
    }

    public static <T> IsOptional<T> matches(Matcher<T> matcher) {
        return new IsOptional<>(matcher);
    }

    private final boolean someExpected;

    private final Optional<T> expected;

    private final Optional<Matcher<T>> matcher;

    private IsOptional(boolean someExpected) {
        this.someExpected = someExpected;
        this.expected = Optional.absent();
        this.matcher = Optional.absent();
    }

    private IsOptional(T value) {
        this.someExpected = true;
        this.expected = Optional.of(value);
        this.matcher = Optional.absent();
    }

    private IsOptional(Matcher<T> matcher) {
        this.someExpected = true;
        this.expected = Optional.absent();
        this.matcher = Optional.of(matcher);
    }

    @Override
    public void describeTo(Description description) {
        if (!someExpected) {
            description.appendText("<Absent>");
        } else if (expected.isPresent()) {
            description.appendValue(expected);// "a Some with " +
            // expected.some());
        } else if (matcher.isPresent()) {
            description.appendText("a present value matching ");
            matcher.get().describeTo(description);
        } else {
            description.appendText("<Present>");
        }
    }

    @Override
    public boolean matchesSafely(Optional<? extends T> item) {
        if (!someExpected) {
            return !item.isPresent();
        } else if (expected.isPresent()) {
            return item.isPresent() && Objects.equal(item.get(), expected.get());
        } else if (matcher.isPresent()) {
            return item.isPresent() && matcher.get().matches(item.get());
        } else {
            return item.isPresent();
        }
    }
}