// ChartData.java
//
// Copyright (C) 1998 Henrik Bjorkman. All rights reserved. 
// No responsibilities taken. Use on your own risk
//
// This program can show your position on a map.
//
//
// History
// 
// 1999-06-19  
// Moved class ChartData to its own file. 
// Henrik Bjorkman 
// 
// 1999-10-09
// Using new class FileLineReader to read files.
// Henrik
//
// 2000-06-13
// Changed package name to chartplotter_package.
// Henrik
//
// 2000-11-21
// Added Rikards function for calculating distance and direction to
// mouse position.


package chartplotter_package;


import java.io.*;
import java.awt.*;
import java.text.*;


public class ChartData
{
  public String imgFileName;
  public ReferencePoint[] ref=new ReferencePoint[6];
  public int nref=0;
  public double north=0, south=0, east=0, west=0;
  double A=0, B=0, C=0, D=0, E=0, F=0; //for the new calibration routine
  public long diff=-1;
  public String title=null;
  public String datum;
  public String compDatum;
  public double compLat=0,compLng=0;

  public int addRefPoint(ReferencePoint r)
  {
    if (nref>=ref.length) {return(1);}
    ref[nref++]=r;
    return(0);
  }

  public String toString()
  {
    String s=new String("begin ChartData\n");

    s+="name "+imgFileName+"\n";

    if (datum!=null) s+="datum "+datum+"\n";

    s+="lim "+ReferencePoint.toStringLatLng(south,'N','S')+" "+
     ReferencePoint.toStringLatLng(north,'N','S')+" "+
     ReferencePoint.toStringLatLng(west,'E','W')+" "+
     ReferencePoint.toStringLatLng(east,'E','W')+"\n";

    for (int i=0; i<nref; i++)
    {
      s+="ref "+ref[i].toString()+"\n";
    }

    if (compDatum!=null)
    {
      s+="comp "+imgFileName+"\n";
    }

    if (title!=null) s+="title "+title+"\n";

    s+="end\n";    

    return(s);
  }


  static void error(String s)
  {
    System.err.println("ChartData: " + s);
    System.exit(0);
  }

  static void debug(String s)
  {
    //System.out.println("ChartData: " + s);
  }

  public boolean onChart(Pos p)
  {
    if ((north!=0) && (south!=0)) return((p.lat<=north) && (p.lat>=south) && (p.lng>=west) && (p.lng<=east));
    if ((ref[0]==null)||(ref[1]==null)) error("No config data for "+imgFileName);
    return( (ref[0].lat>=p.lat) && (ref[1].lat<=p.lat) && (ref[0].lng<=p.lng) && (ref[1].lng>=p.lng) );
  }

  public int transPosX(double lat, double lng)
  {
    return((new Double(A*lng+B*lat+C)).intValue());
  }

  public int transPosY(double lat, double lng)
  {
    return((new Double(D*lng+E*lat+F)).intValue());
  }

  public double transPosLng(int x, int y)
  {
    return((E*x-B*y-(E*C-B*F))/(E*A-B*D));
  }

  public double transPosLat(int x, int y)
  {
    return((D*x-A*y-(D*C-A*F))/(D*B-A*E));
  }


  // Calculate the parameters to be used when translating GPS
  // position to pixel coordinates on the chart.
  public void findCalibrationParams()
  {
    ReferencePoint r0=ref[0];
    ReferencePoint r1=ref[1];
    ReferencePoint r2=ref[2];

    if (nref>=3)
    {
      double tmp1=0, tmp2=0, tmp3=0, Tc=0, Nc=0, Tf=0, Nf=0;
      tmp1=r0.lat*r2.lng-r2.lat*r0.lng;
      tmp2=r1.lat*r0.lng-r0.lat*r1.lng;
      tmp3=r0.lng-r1.lng;
 
      Tc=(r0.lng*r1.x-r1.lng*r0.x)*tmp1+(r2.x*r0.lng-r0.x*r2.lng)*tmp2;
      Nc=(r0.lng-r2.lng)*tmp2+tmp3*tmp1;
      C=Tc/Nc;
      B=(-C*tmp3+r0.lng*r1.x-r1.lng*r0.x)/tmp2;
      A=(r0.x-C-B*r0.lat)/r0.lng;

      Tf=(r0.lng*r1.y-r1.lng*r0.y)*tmp1+(r2.y*r0.lng-r0.y*r2.lng)*tmp2;
      Nf=(r0.lng-r2.lng)*tmp2+tmp3*tmp1;
      F=Tf/Nf;
      E=(-F*tmp3+r0.lng*r1.y-r1.lng*r0.y)/tmp2;
      D=(r0.y-F-E*r0.lat)/r0.lng;

    }
    else if (nref>=2)
    {
      A=(r1.x-r0.x)/(r1.lng-r0.lng);
      B=0;
      C=-A*r0.lng+r0.x;
      D=0;
      E=(r1.y-r0.y)/(r1.lat-r0.lat);
      F=-E*r0.lat+r0.y;
    }
    else
    {
      error("To few reference points");
    }
    debug("A "+A+" B "+B+" C "+C+" D "+D+" E "+E+" F "+F);

    // If we have more than 3 ref points we use them to check the transposition.
    // This gives an estimated error in pixels.
    for (int i=3;i<nref;i++)
    {
      int dx=transPosX(ref[i].lat,ref[i].lng)-ref[i].x;
      int dy=transPosY(ref[i].lat,ref[i].lng)-ref[i].y;
      long d2=Math.round(Math.sqrt(dx*dx+dy*dy));
      if (d2>diff) {diff=d2;}
    }
  }

