class TableDiff {

  public static TableAnswerElement diffTables(
      TableAnswerElement baseTable, TableAnswerElement deltaTable, boolean includeOneTableKeys) {
    checkArgument(
        baseTable
            .getMetadata()
            .getColumnMetadata()
            .equals(deltaTable.getMetadata().getColumnMetadata()),
        "Cannot diff tables with different column metadatas");

    TableMetadata inputMetadata = baseTable.getMetadata();
    TableAnswerElement diffTable = new TableAnswerElement(diffMetadata(inputMetadata));
    Map<String, ColumnMetadata> diffColumnMap = diffTable.getMetadata().toColumnMap();

    List<String> keyColumns =
        inputMetadata.getColumnMetadata().stream()
            .filter(ColumnMetadata::getIsKey)
            .map(ColumnMetadata::getName)
            .collect(Collectors.toList());

    List<ColumnMetadata> valueColumns =
        inputMetadata.getColumnMetadata().stream()
            .filter(cm -> (!cm.getIsKey() && cm.getIsValue()))
            .collect(Collectors.toList());

    Set<Object> baseKeys = new HashSet<>();

    Iterator<Row> baseRows = baseTable.getRows().iterator();
    Map<List<Object>, List<Row>> deltaMap =
        buildMap(deltaTable.getRows(), inputMetadata.getColumnMetadata());
    while (baseRows.hasNext()) {
      Row baseRow = baseRows.next();
      Object baseKey = baseRow.getKey(inputMetadata.getColumnMetadata());
      baseKeys.add(baseKey);
      List<Row> deltaRows = deltaMap.get(baseKey);
      if (deltaRows == null) { // no matching keys in delta table
        if (includeOneTableKeys) {
          RowBuilder diffRowBuilder = Row.builder(diffColumnMap).putAll(baseRow, keyColumns);
          diffRowValues(diffRowBuilder, baseRow, null, inputMetadata);
          diffTable.addRow(diffRowBuilder.build());
        }
      } else {
        for (Row deltaRow : deltaRows) {
          // insert delta rows that are unequal
          if (!baseRow.getValue(valueColumns).equals(deltaRow.getValue(valueColumns))) {
            RowBuilder diffRowBuilder = Row.builder(diffColumnMap).putAll(baseRow, keyColumns);
            diffRowValues(diffRowBuilder, baseRow, deltaRow, inputMetadata);
            diffTable.addRow(diffRowBuilder.build());
          }
        }
      }
    }
    if (includeOneTableKeys) {
      // process keys that are present only in delta
      Iterator<Row> deltaRows = deltaTable.getRows().iterator();
      while (deltaRows.hasNext()) {
        Row deltaRow = deltaRows.next();
        Object deltaKey = deltaRow.getKey(inputMetadata.getColumnMetadata());
        if (baseKeys.contains(deltaKey)) {
          continue;
        }
        RowBuilder diffRowBuilder = Row.builder(diffColumnMap).putAll(deltaRow, keyColumns);
        diffRowValues(diffRowBuilder, null, deltaRow, inputMetadata);
        diffTable.addRow(diffRowBuilder.build());
      }
    }

    return diffTable;
  }

  private  TableDiff();

}