package eu.simstadt.regionchooser.fast_xml_parser;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.logging.Logger;
import com.ximpleware.AutoPilot;
import com.ximpleware.NavException;
import com.ximpleware.VTDGen;
import com.ximpleware.VTDNav;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;


public class CityGmlIterator implements Iterable<BuildingXmlNode>
{

	private static final Logger LOGGER = Logger.getLogger(CityGmlIterator.class.getName());

	private AutoPilot buildingsFinder;
	private VTDNav navigator;
	private int buildingOffset = 0;
	private int buildingLength = 0;
	private Path citygmlPath;

	/*
	 * Simple class to parse a CityGML and extract cityObjectMember XML nodes and their coordinates. No other attribute
	 * is extracted. Not suitable for building simulation, perfect for quick and dirty extract of coordinates (e.g. for
	 * RegionChooser or HullExtractor). It should be fast and not use much memory. A SaxParser would use even less memory
	 * but might be harder to code and possibly slower to run.
	 *
	 * For a more complete and more robust (but slower) implementation, use eu.simstadt.geo.GeoCoordinatesAccessor in
	 * SimStadt geo-libs.
	 *
	 * Based on VTD XML, it provides a Building iterator.
	 *
	 */
	public CityGmlIterator(Path citygmlPath) throws XPathParseException {
		this.citygmlPath = citygmlPath;
		VTDGen parser = new VTDGen();
		parser.parseFile(citygmlPath.toString(), false);
		this.navigator = parser.getNav();
		this.buildingsFinder = new AutoPilot(navigator);
		buildingsFinder.selectXPath("/CityModel/cityObjectMember[Building]"); //TODO: Check it's the only correct possibility. //FIXME: BuildingPart too!
	}

	@Override
	public Iterator<BuildingXmlNode> iterator() {
		return new Iterator<BuildingXmlNode>() {

			@Override
			public boolean hasNext() {
				try {
					return buildingsFinder.evalXPath() != -1;
				} catch (XPathEvalException | NavException ex) {
					LOGGER.warning("Error while parsing " + citygmlPath);
					return false;
				}
			}

			@Override
			public BuildingXmlNode next() {
				try {
					long offsetAndLength = navigator.getElementFragment();
					buildingOffset = (int) offsetAndLength;
					buildingLength = (int) (offsetAndLength >> 32);
					return new BuildingXmlNode(navigator, buildingOffset, buildingLength);
				} catch (NavException | NumberFormatException | XPathParseException | XPathEvalException ex) {
					LOGGER.warning("Error while parsing " + citygmlPath);
				}
				return null;
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}

	/**
	 * Returns Header of the Citygml, from the start of the file to the current building. This method needs to be called
	 * directly after the first building has been found.
	 *
	 * @return Citygml header
	 * @throws NavException
	 */
	public String getHeader() throws NavException {
		return navigator.toRawString(0, buildingOffset);
	}

	/**
	 * Returns footer of the Citygml, from the end of the last building to the end of file. This method needs to be
	 * called after the last building has been found.
	 *
	 * @return Citygml footer
	 * @throws NavException
	 */
	public Object getFooter() throws IOException, NavException {
		int footerOffset = buildingOffset + buildingLength;
		int footerLength = (int) (Files.size(citygmlPath) - footerOffset);
		return navigator.toRawString(footerOffset, footerLength);
	}

}
