// NmeaReceiver.java
//
// Copyright (C) 1998 Henrik Bjorkman. This is free software you 
// may use, copy and distribute this program freely.
//
// This class reads data from com port and interprets the NMEA
// messages.
//
// Credits
//
// SUNs jdk 1.1.5 was used when developing this program.
//
// I had help by looking at how these things where done in a program 
// called "Elgaard Positioning System" when creating this program.
//
//
// History
//
// 1998-05-22 Created by Henrik Bjorkman
// 1998-06-01 Added package. /Henrik
// 1998-06-18 This class don't need to call the ChartThread any longer.
//            The user will call waitForPos instead. /Henrik
// 1998-06-24 Gives time, track and speed also. /Henrik
// 1998-10-30 Doing notify on calling object so that object can wait for 
//            multiple input sources. Taking position also from GPRMC so
//            that position can be updated more often.
// 1998-12-02 Prints a warning if not WGS 84. /Henrik
// 1999-09-04 This class can now give positions to two user
//            classes. /Henrik
// 1999-09-15 Added timeout handling and parser for GPGGA sentence. /Henrik
// 2000-06-13 Changed package name to chartplotter_package.

package chartplotter_package;

import java.io.*;
import java.util.Date;
import java.util.Calendar;
import java.awt.*;
import java.text.*;

public class NmeaReceiver extends Thread  /*implements Runnable */
{
  final long MAX_TIME=2400; /* milli seconds */
  ComPort com_port;
  Pos pos=null;
  double speed=0;
  double track=0;
  Object[] caller=new Object[4];
  long updateTime=0; /* Time when position was updated */
  long trackTime=0;  /* Time when speed and track was updated */

  static void debug(String s)
  {
    //System.out.println("NmeaReceiver: " + s);
  }

  public NmeaReceiver(ComPort com_port) 
  {
    this.com_port=com_port;
    this.start();
  }

  public synchronized void updatePos(double lat, double lng, long time)
  {
    if ((System.currentTimeMillis()-trackTime)>MAX_TIME)
    { 
      track=0;
      speed=0;
    }
    pos=null;
    updateTime=System.currentTimeMillis();
    pos=new Pos();
    pos.lat=lat;
    pos.lng=lng;
    pos.time=time;
    pos.track=track;
    pos.speed=speed;
    for(int i=0; i<caller.length; i++)
    {
      if (caller[i]!=null) {synchronized (caller[i]) {caller[i].notify();}}
    }
  }


  // Returns new position if there is one.
  public synchronized Pos getPos()
  {
    if ((System.currentTimeMillis()-updateTime)>MAX_TIME) {pos=null;}
    return(pos);
  }

  public synchronized void addNotify(Object caller)
  {
    int i=0;
    while(this.caller[i]!=null) {i++;}
    this.caller[i]=caller;
  }

  public synchronized void removeNotify(Object caller)
  {
    for(int i=0;i<this.caller.length;i++)
    {
      if (this.caller[i]==caller) {this.caller[i]=null;}
    }
  }


  // Returns new position if there is one.
  // If not it will wait for one.
  // (not used any more)
  /*
  public synchronized Pos waitForPos()
  {
    Pos p=null;

    if (pos==null)
    {
      try 
      {
        this.wait(5000);
      }
      catch (InterruptedException e) {;}
    }

    p=pos;
    pos=null;

    return(p);
  }
  */


  public static String getCol(String str, int col) 
  {
    int len=str.length();
    int start;
    int i=0;

    while (col>0)
    {
      while ((i<len) && (str.charAt(i)!=',') && (str.charAt(i)!='*')) {i++;}
      if (i<len) {i++;}
      col--;
    }

    start = i;
    while ((i<len) && (str.charAt(i)!=',') && (str.charAt(i)!='*')) {i++;}

    return(str.substring(start,i));
  }

  static public double parseLatLng(String str, int digits, String signString, String negString)
   throws NumberFormatException
  {
    double l=(Double.valueOf(str.substring(0,digits)).doubleValue()*60d) +
             (Double.valueOf(str.substring(digits)).doubleValue());
    if (signString.equals(negString)) {l=-l;}
    return(l);
  }

