package tim.prune.load.xml;

import java.util.ArrayList;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import tim.prune.data.Field;
import tim.prune.load.TrackNameList;


/**
 * Class for handling specifics of parsing Gpx files
 */
public class GpxHandler extends XmlHandler
{
	private boolean _insidePoint = false;
	private boolean _insideWaypoint = false;
	private boolean _startSegment = true;
	private boolean _isTrackPoint = false;
	private int _trackNum = -1;
	private GpxTag _name = new GpxTag(), _trackName = new GpxTag();
	private String _latitude = null, _longitude = null;
	private GpxTag _elevation = new GpxTag(), _time = new GpxTag();
	private GpxTag _type = new GpxTag(), _description = new GpxTag();
	private GpxTag _link = new GpxTag();
	private GpxTag _currentTag = null;
	private ArrayList<String[]> _pointList = new ArrayList<String[]>();
	private ArrayList<String> _linkList = new ArrayList<String>();
	private TrackNameList _trackNameList = new TrackNameList();


	/**
	 * Receive the start of a tag
	 * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	public void startElement(String uri, String localName, String qName,
		Attributes attributes) throws SAXException
	{
		// Read the parameters for waypoints and track points
		String tag = qName.toLowerCase();
		if (tag.equals("wpt") || tag.equals("trkpt") || tag.equals("rtept"))
		{
			_insidePoint = true;
			_insideWaypoint = tag.equals("wpt");
			_isTrackPoint = tag.equals("trkpt");
			final int numAttributes = attributes.getLength();
			for (int i=0; i<numAttributes; i++)
			{
				String att = attributes.getQName(i).toLowerCase();
				if (att.equals("lat")) {_latitude = attributes.getValue(i);}
				else if (att.equals("lon")) {_longitude = attributes.getValue(i);}
			}
			_elevation.setValue(null);
			_name.setValue(null);
			_time.setValue(null);
			_type.setValue(null);
			_link.setValue(null);
			_description.setValue(null);
		}
		else if (tag.equals("ele")) {
			_currentTag = _elevation;
		}
		else if (tag.equals("name")) {
			_currentTag = (_insidePoint?_name:_trackName);
		}
		else if (tag.equals("time")) {
			_currentTag = _time;
		}
		else if (tag.equals("type")) {
			_currentTag = _type;
		}
		else if (tag.equals("description")) {
			_currentTag = _description;
		}
		else if (tag.equals("link")) {
			_link.setValue(attributes.getValue("href"));
		}
		else if (tag.equals("trkseg")) {
			_startSegment = true;
		}
		else if (tag.equals("trk"))
		{
			_trackNum++;
			_trackName.setValue(null);
		}
		super.startElement(uri, localName, qName, attributes);
	}


	/**
	 * Process end tag
	 * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
	 */
	public void endElement(String uri, String localName, String qName)
		throws SAXException
	{
		String tag = qName.toLowerCase();
		if (tag.equals("wpt") || tag.equals("trkpt") || tag.equals("rtept"))
		{
			processPoint();
			_insidePoint = false;
		}
		else {
			_currentTag = null;
		}
		super.endElement(uri, localName, qName);
	}


	/**
	 * Process character text (inside tags or between them)
	 * @see org.xml.sax.ContentHandler#characters(char[], int, int)
	 */
	public void characters(char[] ch, int start, int length)
		throws SAXException
	{
		String value = new String(ch, start, length);
		if (_currentTag != null) {
			_currentTag.setValue(checkCharacters(_currentTag.getValue(), value));
		}
		super.characters(ch, start, length);
	}


	/**
	 * Check to concatenate partially-received values, if necessary
	 * @param inVariable variable containing characters received until now
	 * @param inValue new value received
	 * @return concatenation
	 */
	private static String checkCharacters(String inVariable, String inValue)
	{
		if (inVariable == null) {return inValue;}
		return inVariable + inValue;
	}


	/**
	 * Process a point, either a waypoint or track point
	 */
	private void processPoint()
	{
		// Put the values into a String array matching the order in getFieldArray()
		String[] values = new String[8];
		values[0] = _latitude;
		values[1] = _longitude;
		values[2] = _elevation.getValue();
		if (_insideWaypoint) {values[3] = _name.getValue();}
		values[4] = _time.getValue();
		if (_startSegment && !_insideWaypoint)
		{
			values[5] = "1";
			_startSegment = false;
		}
		values[6] = _type.getValue();
		values[7] = _description.getValue();
		_pointList.add(values);
		_trackNameList.addPoint(_trackNum, _trackName.getValue(), _isTrackPoint);
		_linkList.add(_link.getValue());
	}


	/**
	 * @see tim.prune.load.xml.XmlHandler#getFieldArray()
	 */
	public Field[] getFieldArray()
	{
		final Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE,
			Field.WAYPT_NAME, Field.TIMESTAMP, Field.NEW_SEGMENT,
			Field.WAYPT_TYPE, Field.DESCRIPTION};
		return fields;
	}


	/**
	 * Return the parsed information as a 2d array
	 * @see tim.prune.load.xml.XmlHandler#getDataArray()
	 */
	public String[][] getDataArray()
	{
		int numPoints = _pointList.size();
		// construct data array
		String[][] result = new String[numPoints][];
		for (int i=0; i<numPoints; i++)
		{
			result[i] = _pointList.get(i);
		}
		return result;
	}

	/**
	 * @return array of links, or null if none
	 */
	public String[] getLinkArray()
	{
		int numPoints = _linkList.size();
		boolean hasLink = false;
		String[] result = new String[numPoints];
		for (int i=0; i<numPoints; i++)
		{
			result[i] = _linkList.get(i);
			if (result[i] != null) {hasLink = true;}
		}
		if (!hasLink) {result = null;}
		return result;
	}

	/**
	 * @return track name list
	 */
	public TrackNameList getTrackNameList() {
		return _trackNameList;
	}
}
