package edu.berkeley.nlp.io;

import edu.berkeley.nlp.util.CollectionUtils;
import edu.berkeley.nlp.util.Interner;
import edu.berkeley.nlp.util.MapFactory;

import java.util.*;

/**
 * An <code>AbstractMapLabel</code> implementation which defines equality
 * as equality of the internal map.  Thus it is NOT SAFE for use in cyclic
 * data structures, in which the map may point to something which in turn
 * points to this map again, as calling equals() will lead to an infinite
 * recursion.
 * <p/>
 * As standardly used, both a key and the corresponding value of a
 * FeatureLabel are of type String.
 *
 * @author Jenny Finkel
 * @author Teg Grenager
 */

public class FeatureLabel extends AbstractMapLabel {

  public static String TOSTRING_FORMAT = null;
  public Collection features = null;

  // CONSTRUCTORS

  public FeatureLabel() {
    super();
  }

  public FeatureLabel(MapFactory mapFactory) {
    super(mapFactory);
  }

  public Set keySet() {
    return map.keySet();
  }
  
  /**
   *
   * @param keys
   * @param values
   */
  public FeatureLabel(String[] keys, String[] values) {
    for (int i = 0; i < keys.length && i < values.length; i++) {
      if (keys[i] == null) {
        continue;
      }
      put(keys[i], values[i]);
    }
  }

  /**
   * Uses String representation of a Map to populate Map with String keys and String values.
   */
  public static FeatureLabel valueOf(String s, MapFactory mf) throws Exception {
    return new FeatureLabel(CollectionUtils.getMapFromString(s, Class.forName("java.lang.String"),
                                                             Class.forName("java.lang.String"), mf));
  }

  /**
   * Copy constructor.  Makes a new FeatureLabel using the MapFactory and 
   * Map contents of the other AbstractMapLabel.
   */
  public FeatureLabel(AbstractMapLabel other) {
    super(other.mapFactory);
    map = mapFactory.buildMap();
    map.putAll(other.map);
  }

  /**
   * Copy constructor.  Makes a new FeatureLabel using the MapFactory and
   * Map contents of the other AbstractMapLabel.
   */
  public FeatureLabel(Map map) {
    super(); // default map factory
    this.map = map;
  }

  public static String[] mapStringToArray(String map) {
    String[] m = map.split("[,;]");
    int maxIndex = 0;
    String[] keys = new String[m.length];
    int[] indices = new int[m.length];
    for (int i = 0; i < m.length; i++) {
      int index = m[i].lastIndexOf("=");
      keys[i] = m[i].substring(0, index);
      indices[i] = Integer.parseInt(m[i].substring(index + 1));
      if (indices[i] > maxIndex) {
        maxIndex = indices[i];
      }
    }
    String[] mapArr = new String[maxIndex + 1];
    Arrays.fill(mapArr, null);
    for (int i = 0; i < m.length; i++) {
      mapArr[indices[i]] = keys[i];
    }
    return mapArr;
  }

  /**
   * convenience method for getting word *
   */
  public String word() {
    return getString(WORD_KEY);
  }

  /**
   * convenience method for getting answer *
   */
  public String answer() {
    return getString(ANSWER_KEY);
  }

  /**
   * convenience method for getting gold answer *
   */
  public String goldAnswer() {
    return getString(GOLDANSWER_KEY);
  }

  /**
   * convenience method for setting word *
   */
  public void setWord(String word) {
    put(WORD_KEY, word);
  }

  /**
   * convenience method for setting answer *
   */
  public void setAnswer(String answer) {
    put(ANSWER_KEY, answer);
  }

  /**
   * convenience method for setting gold answer *
   */
  public void setGoldAnswer(String goldAnswer) {
    put(GOLDANSWER_KEY, goldAnswer);
  }

  /**
   * Return the String before the word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#BEFORE_KEY <code>BEFORE_KEY</code>}.
   *
   * @return the String before the word
   */
  public String before() {
    return getString(BEFORE_KEY);
  }

  /**
   * Set the String before the word by storing it in the map under
   * the key {@link AbstractMapLabel#BEFORE_KEY
   * <code>BEFORE_KEY</code>}.
   *
   * @param before the String before the word
   */
  public void setBefore(String before) {
    map.put(BEFORE_KEY, before);
  }

  /**
   * Prepend this String to the current before String
   *
   * @param before the String to be prepended
   */
  public void prependBefore(String before) {
    String oldBefore = before();
    setBefore(before + oldBefore);
  }

  /**
   * Return the String which is the unmangled word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#CURRENT_KEY <code>CURRENT_KEY</code>}.
   *
   * @return the unmangled word
   */
  public String current() {
    return getString(CURRENT_KEY);
  }

  /**
   * Set the String which is the unmangled word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#CURRENT_KEY <code>CURRENT_KEY</code>}.
   *
   * @param current the unmangled word
   */
  public void setCurrent(String current) {
    map.put(CURRENT_KEY, current);
  }

  /**
   * Return the String after the word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#AFTER_KEY <code>AFTER_KEY</code>}.
   *
   * @return the String after the word
   */
  public String after() {
    return getString(AFTER_KEY);
  }

  /**
   * Set the String after the word by storing it in the map under
   * the key {@link AbstractMapLabel#AFTER_KEY
   * <code>AFTER_KEY</code>}.
   *
   * @param after The String after the word
   */
  public void setAfter(String after) {
    map.put(AFTER_KEY, after);
  }

