/*
 * -\-\-
 * Mobius
 * --
 * Copyright (c) 2017-2020 Spotify AB
 * --
 * 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 com.spotify.mobius.test;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;

import com.spotify.mobius.First;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;

/** Provides utility functions for matching against {@link First} instances. */
public final class FirstMatchers {
  private FirstMatchers() {
    // prevent instantiation
  }

  /**
   * Returns a matcher that matches {@link First} instances with a model that is equal to the
   * supplied one.
   *
   * @param expected the expected model
   * @param <M> the model type
   * @param <F> the effect type
   */
  public static <M, F> Matcher<First<M, F>> hasModel(M expected) {
    return hasModel(equalTo(expected));
  }

  /**
   * Returns a matcher that matches {@link First} instances with a model that matches the supplied
   * model matcher.
   *
   * @param matcher the matcher to apply to the model
   * @param <M> the model type
   * @param <F> the effect type
   */
  public static <M, F> Matcher<First<M, F>> hasModel(Matcher<M> matcher) {
    return new TypeSafeDiagnosingMatcher<First<M, F>>() {
      @Override
      protected boolean matchesSafely(First<M, F> item, Description mismatchDescription) {
        if (!matcher.matches(item.model())) {
          mismatchDescription.appendText("bad model: ");
          matcher.describeMismatch(item.model(), mismatchDescription);
          return false;

        } else {
          mismatchDescription.appendText("has model: ");
          matcher.describeMismatch(item.model(), mismatchDescription);
          return true;
        }
      }

      @Override
      public void describeTo(Description description) {
        description.appendText("has a model: ").appendDescriptionOf(matcher);
      }
    };
  }

  /**
   * Returns a matcher that matches {@link First} instances with no effects.
   *
   * @param <M> the model type
   * @param <F> the effect type
   */
  public static <M, F> Matcher<First<M, F>> hasNoEffects() {
    return new TypeSafeDiagnosingMatcher<First<M, F>>() {
      @Override
      protected boolean matchesSafely(First<M, F> item, Description mismatchDescription) {
        if (item.hasEffects()) {
          mismatchDescription.appendText("has effects");
          return false;

        } else {
          mismatchDescription.appendText("no effects");
          return true;
        }
      }

      @Override
      public void describeTo(Description description) {
        description.appendText("should have no effects");
      }
    };
  }

  /**
   * Returns a matcher that matches {@link First} instances whose effects match the supplied effect
   * matcher.
   *
   * @param matcher the matcher to apply to the effects
   * @param <M> the model type
   * @param <F> the effect type
   */
  public static <M, F> Matcher<First<M, F>> hasEffects(Matcher<Iterable<F>> matcher) {
    return new TypeSafeDiagnosingMatcher<First<M, F>>() {
      @Override
      protected boolean matchesSafely(First<M, F> item, Description mismatchDescription) {
        if (!item.hasEffects()) {
          mismatchDescription.appendText("no effects");
          return false;

        } else if (!matcher.matches(item.effects())) {
          mismatchDescription.appendText("bad effects: ");
          matcher.describeMismatch(item.effects(), mismatchDescription);
          return false;

        } else {
          mismatchDescription.appendText("has effects: ");
          matcher.describeMismatch(item.effects(), mismatchDescription);
          return true;
        }
      }

      @Override
      public void describeTo(Description description) {
        description.appendText("has effects: ").appendDescriptionOf(matcher);
      }
    };
  }

  /**
   * Returns a matcher that matches if all the supplied effects are present in the supplied {@link
   * First}, in any order. The {@link First} may have more effects than the ones included.
   *
   * @param effects the effects to match (possibly empty)
   * @param <M> the model type
   * @param <F> the effect type
   * @return a matcher that matches {@link First} instances that include all the supplied effects
   */
  @SafeVarargs
  public static <M, F> Matcher<First<M, F>> hasEffects(F... effects) {
    return hasEffects(hasItems(effects));
  }
}