/*
 * $Id: TableElementModel.java,v 1.2 2006-10-21 23:18:43 pombredanne Exp $
 * Copyright Narushima Hironori. All rights reserved.
 */
package net.sourceforge.phpeclipse.wizards.html;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;

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

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * TableElementModel
 */
public class TableElementModel {

	final static char[] CHAR_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

	StringDivider stringDivider = new StringDivider();

	ElementWriter writer;

	DocumentBuilder docBuilder;

	Document document;

	Element tableElement;

	String[] columnProperties;

	public TableElementModel(String content, boolean parse)
			throws FactoryConfigurationError, ParserConfigurationException,
			SAXException, IOException {
		docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		if (parse) {
			initAsParse(content);
		} else {
			initModel(content);
		}
		columnProperties = createColumnProperties();

		// create elementWriter
		writer = new ElementWriter(0, null);
		writer.setExpandOption("caption", ElementWriter.END_CHANGELINE);
		writer.setExpandOption("table", ElementWriter.BEGIN_CHANGELINE
				| ElementWriter.END_CHANGELINE);
		writer.setExpandOption("thead", ElementWriter.BEGIN_CHANGELINE
				| ElementWriter.END_CHANGELINE);
		writer.setExpandOption("tfoot", ElementWriter.BEGIN_CHANGELINE
				| ElementWriter.END_CHANGELINE);
		writer.setExpandOption("tbody", ElementWriter.BEGIN_CHANGELINE
				| ElementWriter.END_CHANGELINE);
		writer.setExpandOption("tr", ElementWriter.END_CHANGELINE);
	}

	void initModel(String content) throws ParserConfigurationException,
			SAXException, IOException {
		StringReader strReader = new StringReader(content);
		InputSource inputSrc = new InputSource(strReader);

		document = docBuilder.parse(inputSrc);
		tableElement = document.getDocumentElement();

		Element[] rows = getRows();
		for (int i = 0; i < rows.length; i++) {
			Element[] cells = chooseCellElements(rows[i]);
			for (int j = 0; j < cells.length; j++) {
				Element cell = cells[j];
				if (!cell.hasChildNodes()) {
					cell.appendChild(document.createTextNode(""));
				}
			}
		}
	}

	public void initAsParse(String content)
			throws ParserConfigurationException, FactoryConfigurationError {
		// create new table model.
		document = docBuilder.newDocument();
		tableElement = document.createElement("table");

		String[][] cells = stringDivider.divide(content);
		if (cells.length > 0) {
			for (int i = 0; i < cells.length; i++) {
				String[] rows = cells[i];
				Element tr = document.createElement("tr");
				for (int j = 0; j < rows.length; j++) {
					Element e = document.createElement("td");
					e.appendChild(document.createTextNode(rows[j]));
					tr.appendChild(e);
				}
				tableElement.appendChild(tr);
			}

			setColumnCount(cells[0].length);
		} else {
			Element tr = document.createElement("tr");
			Element td = document.createElement("td");
			td.appendChild(document.createTextNode(""));
			tr.appendChild(td);
			tableElement.appendChild(tr);

			setColumnCount(1);
		}
	}

	String[] createColumnProperties() {
		int len = getColumnCount();
		String[] props = new String[len];
		for (int i = 0; i < len; i++) {
			props[i] = toColumnName(i);
		}
		return props;
	}

	public void setRowCount(int rowCount) {
		Element[] rows = getRows();
		if (rowCount > rows.length) {
			for (int i = rows.length; i < rowCount; i++) {
				tableElement.appendChild(createRowElement());
			}
		} else if (rowCount < rows.length) {
			for (int i = rowCount; i < rows.length; i++) {
				tableElement.removeChild(rows[i]);
			}
		}
	}

	public Element[] getRows() {
		ArrayList rows = new ArrayList();
		NodeList nodes = tableElement.getElementsByTagName("tr");
		for (int i = 0; i < nodes.getLength(); i++) {
			rows.add(nodes.item(i));
		}
		return (Element[]) rows.toArray(new Element[rows.size()]);
	}