  /**
   * Append this String to the current after String
   *
   * @param after The String to be prepended
   */
  public void appendAfter(String after) {
    String oldAfter = after();
    setAfter(oldAfter + after);
  }

  /**
   * Return the NER type of the word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#NER_KEY <code>NER_KEY</code>}.
   *
   * @return the String after the word
   */
  public String ner() {
    return getString(NER_KEY);
  }

  /**
   * Set the NER label for the word, using
   * the key {@link AbstractMapLabel#NER_KEY
   * <code>NER_KEY</code>}.
   *
   * @param ner The String ner the word
   */
  public void setNER(String ner) {
    map.put(NER_KEY, ner);
  }

  /**
   * Return the coreferent of the word,
   * which is stored in the map under the key {@link
   * AbstractMapLabel#COREF_KEY <code>NER_KEY</code>}.
   *
   * @return the String after the word
   */
  public String coref() {
    return getString(COREF_KEY);
  }


  /**
   * Pieces a List of MapLabels back together using before, after
   * and current.
   */
  public static String toOriginalString(List<FeatureLabel> sentence) {
    StringBuilder text = new StringBuilder();
    for (int i = 0, sz = sentence.size(); i < sz; i++) {
      FeatureLabel iw = sentence.get(i);
      text.append(iw.before());
      text.append(iw.current());
      if (i == sz - 1) {
        text.append(iw.after());
      }
    }
    return text.toString();
  }

  /**
   * Pieces a List of MapLabels back together using
   * word and setting a white space between each word
   */
  public static String toSentence(List<? extends FeatureLabel> sentence) {
    StringBuilder text = new StringBuilder();
    for (int i = 0, sz = sentence.size(); i < sz; i++) {
      FeatureLabel iw = sentence.get(i);
      text.append(iw.word());
      if (i < sz - 1) {
        text.append(" ");
      }
    }
    return text.toString();
  }



  public String value() {
    return getString(VALUE_KEY);
  }

  public void setValue(String value) {
    put(VALUE_KEY, value);
  }

  public String toString() {
    return toString(TOSTRING_FORMAT);
  }

  public String toString(String format) {
    if (format == null || format.equals("")) {
      StringBuffer sb = new StringBuffer("{");
      List sortedKeys = new ArrayList(map.keySet());
      Collections.sort(sortedKeys);
      boolean first = true;
      for (Object k : sortedKeys) {
        if (!first) { sb.append(", "); }
        sb.append(k).append("=").append(map.get(k));
        first = false;
      }
      sb.append("}");
      return sb.toString();
//      return map.toString(); 
    } else if (format.equals("word")) {
      return word();
    } else if (format.equals("wordtag")) {
      String tag = tag();
      if (tag != null && tag.length() > 0) {
        return word() + "/" + tag;
      } else {
        return word();
      }
    } else {
      return map.toString(); 
    }        
  }

  public void setFromString(String labelStr) {
    put(VALUE_KEY, labelStr);
  }

  public LabelFactory labelFactory() {
    return new FeatureLabelFactory();
  }

  public static LabelFactory factory() {
    return new FeatureLabelFactory();
  }

  /**
   * Interns all of the keys and values in the underlying map of this FeatureLabel.
   * @param interner
   */
  public void internValues(Interner interner) {
    Map newMap = mapFactory.buildMap();
    for (Object o : map.entrySet()) {
      Map.Entry entry = (Map.Entry) o;
      Object key = entry.getKey();
      Object value = entry.getValue();
      newMap.put(key, interner.intern(value));
    }
    map = newMap;
  }

  private static class FeatureLabelFactory implements LabelFactory {

    private FeatureLabelFactory() {}

    public Label newLabel(String labelStr) {
      FeatureLabel result = new FeatureLabel();
      result.setValue(labelStr);
      return result;
    }

    public Label newLabel(String labelStr, int options) {
      FeatureLabel result = new FeatureLabel();
      result.setValue(labelStr);
      return result;
    }

    public Label newLabelFromString(String encodedLabelStr) {
      FeatureLabel result = new FeatureLabel();
      result.setValue(encodedLabelStr);
      return result;
    }

    public Label newLabel(Label oldLabel) {
      FeatureLabel result = new FeatureLabel();
      result.setValue(oldLabel.value());
      return result;
    }

  } // end class FeatureLabelFactory


  public String lemma() {
    return getString(LEMMA_KEY);
  }


  public String tag() {
    return getString(TAG_KEY);
  }

  /** Return the String value of the FeatureLabel for an arbitrary key.
   *  See AbstractMapLabel for standard attribute key names, though anything
   *  can be used.  The return value of this method is always a non-null
   *  String.  If the attribute does not exist in the Map, then the empty 
   *  String is returned.
   * <p/>
   * <i>Note: This behavior is central to how our IE systems
   *  work and should not be changed!!</i>
   */
  public String getString(Object attribute) {
    String v = (String) map.get(attribute);
    if (v == null) {
      return "";
    }
    return v;
  }

  public void set(Object attribute, Object value) {
    put(attribute, value);
  }

  public Map map() {
    return map;
  }

  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof FeatureLabel)) return false;
    final FeatureLabel featureLabel = (FeatureLabel) o;

    return map == null ? featureLabel.map == null : map.equals(featureLabel.map);
  }

  public int hashCode() {
    return (map != null ? map.hashCode() : 7);
  }

  private static final long serialVersionUID = 19L;
  
  public void remove(String key) {
    map.remove(key);
  }

}

