package edu.stanford.nlp.sempre;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import edu.stanford.nlp.sempre.*;
import fig.basic.LispTree;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Represents the full context value (utterance, formula, value, intent, keywords).
 * This is part of an Example and used by error retrieval functions.
 *
 * @author Percy Liang
 * @maintainer emlozin
 */
public class ContextValue extends Value {
  // A single exchange between the user and the system
  // Note: we are not storing the entire derivation right now.
  public static class Exchange {
    public final String utterance;
    public final Formula formula;
    public final Value value;
    public final GeneralInfo genInfo;
    
    public Exchange(String utterance, Formula formula, Value value, GeneralInfo genInfo) {
      this.utterance = utterance;
      this.formula = formula;
      this.value = value;
      this.genInfo = genInfo;
    }

    public Exchange(String utterance, Formula formula, Value value) {
      this.utterance = utterance;
      this.formula = formula;
      this.value = value;
      this.genInfo = null;
    }

    public Exchange(LispTree tree) {
      this.utterance = tree.child(1).value;
      this.formula = Formulas.fromLispTree(tree.child(2));
      this.value = Values.fromLispTree(tree.child(3));
      if (tree.children.size()>4) {
        this.genInfo = new GeneralInfo(tree.child(4).value);
      }
      else{
        this.genInfo = null;
      }
    }
    public LispTree toLispTree() {
      LispTree tree = LispTree.proto.newList();
      tree.addChild("exchange");
      tree.addChild(utterance);
      if (formula!=null)
        tree.addChild(formula.toLispTree());
      if (value!=null)
        tree.addChild(value.toLispTree());
      if (genInfo!=null)
        tree.addChild(genInfo.toLispTree());
      return tree;
    }
    @Override public String toString() { return toLispTree().toString(); }
  }

  public final String user;
  public final DateValue date;
  public final List<Exchange> exchanges;  // List of recent exchanges with the user
  public final KnowledgeGraph graph;  // Mini-knowledge graph that captures the context

  public ContextValue withDate(DateValue newDate) {
    return new ContextValue(user, newDate, exchanges, graph);
  }

  public ContextValue withNewExchange(List<Exchange> newExchanges) {
    return new ContextValue(user, date, newExchanges, graph);
  }

  public ContextValue withGraph(KnowledgeGraph newGraph) {
    return new ContextValue(user, date, exchanges, newGraph);
  }

  public ContextValue(String user, DateValue date, List<Exchange> exchanges, KnowledgeGraph graph) {
    this.user = user;
    this.date = date;
    this.exchanges = exchanges;
    this.graph = graph;

  }

  public ContextValue(String user, DateValue date, List<Exchange> exchanges) {
    this(user, date, exchanges, null);
  }

  public ContextValue(KnowledgeGraph graph) {
    this(null, null, new ArrayList(), graph);
  }

  // Example:
  //   (context (user pliang)
  //            (date 2014 4 20)
  //            (exchange "when was chopin born" (!fb:people.person.date_of_birth fb:en.frederic_chopin) (date 1810 2 22))
  //            (graph NaiveKnowledgeGraph ((string Obama) (string "born in") (string Hawaii)) ...))
  public ContextValue(LispTree tree) {
    String user = null;
    DateValue date = null;
    KnowledgeGraph graph = null;
    exchanges = new ArrayList<Exchange>();
    for (int i = 1; i < tree.children.size(); i++) {
      String key = tree.child(i).child(0).value;
      if (key.equals("user")) {
        user = tree.child(i).child(1).value;
      } else if (key.equals("date")) {
        date = new DateValue(tree.child(i));
      } else if (key.equals("graph")) {
        graph = KnowledgeGraph.fromLispTree(tree.child(i));
      } else if (key.equals("exchange")) {
        exchanges.add(new Exchange(tree.child(i)));
      } else {
        throw new RuntimeException("Invalid: " + tree.child(i));
      }
    }
    this.user = user;
    this.date = date;
    this.graph = graph;
  }

  public LispTree toLispTree() {
    LispTree tree = LispTree.proto.newList();
    tree.addChild("context");
    if (user != null)
      tree.addChild(LispTree.proto.newList("user", user));
    if (date != null)
      tree.addChild(date.toLispTree());
    // When logging examples, logging the entire graph takes too much screen space.
    // I don't think that we ever deserialize a graph from a serialized context,
    // so this should be fine.
    if (graph != null)
      tree.addChild(graph.toShortLispTree());
    for (Exchange e : exchanges)
      tree.addChild(LispTree.proto.newList("exchange", e.toLispTree()));
    return tree;
  }

  @Override public int hashCode() {
    int hash = 0x7ed55d16;
    hash = hash * 0xd3a2646c + user.hashCode();
    hash = hash * 0xd3a2646c + date.hashCode();
    hash = hash * 0xd3a2646c + exchanges.hashCode();
    hash = hash * 0xd3a2646c + graph.hashCode();
    return hash;
  }

  @Override public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    ContextValue that = (ContextValue) o;
    if (!this.user.equals(that.user)) return false;
    if (!this.date.equals(that.date)) return false;
    if (!this.exchanges.equals(that.exchanges)) return false;
    if (!this.graph.equals(that.graph)) return false;
    return true;
  }

  @JsonValue
  public String toString() { return toLispTree().toString(); }

  @JsonCreator
  public static ContextValue fromString(String str) {
    return new ContextValue(LispTree.proto.parseFromString(str));
  }
}
