package de.planetmetax.spreadsheet.reader;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

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

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

/**
 * Utility class for reading and parsing an ODT Spreadsheet file.
 * @author Christian Simon
 */
public class SpreadsheetReader {
	
	/**
	 * Open an ODT Spreadsheet file and save the text contents in a matrix
	 * @param filename the filename of the spreadsheet
	 * @param tablenr the number of the table in the spreadsheet. Counting starts with 0 (i.e. #0 is the first table)
	 * @return SpreadsheetData (String matrix) with contents of the spreadsheet file
	 * @throws IOException if the input or zip streams throw IOExceptions
	 * @throws IndexOutOfBoundsException If the table nr is invalid
	 */
	public static SpreadsheetData openSpreadsheet(String filename, int tablenr) throws IOException, IndexOutOfBoundsException {
		FileInputStream str_file = null;
		ZipInputStream str_zip = null;
		try {
			str_file = new FileInputStream(filename);
			str_zip = new ZipInputStream(str_file);
			ZipEntry zipentry = str_zip.getNextEntry();
			while (zipentry != null && !zipentry.getName().equals("content.xml")) {
				zipentry = str_zip.getNextEntry();
			}
			Document xml = null;
			try {
				DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
				factory.setNamespaceAware(false);
				xml = factory.newDocumentBuilder().parse(str_zip);
			} catch (ParserConfigurationException e) {
				throw new RuntimeException("ParserConfigurationException occurred", e);
			} catch (SAXException e) {
				throw new RuntimeException("SAXException occurred", e);
			}
			
			SpreadsheetData data = new SpreadsheetData();
			
			NodeList tables = xml.getDocumentElement().getElementsByTagName("table:table");
			if (tablenr < 0 || tablenr >= tables.getLength()) {
				throw new IndexOutOfBoundsException();
			}
			Node mytable = tables.item(tablenr);
			int rownr = 0;
			for (Node row : filter(mytable.getChildNodes(), "table:table-row")) {
				fill(row, rownr++, data);
			}
			
			return data;
		} finally {
			try {
				if (str_zip != null) str_zip.close();
			} catch (IOException e) { }
			try {
				if (str_file != null) str_file.close();
			} catch (IOException e) { }
		}
	}
	
	/**
	 * Walk through all child nodes and return iterable list of all children, which match a given node name.
	 * @param nodes {@link NodeList} of child nodes
	 * @param name the node name, to match against
	 * @return list of nodes
	 */
	private static Iterable<Node> filter(NodeList nodes, String name) {
		LinkedList<Node> result = new LinkedList<Node>();
		for (int i = 0; i < nodes.getLength(); i++) {
			if (name.equals(nodes.item(i).getNodeName())) {
				result.add(nodes.item(i));
			}
		}
		return result;
	}
	
	/**
	 * Insert a table row into the spreadsheet data matrix.
	 * @param row the row data (as node)
	 * @param rownr the row number
	 * @param data the data matrix
	 */
	private static void fill(Node row, int rownr, SpreadsheetData data) {
		int colnr = 0;
		NodeList children = row.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			Node item = children.item(i);
			if (item.getNodeName().equals("table:table-cell") || item.getNodeName().equals("table:covered-table-cell")) {
				int repeated = 1;
				if (item.getAttributes().getNamedItem("table:number-columns-repeated") != null) {
					repeated = Integer.parseInt(item.getAttributes().getNamedItem("table:number-columns-repeated").getNodeValue());
				}
				for (int r = 0; r < repeated; r++) {
					data.insert(rownr, colnr++, item.getTextContent());
				}
			}
		}
	}

}

