package fitnesse.slim.converters;

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

import fitnesse.html.HtmlTag;
import fitnesse.html.HtmlUtil;
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 NodeList nodes;

  private NodeList tables;

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

    HtmlTag table = createTag(hash);

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

  protected HtmlTag createTag(Map<?, ?> hash) {
    // 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, HtmlUtil.escapeHTML(entry.getKey().toString()));
      keyCell.addAttribute("class", "hash_key");
      row.add(keyCell);

      HtmlTag valueCell = new HtmlTag("td");
      addCellContent(valueCell, HtmlUtil.escapeHTML(entry.getValue() != null ? HtmlUtil.escapeHTML(entry.getValue().toString()) : ""));
      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 HtmlUtil.unescapeHTML(((CompositeTag) compositeNode).getChildrenHTML());
  }

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

}

@@ -1129,36 +1129,16 @@
 eyCell, 
-HtmlUtil.escapeHTML(
 entry.ge
@@ -1143,28 +1143,16 @@
 getKey()
-.toString())
 );%0A     
@@ -1301,108 +1301,23 @@
 ll, 
-HtmlUtil.escapeHTML(entry.getValue() != null ? HtmlUtil.escapeHTML(entry.getValue().toString()) : %22%22
+entry.getValue(
 ));%0A
@@ -1491,24 +1491,24 @@
 ellValue) %7B%0A
-
     String v
@@ -1566,24 +1566,112 @@
 cellValue);%0A
+    if (!tableIsValid(valueToAdd))%0A      valueToAdd = HtmlUtil.escapeHTML(valueToAdd);%0A%0A
     valueCel

Fix escaping for nested tables in hash table.