package org.batfish.datamodel.table;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Multiset;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.Nonnull;
import org.batfish.common.BatfishException;
import org.batfish.common.util.BatfishObjectMapper;
import org.batfish.datamodel.answers.AnswerElement;
import org.batfish.datamodel.answers.AnswerSummary;
import org.batfish.datamodel.questions.Assertion;
import org.batfish.datamodel.questions.Exclusion;
import org.batfish.datamodel.questions.Question;

/** A base class for tabular answers */
public class TableAnswerElement extends AnswerElement {

  protected static final String PROP_EXCLUDED_ROWS = "excludedRows";

  protected static final String PROP_METADATA = "metadata";

  protected static final String PROP_ROWS = "rows";

  List<ExcludedRows> _excludedRows;

  Rows _rows;

  TableMetadata _tableMetadata;

  @JsonCreator
  public TableAnswerElement(@Nonnull @JsonProperty(PROP_METADATA) TableMetadata tableMetadata) {
    _tableMetadata = tableMetadata;
    _rows = new Rows();
    _excludedRows = new LinkedList<>();
  }

  /**
   * Adds a new row to data rows
   *
   * @param row The row to add
   */
  public void addRow(Row row) {
    _rows.add(row);
  }

  /**
   * Adds a new row to excluded data rows
   *
   * @param row The row to add
   */
  public void addExcludedRow(Row row, String exclusionName) {
    for (ExcludedRows exRows : _excludedRows) {
      if (exRows.getExclusionName().equals(exclusionName)) {
        exRows.addRow(row);
        return;
      }
    }
    // no matching exclusionName found; create a new one
    ExcludedRows rows = new ExcludedRows(exclusionName, new Rows());
    rows.addRow(row);
    _excludedRows.add(rows);
  }

  public AnswerSummary computeSummary(Assertion assertion, String notes) {
    int numPassed = 0;
    int numFailed = 0;
    if (assertion != null) {
      if (evaluateAssertion(assertion)) {
        numPassed = 1;
      } else {
        numFailed = 1;
      }
    }
    return new AnswerSummary(notes, numFailed, numPassed, _rows.size());
  }

  public AnswerSummary computeSummary(Assertion assertion) {
    int numPassed = 0;
    int numFailed = 0;
    if (assertion != null) {
      if (evaluateAssertion(assertion)) {
        numPassed = 1;
      } else {
        numFailed = 1;
      }
    }
    String notes = "Found " + _rows.size() + " results";
    return new AnswerSummary(notes, numFailed, numPassed, _rows.size());
  }

  /**
   * Evaluates the assertion over the rows
   *
   * @return The results of the evaluation
   */
  public boolean evaluateAssertion(Assertion assertion) {
    if (assertion == null) {
      throw new IllegalArgumentException("Provided assertion object cannot be null");
    }
    switch (assertion.getType()) {
      case countequals:
        return _rows.size() == assertion.getExpect().asInt();
      case countlessthan:
        return _rows.size() < assertion.getExpect().asInt();
      case countmorethan:
        return _rows.size() > assertion.getExpect().asInt();
      case equals:
        Rows expectedEntries;
        try {
          expectedEntries =
              BatfishObjectMapper.mapper().readValue(assertion.getExpect().toString(), Rows.class);
        } catch (IOException e) {
          throw new BatfishException("Could not recover Rows object from expect", e);
        }
        return _rows.equals(expectedEntries);
      default:
        throw new BatfishException("Unhandled assertion type: " + assertion.getType());
    }
  }

  @JsonProperty(PROP_EXCLUDED_ROWS)
  public List<ExcludedRows> getExcludedRows() {
    return _excludedRows;
  }

  @JsonProperty(PROP_METADATA)
  public TableMetadata getMetadata() {
    return _tableMetadata;
  }

  @JsonProperty(PROP_ROWS)
  public Rows getRows() {
    return _rows;
  }

  /**
   * Given an initial set of rows produced by an {@link org.batfish.common.Answerer}, this procedure
   * processes exclusions, assertions, and summary to update this object.
   *
   * @param question The question that generated the initial set of rows
   * @param initialSet The initial set of rows
   */
  public void postProcessAnswer(Question question, Multiset<Row> initialSet) {
    initialSet.forEach(
        initialRow -> {
          // exclude or not?
          Exclusion exclusion = Exclusion.covered(initialRow, question.getExclusions());
          if (exclusion != null) {
            addExcludedRow(initialRow, exclusion.getName());
          } else {
            addRow(initialRow);
          }
        });

    setSummary(computeSummary(question.getAssertion()));
  }

  @JsonProperty(PROP_EXCLUDED_ROWS)
  private void setExcludedRows(List<ExcludedRows> excludedRows) {
    _excludedRows = excludedRows == null ? new LinkedList<>() : excludedRows;
  }

  @JsonProperty(PROP_ROWS)
  private void setRows(Rows rows) {
    _rows = rows == null ? new Rows() : rows;
  }
}

@@ -652,24 +652,13 @@
 /** 
-A base class for
+Holds
  tab
@@ -669,16 +669,17 @@
  answers
+.
  */%0Apubl
@@ -681,16 +681,22 @@
 %0Apublic 
+final 
 class Ta
@@ -737,31 +737,29 @@
 ment %7B%0A%0A  pr
-otec
+iva
 te
-d
  static fina
@@ -801,39 +801,37 @@
 udedRows%22;%0A%0A  pr
-otec
+iva
 te
-d
  static final St
@@ -872,15 +872,13 @@
   pr
-otec
+iva
 te
-d
  sta
@@ -917,16 +917,24 @@
 ws%22;%0A%0A  
+private 
 List%3CExc
@@ -962,16 +962,24 @@
 ows;%0A%0A  
+private 
 Rows _ro
@@ -985,16 +985,24 @@
 ows;%0A%0A  
+private 
 TableMet

make final (#1516)