package tim.prune.function.search;

import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXException;
import tim.prune.App;
import tim.prune.I18nManager;
import tim.prune.data.DataPoint;
import tim.prune.data.Distance;
import tim.prune.data.Latitude;
import tim.prune.data.Longitude;
import tim.prune.data.Unit;

/**
 * Function to load nearby point information from OSM
 */
public class SearchOsmPoisFunction extends GenericDownloaderFunction
{
	/** Maximum distance from point in m */
	private static final int MAX_DISTANCE = 250;
	/** Coordinates to search for */
	private double _searchLatitude = 0.0, _searchLongitude = 0.0;


	/**
	 * Constructor
	 * @param inApp App object
	 */
	public SearchOsmPoisFunction(App inApp) {
		super(inApp);
	}

	/**
	 * @return name key
	 */
	public String getNameKey() {
		return "function.searchosmpois";
	}

	/**
	 * @param inColNum index of column, 0 or 1
	 * @return key for this column
	 */
	protected String getColumnKey(int inColNum)
	{
		if (inColNum == 0) return "dialog.osmpois.column.name";
		return "dialog.osmpois.column.type";
	}


	/**
	 * Run method to get the nearby points in a separate thread
	 */
	public void run()
	{
		_statusLabel.setText(I18nManager.getText("confirm.running"));
		// Get coordinates from current point (if any) or from centre of screen
		DataPoint point = _app.getTrackInfo().getCurrentPoint();
		if (point == null)
		{
			_searchLatitude = _app.getViewport().getCentreLatitude();
			_searchLongitude = _app.getViewport().getCentreLongitude();
		}
		else
		{
			_searchLatitude  = point.getLatitude().getDouble();
			_searchLongitude = point.getLongitude().getDouble();
		}

		// Submit search (language not an issue here)
		submitSearch(_searchLatitude, _searchLongitude);

		// Set status label according to error or "none found", leave blank if ok
		if (_errorMessage == null && _trackListModel.isEmpty()) {
			_errorMessage = I18nManager.getText("dialog.osmpois.nonefound");
		}
		_statusLabel.setText(_errorMessage == null ? "" : _errorMessage);
		finishedSearch();
	}

	/**
	 * Submit the search for the given parameters
	 * @param inLat latitude
	 * @param inLon longitude
	 */
	private void submitSearch(double inLat, double inLon)
	{
		String coords = "around:" + MAX_DISTANCE + "," + inLat + "," + inLon;
		String urlString = "http://overpass-api.de/api/interpreter?data=("
			+ "node(" + coords + ")[\"amenity\"][\"name\"];"
			+ "node(" + coords + ")[\"railway\"][\"name\"];"
			+ "node(" + coords + ")[\"highway\"][\"name\"];"
			+ ");out%20qt;";
		//System.out.println(urlString);
		// Parse the returned XML with a special handler
		SearchOsmPoisXmlHandler xmlHandler = new SearchOsmPoisXmlHandler();

		try
		{
			URL url = new URI(urlString).toURL();
			SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
			try (InputStream inStream = url.openStream()) {
				saxParser.parse(inStream, xmlHandler);
			} catch (Exception e) {
				_errorMessage = e.getClass().getName() + " - " + e.getMessage();
			}
		} catch (MalformedURLException | SAXException | ParserConfigurationException | URISyntaxException ignored) {
		}

		// Calculate distances for each returned point
		DataPoint searchPoint = new DataPoint(Latitude.make(_searchLatitude),
			Longitude.make(_searchLongitude));
		Unit distUnit = getConfig().getUnitSet().getDistanceUnit();
		for (SearchResult result : xmlHandler.getPointList())
		{
			DataPoint foundPoint = new DataPoint(Latitude.make(result.getLatitude()),
				Longitude.make(result.getLongitude()));
			double dist = DataPoint.calculateRadiansBetween(searchPoint, foundPoint);
			result.setLength(Distance.convertRadiansToDistance(dist, distUnit));
		}

		// TODO: maybe limit number of results using MAX_RESULTS
		// Add track list to model
		ArrayList<SearchResult> pointList = xmlHandler.getPointList();
		_trackListModel.addTracks(pointList, true);
		_trackListModel.setShowPointTypes(true);
	}
}
