import java.io.File;
import java.io.RandomAccessFile;
import java.io.BufferedReader;
import java.io.FileReader;

/**
   Hotspot and endpoint location files are ascii - each record has
   following format:

   epochSec   latitude   longitude

   NOTE: Location files MUST be in timetag-ascending order!


   timestamp file is ascii; each record has epoch seconds in first column, 
   second column may be a later time if vector plotting is desired.

*/
public class EndpointRangeBearing {

    /** format: epochSec, lat, lon */
    String _hotspotFilename;
    LocationFile _hotspotLocs;

    /** format: epochSec, lat, lon */
    String _endpointFilename;
    LocationFile _endpointLocs;

    long _prevEpochSec = 0;

    public EndpointRangeBearing(String hotspotLocFile, String endpointLocFile) 
	throws Exception {

	_hotspotFilename = hotspotLocFile;
	_endpointFilename = endpointLocFile;

	_hotspotLocs = new LocationFile(hotspotLocFile);

	_endpointLocs = new LocationFile(endpointLocFile);
    }


    /** Get range and bearing of endpoint from hotspot at specified time(s) */
    RangeBearing getRangeBearing(long epochSec) throws Exception {

	if (epochSec < _prevEpochSec) {
	    // Need to start reading at beginning of files
	    _hotspotLocs._file.seek(0);
	    _endpointLocs._file.seek(0);
	}

	_prevEpochSec = epochSec;

	String[] hotspotTokens = null;
	String[] endpointTokens = null;
	double hotspotLat;
	double hotspotLon;
	double endpointLat;
	double endpointLon;

	// Find hotspot record that is closest to specified time
	try {
	    Location loc = _hotspotLocs.getLocation(epochSec);
	    hotspotLat = loc._latitude;
	    hotspotLon = loc._longitude;
	}
	catch (Exception e) {
	    throw new Exception("Error getting hotspot record for et=" + 
				epochSec + ": " + e);
	}

	// Find endpoint record that is closest to specified time
	try {
	    Location loc = _endpointLocs.getLocation(epochSec);
	    endpointLat = loc._latitude;
	    endpointLon = loc._longitude;
	}
	catch (Exception e) {
	    throw new Exception("Error getting endpoint record for et=" + 
				epochSec + ": " + e);
	}

	double bearing = Utility.bearing(hotspotLat, hotspotLon, 
					 endpointLat, endpointLon) * 
	    180./Math.PI;

	// Tranform bearing value for gnuplot
	double plotBearing = gnuplotBearing(bearing);

	double r = Utility.geodeticDistance(hotspotLat, hotspotLon,
					    endpointLat, endpointLon);


	return new RangeBearing(epochSec, r, bearing);
    }



    static Location interpolateLocation(long t, 
					long t1, double lat1, double lon1,
					long t2, double lat2, double lon2) {

	double lat = lat1 + (lat2 - lat1)*(t - t1)/(t2 - t1);
	double lon = lon1 + (lon2 - lon1)*(t - t1)/(t2 - t1);

	return new Location(t, lat, lon);
    }


    /** Transform bearing degrees to gnuplot coordinate system */
    static double gnuplotBearing(double bearing) {
	return -bearing + 90.;
    }

    static void printUsage() {
	System.out.println("usage: [-vector][-join] timestamp-file hotspot-loc-file endpoint-loc-file");
	System.out.println("-vector, -join options must precede input file arguments");
    }