  static public long parseTime(String str) throws NumberFormatException
  {
    int hrs = Integer.parseInt(str.substring(0,2));
    int min = Integer.parseInt(str.substring(2,4));
    int sec = Integer.parseInt(str.substring(4,6));
    return((3600*hrs+60*min+sec)*1000);
  }

  synchronized void GPGLL(String line)
  {
    String latString = getCol(line,1);
    String lngString = getCol(line,3);
    String northSouthString = getCol(line,2);
    String eastWestString = getCol(line,4);
    double lat;
    double lng;
    long time=0;

    if (latString.length()>2 && lngString.length()>3)
    {
      lat = parseLatLng(latString,2,northSouthString,"S");
      lng = parseLatLng(lngString,3,eastWestString,"W");

      //debug("lat="+lat+ " lng= "+ lng);

      if (getCol(line,6).charAt(0)=='A')
      {                 
        time = parseTime(getCol(line,5));
      }
      updatePos(lat,lng,time);
    }
  }

  synchronized void GPRMC(String line)
  {
    String dateString = getCol(line,9);
    String speedString = getCol(line,7);
    String trackString = getCol(line,8);
    String latString = getCol(line,3);
    String northSouthString = getCol(line,4);
    String lngString = getCol(line,5);
    String eastWestString = getCol(line,6);
    /*int year=0, month=0, day=0;*/
    /*int hrs=0, int min=0, int sec=0;*/
    double lat;
    double lng;
    long time=0;

    if (getCol(line,2).charAt(0)=='A')
    {                 
      time = parseTime(getCol(line,1));
    }
	      
    /*if (dateString.length()>=6)
    {
      year = Integer.parseInt(d.substring(4,6));
      month = Integer.parseInt(d.substring(2,4));
      day = Integer.parseInt(d.substring(0,2));
    }*/

    if ((speedString.length()>0) && (trackString.length()>0))
    {
      trackTime=System.currentTimeMillis();
      speed = Double.valueOf(speedString).doubleValue();
      track = Double.valueOf(trackString).doubleValue();
      //debug("speed "+ speed + " course "+course);
      //debug(" date "+year + "-"+month +"-"+day);
    }

    if (latString.length()>2 && lngString.length()>3)
    {
      lat = parseLatLng(latString,2,northSouthString,"S");
      lng = parseLatLng(lngString,3,eastWestString,"W");
      updatePos(lat,lng,time);
    }
  }

  void PGRMM(String line)
  {
    String datum = getCol(line,1);
    if (!datum.equals("WGS 84"))
    {
      System.out.println("NmeaReceiver: Warning not WGS 84 ("+datum+")");
    }
  }

  synchronized void GPGGA(String line)
  {
    String latString = getCol(line,2);
    String lngString = getCol(line,4);
    String northSouthString = getCol(line,3);
    String eastWestString = getCol(line,5);
    double lat;
    double lng;
    long time=0;

    if (latString.length()>2 && lngString.length()>3)
    {
      lat = parseLatLng(latString,2,northSouthString,"S");
      lng = parseLatLng(lngString,3,eastWestString,"W");
      time = parseTime(getCol(line,1));
      updatePos(lat,lng,time);
    }
  }

  public void run() 
  {
    debug("start");

    while(true) 
    {
      String line = com_port.readLine();
      //debug("line "+line);

      if ((line!=null) && line.length()>=6) 
      {
	  try 
        {
  	    if (line.substring(0,6).equals("$GPGLL")) 
          {
            GPGLL(line);
	    } 
          else if (line.substring(0,6).equals("$GPRMC")) 
          {
            GPRMC(line);
          }
          else if (line.substring(0,6).equals("$PGRMM")) 
          {
            PGRMM(line);
          }
          else if (line.substring(0,6).equals("$GPGGA"))
          {
            GPGGA(line);
          }
          else if (!line.substring(0,1).equals("$")) 
          {
            debug(""+line);
          }
        } 
        catch (StringIndexOutOfBoundsException e) {debug("" + e);}
        catch (NumberFormatException e) {debug("" + e);}
      }
    } 
  }
}

