package caevo.sieves;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import caevo.SieveDocument;
import caevo.SieveDocuments;
import caevo.SieveSentence;
import caevo.TemporalContext;
import caevo.TextEvent;
import caevo.tlink.EventEventLink;
import caevo.tlink.TLink;
import caevo.util.CaevoProperties;
import caevo.util.TimebankUtil;
import caevo.util.TreeOperator;
import edu.stanford.nlp.trees.Tree;

/**
 * SUMMARY: This sieve labels pairs of verb events based on a mapping derived
 * from Reichanbach's theory of tense/aspect. The mapping is adapted from
 * Derczynski and Gaizauskas (D&G 2013).
 *
 * PARAMETERS: sameTense - boolean; enforces D&G's "S/R" constraint;
 * essentially, only pairs of verbs with the same tense will be considered.
 * sentWindow - int; only allow events that are within this many sentences of
 * one another simplifyPast, simplifyPresent, simplifyAspect - boolean; if
 * false, don't apply the relevant part of the normalization procedure as
 * suggested in D&G13 (see function simplifyTense and simplifyAspect)
 *
 * SameSentence/SameTense p=0.65 17 of 26 SameOrAdjSent/SameTense p=0.58 47 of
 * 81 SameSentence/AnyTense p=0.57 47 of 82 SameOrAdjSent/AnyTense p=0.53 142 of
 * 270
 *
 * --> "Simplify Past" = false SameSentence/SameTense p=0.65 17 of 26
 * SameOrAdjSent/SameTense p=0.58 47 of 81 SameSentence/AnyTense p=0.62 46 of 74
 * SameOrAdjSent/AnyTense p=0.55 139 of 252
 *
 * --> "Simplify Present" = false SameSentence/SameTense p=0.65 17 of 26
 * SameOrAdjSent/SameTense p=0.58 47 of 81 SameSentence/AnyTense p=0.63 45 of 71
 * SameOrAdjSent/AnyTense p=0.56 137 of 243
 *
 * --> "Simplify Aspect" = false SameSentence/SameTense p=0.65 17 of 26
 * SameOrAdjSent/SameTense p=0.58 47 of 81 SameSentence/AnyTense p=0.57 47 of 82
 * SameOrAdjSent/AnyTense p=0.52 139 of 265
 *
 * --> "Simplify Present" = false; "Simplify Past" = false
 * SameSentence/SameTense p=0.65 17 of 26 SameSentence/AnyTense p=0.70 44 of 63
 * SameOrAdjSent/AnyTense p=0.60 134 of 223
 *
 *
 * After adding the pseudoTense function that puts words that govern a modal
 * word in the future, new results:
 *
 * train: ReichenbachDG13 p=0.59 192 of 326 Non-VAGUE: p=0.90 192 of 213 dev:
 * ReichenbachDG13 p=0.63 20 of 32 Non-VAGUE: p=0.77 20 of 26
 *
 *
 * DETAILS:
 *
 * D&G2013 report the percentage of TLINKs in timebank for which the following
 * criteria hold: 1) The TLINK links two events that are Verbs 2) The
 * disjunction of allen relations (each of which is equivalent to a Freksa
 * (1992) semi-interval) associated with a given pair of the form <
 * <event1.tense, event1.aspect>, <event2.tense, event2.aspect> > contains the
 * interval relation annotated in the TLINK's relType field. 3) The two events
 * are in the same "temporal context"
 *
 * A temporal context is a list of criteria in terms of the properties of two or
 * more events based on which it is inferred that the events share the same
 * reference time.
 *
 * D&G2013 report results for 5 methods of determining temporal context based on
 * two parameters:
 *
 * Method0: baseline - any two events are assumed to be in the same temporal
 * context
 *
 * Other methods based on these parameters Sentence window: value 1: Same
 * sentence - the two events in the same sentence share their temporal context
 * value 2:Same/adjacent sentence - two events within one sentence of one
 * another share their Same tense - the two events have the same tense (the idea
 * is that the have the same relative ordering of their R(eference) and S(peech)
 * times. value 1: true value 2: false
 * 
 * Results can be found in D&G table 6 The percentage of TLINKs for each setting
 * for which the mapping yields a set of possible TLINK relTypes (interval
 * relations) that contains the relType in the gold standard is reported (1.
 * including cases where the mapping is trivial; 2. excluding such cases; a case
 * is trivial if the mapping yields a disjunction of all possible relTypes)
 * 
 * In this implementation, the four non-baseline methods can be used for
 * determining whether events share their temporal context with the sentWindow
 * and sameTense parameters.
 * 
 * @author cassidy
 */
