// ChartThread.java
//
// Copyright 1998 & 1999 Henrik Bjorkman. All rights reserved. 
// No responsibilities taken. Use on your own risk. Etc...
//
// This program can show your position on a map.
//
//
// History
//
// 1998-05-22 
// Created by Henrik Bjorkman
//
// 1998-05-31 
// Changed some synchronization. /Henrik
//
// 1998-06-01 
// Prepared for multiple chart data. Added package and renamed 
// file from ChartPlotter to ChartThread. /Henrik
//
// 1998-06-04 
// Enhanced chart calibration, 3-points. /Rikard
//
// 1998-06-09
// Added north south east west borders to be used to set
// for witch positions a chart shall be used.
//
// 1998-06-17
// A forth reference point can now be used to check the
// other 3 reference points. /Henrik
// 
// 1998-06-18 
// Now using the new function waitForPos in NmeaReceiver. /Henrik
//
// 1998-06-18 
// Adding new functionality comp to be used to compensate for
// other chart datum than WGS84. /Henrik
//
// 1998-06-20
// Clean up. /Henrik
//
// 1998-06-23
// Trying to predict speed and direction. /Henrik
//
// 1998-06-27
// Writing chart name and position in window title. /Henrik
//
// 1998-06-28
// It is now possible to set the thickness of the circle 
// that marks the position. /Henrik
//
// 1998-07-24
// Adding new functionality: a circle of 50 m radius encounters the 
// current position findCircleRadius()
// /Rikard
//
// 1998-10-23
// An error message is now printed if configuration data is missing 
// for a chart. The circle added 24/7 to indicate precision is now 100m.
// The chart data file can now have includes to other chart data files.
// /Henrik
//
// 1998-10-26
// Added time of day to the log printout.
// Henrik
//
// 1998-12-2
// Position is now always written with 3 decimals.
// Henrik
//
// 1999-06-02
// Added a new function changeSeparator to replace all '/'
// in a filename to correct separator. Henrik
//
// 1999-06-20
// Moved classes ChartWord, ReferencePoint and ChartData 
// To their own files. /Henrik
//
// 1999-06-20
// Program will first try to use Communications API and
// if that is not found it will try to start an external
// process for communication.
// Henrik
//
// 1999-08-13
// Now saving the last 1000 positions so that the trace line
// can be rewritten when changing chart.
// Henrik
//
// 1999-08-26
// Doing the trace a little better.
// Henrik
//
// 1999-09-04
// It is now possible to have two windows with
// different charts.
// Henrik
//
// 1999-09-13
// Adding functions to make it possible to move around
// in the maps by clicking with the mouse.
// Henrik
//
// 1999-09-15
// Changed timeout handling
// Henrik
//
// 1999-09-31
// The size of the array for chart data is now dynamic.
// Henrik
//
// 1999-10-09
// Using new class FileLineReader to read files.
// Henrik
//
// 2000-06-04
// Added a new switch "-r" and changed "-t" a little.
// Henrik
//
// 2000-06-13
// Changed package name to chartplotter_package.
// Henrik
//
// 2000-11-21
// Added a call to Rikard functions for printing distance and 
// direction to mouse position.


package chartplotter_package;

import java.io.*;
import java.awt.*;
import java.text.*;


public class ChartThread extends Thread
{
  static boolean alwaysCenter=false;
  static boolean trace=true;
  static int thickness=1;
  static ComPort comPort=null;
  static double scaleCircle=100;      // The radius of the scale circle in meters
  static NmeaReceiver receiver=null;
  static int predScale=120;    // Length in seconds for prediction line
  static ChartTrace chartTrace=new ChartTrace();
  static String port_name=null;
  static int comMethod=ComPort.SEARCH;

  ChartData[] chartData=new ChartData[100];
  int nChartData=0; // number of charts
  ChartData d=null; // current chart
  ChartWindow w=null;
  boolean timeoutReported=false;
  int print=60;
  String chartInfo;
  String posInfo;
  int checkCount=0;
  java.util.Date startDate=new java.util.Date();
  boolean master;
  Pos pos=null;
  Pos oldPos=null;
  Pos oldmpos=null;

  static void error(String s)
  {
    System.err.println("ChartThread: " + s);
    System.exit(0);
  }

  static void debug(String s)
  {
    //System.out.println("ChartThread: " + s);
  }

  public static void println(String s)
  {
    System.out.println(s);
  }


  /**
   * An internal method to change the allocated size of the 
   * chartData if needed. 
   */
  protected void resize() 
  {
    if (nChartData>=chartData.length) 
    {
      ChartData[] oldChartData = chartData;

      // Create a new bigger array.
      chartData = new ChartData[oldChartData.length*2];   

      // Copy array elements.
      System.arraycopy(oldChartData, 0, chartData, 0, oldChartData.length); 

      debug("new size "+chartData.length);
    }
  }