    public static void main(String[] args) {
	// if computeVector is true, then timestamp file's first column is 
	// start time, stop time is second column. If computeVector is false, 
	// then just use first column.
	boolean computeVector = false;
	boolean join = false;
	if (args.length < 3) {
	    printUsage();
	    return;
	}

	if (args.length > 3) {
	    boolean error = false;
	    // Some options were specified
	    for (int i = 0; i < args.length - 3; i++) {
		if (args[i].equals("-vector")) {
		    computeVector = true;
		}
		else if (args[i].equals("-join")) {
		    join = true;
		}
		else {
		    System.out.println(args[i] + ": unknown option");
		    error = true;
		}
	    }
	    if (error) {
		printUsage();
		return;
	    }
	}

	String timestampFile = args[args.length-3];
	String hotspotFile = args[args.length-2];
	String endpointFile = args[args.length-1];

	BufferedReader timestampReader;
	try {
	    timestampReader = 
		new BufferedReader(new FileReader(new File(timestampFile)));
	}
	catch (Exception e) {
	    System.err.println("Error opening " + timestampFile + ": " + e);
	    return;
	}

	EndpointRangeBearing calculator;

	try {
	    calculator = 
		new EndpointRangeBearing(hotspotFile, endpointFile);
	}
	catch (Exception e) {
	    System.err.println("Error creating calculator: " + e);
	    return;
	}
	
	// For each target timestamp in first file, determine 
	// range and bearing of endpoint from hotspot
	String line;
	while (true) {
	    try {
		line = timestampReader.readLine();
	    }
	    catch (Exception e) {
		System.err.println("Error reading timestamp line: " + e);
		break;
	    }

	    if (line == null) {
		// End of file
		break;
	    }

	    line.trim();

	    // String[] tokens = line.split("\\s+");
	    String[] tokens = line.split("[\\s,;\\n\\t]+");

	    if (tokens[0].startsWith("#")) {
		// Found a comment line
		continue;
	    }

	    if (tokens.length == 0) {
		// Blank
		System.err.println("Blank line: " + tokens[0]);
		continue;
	    }

	    if (computeVector && tokens.length < 2) {
		System.err.println("Missing second time stamp: " + tokens[0]);
		continue;
	    }


	    try {
		// Timestamp is first token in line
		long epochSec = Long.parseLong(tokens[0].trim());
		// System.out.println("\nepochSec: " + epochSec);
		RangeBearing rangeBearing = calculator.getRangeBearing(epochSec);

		System.out.print("" + epochSec + " r: " + rangeBearing._range +
				 "  bear: " + rangeBearing._bearing + 
				 " plotbear: " + gnuplotBearing(rangeBearing._bearing));

		if (computeVector) {
		    if (tokens.length < 2) {
			throw new Exception("second timestamp not found");
		    }

		    // Stop time is second token in line
		    epochSec = Long.parseLong(tokens[1].trim());
		    RangeBearing rangeBearing2 = calculator.getRangeBearing(epochSec);

		    System.out.print(" t2: " + epochSec + " deltaR: " + (rangeBearing2._range - rangeBearing._range) +
				     " deltaplotBear: " + 	
				     (gnuplotBearing(rangeBearing2._bearing) - 
				      gnuplotBearing(rangeBearing._bearing)));
		}
		if (join) {
		    // Append all but first token of time-stamp file line 
		    for (int i = 1; i < tokens.length; i++) {
			System.out.print(" " + tokens[i]);
		    }
		}

		System.out.println("  END");
	    }
	    catch (Exception e) {
		System.err.println("Error processing timestamp line " + 
				   line + ": " + e);
	    }
	}

	System.out.println("");
    }



    static class RangeBearing {
	long _epochSec;
	double _range;
	double _bearing;
	
	RangeBearing(long epochSec, double range, double bearing) {
	    _epochSec = epochSec;
	    _range = range;
	    _bearing = bearing;
	}
    }

    static class Location {
	long _epochSec;
	double _latitude;
	double _longitude;

	Location(long epochSec, double lat, double lon) {
	    _epochSec = epochSec;
	    _latitude = lat;
	    _longitude = lon;
	}
    }


    /** Encapsulates RandomAccessFile, remembers last record read */
    class LocationFile {

	String _prevRecord;
	long _prevEpochSec;
	String _filename;
	RandomAccessFile _file;

	LocationFile(String filename) throws Exception {
	    _file = new RandomAccessFile(new File(filename), "r");
	    _filename = new String(filename);
	}

	/** Interpolate file data to get lat/lon at specified time.  
	    File MUST be in timetag-ascending order! */
	Location getLocation(long targetEpochSec)
	    throws Exception {

	    while (true) {
		long startPos = _file.getFilePointer();
		String record = _file.readLine();
		if (record == null) {
		    throw new Exception("Encountered " + _filename +  
					" eof without finding record");
		}

		String[] recordTokens = record.split("\\s+");
		if (recordTokens.length != 3) {
		    System.err.println("zero-length record in " + _filename + "!");
		    continue;
		}

		// First token is epoch time
		long epochSec = Long.parseLong(recordTokens[0].trim());
		if (epochSec == targetEpochSec) {
		    _file.seek(startPos);   // Reposition to last-read location
		    return new Location(epochSec,
					Double.parseDouble(recordTokens[1].trim()),
					Double.parseDouble(recordTokens[2].trim()));
		
		}
		if (epochSec > targetEpochSec) {
		    if (startPos == 0) {
			// First record timestamp exceeds target
			_file.seek(startPos);   // Reposition to last-read location
			throw new Exception("First " + _filename + 
					    " record exceeds target time");
		    }

		    // Reposition to last-read location for next call
		    _file.seek(startPos); 

		    // System.out.println("split prev " + _prevRecord);
		    // Target time falls between records; interpolate position
		    String[] prevTokens = _prevRecord.split("\\s+");

		    // System.out.println("interpolate");
		    return interpolateLocation(targetEpochSec,
					       _prevEpochSec,
					       Double.parseDouble(prevTokens[1].trim()),
					       Double.parseDouble(prevTokens[2].trim()),
					       epochSec, 
					       Double.parseDouble(recordTokens[1]),
					       Double.parseDouble(recordTokens[2].trim()));
		
		}
		_prevEpochSec = epochSec;
		_prevRecord = new String(record);
	    }
	}
    }
}

