package musubi;

import static musubi.Goals.conj;
import static musubi.Goals.ordero;
import static musubi.Goals.same;

import musubi.Cons;
import musubi.LogicValue;
import musubi.Subst;
import musubi.Var;
import musubi.testing.LogicAsserter;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.util.Arrays;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@RunWith(JUnit4.class)
public class CustomValueTypeTest {
  static final class PersonName implements LogicValue {
    private final Object familyName;
    private final Object givenName;

    PersonName(Object familyName, Object givenName) {
      this.familyName = familyName;
      this.givenName = givenName;
    }

    @Override
    public Map<String, ?> asMap() {
      Map<String, Object> map = new HashMap<>();
      map.put("familyName", familyName);
      map.put("givenName", givenName);
      return map;
    }

    @Override
    public Subst unify(Subst subst, LogicValue other) {
      subst = subst.unify(familyName, ((PersonName) other).familyName);
      if (subst == null) {
        return null;
      }
      return subst.unify(givenName, ((PersonName) other).givenName);
    }

    @Override
    public LogicValue replace(Replacer replacer) {
      return new PersonName(replacer.replace(familyName), replacer.replace(givenName));
    }

    @Override
    public String toString() {
      return String.format("PersonName{%s, %s}", familyName, givenName);
    }

    @Override
    public boolean equals(Object o) {
      if (!(o instanceof PersonName)) {
        return false;
      }

      PersonName other = (PersonName) o;
      return ((familyName == null) ? (other.familyName == null) : familyName.equals(other.familyName))
          && ((givenName == null) ? (other.givenName == null) : givenName.equals(other.givenName));
    }

    @Override
    public int hashCode() {
      return Arrays.hashCode(new Object[] {familyName, givenName});
    }
  }

  @Test
  public void unifySubFields() {
    Var family = new Var();
    Var given = new Var();
    Var name = new Var();

    new LogicAsserter()
        .stream(
            conj(
                same(family, "Doe"),
                same(given, "John"),
                same(name, new PersonName(family, given))))
        .startSubst()
        .put(name, new PersonName("Doe", "John"))
        .addRequestedVar(name)
        .workUnits(2)
        .test();
  }

  @Test
  public void failedUnification() {
    Var family = new Var();
    Var given = new Var();
    Var name = new Var();

    new LogicAsserter()
        .stream(
            conj(
                same(family, "Doe"),
                same(new PersonName("Fooey", given), new PersonName(family, "Barson"))))
        .workUnits(1)
        .test();
  }

  @Test
  public void nonTrivialUnification() {
    Var family = new Var();
    Var given = new Var();
    Var name = new Var();

    new LogicAsserter()
        .stream(
            conj(
                ordero(Cons.list(Arrays.asList(family, given)), Cons.list(Arrays.asList(1, 2, 3, 4))),
                same(new PersonName(family, given), name)))
        .workUnits(7)
        .addRequestedVar(name)
        .startSubst()
        .put(name, new PersonName(1, 2))
        .startSubst()
        .put(name, new PersonName(1, 3))
        .startSubst()
        .put(name, new PersonName(1, 4))
        .startSubst()
        .put(name, new PersonName(2, 3))
        .startSubst()
        .put(name, new PersonName(2, 4))
        .startSubst()
        .put(name, new PersonName(3, 4))
        .test();
  }
}

@@ -392,32 +392,62 @@
 va.util.Arrays;%0A
+import java.util.Collections;%0A
 import java.util
@@ -3677,11 +3677,896 @@
 t();%0A  %7D
+%0A%0A  static final class Foo implements LogicValue %7B%0A    public Map%3CString, ?%3E asMap() %7B%0A      return Collections.emptyMap();%0A    %7D%0A%0A    @Override%0A    public Subst unify(Subst subst, LogicValue other) %7B%0A      return subst;%0A    %7D%0A%0A    @Override%0A    public LogicValue replace(Replacer replacer) %7B%0A      return this;%0A    %7D%0A  %7D%0A%0A  static final class Bar implements LogicValue %7B%0A    public Map%3CString, ?%3E asMap() %7B%0A      return Collections.emptyMap();%0A    %7D%0A%0A    @Override%0A    public Subst unify(Subst subst, LogicValue other) %7B%0A      return subst;%0A    %7D%0A%0A    @Override%0A    public LogicValue replace(Replacer replacer) %7B%0A      return this;%0A    %7D%0A  %7D%0A%0A  @Test%0A  public void differingClassesCannotUnion() %7B%0A    Var x = new Var();%0A    Var y = new Var();%0A%0A    new LogicAsserter()%0A        .stream(conj(same(x, y), same(x, new Foo()), same(y, new Bar())))%0A        .workUnits(1)%0A        .test();%0A  %7D
 %0A%7D%0A

Add test: differing classes cannot union