package org.hamcrest.beans;

import org.hamcrest.AbstractMatcherTest;
import org.hamcrest.Matcher;

import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;

@SuppressWarnings("WeakerAccess")
public class SamePropertyValuesAsTest extends AbstractMatcherTest {
  private static final Value aValue = new Value("expected");
  private static final ExampleBean expectedBean = new ExampleBean("same", 1, aValue);
  private static final ExampleBean actualBean = new ExampleBean("same", 1, aValue);
  
  
  @Override
  protected Matcher<?> createMatcher() {
    return samePropertyValuesAs(expectedBean);
  }

  public void test_reports_match_when_all_properties_match() {
    assertMatches("matched properties", samePropertyValuesAs(expectedBean), actualBean);
  }
  
  public void test_reports_mismatch_when_actual_type_is_not_assignable_to_expected_type() {
    assertMismatchDescription("is incompatible type: ExampleBean", 
                              samePropertyValuesAs((Object)aValue), actualBean);
  }

  public void test_reports_mismatch_on_first_property_difference() {
    assertMismatchDescription("stringProperty was \"different\"",
        samePropertyValuesAs(expectedBean), new ExampleBean("different", 1, aValue));
    assertMismatchDescription("intProperty was <2>",
        samePropertyValuesAs(expectedBean), new ExampleBean("same", 2, aValue));
    assertMismatchDescription("valueProperty was <Value other>",
        samePropertyValuesAs(expectedBean), new ExampleBean("same", 1, new Value("other")));
  }

  public void test_matches_beans_with_inheritance_but_no_extra_properties() {
    assertMatches("sub type with same properties", 
        samePropertyValuesAs(expectedBean), new SubBeanWithNoExtraProperties("same", 1, aValue));
  }

  public void test_rejects_subtype_that_has_extra_properties() {
    assertMismatchDescription("has extra properties called [extraProperty]",
        samePropertyValuesAs(expectedBean), new SubBeanWithExtraProperty("same", 1, aValue));
  }

  public void test_ignores_extra_subtype_properties() {
    final SubBeanWithExtraProperty withExtraProperty = new SubBeanWithExtraProperty("same", 1, aValue);
    assertMatches("extra property", samePropertyValuesAs(expectedBean, "extraProperty"), withExtraProperty);
  }

  public void test_ignores_different_properties() {
    final ExampleBean differentBean = new ExampleBean("different", 1, aValue);
    assertMatches("different property", samePropertyValuesAs(expectedBean, "stringProperty"), differentBean);
  }

  public void test_accepts_missing_properties_to_ignore() {
    assertMatches("ignored property", samePropertyValuesAs(expectedBean, "notAProperty"), actualBean);
  }

  public void test_can_ignore_all_properties() {
    final ExampleBean differentBean = new ExampleBean("different", 2, new Value("not expected"));
    assertMatches(
            "different property",
            samePropertyValuesAs(expectedBean, "stringProperty", "intProperty", "valueProperty"),
            differentBean);
  }


  public void testDescribesItself() {
    assertDescription(
            "same property values as ExampleBean [intProperty: <1>, stringProperty: \"same\", valueProperty: <Value expected>]",
            samePropertyValuesAs(expectedBean));

    assertDescription(
            "same property values as ExampleBean [intProperty: <1>, stringProperty: \"same\", valueProperty: <Value expected>] ignoring [\"ignored1\", \"ignored2\"]",
            samePropertyValuesAs(expectedBean, "ignored1", "ignored2"));
  }

  public static class Value {
    public Value(Object value) {
      this.value = value;
    }

    public final Object value;
    @Override
    public String toString() {
      return "Value " + value;
    }
  }
  
  @SuppressWarnings("unused")
  public static class ExampleBean {
    private String stringProperty;
    private int intProperty;
    private Value valueProperty;

    public ExampleBean(String stringProperty, int intProperty, Value valueProperty) {
      this.stringProperty = stringProperty;
      this.intProperty = intProperty;
      this.valueProperty = valueProperty;
    }
    
    public String getStringProperty() {
      return stringProperty;
    }
    public int getIntProperty() {
      return intProperty;
    }
    public Value getValueProperty() {
      return valueProperty;
    }

    @Override public String toString() { return "an ExampleBean"; }
  }
  
  public static class SubBeanWithNoExtraProperties extends ExampleBean {
    public SubBeanWithNoExtraProperties(String stringProperty, int intProperty, Value valueProperty) {
      super(stringProperty, intProperty, valueProperty);
    }
  }
  
  public static class SubBeanWithExtraProperty extends ExampleBean {
    public SubBeanWithExtraProperty(String stringProperty, int intProperty, Value valueProperty) {
      super(stringProperty, intProperty, valueProperty);
    }
    @SuppressWarnings("unused")
    public String getExtraProperty() { return "extra"; }
  }
}
