package caevo.util;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node; // yes?
import org.w3c.dom.NodeList; // yes?
import org.w3c.dom.Text; // yes?
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import caevo.BethardAnnotation;
import caevo.SieveDocument;
import caevo.SieveDocuments;
import caevo.SieveSentence;
import caevo.TextEvent;
import caevo.Timex;
import caevo.tlink.EventEventLink;
import caevo.tlink.EventTimeLink;
import caevo.tlink.TLink;
import edu.stanford.nlp.ling.CoreLabel;
import edu.stanford.nlp.trees.TreeGraphNode;
import edu.stanford.nlp.trees.TypedDependency;

public class TimebankUtil {

  /**
   * Remove any tlinks from the list whose confidence is below the given
   * minimum.
   * 
   * @param links
   *          A list of TLinks.
   * @param minProb
   *          The probability cutoff.
   */
  public static void trimLowProbability(List<TLink> links, double minProb) {
    List<TLink> removal = new ArrayList<TLink>();
    for (TLink link : links)
      if (link.getRelationConfidence() < minProb)
        removal.add(link);

    for (TLink link : removal)
      links.remove(link);
  }

  public static boolean isDayOfWeek(String str) {
    str = str.toLowerCase();
    if (str.equals("sunday") || str.equals("monday") || str.equals("tuesday")
        || str.equals("wednesday") || str.equals("thursday")
        || str.equals("friday") || str.equals("saturday"))
      return true;
    else
      return false;
  }

  /**
   * @return True if event1 is before event2 in the text.
   */
  public static boolean isBeforeInText(TextEvent event1, TextEvent event2) {
    // Same sentence
    if (event1.getSid() == event2.getSid()) {
      if (event1.getIndex() < event2.getIndex())
        return true;
      else
        return false;
    }
    // Different sentence
    else {
      if (event1.getSid() < event2.getSid())
        return true;
      else
        return false;
    }
  }

  public static boolean isBeforeInText(TextEvent event1, Timex timex) {
    // Same sentence
    if (event1.getSid() == timex.getSid()) {
      if (event1.getIndex() < timex.getTokenOffset())
        return true;
      else
        return false;
    }
    // Different sentence
    else {
      if (event1.getSid() < timex.getSid())
        return true;
      else
        return false;
    }
  }

  public static boolean isIntraSentence(TextEvent e1, TextEvent e2) {
    if (e1 == null || e2 == null)
      return false;

    return e1.getSid() == e2.getSid();
  }

  public static boolean isNeighborSentence(TextEvent e1, TextEvent e2) {
    if (e1 == null || e2 == null)
      return false;

    return (Math.abs(e1.getSid() - e2.getSid()) == 1);
  }

  public static boolean isEventDCTLink(TLink link, List<Timex> dcts) {
    boolean isdctlink = false;

    if (link instanceof EventTimeLink) {
      String tid = link.getId1();
      if (tid.startsWith("e"))
        tid = link.getId2();

      for (Timex dct : dcts)
        if (dct.getTid().equalsIgnoreCase(tid))
          isdctlink = true;
    }

    return isdctlink;
  }

  /**
   * Read an XML Document from a path
   * 
   * @return Document representation of the xml
   */
  public static Document getXMLDoc(String filename) {
    try {
      DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
          .newInstance();
      DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
      Document doc = docBuilder.parse(new File(filename));

      // normalize text representation
      doc.getDocumentElement().normalize();
      // System.out.println("Root element: " +
      // doc.getDocumentElement().getNodeName());

      return doc;

    } catch (SAXParseException err) {
      System.out.println("** Parsing error" + ", line " + err.getLineNumber()
          + ", uri " + err.getSystemId());
      System.out.println(" " + err.getMessage());

    } catch (SAXException e) {
      Exception x = e.getException();
      ((x == null) ? e : x).printStackTrace();

    } catch (Throwable t) {
      t.printStackTrace();
    }
    return null;
  }

