package fitnesse.slim.converters;

import java.util.HashMap;
import java.util.Map;

import fitnesse.html.HtmlTag;
import fitnesse.slim.Converter;
import org.htmlparser.Node;
import org.htmlparser.Parser;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.tags.CompositeTag;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;

public class MapConverter implements Converter<Map> {

  private static final String[] specialHtmlChars = new String[] { "&", "<", ">" };
  private static final String[] specialHtmlEscapes = new String[] { "&amp;", "&lt;", "&gt;" };

  private NodeList nodes;

  private NodeList tables;

  @Override
  public String toString(Map hash) {
    if (hash == null) {
      return NULL_VALUE;
    }

    HtmlTag table = createTag(hash, 0);

    return table.html().trim();
  }

  protected HtmlTag createTag(Map<?, ?> hash, int depth) {
    // Use HtmlTag, same as we do for fitnesse.wikitext.parser.HashTable.
    HtmlTag table = new HtmlTag("table");
    table.addAttribute("class", "hash_table");
    for (Map.Entry<?, ?> entry : hash.entrySet()) {
      HtmlTag row = new HtmlTag("tr");
      row.addAttribute("class", "hash_row");
      table.add(row);
      HtmlTag keyCell = new HtmlTag("td");
      addCellContent(keyCell, entry.getKey());
      keyCell.addAttribute("class", "hash_key");
      row.add(keyCell);

      HtmlTag valueCell = new HtmlTag("td");
      addCellContent(valueCell, entry.getValue());
      valueCell.addAttribute("class", "hash_value");
      row.add(valueCell);
    }
    return table;
  }

  protected void addCellContent(HtmlTag valueCell, Object cellValue) {
    String valueToAdd = ElementConverterHelper.elementToString(cellValue);
    valueCell.add(valueToAdd.trim());
  }

  @Override
  public Map<String, String> fromString(String possibleTable) {
    Map<String, String> map = new HashMap<String, String>();
    if (tableIsValid(possibleTable))
      extractRowsIntoMap(map, tables);

    return map;
  }

  private boolean tableIsValid(String possibleTable) {
    if (isValidHtml(possibleTable)) {
      return hasOneTable();
    } else {
      return false;
    }
  }

  private boolean hasOneTable() {
    TagNameFilter tableFilter = new TagNameFilter("table");
    tables = nodes.extractAllNodesThatMatch(tableFilter);
    return tables.size() == 1;
  }

  private boolean isValidHtml(String possibleTable) {
    nodes = parseHtml(possibleTable);
    return nodes != null;
  }

  private void extractRowsIntoMap(Map<String, String> map, NodeList tables) {
    extractRows(map, getRows(tables));
  }

  private void extractRows(Map<String, String> map, NodeList rows) {
    for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
      extractRow(map, rows, rowIndex);
    }
  }

  private void extractRow(Map<String, String> map, NodeList rows, int rowIndex) {
    Node row = rows.elementAt(rowIndex);
    if (row != null)
      extractColumns(map, row);
  }

  private void extractColumns(Map<String, String> map, Node row) {
    TagNameFilter tdFilter = new TagNameFilter("td");
    if (row.getChildren() != null) {
      NodeList cols = row.getChildren().extractAllNodesThatMatch(tdFilter);
      if (cols.size() == 2)
        addColsToMap(map, cols);
    }
  }

  private void addColsToMap(Map<String, String> map, NodeList cols) {
    String key = getText(cols.elementAt(0));
    String value = getText(cols.elementAt(1));
    map.put(key, value);
  }

  private NodeList getRows(NodeList tables) {
    TagNameFilter trFilter = new TagNameFilter("tr");
    Node table = tables.elementAt(0);

    if (table.getChildren() != null)
      return table.getChildren().extractAllNodesThatMatch(trFilter);

    return new NodeList();
  }

  private String getText(Node compositeNode) {
    return ((CompositeTag) compositeNode).getChildrenHTML();
  }

  private NodeList parseHtml(String possibleTable) {
    try {
      Parser parser = new Parser(possibleTable);
      return parser.parse(null);
    } catch (ParserException e) {
      return null;
    }
  }

  public String escapeHTML(String value) {
    return replaceStrings(value, specialHtmlChars, specialHtmlEscapes);
  }

  private String replaceStrings(String value, String[] originalStrings, String[] replacementStrings) {
    String result = value;
    for (int i = 0; i < originalStrings.length; i++)
      if (result.contains(originalStrings[i]))
        result = result.replace(originalStrings[i], replacementStrings[i]);
    return result;
  }

}

@@ -424,187 +424,8 @@
  %7B%0A%0A
-  private static final String%5B%5D specialHtmlChars = new String%5B%5D %7B %22&%22, %22%3C%22, %22%3E%22 %7D;%0A  private static final String%5B%5D specialHtmlEscapes = new String%5B%5D %7B %22&amp;%22, %22&lt;%22, %22&gt;%22 %7D;%0A%0A
   pr
@@ -618,11 +618,8 @@
 hash
-, 0
 );%0A%0A
@@ -703,19 +703,8 @@
 hash
-, int depth
 ) %7B%0A
@@ -3881,456 +3881,6 @@
  %7D%0A%0A
-  public String escapeHTML(String value) %7B%0A    return replaceStrings(value, specialHtmlChars, specialHtmlEscapes);%0A  %7D%0A%0A  private String replaceStrings(String value, String%5B%5D originalStrings, String%5B%5D replacementStrings) %7B%0A    String result = value;%0A    for (int i = 0; i %3C originalStrings.length; i++)%0A      if (result.contains(originalStrings%5Bi%5D))%0A        result = result.replace(originalStrings%5Bi%5D, replacementStrings%5Bi%5D);%0A    return result;%0A  %7D%0A%0A
 %7D%0A

Clean up MapConverter.