public class ReichenbachDG13_update implements Sieve {
  public boolean debug = false;
  private int sentWindow = 0;
  private boolean sameTense = false;
  private boolean simplifyPast = true;
  private boolean simplifyPresent = true;
  private boolean simplifyAspect = true;
  private boolean useExtendedTense = true;
  private boolean useExtendedTenseAcrossSentence = true;

  public List<TLink> annotate(SieveDocument doc, List<TLink> currentTLinks) {
    // Get property values from the config file
    try {
      sentWindow = CaevoProperties.getInt("ReichenbachDG13.sentWindow", 0);
      sameTense = CaevoProperties.getBoolean("ReichenbachDG13.sameTense",
          false);
      simplifyPast = CaevoProperties.getBoolean("ReichenbachDG13.simplifyPast",
          true);
      simplifyPresent = CaevoProperties
          .getBoolean("ReichenbachDG13.simplifyPresent", true);
      simplifyAspect = CaevoProperties
          .getBoolean("ReichenbachDG13.simplifyAspect", true);
      useExtendedTense = CaevoProperties
          .getBoolean("ReichenbachDG13.useExtendedTense", true);
      useExtendedTenseAcrossSentence = CaevoProperties
          .getBoolean("ReichenbachDG13.useExtendedTenseAcrossSentence", true);
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    // proposed will hold all TLinks proposed by the sieve
    List<TLink> proposed = new ArrayList<TLink>();

    // get all events by sentence
    List<List<TextEvent>> allEvents = doc.getEventsBySentence();

    // we need all trees in order to get pos tags
    List<Tree> trees = doc.getAllParseTrees();

    // we need the sentences and td's to pass to the "pseudoTense" util function
    List<SieveSentence> sents = doc.getSentences();

    // TO TEST TEMPORAL CONTEXT CODE
    TemporalContext tc = new TemporalContext(doc);
    tc.addContextSimpleDep();
    // END TEST TEMPORAL CONTEXT CODE

    // contextMap maps a context ID (which is a timex value) to a list of
    // TextEvents that govern a
    // timex with that value as their value. If an event doesn't govern any
    // timex,
    // it belongs to the set associated with the DCT. Note that the key (or
    // contextId) associated
    // with the DCT is the document creation date; that is, if the DCT is
    // specified beyond the
    // day granularity, the value is truncated to be of the form YYYY-MM-DD.
    // Note that this means that events that govern, for example, "today", will
    // be in the same
    // set as those that do not govern any time expressiopn.
    HashMap<String, List<TextEvent>> contextMap = tc.getContextMap();
    for (String key : contextMap.keySet()) {
      // there are no cases where two events govern a timex with the same dct
      // and they are relatable according
      // to the reichenbach rules below, and that dct isn't the same as the
      // dct_day.
      // if (key.equals(tc.getValueFromTimex(doc.getDocstamp().get(0))))
      // continue;

      List<TextEvent> eventsInContext = contextMap.get(key);
      int numEvents = eventsInContext.size();
      for (int i = 0; i < numEvents; i++) {
        for (int j = i + 1; j < numEvents; j++) {
          TextEvent e1 = eventsInContext.get(i);
          TextEvent e2 = eventsInContext.get(j);
          TLink.Type label = this.getLabel(e1, e2, sents.get(e1.getSid()),
              sents.get(e2.getSid()), trees);
          if (label != null)
            addPair(e1, e2, label, proposed, doc);
        }
      }
    }

    return proposed;
  }

  public void train(SieveDocuments trainingInfo) {
    // no training
  }

  // given a tree, return the pos tag for the element with TextEvent index
  // "index"
  private String posTagFromTree(Tree tree, int index) {
    String pos = TreeOperator.indexToPOSTag(tree, index);
    return pos;
  }

  // add (e1, e2, label) to proposed list of TLINKs
  private void addPair(TextEvent e1, TextEvent e2, TLink.Type label,
      List<TLink> proposed, SieveDocument doc) {
    EventEventLink tlink = new EventEventLink(e1.getEiid(), e2.getEiid(),
        label);
    tlink.setDocument(doc);
    proposed.add(tlink);
  }

  // get the label indicated for (e1, e2) by the D&G mapping.
  // this method also applies filters that eliminate certain events and event
  // pairs
  // from consideration.
  private TLink.Type getLabel(TextEvent e1, TextEvent e2, SieveSentence sent1,
      SieveSentence sent2, List<Tree> trees) {
    // TEMPORARY FOR DEBUGGING - SEE IF I_STATEs and I_ACTIONs BEHAVE
    // DIFFERENTLY!
    /*
     * boolean e1IsISTATE = e1.getTheClass() == TextEvent.Class.I_STATE; boolean
     * e2IsISTATE = e2.getTheClass() == TextEvent.Class.I_STATE; boolean
     * e1IsIACTION = e1.getTheClass() == TextEvent.Class.I_ACTION; boolean
     * e2IsIACTION = e2.getTheClass() == TextEvent.Class.I_ACTION;
     * 
     * if (e1IsISTATE || e1IsIACTION || e2IsISTATE || e2IsIACTION) { return
     * null; }
     */

    // END TEMPORARY CODE HERE
    // get pos tags for e1 and e2
    String e1Pos = posTagFromTree(trees.get(e1.getSid()), e1.getIndex());
    String e2Pos = posTagFromTree(trees.get(e2.getSid()), e2.getIndex());
    // if e1 and e2 aren't both verbs, then label is null
    if (!e1Pos.startsWith("VB") || !e2Pos.startsWith("VB")) {
      return null;
    }
    // if sameTense property is true then e1/e2 that don't share the same tense
    // automatically are labeled null
    if (sameTense == true && e1.getTense() != e2.getTense()) {
      return null;
    }
    // if we've made it this far, apply the mapping to (e1, e2) using
    return taToLabel(e1, e2, sent1, sent2);
  }

  // check if events have the same tense. this is used for the setting in which
  // two events (verbs) are assumed
  // not to share their reference time (ie be a part of the same "temporal
  // context") only if they have
  // the same tense
  public boolean eventsShareTense(TextEvent e1, TextEvent e2) {
    return e1.getTense() == e2.getTense();
  }

  // apply mapping adapted from D&G2013
  public TLink.Type taToLabel(TextEvent e1, TextEvent e2, SieveSentence sent1,
      SieveSentence sent2) {
    // First convert e1(2)Tense(Aspect) to their simplified forms
    // as per D&G's mapping (via simplifyTense and simplifyAspect)
    TextEvent.Tense e1Tense = null;
    TextEvent.Tense e2Tense = null;
    if (useExtendedTense == true) {
      if (!useExtendedTenseAcrossSentence && !sent1.equals(sent2)) {
        e1Tense = e1.getTense();
        e2Tense = e2.getTense();
      } else {
        e1Tense = TimebankUtil.pseudoTense(sent1, sent1.getDeps(), e1);
        e2Tense = TimebankUtil.pseudoTense(sent2, sent2.getDeps(), e2);

      }
    } else {
      e1Tense = e1.getTense();
      e2Tense = e2.getTense();
    }

    TextEvent.Tense e1SimpTense = simplifyTense(e1Tense);
    TextEvent.Aspect e1SimpAspect = simplifyAspect(e1.getAspect());
    TextEvent.Tense e2SimpTense = simplifyTense(e2Tense);
    TextEvent.Aspect e2SimpAspect = simplifyAspect(e2.getAspect());

    // define the boolean variables that we need to check to apply mapping
    // each one specifies whether e1 or e2 has a given tense or aspect (after
    // simplification)
    boolean e1Past = (e1SimpTense == TextEvent.Tense.PAST);
    boolean e2Past = (e2SimpTense == TextEvent.Tense.PAST);
    boolean e1Pres = (e1SimpTense == TextEvent.Tense.PRESENT);
    boolean e2Pres = (e2SimpTense == TextEvent.Tense.PRESENT);

    boolean e1Future = (e1SimpTense == TextEvent.Tense.FUTURE);
    boolean e2Future = (e2SimpTense == TextEvent.Tense.FUTURE);

    boolean e1Perf = (e1SimpAspect == TextEvent.Aspect.PERFECTIVE);
    boolean e1None = (e1SimpAspect == TextEvent.Aspect.NONE);
    boolean e2Perf = (e2SimpAspect == TextEvent.Aspect.PERFECTIVE);
    boolean e2None = (e2SimpAspect == TextEvent.Aspect.NONE);

    // this is the mapping, implmented as a long if block
    // see reichenbach_relationmapping.xls
    // note that we only consider cases where the result of applying
    // the mapping is an interval disjunction that translates to only
    // one relation according to our task spec.
    // see the table in the spreadsheet FreksaAllenUsInfo and
    // mapping_FreksaAllenUs

    if (e1Past && e1None && e2Past && e2Perf)
      return TLink.Type.AFTER;
    else if (e1Past && e1None && e2Future && e2None)
      return TLink.Type.BEFORE;
    else if (e1Past && e1None && e2Future && e2Perf)
      return TLink.Type.BEFORE;
    //
    else if (e1Past && e1Perf && e2Past && e2None)
      return TLink.Type.BEFORE;
    else if (e1Past && e1Perf && e2Pres && e2None)
      return TLink.Type.BEFORE;
    else if (e1Past && e1Perf && e2Pres && e2Perf)
      return TLink.Type.BEFORE;
    else if (e1Past && e1Perf && e2Future && e2None)
      return TLink.Type.BEFORE;
    else if (e1Past && e1Perf && e2Future && e2Perf)
      return TLink.Type.BEFORE;
    //
    else if (e1Pres && e1None && e2Past && e2Perf)
      return TLink.Type.AFTER;
    else if (e1Pres && e1None && e2Future && e2None)
      return TLink.Type.BEFORE;
    //
    else if (e1Pres && e1Perf && e2Past && e2Perf)
      return TLink.Type.AFTER;
    else if (e1Pres && e1Perf && e2Future && e2None)
      return TLink.Type.BEFORE;
    else if (e1Pres && e1Perf && e2Future && e2Perf)
      return TLink.Type.BEFORE;
    //
    else if (e1Future && e1None && e2Past && e2None)
      return TLink.Type.AFTER;
    else if (e1Future && e1None && e2Past && e2Perf)
      return TLink.Type.AFTER;
    else if (e1Future && e1None && e2Pres && e2None)
      return TLink.Type.AFTER;
    else if (e1Future && e1None && e2Pres && e2Perf)
      return TLink.Type.AFTER;
    //
    else if (e1Future && e1Perf && e2Past && e2None)
      return TLink.Type.AFTER;
    else if (e1Future && e1Perf && e2Past && e2Perf)
      return TLink.Type.AFTER;
    else if (e1Future && e1Perf && e2Pres && e2Perf)
      return TLink.Type.AFTER;
    else
      return null;

  }

  // Apply D&G's mapping to consolidate tense and aspect labels
  private TextEvent.Tense simplifyTense(TextEvent.Tense tense) {
    // simplify past
    if (tense == TextEvent.Tense.PAST
        || (tense == TextEvent.Tense.PASTPART && simplifyPast)) {
      return TextEvent.Tense.PAST;
    }
    // simplify present
    else if (tense == TextEvent.Tense.PRESENT
        || (tense == TextEvent.Tense.PRESPART && simplifyPresent)) {
      return TextEvent.Tense.PRESENT;
    }
    // future is trivially simplified
    else if (tense == TextEvent.Tense.FUTURE) {
      return tense;
    }
    // no other tenses are considered.
    else
      return null;
  }

  private TextEvent.Aspect simplifyAspect(TextEvent.Aspect aspect) {
    // Return none or perfective based on mapping in D&G13 (else null)
    // Note that although their mapping includes progressive, we don't use
    // any tense/aspect profiles that include progressive because no
    // tense/aspect profile that includes progressive aspect occurs in
    // any tense/aspect profile pair mapped to a single relation (in our
    // relation scheme)
    if ((simplifyAspect
        && aspect.equals(TextEvent.Aspect.PERFECTIVE_PROGRESSIVE))
        || aspect.equals(TextEvent.Aspect.PERFECTIVE)) {
      return TextEvent.Aspect.PERFECTIVE;
    } else if (aspect.equals(TextEvent.Aspect.NONE)) {
      return aspect;
    } else
      return null;
  }
}