// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.elevation;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel;
import java.util.HashMap;

import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.tools.CheckParameterUtil;

/**
 *  Class HgtReader reads data from SRTM HGT files. Currently this class is restricted to a resolution of 3 arc seconds.
 *
 *  SRTM data files are available at the <a href="http://dds.cr.usgs.gov/srtm/version2_1/SRTM3">NASA SRTM site</a>
 *  @author Oliver Wieland <oliver.wieland@online.de>
 */
public class HgtReader {
	private static final int SECONDS_PER_MINUTE = 60;

	public static final String HGT_EXT = ".hgt";

	// alter these values for different SRTM resolutions
	public static final int HGT_RES = 3; // resolution in arc seconds
	public static final int HGT_ROW_LENGTH = 1201; // number of elevation values per line
	public static final int HGT_VOID = -32768; // magic number which indicates 'void data' in HGT file

	private final HashMap<String, ShortBuffer> cache = new HashMap<>();

	public double getElevationFromHgt(LatLon coor) {
		try {
			String file = getHgtFileName(coor);
			// given area in cache?
			if (!cache.containsKey(file)) {

				// fill initial cache value. If no file is found, then
				// we use it as a marker to indicate 'file has been searched
				// but is not there'
				cache.put(file, null);
				// Try all resource directories
				for (String location : Main.pref.getAllPossiblePreferenceDirs()) {
					String fullPath = new File(location + File.separator + "elevation", file).getPath();
					File f = new File(fullPath);
					if (f.exists()) {
						// found something: read HGT file...
						ShortBuffer data = readHgtFile(fullPath);
						// ... and store result in cache
						cache.put(file, data);
						break;
					}
				}
			}

			// read elevation value
			return readElevation(coor);
		} catch (FileNotFoundException e) {
			System.err.println("Get elevation from HGT " + coor + " failed: => " + e.getMessage());
			// no problem... file not there
			return ElevationHelper.NO_ELEVATION;
		} catch (Exception ioe) {
			// oops...
			ioe.printStackTrace(System.err);
			// fallback
			return ElevationHelper.NO_ELEVATION;
		}
	}

	@SuppressWarnings("resource")
	private ShortBuffer readHgtFile(String file) throws Exception {
		CheckParameterUtil.ensureParameterNotNull(file);

		FileChannel fc = null;
		ShortBuffer sb = null;
		try {
			// Eclipse complains here about resource leak on 'fc' - even with 'finally' clause???
			fc = new FileInputStream(file).getChannel();
			// choose the right endianness

			ByteBuffer bb = ByteBuffer.allocateDirect((int) fc.size());
			while (bb.remaining() > 0) fc.read(bb);

			bb.flip();
			//sb = bb.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
			sb = bb.order(ByteOrder.BIG_ENDIAN).asShortBuffer();
		} finally {
			if (fc != null) fc.close();
		}

		return sb;
	}

	/**
	 * Reads the elevation value for the given coordinate.
	 *
	 * See also <a href="http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file">stackexchange.com</a>
	 * @param coor the coordinate to get the elevation data for
	 * @return the elevation value or <code>Double.NaN</code>, if no value is present
	 */
	public double readElevation(LatLon coor) {
		String tag = getHgtFileName(coor);

		ShortBuffer sb = cache.get(tag);

		if (sb == null) {
			return ElevationHelper.NO_ELEVATION;
		}

		// see http://gis.stackexchange.com/questions/43743/how-to-extract-elevation-from-hgt-file
		double fLat = frac(coor.lat()) * SECONDS_PER_MINUTE;
		double fLon = frac(coor.lon()) * SECONDS_PER_MINUTE;

		// compute offset within HGT file
		int row = (int)Math.round(fLat * SECONDS_PER_MINUTE / HGT_RES);
		int col = (int)Math.round(fLon * SECONDS_PER_MINUTE / HGT_RES);

		row = HGT_ROW_LENGTH - row;
		int cell = (HGT_ROW_LENGTH*  (row - 1)) + col;

		//System.out.println("Read SRTM elevation data from row/col/cell " + row + "," + col + ", " + cell + ", " + sb.limit());

		// valid position in buffer?
		if (cell < sb.limit()) {
			short ele = sb.get(cell);
			//System.out.println("==> Read SRTM elevation data from row/col/cell " + row + "," + col + ", " + cell + " = " + ele);
			// check for data voids
			if (ele == HGT_VOID) {
				return ElevationHelper.NO_ELEVATION;
			} else {
				return ele;
			}
		} else {
			return ElevationHelper.NO_ELEVATION;
		}
	}

	/**
	 * Gets the associated HGT file name for the given way point. Usually the
	 * format is <tt>[N|S]nn[W|E]mmm.hgt</tt> where <i>nn</i> is the integral latitude
	 * without decimals and <i>mmm</i> is the longitude.
	 *
	 * @param latLon the coordinate to get the filename for
	 * @return the file name of the HGT file
	 */
	public String getHgtFileName(LatLon latLon) {
		int lat = (int) latLon.lat();
		int lon = (int) latLon.lon();

		String latPref = "N";
		if (lat < 0) latPref = "S";

		String lonPref = "E";
		if (lon < 0) {
			lonPref = "W";
		}

		return String.format("%s%02d%s%03d%s", latPref, lat, lonPref, lon, HGT_EXT);
	}

	public static double frac(double d) {
		long iPart;
		double fPart;

		// Get user input
		iPart = (long) d;
		fPart = d - iPart;
		return fPart;
	}
}