  // Translate a GPS position to pixel position on chart.
  public Point transPos(Pos pos)
  {
    if (pos==null) return(null);
    Point p=new Point();
    p.x=transPosX(pos.lat+compLat, pos.lng+compLng);
    p.y=transPosY(pos.lat+compLat, pos.lng+compLng);
    debug("lat "+pos.lat+" lng "+pos.lng+" x "+p.x+" y "+p.y);
    return(p);
  }

  // Translate a pixel position to Lat/Lng position on chart.
  public Pos transPos(Point p)
  {
    if (p==null) return(null);
    Pos pos=new Pos();
    pos.lat=transPosLat(p.x, p.y)-compLat;
    pos.lng=transPosLng(p.x, p.y)-compLng;

    debug("reverse lat "+pos.lat+" lng "+pos.lng+" x "+p.x+" y "+p.y);
    return(pos);
  }



  public static ChartData loadChartData(FileLineReader in, String n) throws IOException
  {
    ChartData d=new ChartData();
    boolean oldStyle=false;

    debug("loadChartData");

    if (n!=null) 
    {
      d.imgFileName=in.getDirName()+File.separator+n;
      oldStyle=true;
    }

    while(true)
    {

        String str=in.readLine();

        if (str==null) 
        {
          if (oldStyle) break;
          throw new IOException("unexpected end of file (no end)");
        }

        debug("read: "+str);

        if (str.length()==0)
        {
          if (oldStyle) break;
        }
        else if (str.startsWith("name"))
        {
          if (oldStyle) throw new IOException("unexpected input");
          d.imgFileName=in.getDirName()+File.separator+ChartWord.skipWords(str,1);
        }
        else if (str.startsWith("ref"))
        {
          if (d.nref>=d.ref.length) {throw new IOException("to much data: "+str);}
          d.ref[d.nref++]=new ReferencePoint(ChartWord.skipWords(str,1));
        }
        else if (str.startsWith("lim"))
        {
          d.south=ReferencePoint.parseLatLng(ChartWord.getWord(str,1),'N','S');
          d.north=ReferencePoint.parseLatLng(ChartWord.getWord(str,2),'N','S');
          d.west=ReferencePoint.parseLatLng(ChartWord.getWord(str,3),'E','W');
          d.east=ReferencePoint.parseLatLng(ChartWord.getWord(str,4),'E','W');
        }
        else if (str.startsWith("comp"))
        {          
          d.compLat=Double.valueOf(ChartWord.getWord(str,1)).doubleValue();
          d.compLng=Double.valueOf(ChartWord.getWord(str,2)).doubleValue();
          d.compDatum=ChartWord.skipWords(str,3);
        }
        else if ((str.startsWith("rem")) || (str.startsWith("title")))
        {          
          d.title=ChartWord.skipWords(str,1);
        }
        else if (str.startsWith("datum"))
        {          
          d.datum=ChartWord.skipWords(str,1);
        }
        else if (str.startsWith("begin"))
        {          
          throw new IOException("unknown input data: "+str);
        }
        else if (str.startsWith("end"))
        {          
          break;
        }
        else
        {
          throw new IOException("unknown input data: "+str);
        }
    }

    d.findCalibrationParams();

    return(d);
  }

  // As long as scale is the same in horisontal as in vertical this
  // should work fine but it would have been better if distance and 
  // bearing was calculated without translating to pixels.
  public String distanceAndBearingToString(Pos pos,Pos mpos)
  {
    String s=new String("");

    if ((pos==null)||(mpos==null)) {return s;}

    Pos testPos=new Pos();
    Point pix_position = transPos(pos);
    Point m = transPos(mpos);

    double pix_distance = 
    Math.sqrt(((m.x - pix_position.x)*(m.x - pix_position.x))+ 
			  ((m.y - pix_position.y)*(m.y - pix_position.y)));  // in pixels

    //wanted dist = dpix_dist * dlat/dy  
    double distance;

    distance = pix_distance*(Math.abs(ref[1].lat-ref[0].lat))/
				     (Math.abs(ref[1].y-ref[0].y));
    s = "Distance to cursor "+ distance + 
        " Time to possible arrival " + (distance/pos.speed)*60d + " minutes\n";

    s += "Bearing to cursor ";

    //Calculate bearing
    if (pix_distance > 1)
    {
      if ((m.x - pix_position.x) > 0 && (m.y - pix_position.y) < 0)
      {
        s += (Math.asin(Math.abs(m.x - pix_position.x)/pix_distance)*180/(Math.PI));
      }
      else if  ((m.x - pix_position.x) > 0 && (m.y - pix_position.y) > 0)
      {
        s += (90d+Math.acos(Math.abs(m.x - pix_position.x)/pix_distance)*180/(Math.PI));
      }
      else if  ((m.x - pix_position.x) < 0 && (m.y - pix_position.y) < 0)
      {
        s += (360d-Math.acos(Math.abs(m.y - pix_position.y)/pix_distance)*180/(Math.PI));
      }
      else if  ((m.x - pix_position.x) < 0 && (m.y - pix_position.y) > 0)
      {
	  s += (270d-Math.acos(Math.abs(m.x - pix_position.x)/pix_distance)*180/(Math.PI));
      }
    }
    return s;
  }

}