	public int getRowCount() {
		return getRows().length;
	}

	Element createRowElement() {
		Element tr = document.createElement("tr");
		for (int i = 0, columnCount = getColumnCount(); i < columnCount; i++) {
			Element td = document.createElement("td");
			td.appendChild(document.createTextNode(""));
			tr.appendChild(td);
		}
		return tr;
	}

	public void setColumnCount(int newLength) {
		NodeList trs = tableElement.getElementsByTagName("tr");
		for (int i = 0; i < trs.getLength(); i++) {
			Element tr = (Element) trs.item(i);
			Element[] cells = chooseCellElements(tr);
			int colLen = cells.length;

			if (newLength > colLen) {
				for (int j = 0, len = newLength - colLen; j < len; j++) {
					Element cell = document.createElement("td");
					cell.appendChild(document.createTextNode(""));
					tr.appendChild(cell);
				}
			} else if (newLength < colLen) {
				for (int j = newLength; j < colLen; j++) {
					tr.removeChild(cells[j]);
				}
			}
		}
		columnProperties = createColumnProperties();
	}

	public int getColumnCount() {
		NodeList trs = tableElement.getElementsByTagName("tr");
		if (trs.getLength() > 0) {
			Element tr = (Element) trs.item(0);
			return chooseCellElements(tr).length;
		} else {
			return 0;
		}
	}

	public static Element[] chooseCellElements(Element tr) {
		NodeList nodeList = tr.getChildNodes();

		ArrayList result = new ArrayList();
		for (int i = 0; i < nodeList.getLength(); i++) {
			Node node = nodeList.item(i);
			if (node instanceof Element) {
				String nodeName = node.getNodeName();
				if (nodeName.equals("td") || nodeName.equals("th")) {
					result.add(node);
				}
			}
		}

		return (Element[]) result.toArray(new Element[result.size()]);
	}

	public String expandCodes() {
		return writer.expandTag(tableElement);
	}

	public static String toColumnName(int i) {
		StringBuffer buff = new StringBuffer();
		int u = i / CHAR_TABLE.length;
		if (u > 0) {
			buff.append(CHAR_TABLE[u - 1]);
		}
		buff.append(CHAR_TABLE[i % CHAR_TABLE.length]);
		return buff.toString();
	}

	/**
	 * Return index of char map. If can not parse values return -1.
	 */
	public static int toNumeric(String code) {
		int result = -1;
		for (int i = 0; i < code.length(); i++) {
			char c = code.charAt(i);
			int match = Arrays.binarySearch(CHAR_TABLE, c);
			if (match >= 0) {
				if (result == -1) {
					result = 0;
				}
				int v = match;
				int u = code.length() - 1 - i;
				if (u > 0) {
					v = CHAR_TABLE.length * u * (v + 1);
				}
				result += v;
			}
		}
		return result;
	}

	public void move(Element tr, int moveCount) {
		Element[] rows = getRows();
		int index = -1;
		for (int i = 0; i < rows.length; i++) {
			if (tr.equals(rows[i])) {
				index = i;
			}
		}
		if (index == -1) {
			throw new IllegalArgumentException(
					"Invalid row node (not countained in this table):" + tr);
		}
		if (moveCount > 0) {
			// move down;
			for (int i = index; i < moveCount + index && i < rows.length - 1; i++) {
				tableElement.insertBefore(rows[i + 1], rows[i]);
			}
		} else if (moveCount < 0) {
			// move up
			for (int i = index; i >= moveCount + index + 1 && i >= 1; i--) {
				tableElement.insertBefore(rows[index], rows[i - 1]);
			}
		} else {
			return;
		}
	}

	public void insertNewRowBefore(Element tr) {
		Element newRow = createRowElement();
		if (tr == null) {
			tableElement.appendChild(newRow);
		} else {
			tableElement.insertBefore(newRow, tr);
		}
	}

	public void removeRow(Element tr) {
		tableElement.removeChild(tr);
	}

	public String[] getColumnProperties() {
		return (String[]) columnProperties.clone();
	}

}