Explanation.java

package com.github.dakusui.pcond.validator;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;

import static com.github.dakusui.pcond.internals.InternalUtils.newLine;
import static com.github.dakusui.pcond.validator.ReportComposer.Utils.composeReport;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;

public class Explanation {
  private final String                message;
  private final ReportComposer.Report expected;
  private final ReportComposer.Report actual;

  public Explanation(String message) {
    this(message, composeReport("", null), composeReport("", null));
  }

  public Explanation(String message, ReportComposer.Report expected, ReportComposer.Report actual) {
    this.message = message;
    this.expected = requireNonNull(expected);
    this.actual = requireNonNull(actual);
  }

  public String message() {
    return this.message;
  }

  public ReportComposer.Report expected() {
    return this.expected;
  }

  public ReportComposer.Report actual() {
    return this.actual;
  }

  public String toString() {
    return actual != null ?
        format("%s%n%s", message, composeDiff(expected, actual)) :
        message;
  }

  private static String composeDiff(ReportComposer.Report expected, ReportComposer.Report actual) {
    String[] e = splitAndTrim(expected.summary());
    String[] a = splitAndTrim(actual.summary());
    List<String> b = new LinkedList<>();
    for (int i = 0; i < Math.max(a.length, e.length); i++) {
      if (i < Math.min(e.length, a.length) && Objects.equals(e[i], a[i])) {
        b.add(format("          %s", a[i]));
      } else {
        b.add(format("Mismatch<:%s", i < e.length ? e[i] : ""));
        b.add(format("Mismatch>:%s", i < a.length ? a[i] : ""));
      }
    }
    b.add(newLine());
    assert expected.details().size() == actual.details().size();
    return !expected.details().isEmpty() ?
        b.stream().collect(joining(newLine()))
            + IntStream.range(0, expected.details().size())
            .mapToObj(i -> formatDetailItemPair(i, expected.details().get(i), actual.details().get(i)))
            .collect(joining(newLine())) :
        "";
  }

  private static String formatDetailItemPair(int i, String detailItemForExpectation, String detailItemForActual) {
    return format(".Detail of failure [%s] (expectation)%n", i)
        + format("----%n")
        + detailItemForExpectation
        + newLine()
        + format("----%n")
        + newLine()
        + format(".Detail of failure [%s] (actual value)%n", i)
        + format("----%n")
        + detailItemForActual
        + newLine()
        + "----";
  }

  public static String reportToString(ReportComposer.Report report) {
    String ret = report.summary();
    ret += newLine();
    ret += newLine();
    ret += IntStream.range(0, report.details().size())
        .mapToObj(i -> formatDetailItem(i, report.details().get(i)))
        .collect(joining(newLine()));
    return ret;
  }

  private static String formatDetailItem(int i, String detailItem) {
    return format(".Detail of failure [%s]%n", i)
        + format("----%n")
        + detailItem
        + newLine()
        + format("----%n");
  }

  public static Explanation fromMessage(String msg) {
    return new Explanation(msg);
  }

  private static String[] splitAndTrim(String expected) {
    String[] in = expected.split(newLine());
    List<String> out = new LinkedList<>();
    boolean nonEmptyFound = false;
    for (int i = in.length - 1; i >= 0; i--) {
      if (!"".equals(in[i]))
        nonEmptyFound = true;
      if (nonEmptyFound)
        out.add(0, in[i]);
    }
    return out.toArray(new String[0]);
  }
}