package edu.jhu.thrax.util;

import java.util.Map;
import java.util.regex.Pattern;

import org.apache.hadoop.io.FloatWritable;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;

import edu.jhu.thrax.hadoop.datatypes.AlignmentWritable;
import edu.jhu.thrax.hadoop.datatypes.RuleWritable;

public class FormatUtils {

  /**
   * Field delimiter.
   */
  private static final String DELIMITER = "|||";

  /**
   * Regular expression for the field delimiter.
   */
  private static final String DELIMITER_REGEX = " \\|\\|\\| ";

  public static final String DELIM = String.format(" %s ", DELIMITER);

  public static final Pattern P_DELIM = Pattern.compile(DELIMITER_REGEX);
  public static final Pattern P_SPACE = Pattern.compile("\\s+");
  public static final Pattern P_EQUAL = Pattern.compile("=");
  public static final Pattern P_SLASH = Pattern.compile("/");
  public static final Pattern P_BSLASH = Pattern.compile("\\\\");
  public static final Pattern P_DASH = Pattern.compile("-");
  public static final Pattern P_COMMA_OR_SPACE = Pattern.compile("\\s+|,");


  public static boolean isNonterminal(String token) {
    return (token.charAt(0) == '[') && (token.charAt(token.length() - 1) == ']');
  }

  public static String stripNonterminal(String nt) {
    return nt.substring(1, nt.length() - 1);
  }

  public static String stripIndexedNonterminal(String nt) {
    return nt.substring(1, nt.length() - 3);
  }

  public static int getNonterminalIndex(String nt) {
    return Integer.parseInt(nt.substring(nt.length() - 2, nt.length() - 1));
  }

  public static String markup(String nt) {
    return "[" + nt + "]";
  }

  public static String markup(String nt, int index) {
    return nt.substring(0, nt.length() - 1) + "," + index + "]";
  }

  public static boolean isMonotonic(String input) {
    int one_pos = input.indexOf(",1]");
    int two_pos = input.indexOf(",2]");
    if (two_pos == -1 || one_pos == -1) return true;
    return (one_pos < two_pos);
  }

  public static String applyIndices(String input, boolean monotonic) {
    int first_nt = input.indexOf(",0]");
    if (first_nt == -1) return input;

    StringBuilder output = new StringBuilder(input);
    int second_nt = input.indexOf(",0]", first_nt + 1);
    if (second_nt == -1)
      output.setCharAt(first_nt + 1, '1');
    else {
      output.setCharAt(first_nt + 1, (monotonic ? '1' : '2'));
      output.setCharAt(second_nt + 1, (monotonic ? '2' : '1'));
    }
    return output.toString();
  }

  public static int[] applyIndices(int[] input, boolean monotonic) {
    boolean seen_first = false;
    for (int i = 0; i < input.length; ++i) {
      if (input[i] == 0) {
        if (seen_first) {
          input[i] = (monotonic ? -2 : -1);
        } else {
          input[i] = (monotonic ? -1 : -2);
          seen_first = true;
        }
      }
    }
    return input;
  }

  // TODO: this isn't a good place for this method any more.
  public static Text ruleToText(RuleWritable r, Map<String, Writable> fs, boolean label,
      boolean sparse) {
    if (r == null) throw new IllegalArgumentException("Cannot convert a null rule to text.");
    
    // Both alignment and count are features that are handled specially
    String alignment = null;    
    String ruleCount = "";
    StringBuilder sb = new StringBuilder();
    sb.append(Vocabulary.word(r.lhs));
    sb.append(DELIM);
    int n = 1;
    for (int i = 0; i < r.source.length; ++i) {
      if (i != 0) sb.append(" ");
      if (Vocabulary.nt(r.source[i]))
        sb.append(markup(Vocabulary.word(r.source[i]), n++));
      else
        sb.append(Vocabulary.word(r.source[i]));
    }
    sb.append(DELIM);
    n = (r.monotone ? 1 : 2);
    for (int i = 0; i < r.target.length; ++i) {
      if (i != 0) sb.append(" ");
      if (Vocabulary.nt(r.target[i]))
        sb.append(markup(Vocabulary.word(r.target[i]), (r.monotone ? n++ : n--)));
      else
        sb.append(Vocabulary.word(r.target[i]));
    }

    sb.append(DELIM);
    for (String t : fs.keySet()) {
      String score;
      Writable val = fs.get(t);
      if (t.toLowerCase().equals("count")) {
        ruleCount = String.format("%d", ((IntWritable) fs.get(t)).get());
        continue;
      } else if (val instanceof FloatWritable) {
        float value = ((FloatWritable) fs.get(t)).get();
        if (value == -0.0 || Math.abs(value) < 0.000005)
          score = "0";
        else
          score = String.format("%.5f", value);
        if (sparse && Float.parseFloat(score) == 0) continue;
      } else if (val instanceof IntWritable) {
        score = String.format("%d", ((IntWritable) fs.get(t)).get());
        if (sparse && Integer.parseInt(score) == 0) continue;
      } else if (val instanceof Text) {
        score = ((Text) fs.get(t)).toString();
      } else if (val instanceof AlignmentWritable) {
        alignment = ((AlignmentWritable) val).toString(" ");
        continue;
      } else {
        throw new RuntimeException("Expecting float, integer, or string feature values.");
      }
      if (label)
        sb.append(String.format("%s=%s ", t, score));
      else
        sb.append(String.format("%s ", score));
    }
    if (alignment != null)
      sb.append(DELIMITER + " ").append(alignment + " ");

    if (! ruleCount.equals("")) {
      // If there was no alignment, output a blank
      if (alignment == null)
        sb.append(DELIMITER + "  ");

      sb.append(DELIMITER + " ").append(ruleCount).append(" ");
    }
    return new Text(sb.substring(0, sb.length() - 1));
  }

  public static Text contextPhraseToText(Text phrase, Map<Text, Integer> fs) {
    if (phrase == null)
      throw new IllegalArgumentException("Cannot convert a null " + "phrase to Text.");
    StringBuilder sb = new StringBuilder();
    sb.append(phrase.toString());
    sb.append(DELIM);
    for (Text t : fs.keySet()) {
      int i = fs.get(t);
      if (i != 0) sb.append(String.format("%s=%d ", t, i));
    }
    return new Text(sb.substring(0, sb.length() - 1));
  }
}