  /** Read calibration data from file. */
  void readData(String file_name)
  {
    ChartData d=null;

    FileLineReader in=new FileLineReader(file_name);

    try
    {
      while(true)
      {
        String str=in.readLine();

        if (str==null) break;

        //debug("read: "+line+" "+str);

        if (str.length()==0) {;}
        else if (str.startsWith("name"))
        {
          resize();
          d=ChartData.loadChartData(in,ChartWord.skipWords(str,1));
          if (d!=null) {chartData[nChartData++]=d;}
        }
        else if (str.startsWith("begin"))
        {          
          resize();
          d=ChartData.loadChartData(in,null);
          if (d!=null) {chartData[nChartData++]=d;}
        }
        else
        {
          error(in.getInfo()+" unknown data: "+str);
        }
      }      
      in.close();
    }
    catch (IOException e) {error(in.getInfo()+" "+e); }
    catch (NumberFormatException e) {error(in.getInfo()+" "+e);}
  }

 

  /** Find first chart that covers current position. */
  ChartData findChart(Pos pos)
  {
    if ((pos==null) && (nChartData>0)) return(chartData[nChartData-1]);
    for (int i=0;i<nChartData;i++)
    {
      if (chartData[i].onChart(pos))
      {
        return(chartData[i]);
      }
    }
    return(null);
  }


  /**
   * Calculate where we will be in <predScale> seconds with current 
   * speed and direction. 
   */
  Pos calcPred(Pos pos)
  {
    Pos pred=new Pos();
    double r=predScale*pos.speed/3600;
    pred.lng=pos.lng+r*Math.sin(Math.PI*pos.track/180)/Math.cos(Math.PI*pos.lat/(180*60));
    pred.lat=pos.lat+r*Math.cos(Math.PI*pos.track/180);
    return(pred);
  }


  // Returns the radius in pixels of the scale circle.
  int findCircleRadius(Pos pos)
  {
    if ((scaleCircle<=0)||(pos==null)) return(-1);
    Pos circpos = new Pos();
    circpos.lat = (pos.lat - 1*(scaleCircle/1852d));
    int y0 = d.transPosY(circpos.lat, pos.lng);
    int y1 = d.transPosY(pos.lat, pos.lng);
    int dy0 = Math.abs(y1-y0);
    return(dy0);
  }

  /**
   * Find first chart that covers current position and
   * if a new chart was found change to that chart.
   */
  void checkChart(Pos pos)
  {
    ChartData d=findChart(pos);
    
    // Did we find another chart than current?
    if ((d!=null) && (d!=this.d))
    {
      // if so set it to current and prepare parameters and info.
      this.d=d;
      println("selected chart "+d.imgFileName);
      d.findCalibrationParams();
      println("precision"+((d.diff>=0)?" "+d.diff+" pixels":" unknown"));
      if (d.diff>20) 
      {
        ChartThread.println("Warning! Bad precision. Perhaps a reference point problem.");
      }
      chartInfo=(new File(d.imgFileName)).getName()+((d.diff>=0)?" "+d.diff:" -");
      if (d.title!=null) {chartInfo=chartInfo+"  "+d.title; println(d.title);}
      if (d.datum!=null) {chartInfo=chartInfo+"  "+d.datum; println(d.datum);}
      if (chartInfo.length()<30) {chartInfo=chartInfo+"  "+ChartVersion.name;}
      w.changeChart(d.imgFileName,findCircleRadius(pos),-1);
      writePosInTitle(null);

      // Send trace positions.
      chartTrace.plotTrace(w/*.getChartComp()*/,d);

      checkCount=10;
    }
  }



  /** Check if we have a new position, if not wait for one. */
  public synchronized void waitForPos()
  {
    //pos=receiver.getPos();

    //if ((pos==null) || (pos==oldPos))
    {
      try 
      {
        //receiver.addNotify(this);
        this.wait(60);
        //receiver.removeNotify(this);
        pos=receiver.getPos();
      }
      catch (InterruptedException e) {;}
    }
    //if (pos==oldPos) {pos=null;}
  }

  /** Write position info in the window title. */
  void writePosInTitle(Pos pos)
  {
    if (pos!=null)
    {
      posInfo=
       ChartWord.addSpace(ReferencePoint.toStringLatLng(pos.lat+d.compLat,'N','S'),9)+
       ChartWord.addSpace(ReferencePoint.toStringLatLng(pos.lng+d.compLng,'E','W'),10)+
       ChartWord.addSpace(""+pos.track,5)+
       ChartWord.addSpace(""+pos.speed,5);
    }
    else
    {
      posInfo="No data from GPS";
    }
    w.setTitle(chartInfo+"  "+posInfo);
  }