  /**
   * Add Bethard's tlink annotations into the full TimeBank corpus. The given
   * infoFile receives the new tlinks destructively.
   * 
   * @param path
   *          The path to Bethard's single file containing all new tlinks.
   * @param infoDocs
   *          The current TimeBank info file.
   */
  public static void mergeBethard(String path, SieveDocuments infoDocs) {
    BethardAnnotation bethard = new BethardAnnotation(path);
    for (SieveDocument doc : infoDocs.getDocuments()) {
      List<TLink> newlinks = new ArrayList<TLink>();
      List<TLink> bethardLinks = bethard.getTLinks(doc.getDocname());
      List<TLink> tbLinks = doc.getTlinks();
      // Create a reverse EIID to EID lookup table.
      Map<String, String> eiidToID = eiidToID(doc.getEvents());

      // Bethard only annotated the WSJ docs, some TimeBank docs will be null.
      if (bethardLinks != null) {
        for (TLink bethardLink : bethardLinks) {
          // Convert from the instance ID to the event ID.
          String event1 = eiidToID.get(bethardLink.getId1());
          String event2 = eiidToID.get(bethardLink.getId2());
          if (event1 == null)
            System.out.println(
                "ERROR: Bethard event1 id unknown " + bethardLink.getId1());
          else if (event2 == null)
            System.out.println(
                "ERROR: Bethard event2 id unknown " + bethardLink.getId2());

          else {
            // Create a new tlink with the event IDs
            TLink newlink = new EventEventLink(bethardLink.getId1(),
                bethardLink.getId2(), bethardLink.getRelation());
            newlink.setOrigin("bethard");

            // Now make sure it is a new tlink.
            boolean duplicate = false;
            for (TLink current : tbLinks) {
              if (current.getRelation() != TLink.Type.NONE) {
                // Conflicts should be skipped.
                if (newlink.conflictsWith(current)) {
                  System.out.println("CONFLICTS Bethard: " + newlink
                      + " with previous " + current);
                  duplicate = true;
                }
                // Exact duplicates should be skipped.
                if (newlink.compareToTLink(current)) {
                  System.out.println("Duplicate Bethard: " + newlink
                      + " with timebank's " + current);
                  duplicate = true;
                }
              }
            }

            // Now add the link.
            if (!duplicate) {
              newlinks.add(newlink);
              System.out.println("Adding Bethard: " + newlink);
            }
          }
        }

        doc.addTlinks(newlinks);
      }
    }
  }

  /**
   * Create a reverse EIID to EID lookup table.
   */
  public static Map<String, String> eiidToID(Collection<TextEvent> events) {
    Map<String, String> eiidToID = new HashMap<String, String>();
    for (TextEvent event : events) {
      for (String eiid : event.getAllEiids())
        eiidToID.put(eiid, event.getId());
    }
    return eiidToID;
  }

  /**
   * @returns The text of all text node leafs appended together
   */
  public static String stringFromElement(Node node) {
    if (node instanceof Text) {
      return ((Text) node).getData().trim();
    } else {
      String str = "";
      NodeList list = node.getChildNodes();
      for (int i = 0; i < list.getLength(); i++) {
        if (i == 0)
          str = stringFromElement(list.item(i));
        else
          str += " " + stringFromElement(list.item(i));
      }
      return str;
    }
  }

  /**
   * There are syntactic contexts that aren't considered "future tense" but
   * where intuitively, the event in question is located in the future. For
   * example, in "X would Y", where X is the agent of the event Y, Y most likely
   * has not yet happened (how to interpret the likelihood of Y ever hapening at
   * all is a different concern - the point is that if it does happen it will be
   * in the future).
   * 
   * 
   * @returns the tense of event, where tense is less strictly defined.
   */

  // Basically if a word governs a modal word then it is considered as being in
  // the future tense.
  public static TextEvent.Tense pseudoTense(SieveSentence sent,
      List<TypedDependency> tds, TextEvent event) {
    int eventIndex = event.getIndex();
    for (TypedDependency td : tds) {
      TreeGraphNode dep = td.dep();
      if (eventIndex == td.gov().index()
          && isModalWord(dep.toString("value").toLowerCase())
          && td.reln().toString().equals("aux")) {
        return TextEvent.Tense.FUTURE;
      }

    }
    return event.getTense();
  }

  // This method was meant to generalize the above, but the intuition is not
  // quite right.
  // a modal verb is governed by the verb it modifies, but a verb that governs
  // that verb is not int he modal context.
  // Need to hash this out still.
  public static TextEvent.Tense pseudoTense2(SieveSentence sent,
      List<TypedDependency> tds, TextEvent event) {
    int eventIndex = event.getIndex();
    List<CoreLabel> tokens = sent.tokens();
    for (int t = 0; t < tokens.size(); t++) {
      String tokenText = tokens.get(t).originalText();
      if (isModalWord(tokenText)) {
        String dp = TreeOperator.directPath(eventIndex, t + 1, tds); // add 1
                                                                     // because
                                                                     // of
                                                                     // stanford
                                                                     // indexing
                                                                     // starting
                                                                     // at 1
        if (dp != null) {
          System.out.println("dp: " + dp);
          System.out.println(sent.sentence());
          System.out.println(event.getString());
          return TextEvent.Tense.FUTURE;
        }
      }
    }
    return event.getTense();
  }

  public static boolean isModalWord(String word) {
    if (word.toLowerCase().equals("would") || word.toLowerCase().equals("could")
        || word.toLowerCase().equals("might")
        || word.toLowerCase().equals("may")
        || word.toLowerCase().equals("should")
        || word.toLowerCase().equals("'d")
        || word.toLowerCase().equals("will")) {
      return true;
    } else
      return false;
  }

  /**
   * return true if two timexes have the same value, or if both arguments are
   * null
   * 
   * @param t1
   * @param t2
   * @return
   */
  public static boolean compareTimexesByValue(Timex t1, Timex t2) {
    if (t1 == null && t2 == null || t1.getValue().equals(t2.getValue())) {
      return true;
    } else
      return false;
  }
}