  public void run()
  {
    debug("start");

    while(true)
    {
      waitForPos();

      if (pos==null)
      {
        if (!timeoutReported)
        {
          writePosInTitle(pos);
          w.timeOut();
          println("timeout (no data from GPS)");
          timeoutReported=true;
        }
      }

      if ((pos!=null) && (pos!=oldPos))
      {
        writePosInTitle(pos);

        if ((!d.onChart(pos)) || (++checkCount>=10)) 
        {
          checkChart(pos);
          checkCount=0;
        }

        Point p=d.transPos(pos);
        w.updatePos(p,d.transPos(calcPred(pos)));

        if ((++print>=60) && (master)) 
        {
          println(ChartWord.timeString(pos.time)+" "+posInfo+" ("+p.x+","+p.y+") ");
          if (trace) {chartTrace.logPos(pos);}
          print=0;
        }

        timeoutReported=false;
        oldPos=pos;
        pos=null;
      }


      {
        Point m=w.getChartComp().getMousePos();
        if (m!=null) 
        {
          println("mouse at "+m.x+" "+m.y);
          Pos mpos=d.transPos(m);  
          println(mpos.toString());
          println(d.distanceAndBearingToString(oldmpos,mpos));
          if ((timeoutReported) && (pos==null)) 
          {
            checkChart(mpos);

            Point mp=d.transPos(mpos);
            w.updatePos(mp,null);

            oldmpos=mpos;
          }
        }
      }


      if (w.closed)
      {
        if (master)
        {
          debug("Closing...");
          w.dispose();
          chartTrace.saveTrace();
          System.exit(0);
        }
      }
    }
  }

  static void help()
  {
    println("Usage: java ChartPlotter [switches] [chart-data-file]");
    println("switches:");
    println("-c      Keep mark in center of view");
    println("-h      This text ");
    println("-p<n>   Use serial port <n>. eg COM1 on Windows. ");
    println("-l<n>   Set length of prediction line to <n> in seconds");
    println("-t      Don't plott the blue trace ");
    println("-w<n>   Thickness of the red circle <n>");
    println("-s<r>   Set radius of scale circle to <r> in meters (1852 for 1nm)");
    println("-a      Use java communications API");
    println("-b      Start an external process for communication");
    println("-f<n>   Set log file name to <n> ");
    println("-f      Don't write log file");
    println("-r<n>   Read old log file <n>");
    println("");
    println("Example: java ChartPlotter -pCOM2 -c");
    debug(ChartWord.timeString((6*3600+4*60+3)*1000));
    System.exit(0);
  }

  public ChartThread(String file_name, boolean master) 
  {
    this.master=master;
    //receiver.addCaller(this);

    println("loading data "+file_name);
    readData(file_name);
    println("data loaded");

    w=new ChartWindow(alwaysCenter,trace,thickness);
    checkChart(null);

    //w.getChartComp().setNotify(this);

    this.start();
  }

  static public void main(String[] args)
  {
    String file_name_1="charts.dat";
    String file_name_2=null;
 
    int i=0;

    while (i<args.length)
    {
      if (args[i].charAt(0)!='-') break;
      if (args[i].length()<2) {help();}
      switch(args[i].charAt(1))
      {
        case 'c' : alwaysCenter=true;break;
        case 'p' : port_name=args[i].substring(2);break;
        case 'l' : predScale=Integer.parseInt(args[i].substring(2));break;
        case 't' : trace=false;break;
        case 'w' : thickness=Integer.parseInt(args[i].substring(2));break;
        case 's' : scaleCircle=Double.valueOf(args[i].substring(2)).doubleValue();break;
        case 'a' : comMethod=ComPort.API;break;
        case 'b' : comMethod=ComPort.PROCESS;break;
        case 'f' : chartTrace.log_file_name=args[i].substring(2);break;
        case 'r' : chartTrace.readTrace(args[i].substring(2));break;
        default  : help();break;
      }
      i++;
    }

    if (args.length>i) {file_name_1=args[i++];}
    if (args.length>i) {file_name_2=args[i++];}

    println(ChartVersion.name);
    println("You may not use this program as your only means of navigation.");
    println("Copyright 1998 Henrik Bjorkman www.stacken.kth.se/~bjorkman");
    println("You use this program on your own risk!");

    comPort=ComPort.startCommunication(comMethod,port_name);
    receiver=new NmeaReceiver(comPort);

    new ChartThread(file_name_1,true);
    if (file_name_2!=null) {new ChartThread(file_name_2,false);}
  }

}




